Skip to content

feat(datetime): add ability to specify custom colors for specific dates #26775

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f6b3493
add markup scaffolding with test colors
Feb 8, 2023
8c7bc74
add logic for checking events array
Feb 8, 2023
1646492
handle when events is a callback
Feb 8, 2023
f82a37e
allow only supplying one color or the other
Feb 9, 2023
e8f7c2f
add events prop
Feb 9, 2023
1a38655
rename to use highlight language instead of events
Feb 9, 2023
c69943e
log warning when using prop with non-calendar view
Feb 9, 2023
ff649f0
lint
Feb 9, 2023
833ba42
add test html
Feb 10, 2023
c0b533f
lint
Feb 10, 2023
6be6bef
add screenshot tests
Feb 10, 2023
36579da
chore(): add updated snapshots
Ionitron Feb 10, 2023
7001134
remove :host selectors from changed styles
Feb 10, 2023
31dc5b2
also log element in warnings
Feb 13, 2023
0c7a043
import interfaces directly from datetime-specific file
Feb 13, 2023
4a8b4f6
run build
Feb 13, 2023
a1e7061
explicit undefined check
Feb 13, 2023
15ce761
strip time information before finding matching highlights
Feb 13, 2023
8fc131d
pull highlight styles search into helper function, run lint
Feb 13, 2023
205ae0c
move date::after into highlight, focus state matches highlight BG
Feb 14, 2023
54c6e98
focus style uses default color instead of matching highlight BG
Feb 15, 2023
03b736c
rename color in highlight interface to textColor
Feb 16, 2023
5030fe6
remove hacky styles now that we're not using a pseudo-element
Feb 16, 2023
564fefe
Merge branch 'feature-6.6' into FW-2142
averyjohnston Feb 16, 2023
ff21382
split styles to better show why !important is needed
Feb 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,14 +524,14 @@ export declare interface IonDatetime extends Components.IonDatetime {

@ProxyCmp({
defineCustomElementFn: undefined,
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'],
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'],
methods: ['confirm', 'reset', 'cancel']
})
@Component({
selector: 'ion-datetime',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues']
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues']
})
export class IonDatetime {
protected el: HTMLElement;
Expand Down
1 change: 1 addition & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ ion-datetime,prop,dayValues,number | number[] | string | undefined,undefined,fal
ion-datetime,prop,disabled,boolean,false,false,false
ion-datetime,prop,doneText,string,'Done',false,false
ion-datetime,prop,firstDayOfWeek,number,0,false,false
ion-datetime,prop,highlightedDates,((dateIsoString: string) => DatetimeHighlightStyle | undefined) | DatetimeHighlight[] | undefined,undefined,false,false
ion-datetime,prop,hourCycle,"h12" | "h23" | undefined,undefined,false,false
ion-datetime,prop,hourValues,number | number[] | string | undefined,undefined,false,false
ion-datetime,prop,isDateEnabled,((dateIsoString: string) => boolean) | undefined,undefined,false,false
Expand Down
11 changes: 10 additions & 1 deletion core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { AccordionGroupChangeEventDetail, ActionSheetAttributes, ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, BreadcrumbCollapsedClickEventDetail, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DatetimePresentation, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, LoadingAttributes, MenuChangeEventDetail, ModalAttributes, ModalBreakpointChangeEventDetail, ModalHandleBehavior, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerAttributes, PickerButton, PickerColumn, PopoverAttributes, PopoverSize, PositionAlign, PositionReference, PositionSide, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, TitleSelectedDatesFormatter, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, TriggerAction, ViewController } from "./interface";
import { AccordionGroupChangeEventDetail, ActionSheetAttributes, ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, BreadcrumbCollapsedClickEventDetail, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, LoadingAttributes, MenuChangeEventDetail, ModalAttributes, ModalBreakpointChangeEventDetail, ModalHandleBehavior, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerAttributes, PickerButton, PickerColumn, PopoverAttributes, PopoverSize, PositionAlign, PositionReference, PositionSide, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, TriggerAction, ViewController } from "./interface";
import { IonicSafeString } from "./utils/sanitization";
import { AlertAttributes } from "./components/alert/alert-interface";
import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimePresentation, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
import { CounterFormatter } from "./components/item/item-interface";
import { PickerColumnItem } from "./components/picker-column-internal/picker-column-internal-interfaces";
import { PickerInternalChangeEventDetail } from "./components/picker-internal/picker-internal-interfaces";
Expand Down Expand Up @@ -757,6 +758,10 @@ export namespace Components {
* The first day of the week to use for `ion-datetime`. The default value is `0` and represents Sunday.
*/
"firstDayOfWeek": number;
/**
* Used to apply custom text and background colors to specific dates. Can be either an array of objects containing ISO strings and colors, or a callback that receives an ISO string and returns the colors. Only applies to the `date`, `date-time`, and `time-date` presentations, with `preferWheel="false"`.
*/
"highlightedDates"?: DatetimeHighlight[] | DatetimeHighlightCallback;
/**
* The hour cycle of the `ion-datetime`. If no value is set, this is specified by the current locale.
*/
Expand Down Expand Up @@ -4723,6 +4728,10 @@ declare namespace LocalJSX {
* The first day of the week to use for `ion-datetime`. The default value is `0` and represents Sunday.
*/
"firstDayOfWeek"?: number;
/**
* Used to apply custom text and background colors to specific dates. Can be either an array of objects containing ISO strings and colors, or a callback that receives an ISO string and returns the colors. Only applies to the `date`, `date-time`, and `time-date` presentations, with `preferWheel="false"`.
*/
"highlightedDates"?: DatetimeHighlight[] | DatetimeHighlightCallback;
/**
* The hour cycle of the `ion-datetime`. If no value is set, this is specified by the current locale.
*/
Expand Down
14 changes: 14 additions & 0 deletions core/src/components/datetime/datetime-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,17 @@ export interface DatetimeParts {
export type DatetimePresentation = 'date-time' | 'time-date' | 'date' | 'time' | 'month' | 'year' | 'month-year';

export type TitleSelectedDatesFormatter = (selectedDates: string[]) => string;

export type DatetimeHighlightStyle =
| {
color: string;
backgroundColor?: string;
}
| {
color?: string;
backgroundColor: string;
};

export type DatetimeHighlight = { date: string } & DatetimeHighlightStyle;

export type DatetimeHighlightCallback = (dateIsoString: string) => DatetimeHighlightStyle | undefined;
17 changes: 7 additions & 10 deletions core/src/components/datetime/datetime.ios.scss
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,15 @@
font-size: 20px;
}

:host .calendar-day:after {
.calendar-day:focus .calendar-day-highlight,
.calendar-day.calendar-day-active .calendar-day-highlight {
// !important is needed to overwrite custom highlight background, which is inline.
/* stylelint-disable-next-line declaration-no-important */
background: current-color(base) !important;

opacity: 0.2;
}

:host .calendar-day:focus:after {
background: current-color(base);
}

/**
* Day that today but not selected
* should have ion-color for text color.
Expand All @@ -119,10 +120,6 @@
font-weight: 600;
}

:host .calendar-day.calendar-day-active:after {
background: current-color(base);
}

/**
* Day that is selected and is today
* should have white color.
Expand All @@ -131,7 +128,7 @@
color: current-color(contrast);
}

:host .calendar-day.calendar-day-today.calendar-day-active:after {
.calendar-day.calendar-day-today.calendar-day-active .calendar-day-highlight {
background: current-color(base);

opacity: 1;
Expand Down
6 changes: 3 additions & 3 deletions core/src/components/datetime/datetime.md.scss
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
font-size: $datetime-md-calendar-item-font-size;
}

:host .calendar-day:focus:after {
.calendar-day:focus .calendar-day-highlight {
background: current-color(base, 0.2);

box-shadow: 0px 0px 0px 4px current-color(base, 0.2);
Expand All @@ -88,7 +88,7 @@
color: current-color(base);
}

:host .calendar-day.calendar-day-today:after {
.calendar-day.calendar-day-today .calendar-day-highlight {
border: 1px solid current-color(base);
}

Expand All @@ -101,7 +101,7 @@
color: current-color(contrast);
}

:host .calendar-day.calendar-day-active:after {
.calendar-day.calendar-day-active .calendar-day-highlight {
border: 1px solid current-color(base);

background: current-color(base);
Expand Down
4 changes: 1 addition & 3 deletions core/src/components/datetime/datetime.scss
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ ion-picker-column-internal {
opacity: 0.4;
}

:host .calendar-day:after {
.calendar-day-highlight {
@include border-radius(32px, 32px, 32px, 32px);
@include padding(4px, 4px, 4px, 4px);

Expand All @@ -379,8 +379,6 @@ ion-picker-column-internal {

transform: translate(-50%, -50%);

content: " ";

z-index: -1;
}

Expand Down
73 changes: 61 additions & 12 deletions core/src/components/datetime/datetime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ import { Component, Element, Event, Host, Method, Prop, State, Watch, h, writeTa
import { caretDownSharp, caretUpSharp, chevronBack, chevronDown, chevronForward } from 'ionicons/icons';

import { getIonMode } from '../../global/ionic-global';
import type {
Color,
DatetimePresentation,
DatetimeChangeEventDetail,
DatetimeParts,
Mode,
StyleEventDetail,
TitleSelectedDatesFormatter,
} from '../../interface';
import type { Color, Mode, StyleEventDetail } from '../../interface';
import { startFocusVisible } from '../../utils/focus-visible';
import { getElementRoot, raf, renderHiddenInput } from '../../utils/helpers';
import { printIonError, printIonWarning } from '../../utils/logging';
import { isRTL } from '../../utils/rtl';
import { createColorClasses } from '../../utils/theme';
import type { PickerColumnItem } from '../picker-column-internal/picker-column-internal-interfaces';

import type {
DatetimePresentation,
DatetimeChangeEventDetail,
DatetimeParts,
TitleSelectedDatesFormatter,
DatetimeHighlight,
DatetimeHighlightStyle,
DatetimeHighlightCallback,
} from './datetime-interface';
import { isSameDay, warnIfValueOutOfBounds, isBefore, isAfter } from './utils/comparison';
import {
generateMonths,
Expand Down Expand Up @@ -60,6 +61,7 @@ import {
} from './utils/parse';
import {
getCalendarDayState,
getHighlightStyles,
isDayDisabled,
isMonthDisabled,
isNextMonthDisabled,
Expand Down Expand Up @@ -322,6 +324,17 @@ export class Datetime implements ComponentInterface {
*/
@Prop() multiple = false;

/**
* Used to apply custom text and background colors to specific dates.
*
* Can be either an array of objects containing ISO strings and colors,
* or a callback that receives an ISO string and returns the colors.
*
* Only applies to the `date`, `date-time`, and `time-date` presentations,
* with `preferWheel="false"`.
*/
@Prop() highlightedDates?: DatetimeHighlight[] | DatetimeHighlightCallback;

/**
* The value of the datetime as a valid ISO 8601 datetime string.
* Should be an array of strings if `multiple="true"`.
Expand Down Expand Up @@ -1235,7 +1248,7 @@ export class Datetime implements ComponentInterface {
};

componentWillLoad() {
const { el, multiple, presentation, preferWheel } = this;
const { el, highlightedDates, multiple, presentation, preferWheel } = this;

if (multiple) {
if (presentation !== 'date') {
Expand All @@ -1247,6 +1260,19 @@ export class Datetime implements ComponentInterface {
}
}

if (highlightedDates !== undefined) {
if (presentation !== 'date' && presentation !== 'date-time' && presentation !== 'time-date') {
printIonWarning(
'The highlightedDates property is only supported with the date, date-time, and time-date presentations.',
el
);
}

if (preferWheel) {
printIonWarning('The highlightedDates property is not supported with preferWheel="true".', el);
}
}

this.processMinParts();
this.processMaxParts();
const hourValues = (this.parsedHourValues = convertToArrayOfNumbers(this.hourValues));
Expand Down Expand Up @@ -1971,7 +1997,7 @@ export class Datetime implements ComponentInterface {
<div class="calendar-month-grid">
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => {
const { day, dayOfWeek } = dateObject;
const { isDateEnabled, multiple } = this;
const { el, highlightedDates, isDateEnabled, multiple } = this;
const referenceParts = { month, day, year };
const { isActive, isToday, ariaLabel, ariaSelected, disabled, text } = getCalendarDayState(
this.locale,
Expand All @@ -1983,6 +2009,7 @@ export class Datetime implements ComponentInterface {
this.parsedDayValues
);

const dateIsoString = convertDataToISO(referenceParts);
let isCalDayDisabled = isCalMonthDisabled || disabled;

if (!isCalDayDisabled && isDateEnabled !== undefined) {
Expand All @@ -1992,15 +2019,26 @@ export class Datetime implements ComponentInterface {
* to prevent exceptions in the user's function from
* interrupting the calendar rendering.
*/
isCalDayDisabled = !isDateEnabled(convertDataToISO(referenceParts));
isCalDayDisabled = !isDateEnabled(dateIsoString);
} catch (e) {
printIonError(
'Exception thrown from provided `isDateEnabled` function. Please check your function and try again.',
el,
e
);
}
}

let dateStyle: DatetimeHighlightStyle | undefined = undefined;

/**
* Custom highlight styles should not override the style for selected dates,
* nor apply to "filler days" at the start of the grid.
*/
if (highlightedDates !== undefined && !isActive && day !== null) {
dateStyle = getHighlightStyles(highlightedDates, dateIsoString, el);
}

return (
<button
tabindex="-1"
Expand All @@ -2016,6 +2054,11 @@ export class Datetime implements ComponentInterface {
'calendar-day-active': isActive,
'calendar-day-today': isToday,
}}
style={
dateStyle && {
color: dateStyle.color,
}
}
aria-selected={ariaSelected}
aria-label={ariaLabel}
onClick={() => {
Expand Down Expand Up @@ -2050,6 +2093,12 @@ export class Datetime implements ComponentInterface {
}
}}
>
<div
class="calendar-day-highlight"
style={{
backgroundColor: dateStyle?.backgroundColor,
}}
></div>
{text}
</button>
);
Expand Down
Loading