Skip to content

Commit

Permalink
feat(datetime): add ability to specify custom colors for specific dat…
Browse files Browse the repository at this point in the history
…es (#26775)
  • Loading branch information
amandaejohnston committed Feb 22, 2023
1 parent fc5fcc0 commit 2a761af
Show file tree
Hide file tree
Showing 30 changed files with 363 additions and 43 deletions.
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 @@ -4727,6 +4732,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 =
| {
textColor: string;
backgroundColor?: string;
}
| {
textColor?: string;
backgroundColor: string;
};

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

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

:host .calendar-day:after {
.calendar-day:focus .calendar-day-highlight,
.calendar-day.calendar-day-active .calendar-day-highlight {
opacity: 0.2;
}

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

// !important is needed here to overwrite custom highlight background, which is inline.
// Does not apply to the active state because highlights aren't applied at all there.
.calendar-day:focus .calendar-day-highlight {
/* stylelint-disable-next-line declaration-no-important */
background: current-color(base) !important;
}

/**
* Day that today but not selected
* should have ion-color for text color.
Expand All @@ -119,10 +127,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 +135,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
18 changes: 1 addition & 17 deletions core/src/components/datetime/datetime.scss
Original file line number Diff line number Diff line change
Expand Up @@ -356,31 +356,15 @@ 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);

position: absolute;

/**
* Explicit position values are required here
* as pseudo element positioning is incorrect
* in older implementations of css grid.
*
* TODO: FW-1720: Remove top/left styles when deprecating iOS 13 support
*/
/* stylelint-disable-next-line property-disallowed-list */
top: 50%;
/* stylelint-disable-next-line property-disallowed-list */
left: 50%;

width: 32px;
height: 32px;

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.textColor,
}
}
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

0 comments on commit 2a761af

Please sign in to comment.