From 7725f93eb5199566219f0c96c0a379ce4a98f25b Mon Sep 17 00:00:00 2001 From: anveshmekala Date: Mon, 18 Mar 2024 18:23:34 -0500 Subject: [PATCH] feat(year-picker): adds year-picker component --- .../calcite-components/src/components.d.ts | 192 ++++++++++++ .../src/components/year-picker/readme.md | 49 +++ .../components/year-picker/year-picker.scss | 14 + .../components/year-picker/year-picker.tsx | 293 ++++++++++++++++++ packages/calcite-components/stencil.config.ts | 1 + 5 files changed, 549 insertions(+) create mode 100644 packages/calcite-components/src/components/year-picker/readme.md create mode 100644 packages/calcite-components/src/components/year-picker/year-picker.scss create mode 100644 packages/calcite-components/src/components/year-picker/year-picker.tsx diff --git a/packages/calcite-components/src/components.d.ts b/packages/calcite-components/src/components.d.ts index 8718f2d43d6..d2e55342937 100644 --- a/packages/calcite-components/src/components.d.ts +++ b/packages/calcite-components/src/components.d.ts @@ -3232,6 +3232,29 @@ export namespace Components { */ "widthScale": Scale; } + interface CalciteMonthPicker { + /** + * Focused date with indicator (will become selected date if user proceeds) + */ + "activeDate": Date; + "activeMonthIndex": number; + /** + * Specifies the latest allowed date (`"yyyy-mm-dd"`). + */ + "max": Date; + /** + * Specifies the earliest allowed date (`"yyyy-mm-dd"`). + */ + "min": Date; + /** + * Already selected date. + */ + "selectedMonthYear": Date; + } + interface CalciteMonthPickerItem { + "isActive": boolean; + "value": string; + } interface CalciteNavigation { /** * When `navigationAction` is `true`, specifies the label of the `calcite-action`. @@ -5440,6 +5463,38 @@ export namespace Components { */ "value": any; } + interface CalciteYearPicker { + /** + * When `true`, disables year's before the earliest allowed year in end year and after the latest year in start year of range. + */ + "disableYearsOutOfRange": boolean; + /** + * When `true`, disables the component + */ + "disabled": boolean; + /** + * Specifies the latest allowed year (`"yyyy"`). + */ + "max": number; + /** + * Specifies the earliest allowed year (`"yyyy"`). + */ + "min": number; + "nextYear": () => Promise; + /** + * Specifies the Unicode numeral system used by the component for localization. + */ + "numberingSystem": NumberingSystem; + "prevYear": () => Promise; + /** + * When `true`, activates the component's range mode to allow a start and end year. + */ + "range": boolean; + /** + * Specifies the selected year as a string (`"yyyy"`), or an array of strings for `range` values (`["yyyy", "yyyy"]`). + */ + "value": number | number[]; + } } export interface CalciteAccordionCustomEvent extends CustomEvent { detail: T; @@ -5601,6 +5656,14 @@ export interface CalciteModalCustomEvent extends CustomEvent { detail: T; target: HTMLCalciteModalElement; } +export interface CalciteMonthPickerCustomEvent extends CustomEvent { + detail: T; + target: HTMLCalciteMonthPickerElement; +} +export interface CalciteMonthPickerItemCustomEvent extends CustomEvent { + detail: T; + target: HTMLCalciteMonthPickerItemElement; +} export interface CalciteNavigationCustomEvent extends CustomEvent { detail: T; target: HTMLCalciteNavigationElement; @@ -5753,6 +5816,10 @@ export interface CalciteValueListItemCustomEvent extends CustomEvent { detail: T; target: HTMLCalciteValueListItemElement; } +export interface CalciteYearPickerCustomEvent extends CustomEvent { + detail: T; + target: HTMLCalciteYearPickerElement; +} declare global { interface HTMLCalciteAccordionElementEventMap { "calciteInternalAccordionChange": RequestedItem; @@ -6613,6 +6680,40 @@ declare global { prototype: HTMLCalciteModalElement; new (): HTMLCalciteModalElement; }; + interface HTMLCalciteMonthPickerElementEventMap { + "calciteMonthPickerChange": void; + } + interface HTMLCalciteMonthPickerElement extends Components.CalciteMonthPicker, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLCalciteMonthPickerElement, ev: CalciteMonthPickerCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLCalciteMonthPickerElement, ev: CalciteMonthPickerCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLCalciteMonthPickerElement: { + prototype: HTMLCalciteMonthPickerElement; + new (): HTMLCalciteMonthPickerElement; + }; + interface HTMLCalciteMonthPickerItemElementEventMap { + "calciteInternalMonthPickerItemSelect": string; + } + interface HTMLCalciteMonthPickerItemElement extends Components.CalciteMonthPickerItem, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLCalciteMonthPickerItemElement, ev: CalciteMonthPickerItemCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLCalciteMonthPickerItemElement, ev: CalciteMonthPickerItemCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLCalciteMonthPickerItemElement: { + prototype: HTMLCalciteMonthPickerItemElement; + new (): HTMLCalciteMonthPickerItemElement; + }; interface HTMLCalciteNavigationElementEventMap { "calciteNavigationActionSelect": void; } @@ -7425,6 +7526,23 @@ declare global { prototype: HTMLCalciteValueListItemElement; new (): HTMLCalciteValueListItemElement; }; + interface HTMLCalciteYearPickerElementEventMap { + "calciteYearPickerChange": void; + } + interface HTMLCalciteYearPickerElement extends Components.CalciteYearPicker, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLCalciteYearPickerElement, ev: CalciteYearPickerCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLCalciteYearPickerElement, ev: CalciteYearPickerCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLCalciteYearPickerElement: { + prototype: HTMLCalciteYearPickerElement; + new (): HTMLCalciteYearPickerElement; + }; interface HTMLElementTagNameMap { "calcite-accordion": HTMLCalciteAccordionElement; "calcite-accordion-item": HTMLCalciteAccordionItemElement; @@ -7481,6 +7599,8 @@ declare global { "calcite-menu-item": HTMLCalciteMenuItemElement; "calcite-meter": HTMLCalciteMeterElement; "calcite-modal": HTMLCalciteModalElement; + "calcite-month-picker": HTMLCalciteMonthPickerElement; + "calcite-month-picker-item": HTMLCalciteMonthPickerItemElement; "calcite-navigation": HTMLCalciteNavigationElement; "calcite-navigation-logo": HTMLCalciteNavigationLogoElement; "calcite-navigation-user": HTMLCalciteNavigationUserElement; @@ -7534,6 +7654,7 @@ declare global { "calcite-tree-item": HTMLCalciteTreeItemElement; "calcite-value-list": HTMLCalciteValueListElement; "calcite-value-list-item": HTMLCalciteValueListItemElement; + "calcite-year-picker": HTMLCalciteYearPickerElement; } } declare namespace LocalJSX { @@ -10782,6 +10903,37 @@ declare namespace LocalJSX { */ "widthScale"?: Scale; } + interface CalciteMonthPicker { + /** + * Focused date with indicator (will become selected date if user proceeds) + */ + "activeDate"?: Date; + "activeMonthIndex"?: number; + /** + * Specifies the latest allowed date (`"yyyy-mm-dd"`). + */ + "max"?: Date; + /** + * Specifies the earliest allowed date (`"yyyy-mm-dd"`). + */ + "min"?: Date; + /** + * Emits whenever the component is selected. + */ + "onCalciteMonthPickerChange"?: (event: CalciteMonthPickerCustomEvent) => void; + /** + * Already selected date. + */ + "selectedMonthYear"?: Date; + } + interface CalciteMonthPickerItem { + "isActive"?: boolean; + /** + * Emits whenever the component is selected. + */ + "onCalciteInternalMonthPickerItemSelect"?: (event: CalciteMonthPickerItemCustomEvent) => void; + "value"?: string; + } interface CalciteNavigation { /** * When `navigationAction` is `true`, specifies the label of the `calcite-action`. @@ -13065,6 +13217,40 @@ declare namespace LocalJSX { */ "value": any; } + interface CalciteYearPicker { + /** + * When `true`, disables year's before the earliest allowed year in end year and after the latest year in start year of range. + */ + "disableYearsOutOfRange"?: boolean; + /** + * When `true`, disables the component + */ + "disabled"?: boolean; + /** + * Specifies the latest allowed year (`"yyyy"`). + */ + "max"?: number; + /** + * Specifies the earliest allowed year (`"yyyy"`). + */ + "min"?: number; + /** + * Specifies the Unicode numeral system used by the component for localization. + */ + "numberingSystem"?: NumberingSystem; + /** + * Emits whenever the component is selected. + */ + "onCalciteYearPickerChange"?: (event: CalciteYearPickerCustomEvent) => void; + /** + * When `true`, activates the component's range mode to allow a start and end year. + */ + "range"?: boolean; + /** + * Specifies the selected year as a string (`"yyyy"`), or an array of strings for `range` values (`["yyyy", "yyyy"]`). + */ + "value"?: number | number[]; + } interface IntrinsicElements { "calcite-accordion": CalciteAccordion; "calcite-accordion-item": CalciteAccordionItem; @@ -13121,6 +13307,8 @@ declare namespace LocalJSX { "calcite-menu-item": CalciteMenuItem; "calcite-meter": CalciteMeter; "calcite-modal": CalciteModal; + "calcite-month-picker": CalciteMonthPicker; + "calcite-month-picker-item": CalciteMonthPickerItem; "calcite-navigation": CalciteNavigation; "calcite-navigation-logo": CalciteNavigationLogo; "calcite-navigation-user": CalciteNavigationUser; @@ -13174,6 +13362,7 @@ declare namespace LocalJSX { "calcite-tree-item": CalciteTreeItem; "calcite-value-list": CalciteValueList; "calcite-value-list-item": CalciteValueListItem; + "calcite-year-picker": CalciteYearPicker; } } export { LocalJSX as JSX }; @@ -13238,6 +13427,8 @@ declare module "@stencil/core" { "calcite-menu-item": LocalJSX.CalciteMenuItem & JSXBase.HTMLAttributes; "calcite-meter": LocalJSX.CalciteMeter & JSXBase.HTMLAttributes; "calcite-modal": LocalJSX.CalciteModal & JSXBase.HTMLAttributes; + "calcite-month-picker": LocalJSX.CalciteMonthPicker & JSXBase.HTMLAttributes; + "calcite-month-picker-item": LocalJSX.CalciteMonthPickerItem & JSXBase.HTMLAttributes; "calcite-navigation": LocalJSX.CalciteNavigation & JSXBase.HTMLAttributes; "calcite-navigation-logo": LocalJSX.CalciteNavigationLogo & JSXBase.HTMLAttributes; "calcite-navigation-user": LocalJSX.CalciteNavigationUser & JSXBase.HTMLAttributes; @@ -13306,6 +13497,7 @@ declare module "@stencil/core" { * @deprecated Use the `list` component instead. */ "calcite-value-list-item": LocalJSX.CalciteValueListItem & JSXBase.HTMLAttributes; + "calcite-year-picker": LocalJSX.CalciteYearPicker & JSXBase.HTMLAttributes; } } } diff --git a/packages/calcite-components/src/components/year-picker/readme.md b/packages/calcite-components/src/components/year-picker/readme.md new file mode 100644 index 00000000000..744ed01ab30 --- /dev/null +++ b/packages/calcite-components/src/components/year-picker/readme.md @@ -0,0 +1,49 @@ +# calcite-year-picker + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ------------------------ | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ----------- | +| `disableYearsOutOfRange` | `disable-years-out-of-range` | When `true`, disables year's before the earliest allowed year in end year and after the latest year in start year of range. | `boolean` | `false` | +| `disabled` | `disabled` | When `true`, disables the component | `boolean` | `undefined` | +| `max` | `max` | Specifies the latest allowed year (`"yyyy"`). | `number` | `2100` | +| `min` | `min` | Specifies the earliest allowed year (`"yyyy"`). | `number` | `1900` | +| `numberingSystem` | `numbering-system` | Specifies the Unicode numeral system used by the component for localization. | `"arab" \| "arabext" \| "latn"` | `undefined` | +| `range` | `range` | When `true`, activates the component's range mode to allow a start and end year. | `boolean` | `undefined` | +| `value` | `value` | Specifies the selected year as a string (`"yyyy"`), or an array of strings for `range` values (`["yyyy", "yyyy"]`). | `number \| number[]` | `undefined` | + +## Events + +| Event | Description | Type | +| ------------------------- | ----------------------------------------- | ------------------- | +| `calciteYearPickerChange` | Emits whenever the component is selected. | `CustomEvent` | + +## Methods + +### `nextYear() => Promise` + +#### Returns + +Type: `Promise` + +### `prevYear() => Promise` + +#### Returns + +Type: `Promise` + +## Dependencies + +### Used by + +- [calcite-date-picker-month-header](../date-picker-month-header) +- [calcite-month-picker](../month-picker) + +### Depends on + +- [calcite-select](../select) +- [calcite-option](../option) + +--- diff --git a/packages/calcite-components/src/components/year-picker/year-picker.scss b/packages/calcite-components/src/components/year-picker/year-picker.scss new file mode 100644 index 00000000000..d65952fa956 --- /dev/null +++ b/packages/calcite-components/src/components/year-picker/year-picker.scss @@ -0,0 +1,14 @@ +:host { + display: flex; + flex-direction: row; + inline-size: 100%; +} + +.start-year, +.end-year { + inline-size: 50%; +} + +.year { + inline-size: 100%; +} diff --git a/packages/calcite-components/src/components/year-picker/year-picker.tsx b/packages/calcite-components/src/components/year-picker/year-picker.tsx new file mode 100644 index 00000000000..b2e51c5a958 --- /dev/null +++ b/packages/calcite-components/src/components/year-picker/year-picker.tsx @@ -0,0 +1,293 @@ +import { + Component, + VNode, + h, + Element, + Prop, + State, + Watch, + Event, + EventEmitter, + Host, + Method, +} from "@stencil/core"; +import { + LocalizedComponent, + NumberingSystem, + connectLocalized, + disconnectLocalized, + numberStringFormatter, +} from "../../utils/locale"; +import { DateLocaleData, getLocaleData } from "../date-picker/utils"; +// import { DateLocaleData } from "../date-picker/utils"; + +@Component({ + tag: "calcite-year-picker", + styleUrl: "year-picker.scss", + shadow: true, +}) +export class YearPicker implements LocalizedComponent { + //-------------------------------------------------------------------------- + // + // Public Properties + // + //-------------------------------------------------------------------------- + + /** When `true`, disables the component */ + @Prop({ reflect: true }) disabled: boolean; + + /** When `true`, disables year's before the earliest allowed year in end year and after the latest year in start year of range. */ + @Prop() disableYearsOutOfRange = false; + + /** Specifies the latest allowed year (`"yyyy"`). */ + @Prop({ mutable: true }) max = 2100; + + /** Specifies the earliest allowed year (`"yyyy"`). */ + @Prop({ mutable: true }) min = 1900; + + @Watch("min") + handleMinChange(value: number): void { + if (!value) { + this.min = 1900; + return; + } + this.getYearList(); + } + + @Watch("max") + handleMaxChange(value: number): void { + if (!value) { + this.max = 2100; + return; + } + this.getYearList(); + } + + /** + * Specifies the Unicode numeral system used by the component for localization. + */ + @Prop({ reflect: true }) numberingSystem: NumberingSystem; + + /** When `true`, activates the component's range mode to allow a start and end year. */ + @Prop() range: boolean; + + /** + * Specifies the selected year as a string (`"yyyy"`), or an array of strings for `range` values (`["yyyy", "yyyy"]`). + */ + @Prop({ mutable: true }) value: number | number[]; + + @Watch("value") + handleValueChange(value: number | number[]): void { + if (Array.isArray(value)) { + this.startYear = value[0]; + this.endYear = value[1]; + } else if (value) { + this.startYear = value; + } + } + + //-------------------------------------------------------------------------- + // + // Events + // + //-------------------------------------------------------------------------- + + /** + * Emits whenever the component is selected. + * + */ + @Event() calciteYearPickerChange: EventEmitter; + + // -------------------------------------------------------------------------- + // + // Lifecycle + // + // -------------------------------------------------------------------------- + + connectedCallback() { + connectLocalized(this); + if (Array.isArray(this.value)) { + if (this.range) { + this.startYear = this.value[0]; + this.endYear = this.value[1]; + } + } else { + this.startYear = this.value; + } + if (!this.value) { + this.value = new Date().getFullYear(); + } + this.getYearList(); + } + + disconnectedCallback() { + disconnectLocalized(this); + } + + @Method() + async prevYear(): Promise { + if (Array.isArray(this.value)) { + this.value = + this.activeRange === "start" + ? [this.value[0] - 1, this.value[1]] + : [this.value[0], this.value[1] - 1]; + } else { + this.value = this.value - 1; + } + } + + @Method() + async nextYear(): Promise { + // console.log("next year", this.value); + if (Array.isArray(this.value)) { + this.value = + this.activeRange === "start" + ? [this.value[0] + 1, this.value[1]] + : [this.value[0], this.value[1] + 1]; + } else { + this.value = this.value + 1; + } + } + //-------------------------------------------------------------------------- + // + // Private State/Props + // + //-------------------------------------------------------------------------- + + @Element() el: HTMLCalciteYearPickerElement; + + @State() effectiveLocale = ""; + + @Watch("effectiveLocale") + @Watch("numberingSystem") + async updateNumberStringFormatter(): Promise { + numberStringFormatter.numberFormatOptions = { + locale: this.effectiveLocale, + numberingSystem: this.numberingSystem, + useGrouping: false, + }; + + this.localeData = await getLocaleData(this.effectiveLocale); + } + + @State() yearList: number[] = []; + + private endYear: number; + + private startYear: number; + + private activeRange: "start" | "end" = "start"; + + @State() localeData: DateLocaleData; + + // maxValueSelectEl: HTMLCalciteSelectElement; + + // selectEl: HTMLCalciteSelectElement; + + //-------------------------------------------------------------------------- + // + // Private Methods + // + //-------------------------------------------------------------------------- + + getYearList(): void { + this.yearList = []; + for (let i = this.min; i <= this.max; i++) { + this.yearList.push(i); + } + } + + handleSelectChange = (event: CustomEvent): void => { + event.stopPropagation(); + + this.activeRange = "start"; + const target = event.target as HTMLCalciteSelectElement; + const newValue = Number(target.value); + + if (this.range && Array.isArray(this.value)) { + this.value = [newValue, this.value[1]]; + this.startYear = newValue; + } else { + this.value = newValue; + } + + this.calciteYearPickerChange.emit(); + }; + + handleEndYearSelectChange = (event: CustomEvent): void => { + event.stopPropagation(); + const target = event.target as HTMLCalciteSelectElement; + const newValue = Number(target.value); + this.activeRange = "end"; + + if (Array.isArray(this.value)) { + this.value = [this.value[0], newValue]; + this.endYear = newValue; + } + + this.calciteYearPickerChange.emit(); + }; + + private isYearSelected(year: number): boolean { + return !Array.isArray(this.value) ? year === this.value : false; + } + + // setSelectEl = (el: HTMLCalciteSelectElement): void => { + // this.selectEl = el; + // }; + + // setMaxValueSelectEl = (el: HTMLCalciteSelectElement): void => { + // this.maxValueSelectEl = el; + // }; + + render(): VNode { + const suffix = this.localeData?.year?.suffix; + return ( + + + {this.yearList?.map((year: number) => { + const yearString = year.toString(); + return ( + this.endYear && this.disableYearsOutOfRange} + selected={(this.range && year === this.startYear) || this.isYearSelected(year)} + value={yearString} + > + {numberStringFormatter?.localize(yearString)} + {suffix} + + ); + })} + + {this.range && ( + + {this.yearList?.map((year: number) => { + const yearString = year.toString(); + return ( + + {numberStringFormatter?.localize(yearString)} + + ); + })} + + )} + + ); + } +} diff --git a/packages/calcite-components/stencil.config.ts b/packages/calcite-components/stencil.config.ts index 22f2096c1f2..3ce63045417 100644 --- a/packages/calcite-components/stencil.config.ts +++ b/packages/calcite-components/stencil.config.ts @@ -53,6 +53,7 @@ export const create: () => Config = () => ({ { components: ["calcite-list", "calcite-list-item", "calcite-list-item-group"] }, { components: ["calcite-loader"] }, { components: ["calcite-meter"] }, + { components: ["calcite-year-picker"] }, { components: ["calcite-modal"] }, { components: ["calcite-navigation", "calcite-navigation-user", "calcite-navigation-logo"] }, { components: ["calcite-menu", "calcite-menu-item"] },