From 7a9719d4df9dddce51f6946d313af5bbf3de8a8a Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso Date: Fri, 14 Aug 2026 08:43:43 +0200 Subject: [PATCH 01/12] Year Picker fixes --- .../lib/src/date-input/DateInput.stories.tsx | 50 +++++- .../lib/src/date-input/DateInput.test.tsx | 73 +++++++- packages/lib/src/date-input/DatePicker.tsx | 78 ++++++++- .../lib/src/date-input/DateUnitPicker.tsx | 162 ++++++++++++++++++ packages/lib/src/date-input/YearPicker.tsx | 121 ------------- packages/lib/src/date-input/types.ts | 15 +- 6 files changed, 360 insertions(+), 139 deletions(-) create mode 100644 packages/lib/src/date-input/DateUnitPicker.tsx delete mode 100644 packages/lib/src/date-input/YearPicker.tsx diff --git a/packages/lib/src/date-input/DateInput.stories.tsx b/packages/lib/src/date-input/DateInput.stories.tsx index 23a5f373b3..b465118fb3 100644 --- a/packages/lib/src/date-input/DateInput.stories.tsx +++ b/packages/lib/src/date-input/DateInput.stories.tsx @@ -8,7 +8,7 @@ import DxcContainer from "../container/Container"; import Calendar from "./Calendar"; import DxcDateInput from "./DateInput"; import DxcDatePicker from "./DatePicker"; -import YearPicker from "./YearPicker"; +import DateUnitPicker from "./DateUnitPicker"; import { fireEvent, screen, userEvent, within } from "storybook/internal/test"; import { HalstackProvider } from "../HalstackContext"; @@ -169,24 +169,24 @@ const DatePickerButtonStates = () => ( const YearPickerButtonStates = () => ( <> - - <YearPicker + <Title title="Isolated date unit picker focused" theme="light" level={4} /> + <DateUnitPicker selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} onYearSelect={() => {}} today={dayjs("1904-04-03", "YYYY-MM-DD")} /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Isolated year picker hovered" theme="light" level={4} /> - <YearPicker + <Title title="Isolated date unit picker hovered" theme="light" level={4} /> + <DateUnitPicker selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} onYearSelect={() => {}} today={dayjs("1904-04-03", "YYYY-MM-DD")} /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-active"> - <Title title="Isolated year picker actived" theme="light" level={4} /> - <YearPicker + <Title title="Isolated date unit picker actived" theme="light" level={4} /> + <DateUnitPicker selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} onYearSelect={() => {}} today={dayjs("1904-04-03", "YYYY-MM-DD")} @@ -208,8 +208,17 @@ const DatePickerToday = () => ( /> </ExampleContainer> <ExampleContainer> - <Title title="Isolated year picker with today" theme="light" level={4} /> - <YearPicker + <Title title="Isolated month picker with today" theme="light" level={4} /> + <DateUnitPicker + selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} + onYearSelect={() => {}} + isMonth={true} + today={dayjs("1904-04-03", "YYYY-MM-DD")} + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Isolated date unit picker with today" theme="light" level={4} /> + <DateUnitPicker selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} onYearSelect={() => {}} today={dayjs("1904-04-03", "YYYY-MM-DD")} @@ -227,6 +236,15 @@ const Tooltip = () => ( </> ); +const YearMonthPickerZhCn = () => ( + <ExampleContainer> + <Title title="Year/Month picker order zh-CN" theme="light" level={4} /> + <HalstackProvider localeTag="zh-CN"> + <DxcDateInput label="Date input" defaultValue="06-04-1905" /> + </HalstackProvider> + </ExampleContainer> +); + type Story = StoryObj<typeof DxcDateInput>; export const Chromatic: Story = { @@ -290,3 +308,17 @@ export const DatePickerTooltipAfter: Story = { } }, }; + +export const YearMonthPickerWithLocale: Story = { + render: YearMonthPickerZhCn, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const calendarAction = await canvas.findByRole("combobox"); + await userEvent.click(calendarAction); + const dialog = await screen.findByRole("dialog"); + const yearMonthTrigger = within(dialog).getAllByRole("button")[1]; + if (yearMonthTrigger != null) { + await userEvent.click(yearMonthTrigger); + } + }, +}; diff --git a/packages/lib/src/date-input/DateInput.test.tsx b/packages/lib/src/date-input/DateInput.test.tsx index b954489a2a..d1a4d045a7 100644 --- a/packages/lib/src/date-input/DateInput.test.tsx +++ b/packages/lib/src/date-input/DateInput.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render } from "@testing-library/react"; +import { fireEvent, render, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import dayjs from "dayjs"; import DxcDateInput from "./DateInput"; @@ -214,6 +214,77 @@ describe("DateInput component tests", () => { fireEvent.keyDown(document, { key: "Escape", code: "Escape", keyCode: 27, charCode: 27 }); expect(input.value).toBe(d.format("DD-MM-YYYY")); }); + test("YearMonthPicker opens with no selected options", () => { + const { getByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); + userEvent.click(getByRole("combobox")); + userEvent.click(getByRole("button", { name: "August 2021" })); + + const yearListbox = getByRole("listbox", { name: "Year Picker" }); + const monthListbox = getByRole("listbox", { name: "Month Picker" }); + + const selectedYearOptions = within(yearListbox) + .getAllByRole("option") + .filter((option) => option.getAttribute("aria-selected") === "true"); + const selectedMonthOptions = within(monthListbox) + .getAllByRole("option") + .filter((option) => option.getAttribute("aria-selected") === "true"); + + expect(selectedYearOptions).toHaveLength(0); + expect(selectedMonthOptions).toHaveLength(0); + }); + test("YearMonthPicker focuses the left column by default on open", () => { + const { getByRole, getAllByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); + userEvent.click(getByRole("combobox")); + userEvent.click(getByRole("button", { name: "August 2021" })); + + const [leftListbox] = getAllByRole("listbox"); + expect(leftListbox.contains(document.activeElement)).toBeTruthy(); + }); + test("Selecting month and then year closes the YearMonthPicker", () => { + const { getByRole, queryByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); + userEvent.click(getByRole("combobox")); + userEvent.click(getByRole("button", { name: "August 2021" })); + + const monthListbox = getByRole("listbox", { name: "Month Picker" }); + const yearListbox = getByRole("listbox", { name: "Year Picker" }); + + userEvent.click(within(monthListbox).getByRole("option", { name: "September" })); + expect(getByRole("listbox", { name: "Year Picker" })).toBeTruthy(); + + userEvent.click(within(yearListbox).getByRole("option", { name: "2024" })); + expect(queryByRole("listbox", { name: "Year Picker" })).toBeFalsy(); + expect(queryByRole("listbox", { name: "Month Picker" })).toBeFalsy(); + }); + test("Selecting year and then month closes the YearMonthPicker", () => { + const { getByRole, queryByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); + userEvent.click(getByRole("combobox")); + userEvent.click(getByRole("button", { name: "August 2021" })); + + const monthListbox = getByRole("listbox", { name: "Month Picker" }); + const yearListbox = getByRole("listbox", { name: "Year Picker" }); + + userEvent.click(within(yearListbox).getByRole("option", { name: "2024" })); + expect(getByRole("listbox", { name: "Month Picker" })).toBeTruthy(); + + userEvent.click(within(monthListbox).getByRole("option", { name: "September" })); + expect(queryByRole("listbox", { name: "Year Picker" })).toBeFalsy(); + expect(queryByRole("listbox", { name: "Month Picker" })).toBeFalsy(); + }); + test("Selecting January (month 0) and then year closes the YearMonthPicker", () => { + const { getByRole, queryByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); + userEvent.click(getByRole("combobox")); + userEvent.click(getByRole("button", { name: "August 2021" })); + + const monthListbox = getByRole("listbox", { name: "Month Picker" }); + const yearListbox = getByRole("listbox", { name: "Year Picker" }); + + userEvent.click(within(monthListbox).getByRole("option", { name: "January" })); + expect(getByRole("listbox", { name: "Year Picker" })).toBeTruthy(); + + userEvent.click(within(yearListbox).getByRole("option", { name: "2024" })); + expect(queryByRole("listbox", { name: "Year Picker" })).toBeFalsy(); + expect(queryByRole("listbox", { name: "Month Picker" })).toBeFalsy(); + }); test("Selecting a date from the calendar (using keyboard presses)", () => { const { getByRole, getAllByText, getByText } = render(<DxcDateInput />); const calendarAction = getByRole("combobox"); diff --git a/packages/lib/src/date-input/DatePicker.tsx b/packages/lib/src/date-input/DatePicker.tsx index af68c49ff5..8bea633a02 100644 --- a/packages/lib/src/date-input/DatePicker.tsx +++ b/packages/lib/src/date-input/DatePicker.tsx @@ -3,10 +3,11 @@ import dayjs, { Dayjs } from "dayjs"; import styled from "@emotion/styled"; import { DatePickerPropsType } from "./types"; import Calendar from "./Calendar"; -import YearPicker from "./YearPicker"; +import DateUnitPicker from "./DateUnitPicker"; import DxcIcon from "../icon/Icon"; import { Tooltip } from "../tooltip/Tooltip"; import { HalstackLanguageContext } from "../HalstackContext"; +import { getFormatFromLocale, validateLocale } from "./utils"; const DatePickerContainer = styled.div` padding: var(--spacing-padding-m) var(--spacing-padding-xs) var(--spacing-padding-xs) var(--spacing-padding-xs); @@ -78,28 +79,82 @@ const HeaderYearTriggerLabel = styled.span` font-size: var(--typography-label-m); `; +const YearMonthPickersContainer = styled.div` + display: flex; + width: 292px; + justify-content: center; +`; + const today = dayjs(); const DatePicker = ({ date, onDateSelect, id }: DatePickerPropsType): JSX.Element => { const [innerDate, setInnerDate] = useState(date?.isValid() ? date : dayjs()); const [content, setContent] = useState("calendar"); const selectedDate = date?.isValid() ? date : dayjs(null); - const translatedLabels = useContext(HalstackLanguageContext).labels; + const languageContext = useContext(HalstackLanguageContext); + const translatedLabels = languageContext.labels; + const localeTag = languageContext.locale; + const localeFormat = localeTag && validateLocale(localeTag) ? getFormatFromLocale(localeTag) : "MM/dd/yyyy"; + const isYearFirst = localeFormat.indexOf("yyyy") < localeFormat.indexOf("MM"); const handleDateSelect = (chosenDate: Dayjs) => { setInnerDate(chosenDate); onDateSelect(chosenDate); }; + const closeYearMonthPickerIfCompleted = (year: number | null, month: number | null) => { + if (year != null && month != null) { + setInnerDate((prevDate) => prevDate.set("year", year).set("month", month)); + setContent("calendar"); + } + }; + const handleOnYearSelect = (year: number) => { setInnerDate(innerDate.set("year", year)); - setContent("calendar"); + closeYearMonthPickerIfCompleted(year, content === "yearPickerMonthSelected" ? innerDate.get("month") : null); + if (content === "yearPicker") { + setContent("yearPickerYearSelected"); + } + }; + + const handleOnMonthSelect = (month: number) => { + setInnerDate(innerDate.set("month", month)); + closeYearMonthPickerIfCompleted(content === "yearPickerYearSelected" ? innerDate.get("year") : null, month); + if (content === "yearPicker") { + setContent("yearPickerMonthSelected"); + } }; const handleMonthChange = (chosenDate: Dayjs) => { setInnerDate(chosenDate); }; + const yearPickerSelectedDate = + content === "yearPicker" || content === "yearPickerMonthSelected" ? dayjs(null) : innerDate; + const monthPickerSelectedDate = + content === "yearPicker" || content === "yearPickerYearSelected" ? dayjs(null) : innerDate; + + const yearPicker = ( + <DateUnitPicker + selectedDate={yearPickerSelectedDate} + focusValue={innerDate.get("year")} + onYearSelect={handleOnYearSelect} + today={today} + autoFocus={content === "yearPicker" && isYearFirst} + /> + ); + + const monthPicker = ( + <DateUnitPicker + selectedDate={monthPickerSelectedDate} + focusValue={innerDate.get("month")} + onYearSelect={handleOnMonthSelect} + today={today} + isMonth + autoFocus={content === "yearPicker" && !isYearFirst} + /> + ); + return ( <DatePickerContainer id={id}> <PickerHeader> @@ -114,13 +169,19 @@ const DatePicker = ({ date, onDateSelect, id }: DatePickerPropsType): JSX.Elemen </Tooltip> <HeaderYearTrigger aria-live="polite" - onClick={() => setContent((currentContent) => (currentContent === "yearPicker" ? "calendar" : "yearPicker"))} + onClick={() => { + if (content === "calendar") { + setContent("yearPicker"); + } else { + setContent("calendar"); + } + }} type="button" > <HeaderYearTriggerLabel> {translatedLabels.calendar.months[innerDate.get("month")]} {innerDate.format("YYYY")} </HeaderYearTriggerLabel> - <DxcIcon icon={content === "yearPicker" ? "arrow_drop_up" : "arrow_drop_down"} /> + <DxcIcon icon={content === "calendar" ? "arrow_drop_down" : "arrow_drop_up"} /> </HeaderYearTrigger> <Tooltip label={translatedLabels.calendar.nextMonthTitle}> <HeaderButton @@ -141,8 +202,11 @@ const DatePicker = ({ date, onDateSelect, id }: DatePickerPropsType): JSX.Elemen today={today} /> )} - {content === "yearPicker" && ( - <YearPicker selectedDate={selectedDate} onYearSelect={handleOnYearSelect} today={today} /> + {content !== "calendar" && ( + <YearMonthPickersContainer> + {isYearFirst ? yearPicker : monthPicker} + {isYearFirst ? monthPicker : yearPicker} + </YearMonthPickersContainer> )} </DatePickerContainer> ); diff --git a/packages/lib/src/date-input/DateUnitPicker.tsx b/packages/lib/src/date-input/DateUnitPicker.tsx new file mode 100644 index 0000000000..d5dcd520cc --- /dev/null +++ b/packages/lib/src/date-input/DateUnitPicker.tsx @@ -0,0 +1,162 @@ +import dayjs from "dayjs"; +import { KeyboardEvent, useContext, useEffect, useId, useMemo, useState } from "react"; +import styled from "@emotion/styled"; +import { DateUnitPickerPropsType } from "./types"; +import scrollbarStyles from "../styles/scroll"; +import { HalstackLanguageContext } from "../HalstackContext"; + +const PickerContainer = styled.div<{ isMonth?: boolean }>` + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: var(--spacing-gap-xs); + align-items: center; + overflow-y: scroll; + width: ${({ isMonth }) => (isMonth ? "60%" : "40%")}; + max-width: 292px; + height: 312px; + box-shadow: var(--shadow-200); + ${scrollbarStyles} +`; + +const PickerButton = styled.button<{ + selected: boolean; + isCurrent: boolean; +}>` + min-width: fit-content; + display: flex; + align-items: center; + justify-content: center; + width: 80px; + min-height: var(--height-m); + height: var(--height-m); + background-color: transparent; + border: none; + border-radius: var(--border-radius-xl); + cursor: pointer; + font-family: var(--typography-font-family); + font-size: var(--typography-label-m); + color: var(--color-fg-neutral-dark); + font-weight: var(--typography-label-regular); + + ${(props) => + props.selected + ? `font-size: var(--typography-label-xl); + color: var(--color-fg-neutral-bright) !important; + background-color: var(--color-bg-primary-strong) !important;` + : props.isCurrent + ? `border: var(--border-width-s) var(--border-style-default) var(--border-color-primary-lighter); + color: var(--color-fg-primary-strong);` + : ``} + + &:hover, &:focus, &:active { + font-size: var(--typography-label-xl); + } + &:hover { + background-color: var(--color-bg-primary-light); + color: var(--color-fg-neutral-dark); + } + &:focus { + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); + } + &:active { + color: var(--color-fg-neutral-bright); + background-color: var(--color-bg-primary-stronger); + } +`; + +const getYearsArray = () => { + const yearList = []; + for (let i = 1899; i <= 2100; i++) { + yearList.push(i); + } + return yearList; +}; + +const yearList = getYearsArray(); + +const DateUnitPicker = ({ + onYearSelect, + selectedDate, + today, + focusValue, + isMonth, + autoFocus = true, +}: DateUnitPickerPropsType): JSX.Element => { + const translatedLabels = useContext(HalstackLanguageContext).labels; + const items = useMemo( + () => + isMonth + ? translatedLabels.calendar.months.map((month, index) => ({ + value: index, + label: month, + })) + : yearList.map((year) => ({ + value: year, + label: year.toString(), + })), + [isMonth, translatedLabels] + ); + const selectedValue = selectedDate?.isValid() ? selectedDate.get(isMonth ? "month" : "year") : undefined; + const currentValue = today?.isValid() + ? today.get(isMonth ? "month" : "year") + : dayjs().get(isMonth ? "month" : "year"); + const ariaLabel = isMonth ? "Month Picker" : "Year Picker"; + const idPrefix = isMonth ? "month" : "year"; + + const id = useId(); + const focusIndex = focusValue == null ? -1 : items.findIndex((item) => item.value === focusValue); + const currentIndex = items.findIndex((item) => item.value === currentValue); + const initialFocusIndex = focusIndex >= 0 ? focusIndex : currentIndex >= 0 ? currentIndex : 0; + const [itemToFocus, setItemToFocus] = useState(initialFocusIndex); + + useEffect(() => { + const itemToFocusEl = document.getElementById(`${id}_${idPrefix}_${itemToFocus}`); + itemToFocusEl?.scrollIntoView?.({ block: "center", inline: "center" }); + if (autoFocus) { + itemToFocusEl?.focus(); + } + }, [itemToFocus, id, idPrefix]); + + const handleKeyboardEvent = (event: KeyboardEvent<HTMLButtonElement>) => { + switch (event.key) { + case "ArrowUp": + setItemToFocus((prev) => (prev > 0 ? prev - 1 : prev)); + break; + case "ArrowDown": + setItemToFocus((prev) => (prev < items.length - 1 ? prev + 1 : prev)); + break; + default: + break; + } + }; + + return ( + <PickerContainer role="listbox" aria-label={ariaLabel} isMonth={isMonth}> + {items.map((item, index) => { + const isSelected = item.value === selectedValue; + return ( + <PickerButton + aria-label={item.label} + key={item.value} + selected={isSelected} + aria-selected={isSelected} + tabIndex={itemToFocus === index ? 0 : -1} + isCurrent={item.value === currentValue} + onKeyDown={(event) => handleKeyboardEvent(event)} + id={`${id}_${idPrefix}_${index}`} + onClick={() => { + onYearSelect(item.value); + }} + role="option" + type="button" + > + {item.label} + </PickerButton> + ); + })} + </PickerContainer> + ); +}; + +export default DateUnitPicker; diff --git a/packages/lib/src/date-input/YearPicker.tsx b/packages/lib/src/date-input/YearPicker.tsx deleted file mode 100644 index d32ecc103c..0000000000 --- a/packages/lib/src/date-input/YearPicker.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import dayjs from "dayjs"; -import { useEffect, useId, useState, memo, KeyboardEvent } from "react"; -import styled from "@emotion/styled"; -import { YearPickerPropsType } from "./types"; - -const YearPickerContainer = styled.div` - box-sizing: border-box; - display: flex; - flex-direction: column; - gap: var(--spacing-gap-xs); - align-items: center; - overflow-y: scroll; - width: 292px; - height: 312px; - box-shadow: var(--shadow-200); -`; - -const YearPickerButton = styled.button<{ - selected: boolean; - isCurrentYear: boolean; -}>` - display: flex; - align-items: center; - justify-content: center; - width: 80px; - min-height: var(--height-m); - height: var(--height-m); - background-color: transparent; - border: none; - border-radius: var(--border-radius-xl); - cursor: pointer; - font-family: var(--typography-font-family); - font-size: var(--typography-label-m); - color: var(--color-fg-neutral-dark); - font-weight: var(--typography-label-regular); - - ${(props) => - props.selected - ? `font-size: var(--typography-label-xl); - color: var(--color-fg-neutral-bright) !important; - background-color: var(--color-bg-primary-strong) !important;` - : props.isCurrentYear - ? `border: var(--border-width-s) var(--border-style-default) var(--border-color-primary-lighter); - color: var(--color-fg-primary-strong);` - : ``} - - &:hover, &:focus, &:active { - font-size: var(--typography-label-xl); - } - &:hover { - background-color: var(--color-bg-primary-light); - color: var(--color-fg-neutral-dark); - } - &:focus { - outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); - } - &:active { - color: var(--color-fg-neutral-bright); - background-color: var(--color-bg-primary-stronger); - } -`; - -const getYearsArray = () => { - const yearList = []; - for (let i = 1899; i <= 2100; i++) { - yearList.push(i); - } - return yearList; -}; - -const yearList = getYearsArray(); - -const YearPicker = ({ onYearSelect, selectedDate, today }: YearPickerPropsType): JSX.Element => { - const id = useId(); - const [yearToFocus, setYearToFocus] = useState(selectedDate ? selectedDate.get("year") : dayjs().get("year")); - - useEffect(() => { - const yearToFocusEl = document.getElementById(`${id}_year_${yearToFocus}`); - yearToFocusEl?.scrollIntoView?.({ block: "nearest", inline: "start" }); - yearToFocusEl?.focus(); - }, [yearToFocus]); - - const handleDayKeyboardEvent = (event: KeyboardEvent<HTMLButtonElement>) => { - switch (event.key) { - case "ArrowUp": - setYearToFocus((prev) => (prev > 1899 ? prev - 1 : prev)); - break; - case "ArrowDown": - setYearToFocus((prev) => (prev < 2100 ? prev + 1 : prev)); - break; - default: - break; - } - }; - - return ( - <YearPickerContainer role="listbox" aria-label="Year Picker"> - {yearList.map((year) => ( - <YearPickerButton - aria-label={year.toString()} - key={year} - selected={selectedDate?.get("year") === year} - aria-selected={selectedDate?.get("year") === year} - tabIndex={yearToFocus === year ? 0 : -1} - isCurrentYear={today.get("year") === year} - onKeyDown={(event) => handleDayKeyboardEvent(event)} - id={`${id}_year_${year}`} - onClick={() => { - onYearSelect(year); - }} - role="option" - type="button" - > - {year} - </YearPickerButton> - ))} - </YearPickerContainer> - ); -}; - -export default memo(YearPicker); diff --git a/packages/lib/src/date-input/types.ts b/packages/lib/src/date-input/types.ts index 5913922443..030f112399 100644 --- a/packages/lib/src/date-input/types.ts +++ b/packages/lib/src/date-input/types.ts @@ -139,7 +139,7 @@ export type CalendarPropsType = { today: Dayjs; }; -export type YearPickerPropsType = { +export type DateUnitPickerPropsType = { /** * Initial selected date value. If invalid the actual date will be used instead. */ @@ -152,6 +152,19 @@ export type YearPickerPropsType = { * Current date. */ today: Dayjs; + /** + * Value used to set the initial visible/focused option. + * If undefined, the current date value is used. + */ + focusValue?: number; + /** + * If true, month options are displayed instead of year options. + */ + isMonth?: boolean; + /** + * If true, this picker moves focus to its initial option when mounted. + */ + autoFocus?: boolean; }; /** * Reference to the component. From cfd1f0925266881af6daa8b6abb0466d89928317 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Fri, 14 Aug 2026 14:02:55 +0200 Subject: [PATCH 02/12] Fix failing test amd change image --- .../overview/images/date_input-datepicker.png | Bin 45828 -> 24044 bytes .../lib/src/date-input/DateInput.test.tsx | 10 ++-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/website/screens/components/date-input/overview/images/date_input-datepicker.png b/apps/website/screens/components/date-input/overview/images/date_input-datepicker.png index d618ee1d5eed0a1f8b37758fa446530c07d76303..0b3ad1515b7ac4a3c5bf8ead81f1d35135696c3c 100644 GIT binary patch literal 24044 zcmeFYXH-*dwC@|hfFg)W4^3YbkuF`jBA_C@cMw7+NS9uG6%hhh00HTp(0dUA7Qi4v zfPj<`5RnoRY6uVlXW>43pFQrl_rtySjB&qQ$8fA9YmxP2t!K^ooB#QL5)Acqm>D6A zAP|W8!To#2AP`*-2t<>9mLB+|HFwPt*qnQE-zoqEI?sCgp9YlmfeZMMCcs$dE~w@Q z?<(--jN2W(J0MVf%K0NFF!26Dpq6EziLYy5h?74Qr0wD6<CI&U?hFFixjndd$Mh); z0ee1`eKKeHz{JcDnx6`dh2Kf$_<8x!Fr=2@?DK2B3jbWwm1celosyyNC~FP|y~0Lu zudKYD^vs|1B6x%F^;jO23G9NE$xot>7x;a;i*n2qp$~uY(agZu`GLzx*XvwTubbSh zq^<mOeBn4l|9U3bj6C-}`+$0Hw!HE?MKLXt{QF?}<ilIOXV0E}z5t9F6!QTec)A6# zfoP;oxA_;(#?YL;c&-6rI^7DNp>;gnYFxbtqC0)T7z;YR79hj3XL?SzF?X+kPA?pY zjrRZlivOM0;*%l++boao$Azfvw|Q;nIVIW#GbEKkUr9egy2pkxW%~8rOZ6_%ka$3E zTMwDbE&Au@=j-SgAHqKi9L+PpCaSECZ^3vc|BWd$j@fU_qkhj(2_3}-&-nP@?iWYV zIc}RHX`JtXlI~K_DN(;m^>fx-e|%U4X^d;Agb+X2)H%j_xsCgR3b&1c<9fLq*`^02 zk?V6ixnxC<6eAY~+J-ek(!50JQt6YxG*XNnPoMr&{}&buy1Uq)LdJrtlid}Z+ADCN zf2X^m=pewfY3DTGHr)q#vfE1yHU!_i0Rnma1JWi21qRyggtsw)-ZYtLa8G!h`2?!G zV;B1mux}ytI*nmU)dY{ZL|7-NT+0?q!$w0R<)5N`H74u}hSLh6Y03|cs+a*KfrHMz z`WHBd8<w^xO*u_9#v1Cke<p}KM*E6jQlVqm{ojx@#hCg!CNB<P*yR}*0Y4JYPVtqE z0g&LNX+po*Z&_X%)61@!4VG?(zyX|7_H5k1sU$1#GHGft58tdMRGs+>T#&Ui*<qTu z&;Qw2p@q+iSk%*LsD6?_djzwJDcs=<;*GI(DY2)se$FJtc*Db34D^zfz?iewHt)G6 zv8Di8dWToNOO5>~2s|@av~4w|5S%0Kb+qL=qt*XZn54R$=@I4?2?9B%{pkuBgywyl z1C?vrs@@^Hv7KJxtJA{8v1jw&_>=BUtX+H&3@T^eq0cWr8}r)6=~=4Hb5Oa(QyOtm zlD6!;9%PsSv}9<J|MGP2l5}_A4vmz#2S(6IgFpCdX~uaFDD4tRkUXwY^-i27rd(=3 zcA@%igJsGSKNbx+cZPxi;P1j8y!~W&Kv#YZPhzmkqZv;;q}9>_;(|s}9r-Ni_qiaR zS4luLh&kh8N%Q+s5chw)<C48d0~!$cZ*fB}_}7<nt*%3mkcCfRhWseEA0M`GC5>*w zN-lIHo)NBbUk!Xx04Phyo0mA#IxOzcJ=@#f6;3{U`?00}_qVrR_UarhlF0(<L|pjl zXbEg@eX1xo;P=2KhZk)m^dv5HIR|!pbWmiE(7BKQyqvF=E$_2EV~(pJ%(Ql{j+H4F z#~+IGlX%wdQ0bd4a69Hd_|&(shCz7n(vSB{cZIi}aJw!4$cc)Kjz)T2BP2ox(+~P` zAl@>r-!~~M^S#dmhtbQGS0CdiYaP@Z)QBW&fn$L%SX1oFzMpkY&v1WS9**iz)4#7N z_!J5AbyhkAum40h9qQ%1Hbt%mRNxvC4w>Egq<b`2@A1TkIm*h_jlX{-{~;}NB+XZj zwy;SR<6cEZwMj$5_tv@7g6fmvrTGe9ARzywqYZ=a!L_R=@xg@YC7zpCsRFv`Vp~W= z1gi2U&bgrBI`k!~dQjAd>@(BS9?u`<Ui|ugX)&oBCt^Y^2-EC$yWd;1_M$B@y1E>< z8{U2>ei-IqPJMwc$Hh!srDKxif`48srz!PeA9};96gZ$zSNJZ$F!(XHBM8<T2ku4- z9aa$dj2G_=Cv?W~`^>KT@*v9_R>*bAd61W$P16B|y|w=?3XrC{y1HsE?W+6EwryoC zq2}G~E?Eh_e)K@899LNcx>co+BvEH21!*|cn~{ef4$X-Y3#}X1T2sTQD@K99-S+~L z3-i6jx-d`oiq~Ah!(-<hW~3xK>w*c^A@e;nlarInjclG=p30)nczxX0eC4()+x=^X zTVrfCF8Rz>eDF-VCU<w&3Qb=<XA+A~7q`)-MmHPR)t;wyx>z-Du~8AEr6qWaZ`>DS z(p8{zpQaYQXF@NhBxKPfZicLYnM@eedO@iilZ$<7q93nRkljtGu5A02YLm~dya#R_ z7?{fyM)O?q7unZ`vK1uv+MWJjqAZ-HYYHlGiwy%to=+;AoM>7hJWCzC{ayC(={la) zIj<idZVNiEw7{@RZ-y$;ASh2Jn~UDPwdr5w3Ta&8<E)uir7APt^gWEnxncMw_&Ur; zHlz3=&TD4J+8E!*y}tXIXL><d@LN3^bXGluqAkJ3H+0`Tf(zs0+~6O9aX9R#$k8|J z_vS&Kpb-nxeT)}LCh(7S=!|>L3%h6gSDs_RMR^WG`0LYBn3LwmUte{&MKGt_yt>~t zo!aW>@A<EcevMo?&a-HvJI)e;+clZ-diwO~_)b-nNn|0DPoTe8S6@p=?wSH6X{GM6 z_!zmpd@-p47xM>*UXUd1$3E}C6TzeM#*2~?Z6@h=;IcjzUM?O?AI|Y)F}~rHu(jNi z!Spno^ZfMUxtj9y{`m6RrF79%lHYnz`cOqYpUGbMYqa7cPLhoe8&{?N=vL*TEG$kP zfs^gEH>Q?8@}65$0Jl9Vm4g5=0umTYpVb3N@Vxl*r_h(iZ;#*67ztfXh7CG3zI!<P zRL;v?NUsozk|T<-UYh%(xlysSH`OeA;j7RA1pnohQt8<!4_;Rqk`2k!cgpAWo3-oN z+|z6C+h%So0m%vY{)a9xu|f&14+HNmdXk&HlWf`(a>ied|F%e1H4+rh@-d|ILl<gA zDaSW{crm_dV&OrEf8w26Zd5_Gkk@tg@u|v%)TA#|?uG^Vn9A$q)zTMqh)m-Xg9i&; zN1_V+-ZKMpul~HnQinKRu_f_rSS~Z^tPyfa064XhU5d)~fHnhLs{WrcgS_C@S^q?@ z^qVH7ZOX*9&pAp#D22;mVPU*AqxVFn)%GZ}?E$qOKUaNyWT@*z;vVzo<m3wi!U&_= z0e`-~_NmOXKAC^UBINoIqi2xEJ!xse-DJCDxTsHg1iwU3$X;q6#bcgub8&6fVl;OT zJ%y_7KquHu8;9jP{hn_Beh?k8`t-cOWA!4W+c}a=N0r(o%=ts*s*Ec8F#|l-e}B{I zq;*Y!N<AhckvB=%-O7jjMo6eG$6Bj<WIE=MnG2EJ+uQ5J@H<sc_Oia>WeFY#WE=a9 zL(+bI<aqr3Sfp%P`#kD)pXbRjHL42AK{wx~G!)f}tiWwQKV*i#Geuqk6$!7<GUr!M z5$=x58Q78Cd1(jE1Ig1}1$Yj9hF}7Ph^g<|=dT!)<3Raa0I)X6$uW9x(GCdyJqY&f z%qfrlzadwtA2cA)m5>)NUd%o}&pEp^n1S8gtiuzD!KZ<)@$Hcge_eF!;U+V1*Z?<J z2YIw4i7nC1h<I`hVC$&NiGr?rVE?64Wd8jqI{M_kBipDnlo>tu6RAP{i`f3!Q$5$# zq!ai0j=^g>r?72!#V=FyHv@%JIOYEqVk1k#Z{(_mPtXYScb|X^nHG6tvMLIi!pSg& z@ICL4<(!Z{HMjm`K3HE8H|*P+>osP(nGa0$(^{vzXT*S1tCy=1>T)pG6;8#W2_@2! zb{*q-d1_cWn-W4%0}#>C8O>?l*;+$+>Ns_4!oX`mB9IZgZCi#%h2zVaD0_r}{uBXn z^e}*=8D;GK=Np#3XUY&q3N*W69`D3$TK1hEl6DhVZx|mO3)=5v4JzYJEvDMFmS1o3 zTHx^nMNn!q@bD?Ee5rKw94pm3CJW~nvX2x`rH(EmdVhL)-d}0Nyk{cJwvYMNrsftA ze$LF`t;MSeto_MlVurUTY8^JnMRFXAaXaQ#?fxY!eP$eThC$!U(XW0P@f9vMqABX| zLx!aaecKlAOcr|*cJiwWMwihY{-ai@tlYkiR4JOYmb`-h+#z-t#%HsDr}@nZFoHfx zW3{-V&r%jq@pkvq^YhM~W2;~0a5&sMrU`%@bkgovd>I><^$52d`G<o^w0v~0VQE6~ zoXpokLaDY#;DM#tw3^fBj!a5L*7jEqt;{CeM752@E!NShNVd`Ak&};Z!~6`Ec03c} zMwkel^}>>5J3jmXKY=H5qllEa*M-sRxYFSC`NeDJJ5vtOT|&f1pBee+<mB$Tu!}3C z$^Zg&Hw^skL01RpHv_k$BU4~ul577vGt(y*eex^N%>Zpho6SVEC^eRf&M_X|^)!{R ztm~0`1G(%(Z<%+?cTOeR=n9iGCg;|ALuq~>5Zt<rDhY4R%}w(VkuH>*gruUDgqAW? z1CTeo8u0Mq`KvV+&y47dm(VRnFSMW{<fHn!KHpIa{;B!xyVDkEXI_E<#rh6my4fpI z^^&Sk;G;@4%q<VxjFe6JMiW0{YLk9!@=E){PAZ=t=cG3habdh&US)VXv|mC+=NV24 zxVtQpyQ^JX3>VnwXrv5p<XrQawfxC7W(Ea^8QHYL_}VM0<7@O4ij*BVEc3l)wC=o( z^R|;Zd*a}u+&Nv-_p(LD!67eteY$y2!QG}SvTCbH+FgA$8r{|->!Dn^P_xae_$0r= zxjl^!9c5^Eaq{B0+sv)1__#N&;ERW1UC1~{hbm__>8BxI<>EhK-&d6j`L74tiWp9W z2vud9<*kqoFZkfPu?1cKa<(=(6j?<PY`|R)G;NakRh9g^6T-XRg{#IK0p)OCkI=+f znRcZIqPm<VOfNcYrp$b@uZX5NC+{Lxoj4h)W*Y4ZCL31RBYZTW3DriAXKuj##+@u& z!2edG&RR}P@h>dD=+~E7Ju%yK>;khL#V?ISof|Bmy0MzdJ)>pkV}Kwyht>+XObyDr z>%8rZySOJh<&CVLeq3p*mi-QzDo*rYxMk>7%i8(gdgz)3@OT|LGf7ElD;0>`H~jn% zE-lwlXpv5M<u4mh=J-$TTbOWwr_4Fg`wc#qU41_hz1G4IOK4CTrqRFjcIT5OKmPRV z(>b-7Z2M~ki{`4FY)PVmzRqnJu4ejjNTQ!AKjdp1hbr5<88-!gAxD?&FwD!Kkk<3= z3lX%JyEKZw!9Qs{;QTofwf^y<E5WOst9()fG=WEi^2vOsI{;!|(rbt#<J>q`BP{Fz zo7lfMCSBn<-)X@eo`LQ&|Gs_oFE+pEEnK+#=612H2i8+KA&v;p4`nM&DcFa%6Y|E~ z99&$zChd+Bk1PfEgdSg55W#Rx<D9S+9WApimT$&~*a?~xD_jWX_CojghEnUj3JJz% zv;MWMO5zbwyCl$dg2adhUAdoY>v6)MrAVrw2G`JDOV}s=oS#MT7p`DQMp(0|cd1(u zq3A@}gO=4L5&V84&5m+&*4JZQ#w&l#O&0Wr3&qJpTfh&Rkh#M`<;!6bU4P|k^~-#g z!aMYu?c*!b%X7>0BlVA$r#<ZjRF_wdc)R&EqF&R`Lo{gW8eEn-@FI;KGAU~=RotoB z*hpCv>~^ul3`f2^;(Uvqc~OkAx8YBJ>c)aU&j!NBW#-<JNoKpa7GC(Xm)%73Mw3Ak z^|Ouj>f;o?&mF1gD*7Tw>tRF?6vI@b<hnc6K}xXPNHpgiz8@d=zRj;QIO#=e!C1Xh z>+<Nv;R7r>x5<Ijm7L9Rms6u@!;Hlp2EsEJM#B0Z%GFK=ytQY!g6rT9{rwt#K{$V5 z!71c$*CS+5ytOTCcXv1G4EWG47Td3Wy!CqBR7WR1TPbMm{*!#sLGo-sigLt$oo_k9 z=w$OVi$ZfJ24EMRE+ndA-Z-x|9#!0<a^wK<K)t)!&4dO$fWDHj;O2iiPop3uHP0jI zIq)M(ZcxFoxrV!NMyIBesDxA*uOgTXy1CmmwrIX<p;nlb%^vJ5Q`JH@dl{(ye3Byl z_0IiATSYH^cai7t<Q*Up*~tj^n7IxMm7=#GQ5(BCuat9(D0SvH403l*-gfsdXs)~M zlEt{asOjv}8<ljw)*kSM6jF;H<<au<wJLEC+ZK~wJNK^azf{&Y<D3cq8hJ8<`I^W% zSd*vdS2!pVxzTL!Yp-)JBi+kkjkoadKIbpCkJI23Plwu$FJaf7*ZMLzPaz+WY?{;t zJprY}>_220|NG26n{I(qBO|H9j^g$of6{2U13+~A*yz82DA63bD(@9s>u4K9oNaHw z2gPy_yu>I96;Xm9Z6hiA|E9v_S2u6VJAq>M`mO=uWqvhbs<EF2WXLSta|%NLTNOG$ zl>&iAuK9jcn@{FbTgp`p?(5n=x=jXTpeQT;NAJfMVecpKun%$=tV+m>>~P8>Boipr z?o=zxU$75djZ<z~?kC3MlwW7ZC_FrwOdMYIo1<=0`g16S0j+a#{n*yWcWz4Mu7f~R znx|9*(Y~|58fnm}nIaINRjgr|@?3zbar0uXfi@RN`o$?I$^P5W?GXAjC46b=e9g)Q z)nn(7_01h33%{MVJaEjv59&7#|zQoS4Q(;U-6l4>CU-ZlKyF_RLckq*=&)-%Xk zO?usj2REqlw4ma@{57uU9QExe-7Rzeqdq-7hyaL7AkerTtNo=T^HQJPd$>ysp!=Ms znrC?;5@U47ExaaPsIO2|7F3*Ql}`iGuzN@KxrWswPjoIP9aY0VKY%@*bZPx1lc;?h z<-LrfB(yc?leN2}G4Z^hD|~;ikSk=kl7f|g1LF+bndKIY#*^JrauOnmk)j+)VX)3w zUovpzh=V_<u{5WDq4D;EqTeUJ+x}lpC6kg>fKp(%?nvVP)*@{C_c!7X<&D6d<>3&I zgmgf1xqS+M66b*zi-RqJtHuhEhk+UNd@^DGHX%o6-3q1oRGyA(sQcu}qKY=&p)TGS ziZnOvzmVrYR5))<^J{j-^*VhI$I*1a%vYXTmO~#-*ABmG&mVS`8GA%5eiCuvy=r){ zUTsFz2Lw&i1!th1`UyxYHpr+hJRTq5VFS>YFKFe9x^o|sgG8DnGozFlKecIl{b#*k z^Y*<{zRS>A(oLlO=nneOBIh-+Pblmwfn<6<a(ysOsVaX%JEmPXe>l<U)?Z_>iSXX4 z7f^AK-#1h62tt_mnX!P!7j{UUbGj+T<cg+l{-N36(ENEh;%rIGgzzS|$>S~Zr6>OL zc^6TOnyRP7!O?bz20j%CzmIO1=DfG4wqD}-<1y3hm7#tRKYsuIqGv0r-fcM7h{n{{ zw-%@G;Sf0)#lz3fpLXZICLj~BNH@M=Z*_hO;ID+wv;vx1w2_sJXp;G@wfk#l*|yQW z&jp;o5ur_|4Q?*fu)5>(!62GA_cQ1?_v^_HE0SMQWj(KK-h45$$T^F2TmHwsq*^n= zsr6Ra*1>v;nf}`!m(T&vS7xC>MG}@UKxF#eS=-?>m@ck1COXsNBN|Q|oc_-r;Jxta zxtit6+^1ug#p7vz$bN;gjQtQ62`_(<DxJfqF(#oqO%X0m8U&!#&b;E%ho$YMDN#we zjn2aF)!;7lmEcdsFkGZ%tatmZ0mdgJ{w!WRA}{)r`Yr^T>~mQpNauKgtiO-@y!EB8 z<EH4??0}7G*Y;?wt?0+SX)mG;?l7G)h&<A54Z@p~_ZL$rP{I}B$NcYg<ueVWYtoD{ z%}47~jmH}gm({?dR!SChf0eLjP^wGV?h5*~=x>jD`d2l5c#$K39PP6B!&a^e+=D`y zDq0fxJwcVR8576G&L;3jX_}erauS#U+xo{^LM>@Q?86URKDtBFnDq*6=qqL#I_`+d zc;&g#_?5uEJdiPsJ`5nk13f0fMcVB4(e0Bi+3f$?7|yP2{b2l<trtt2L>>nzNNr9; z9j+Qguh~Cc0FyhBlup>%ctuUl3Z-W5R$<3YYle#4(F2Phb6YB#DjDh{%<xw>V|y8= zC-=|j1ul8mtfF*H;M~X1C?*SQQ+g<&@$$V2-f4b$n4VR3Q^)eubH>PSNHTt+dIzpe z=oB+-A2|M#5qA$<#AxapvGMNpQhHi-5d9j0X2Wqar1hj?xGrBoiLTicCF_<ZQTV*K z7Ts_oFJiQrHF7eD=Es`H;28^1(PcHzlm<PQ@Yx`!nV^PU_h|PvU-AvJ&)PI;ec{bn zLx)v<qgGDb7m742xh>!}x?aeeo6SkDq<fT-#dtn&5R}0yNmRWQsA2YlDJyUx>I^=& zCq2!~(E28wcdf~$tp}|rg%=tp9^f4tEF)CaFyEQ^j-a=l=y3F@`8)<4_U|&>dhL@~ z4h7^w2UJR+agbqt1--@ujJ=2{7c5g<%6%{G!H9Q3mfyH*`9xJd%GfOyoavFI*K#WW z?W_%b7(M}oQ_VJLV3F@~c}nG?t2&68q8wk610NjsRy5R_%u^UrBzA@;EgiwlK*FU7 zS=n+<$a8Kr*_L_4fX!ne9MpR?@s!#vkFsTO|0paD$cRcou=)&WmrT+L%L78|4`>&S zW2`BJRsCioH{oGfzU9<Jl?z9jN1AL)N{6H9PqWE8GyKyIhss0e&&YdZ^4}kZH<liU z2`&AphmA05%W?d4jh@(__g@cX^rmz_b#yrxl62U#nrj<IyI5M6-Rc9fNYD!}Xjb-? z`kbQIE6vcQA9nJS+Lj7aNt?mw)Ok*g%e0PH`o#><ZDsCt`86%oFt&0gk+*$qBKc$N z5kYz;9E$K7j|X;77h$bqjEw8De=L}ld<39h$7a<E6VIStB7mb&Md#0W$P1@YF}L)J z<n0(A+T4<#=(vFkn^fP)bOe{Bxw=d|8oo^EuKEsZA8v<$hIzRIKFGeFu>3lt2yZnF zAdZTsuE&*F+F23sG;Bb+8o8#_>$&cl#U=~&cyp!GxVEZPx>m&DsQ@rkXOZg6kEHgj zRMA<rYz1HaAj(RkoVGPwH|;@x;56&El}>g(QTW7ZR#$Lx+v%qfP4sdOy<Wi+Fwi06 z#am0XIMP1JKT(ySczH{U;lN5)V_<AP%GVHoW=j$dc2)HDN;+<;WQ_Y02R{MP%L~v) z^a>sOY95tcuYTlu+2?H)PDq3hnf|p0P^&WMs~rC<d$&Oxf7FOv;X5B>V6NaYm}SoT zP7NLM1&ds}aFV4?9la5^WI!RJIp(#kTBf3We|JU0)sUmbwptd1c3m3ZS@0+8ACXpy zK-dun)FY5##`8h7kr9w(Z*|=GO503-@Nt(wHsV%0I)A+5rdJM{YeC_Vp=NHQwY9UU z2FK7879CX3z13ynyzv!5q0S-KG>=2FV-V<fL-e5H_`gh`m1!Cz3NyXPJNH(hI7ZP! zy0X}>1Cqz9plu~{|HjZid~=l6(MMyXk(P%jH9C;>vzaS2pqP%EMOHBXir@m+xAYvJ zzMl?0f(=Mes@f^E<--_xgNCuSDj&;kI=f~Mb*Ol$G#+z8qj^**jd8${eaEyKhXa7T z6m!d{bq+`^-7VkG;9GRovb=kJi|9gqSdl&}hfJVMAWVQjNtrN{@#M_;Zb|-4^&ot4 zv0opl;k#t7B*=x~6ky!Xg2SQ@mr^JaUxvC>jf-UQ_DGb7*LO*5F3lC2Q@Kjxv+(>_ zn>F^I2eD6PAAJvnlC>vJ^d~q$4GDh{>;r8Nd!E&AqC;dn5?O;}*w|S<QVqpm6<OFe z=cKA9v5H=m$mgT8Gj4CTz%fau?JtmCfSjmOzd8tXpQlmd|AXE#Ao97l0bQmJL7n7% z9H*G&e7cy`uTht!OqI|EoBGGkv(+LyyqZ~W=<iDdI@wWHkadl8WyC2^h0KMPdA0a2 zRk%hXPys+wtJN2$7IL)jhm36UX@WNx=K^AE)9?Kn*XR$o1Nt(2fUZ>q@1pw!zJSh< zjbMaDZLn~%=UVsXY{3Q<$c~7p$iPU%4^_~p4n3E9-Kl8Sm!)4E2C&g*N!?BrrS{?X zw?0=}HTIn{TJsw<hrF4|$&eu>IeU4Z>ECoW(|myxqbKRmzWwu%grB@o;|mQnud!X_ zac?;>l~7fOZwvRBhVG7`iWZft{|mM>ql697QCfcPd6G6Q>scKJG>C*;<t8<i5YtNK zB76|hBiN4q3J);Pm`hoA_Xt20b`Otg(EkXWETFnnL*quE9GjS6sec7}q*4^Sci=Ty z1NZqN;yzX9^m!mHG|k{ncq7W9LZu}NQ)wpd5ojSY+~PA6u_#uoZQm$8wuCD32Y$WV zc*ESB{mDmJ6l>(%=H_NO)5R+QdXf4o)_#Re6p@-FO(*Y>6V}x5e#Wb_Mis;)o7#?2 zv#u!M0c4UIab%PYB25dQ)k7$kLV^zg_QzNn9yHakb4b{(NS?jN11W@6r|ZdjesK>4 zMpL7Lsqzg9EgZxc8Z@f-WpOK-n(*^!dbWw^&f;(zslFW-8~hggj0U|=c%Uro>nmmH zlGaTtS-FZGmK96Zwac%eKhoR6zW%7jd{7p_<W$$#(6|5!Tz>Dklu6%YjWOa<x;dx2 z+?7I_SO~M{y(SSUNzS%%UbJ-i_b@$R05|Kr%;s%2mibh}nkuF!o+xMLM|?A=k0Q$1 zuA!&+qptSO4|%pi7hhx78IkiCOZMR{KQ`~Gf{~P9-dz*TkCRkA%eJ~9%c7*eBe3?} z04nseF|qVx2^P%4V0u7!x~LD_sG64u6-JcYL7JYBrUi1ZII>LJQHJh^=gL##i&cvc zxUJ#6x`_?JBAU~zLs!ECjlQfYfwe1H2*2Rnx=JuH!_hFX4(b{!$StLN8AXK6eVjVs z+iR%QF6odF4HAu+$UiST;)wdE+$acW(y70A!2Pq%%N|kDo3EYUz~?#ZA=Ut4ss0i8 z*n8a01TGuIpiiNFfy#Hb>2K4kN~g~zWlqIjDzi-iVFs0gWp$CNrt1HMH|2T5m`!x~ zEGvlLjj@dRa;LMeSag6<qWVtFyar8qU*_ss5QKHNoN3{Vtw>Qiw4k#bbx~o$B9@u? zb!&NSDbq`cyA}Qq!r2bmxb>&H_}lDm<e1itB4-`Vi|??B$*fvY7@0krw|phHZeOj; zFeR^&+amc@Pv?B2Z7Z!iKdobaEDG$?96aU8VYm9@VTchvy*z)mgHPnV^WbYLeHPYV z7!Et0$i7;fZyC#swcHU8TX@9D<yCN~6?B#6NpKp1QpI4>9>CBm8FLLN)99w&&pf#- zCfDF?`vfvB)%2ito&S|p$IvtBmmEzmTR82kYmFE6tse2|qKTPVRV5g>eA*r8=z9n7 zM^w+x<A;LId4aF*4YtR;aj4W~Z(AwYh*?v_4mYPe8<OD3ZbR_=jW@||T`6;&X00zZ z8$8$4u(UUUCuNnSbi=g*1|G2n2Jvjwv%$3=qdETmq0C|>YnT3}CvRmzs`;WmGoze* zktx4jvjw-=#AbK0$Gfs=Qnhf(o|QEx-7l0g<*P(xd4$!JgLc2-;>?a-f3~Zyc}~Ik za0n7Xt+9&GoAt{NQ~W6Pc%#7aL7-E7F5Q%&9=0qXKDR8uXZLg%9rL}<&GXxo=ouK| z`BF7MfBsw)rJcdd%-pUSf1w?aQ<Onx2{<G!MUc3%!bQ4B;ih(({aTZyV2^*8P)on( z@@G9|#!%MX)NO*PoZXw?_`UxSMauMZvf3mnq7o<EJb*s3C{WOWK$%4nKOikmQ>uV^ z@VCl$N$x10-nVsq&H$2SJWY55*MtOVL9*6mf74Dr=)X))obG^Nl0WpfviWu+FA%x^ zRdTkcCng+6nOpW{A?>vyu{8I!|B5f}f{anfV^VD=r~T7j2ITfw-toBPm=fsQ-*1bN z_uriFechu_3`H^0-Pb<VGC)n2py6-Jb!B5*{+g#W^uIt~(0qJ;nnM4}QFVK4Y-|w( zn*8T9M-F6$bnoOO;9=bdt_+}4^F{uX7x6hf5zYf$TZqA$lOre@;b0qF+ZFgteYZ0m z_)zrtc9;aHDVsv<l$1YT1?{j91F>NM*#>+o@N0W)qQz{9LOgcUFv10~Wbxudr+<6X zG4nFr0`Xi<jhk@>e%&85!f~p$k;9SX@Z;dy!)lZ&mWVWVbH}P6kfA9sa(@G>+1O*z zlKGGUjAo{zYM4+u?ZTYIBzggKWruA=!lvar4QTEzi0R7YJ5nSyBSTlce=fs{X165P zNnw?5aeyAU?oC(wKy(Auq8U7#N06?Jxv`MffF}n0eVch4&|fqpZtjF{VfyHizFp)e zt6jDG!@!NfykFue7od@fnMJ?HX}d)giLrhcLxW<>ozs2uqfSSr!)h-lvoCkZ6v+Vs zRl77s*tA&0ZiLV6@FSC~x?{KBpE<8lW7~x>ifmM!)Xc-EzP7nW1F}v8_8kI~Csow9 zd+so)iYN};u9$UzQ&M>#DdDT|2`IpoZ|i$B<a172Z6HUWLn%7|bhTz*3+l{nFAeI5 zPfDxa1>k_30_3rDJkTQ}0(wl;rzBT{tV<UB;wId`#QtY%4fAU>8^eGcRtp)WuJr3* z=}rj94p;1393`28t}i+!pM()rLN>5SlfABpdAq}P;yIFoiC8Q0uh0VY>!zwL>kI!7 zjL-A2{Ad3yuMFr}R8@+Jyq)l}lS#?(L<y!Phx~q)9FOe`>BJx`ET~z4sM5yFBQM+a zL(3Us&AIyF7GOB3bP%vpcl*KH>hkR}!<CcMaGOt`39*WdjGU}8iQ#09852u!$0qYB zM>yYszu=Z}ZF8+O%@s}~0P1Rud4{A}#tZ7q2_+kbMIH#uwvr=uvVx-97ZZuT0w<-E z5yE-uqE~14+A*2$q~=$+J$IG*jxCJhJ=O5F*5b1s<-xFShx^D`h(RfRLF61jFVtlo zIe7&DYj_f^kwN$g(?P*Qn?ZzX_eeKeQ+bp|w}*OvB$>S~%iXS+<Yd?6zl8eIp}EXi zJ!xQ5yC`Da3L3Z}-P6Oh`fmm67N99O^SDxhel;opA#IX|a=x*D>IyO!w%`ZPFR+KB z129|_H*91~wLnL$Dbb%*52#ckpb7R9apXu+7Reye7ddl?jQHBM(Pc4+QLdPz9-QpD z9Um;&wy&=l!ze)8056V55K6Qi8XIalg9#mf;$A}1f{y^zHo#O~Z^<Y%*F4mlQf1xz zs^{#$Ij9SHeQGiK#V)p0%%`No9guSpg5uNhR863_+<l>d^W5Kl=q>BECj~4*ZyU-P zA7@QLn<uu%rz7j8mJ*DMO6RtjnawW5s0Yq`GLE5Sz@|HYk-s&0jM<&wse;ibM<tCV z(6mAO4WA5@j{w|myq=X`-E;k>@@SPG9PM9j<UT(1Q(8zHt}kv-q+*QUdLDuPn!oa# zh1vyxk5{gjs(9f$5APsJK2r|nD6OD8Qxi_JO65xGBhY~+Jr`GzCuFW;tx89<?@_vF z3WUW|ww$-axTY@-!aT%lVXf=<7(No#@5F3@K7W86L!IPB?`00RtDhX@wTBZsl!)Yw z=qCfzuYxBNW=HEvH+n@{Bb)NZpIvdW`)m{Bw6uaQ66{AL=dHL^CLIcl3a4D$FSM?- zVB<yP*7tnt_%5xcNNK{!y6CJ`Bo+q6Th>)AYH;2kXJybh2HGiEkKht;qXs{1h)kxr zg`V;heg|{WvK5x&EBghiHJb0=EUnDvHr*6LNvM^*u#Y2C1&DvhKlZTsWBG}!o&z_t zp>I`I_dVFpb2LcbdNhH0wpZAp-><KzzkAx!=A>!R!$o3Z_pq&xo;0(~_F?!cZG7~T zLU;Zg1jxl40TCnXkwukU=|I#eFXefAsm3b2p>+WNW~^g-QYq$XTl~O+z@DFSoplTy z1JQfyd{g)1vW-EzT{MYcOurXrV`FW;)u?Q1vhuV(sjIKU6Xy{v*NMm#%0_Eq6B|dw zrM#BzgpWQ~=hHd7uv)P3F0AKD=ckSCijoT>UhJTL44-vreGkJ0Uam|Rsi6Rwm<gaE zY5iR&@}b~-(=9_Z(MiNawNXOhP2cev<B??tIyg94oRQiuAV6}CIw8F>pdi`LA}Bk$ zlGIu1Zu>R`(TKQxsSW~V48s_jwA_Wuh$rgvPu%yETKfmCCNnE=s0yp94MG4&9b-N9 zTpb#fc5VX4Ycge4Bk}42%WHQ#Q(bI9VsG$O4vlW0;1_p|b(Nuee@(vAUvVK3Rph8L zRO2A>O!}5H;T86FpA|Do@6BcXB=#~d+qcF&mryRUjf1Z3^hMygg)QgCU<LeBp?$5} zhd;C>=gd0V%EjuEZTM{DOWCGODkR?c2N_id#q;Tm%V-?mI>#|`PpQE3dug1$+;|{# z%GF69!BeJYL)IKONPDn^ZY!5HI7A)G7cwn&C}T?$YTLp*KGwQMO!%Y_+r#S}_6Yq* zIcbTu4>iO;XL>xp&-{i4vpfn)gwe|hr9mgU4V|TnHSSP!`r)o~%)XlARem#=cMM-k z9Vdtzm4}a5`BpoPiE&BEH&h*PqJ1Z>CUUq)fXgZ*Dt8ad@}j2IbvUDfW`(mq?6q@i z;7=WJg{=O8VwpACh*;>IB>gHzd+S?^?zW-#@~XoT)^vu$9$$;^K3J*`f)9L2inb{W zZ#;R3X}lBdJq2WsgJj9*A=QJfjkf6h-ssKf=#>^+Fgfa}-&gXOTZ-1`UcKKdrQq=q zuRlz`4wD}CR;fAkH=~Lho|6|I6{(cYZaqJG861wOFLO5RO-k13Tn>BB-a_ey@L4dX zvhQD9cQI)WdYxNVvySd4AH@?w7uwr10e7wUDi<$r)y^M2jTYvU!*1Eny;P4+P)$wE zDBhpHzNS6nX-ZL$u=RV!iFg^qQ{KsTr2jNLLo;d}fIWwgYp1Z6n(Wxun4CNj7|HS# z<FVIC&6zyZ<0J-dmDJV_!=DqW+2ojIAe2HU4RVw)1(9&*i4evYeJBP$b>;zTiGPXP zW4A%N`CR)_6HwLzJ3H|~o*(qK=ex|gUC!rz+oRFA1<bnv_MyI6)p`WmnMrkyHV$k_ zOsAEM23_03DeIvEsdPWwnh4^6ewUv_5Aehu(3HLsGG~q&u;*|4^&*RxE+p8FDh3)- zn0PQOI}B<BWE}T)ngKp$o2=brKQ#>>j;>a<XSaa01R%n*Q+lbe+5UphW<7&ft?gC- zgcgv7@>KvPZ0r`5Ai3vSzSqw2oE{6HpGM71-5F)*$2=~tD%l0I4K``y>7G|W+v<1j zi&g#iv#Vi3!EFkZcmUiGNIhenRq2KO)}X@~(M$WZ07oeRy4LfBelW6YklA6<iZL!8 zyVUCxvb0^t!#}#X7QnU{92neIV@y!2>1<>>3YJ2%1H;h=CY&%YxCzU;`vf?j@I`>e zfbIXOzh7H|nT199bnjWfGo10hnG<-a!S?^q@2?U2r1So%w;K2o!z(~7a6wq)RGK?o zWB|B;`+omlw6SW#BBz%!W!#c&8e9^*(yfnAWZZu2FH8KVUrDoMn-^$>so%B6RYjT; z_KY$w@T;uk)h@4~*S1d^Wf@oX8~3c_X52RBx&$fSfWcKyH5Y9=ol*JLbvTz`=#7aW zle#8v6_F~ReOeUqTIL5$p-BURDc*q^G!>L#z)6f1Z=L<s_p6&i3;?qA@t0|U)%hcL zjZOl9Y#&9vp|*8y5{u1g>qeu}bZ#6NM6Q{@fcEyWulULIWcMG34DHZDz!16?yfH`M z5dr+gPlgODR1S~aazkCmm+|B#l%Ur{)fRKtj4u+)@YJMCpG2v{EZvSi{-_+ZHZE5? zRqw1bwr5pBXoL=OkjG67+8Owt_6&WvJr=nn>af*+z!x&R(Dw^4O&?Qb^(7+iD+Ws- z7FQr<$mTbDGny=_zf&5+iS7N+gk(QH9AokH*Z<bc2G;@|rp`Y=t7$mS7Tm4mR!&4m zp*I-qNnL5yKf0<F$+FXyd%nh0j_P5q8%ZDkp<_xy10qGuZ6^`kmWTuY6AB>jpiaf7 zrikHO)uM@t*n&Pg$kMrrtpe@vMj7=q)EJ<#@Z1@i=5J2dnbB(T#SyIwY(txT3n@Bq zLsg^|_x8gX9V;Fm9?=ZQC%|wWa;i1CODcyDcexeq){Q`>26YvatID>%9K-gtVYiMV z1}_xKH4%bpUh&f%5^Y#m_#}Dcy}2sFO&M0=-jk9m_GZ17b6xko)250i(a`q^hsBwF zu1F_UOBj4`b(O2UnIf&uSp06)bg?m0BuxkE0p)CaxMZ~GO<Kc9hyuJN*p#`1Fy$wR zg2hWQ6c%_<k4107ULNg_)HQm$^7B5^P2YTG+x&j}RyzRpGl$akg+i^w*;nkMM_W!L z-QAr}`4jfF%RmvF`cD`OpGAv`2Nwt;O8n@83u5jcQ9Jj{9Nc3awYP3{5>ma)AI=9r z1mMj6i7wI^OdhUk>PG)k8`j?i$fU=*Gq~C-t!=w}rYNplmNuhp+r4w8fc`zi(t{u= zw>Pd~a{JvBVD*Hn$PL&uSEKl9<%un6&ak2-qq-uEuc&-#sn7Gh!YlV^UM2ftz|DIP z(e>r|R;u*7k!Z4ZZ&etBX+W$2P8{>xX~JmZDo_DLt;EhHCW@|}le8>{B@xdP6adE) zXKzC=ktUTN=(EsOpiAEd8f)a!o41pyMN;DE2g|=_D-0Vby)_SQI_*qWJhEl8;HTCQ z3VJNyH{r~QuDRA`{GEnQ&fb`63;0KOA%xjdf(;I}9%vgn{shxK8lsD5*O2SX07DFV z5fbY!_sVETS6l*@gjddWirefNY?v_PA3RwZDR>PfNOLru-NjCHH*G}y#1}#Qmgi)L z0TopXP*E?+&3mgnw(Yxv54>C>FNprW{qN!f%vnPXc0QijSKB|oXQQT@tVj0}l6Op; zZJ7h0MOp1Q6hRW)onBxVQ?@wJ(ChI|r)h&NztwZr);Kh9eeG9IEFD8cW?=8#!FSD? z;UiIven`|A9_S8GoCGqt<7UWrH{4VeD+`tZ_6z+Tm(jh!m^AG%iulNgq?D#pFNYBI zc4q0WM4oCwEB7k3O^i9)AL4E7a#Mp8Or^%E8m_15IPZ_Im|18Qy-4V2T0=!BcGt2m zB{<(Hg-i@F?!7@dXMT6*HWTtGa|QQcyai6%XtA;>Zs6NWF!A%%NjY&`aH{tnm{vA< zua?#_av8pdfJfH~{X_@s+S(nBL{0h`d;Ds1=Rm_GOBWyJRf9g7y&b-4KN{kI>>uKE z($p5|QmYPNiSfPy(w6Yh?l<>Xp11J+VAI$E2Vh@18?9Xq5khr0DgAtOYFl$0bG0yk zHro=9Dw?mc!cHKj)a3mhy|4C4_g}in$%UELy1a^3yWqAXCtow^T9-mJsh-s0D#|y@ zJSkq3KRm4K47k5ELbl^HUdw?)oGmI~@9JPp+^f|!Kc?FmG=eVz`6h6q!~9II(!tup z`VN%VGn$|hvwTBXWD(u3hfAS{t7X&xHkF(681`N31~IsVfw3WXNN0}uJDvWd4vk8h zVuFbmM<sd*y;U%5ZhlwYOejrf*thTNJD@@a=nuiv{H|X*a^afgw3@sG*OZ%#NIyXm z>P%>nV+<r=O=F5SVufU1jYhG^ge?5)H=$+CTI|u@Jp*`N1ww0*<@lWCAG|rfut7m= zuQto;fZhtpS;z&wmdLUk5m`t&uu>+22GVPE_(NTa=os(!{`k)%vQ`4}2Qou_?`!A^ zs@gZu(5ymb!W{_m@1mJ5T<BmBh^X@*w^o_v7N0}|xkc}sqG*~9^IU9f>}!Icr(sEZ zq-p7CAm>wy_?fXf7rsHFh*fXDlpbLe%gW(Kl&Y|(Y{uZ+S|Vj_Y8#YQRaIeP`nf8r zr)#_<OP{mU=b>?p#g|Anq94?w=NK}kCycC1NsRYRZ>(4<2Cu&de6l&?nuEJ4VD;$} z#;0O0(I>S@b;+0bSPuDBcfQLUhaTEb`8_7V*EE%rU5B!k_@nk0UN7E<j{D;y3x<a} z5;-N)?bQ8#-ODfo(rPoVIWOqZ%Hzg0&(4_F%>f27N&ks)2ojH!IbR7QGAL3jmZO2` z$@_B`PNl&Ei6_8!1cNS4BcqW5=ih$?Dc0yW;s_gs7~i#;s+t<7J^eeci8F{?Nv*)Y z4k<tkTpQ``Ylwck1~3sDxb+TzM?QM7wH>-MZTf)G5}^hHovKctn3mG5e8#78=bJi^ z(~3`c1Q<av0G^#5?HtB_h7M$S&JM8f0RR4Xtb_dPCI>Og2ZsK%Q;eQs?vDD(Y|HDP z!4OYvP+8%fT<8nR;8)FojVgD{{`+n5Au>b}GzAdo?-9Uf)8-s+&<?nO{3O+)e{=zh z@f?I$g-C5YrKAoXH@zT*;hcp(b82IfbfEl8b4M6<GAi?q!jpwpc-{@$$u|ATsU_Rt zV*Sb2Z@K*1*!o#mm^%mQG>I9KwtmYJ%x&i;QvxQD;UB|ieoHW1=>&QRyrD}a=ZPb^ z0-XX;8(;4C@RTe`s+IMJl0R<<gb)*dV8bSPLvXLaB|T3YfOGOQ(mO;&^)T-`_$}&Y zqFe1I)BYN}EOad5S=Q~ym}ujZlc_~t;7srU6W_I~f=n&|IHuAudgiT!PxFNxy+_2; zI;nC*?b4NKJXPa%0%_>kQx2|^2aP8}Y;P_qFLXS1)ribkAxeZ)u$@KjzfB|_a$|{v zk|e1ZZ{Vg)fB0YaFId(8(f(za=FqLUVy~*a5*F5Jdt(fd-hktSJ&lMDUwTfX5D5L* zwb%z^xT0KM8Y5o{`wnM}g3sx09Q+Dc<^_S4g!dx}%V&^Ad?h|xEu-?GW|>=~^0cXZ zre+%1#(&6wO{TD*cVi$NSw)T-jRxkA-#Ec46Gwb-2I&`ZQKe$cSI|ck@M;8so36qS zd;<R{prm^ry!D^~y&4>jig@we_qt%_{Af#uN@|LY1`Q!u<h;9P)@EfU;QMe>0ovR& zpo>c6yjuOITAMbRaq~a6_=ro(bAdpguo>LM9gQeb`d_xQo)tBu@5kUNIKF_}N?b2V zZZpiI)2{8;Uq4!`e9^iuVs-8lx%M!X4y3`V43D;Pj>9>htg3fxP~(xJ)FU18c6%di z^oxDSOdQT%RA7H&wsY<_HES8Uo7hN9oZ$}|&QV*nNB-QH3m+4QP3fpD5RVBdU%JVQ z21oF4;&>xFj`CBTsxwKBrUrR)<HE;=U>M}Fpn^GLT=c=G{OrwaR^FK1C#x6S1tBR2 z3aMq91sU?8z6Zp(2=oCjY{~9Tx<z*?&syG8jNIReHf2;hL1Vk;k$%w)e;U<NF}Udc z&9aBF_-{tTfCX$>J><}ej5Z$WTq7S3FqApW?tv%Ct<ijxxmskD)9)@{Nib6V>EVgr zNomv3l0(v5KVv%k8D$0D9rvUfqQ1gN^jBs(S23wnp;NukuI))LAPo-lsYjiziI~Fv z=d!}w#*hvB5Ma3>GV1FqcG%(WDk<Wfa`3+C*X7yGnr|>7K?V!BN7Caa?nEmWxsOBu z-aX<(=bwFCo@#LEW5a4P+6@;*=Cweq?ETOB<(u5Fvfr<NaoWI|-VhJPQ@juOla#F- zQlmQtv>TK|w~GPd{eWLx^#h8WD#}w2?a<OknZHi!lm?Cd^kkkioqmo{&M2G~U~5kB z;QvgrY;=Xs0DV`vpxYldJ|VWM;7?|ba3WtbK9@M|Ine8>8>3c^b;+yh$N%Cfdk&q$ z)znV|S=f>*<RyorSt<re$u_1ZV+99q=&gx^d!o%4b2oUtt^c47v82=fR_7(ng-9N~ z>_o7wh)6{pZ5+Frnp;e{Kwk<({vNEcQhJzOjc6iBeCS-CJ0@;j&`<(|WalRDDQVnK z6HSlPr6<%w5{yj53gTP6r+%HZkUT9<o7ou6UeDVK-655f9i;Ut(Wbr!D^<;Q7AHSe zz5o56kdsPf?cAcE=*^Xe(SWl3Wq0bQ6!Qy*B9Xzo;CIt%haM*<fHJM^BY?(D!vJ8F zU>;W4Num%#kodprrkf&nvT?h;Z7peW9c8&;C_d$hj$62^U>V$gpTd!WXo`e!u~}|; zkxt5?4q)rklrN7O1FM6(!+t)m)<#oq_GpLMM=!m?LnhyYo?730$;Wrp-F(y~<GY9b zprh8xmS2h3wJTxr<2ph$P-?p4CwPM3$>AIACr1wZ8VV7<C&#s9ibltjlI{#_^Pk0- zSlQP!r{#2OP55c$6!GMuaSY;7DXls<#Pgyx{k^G5y=?h|s{7UU5y!R%q!RC1m>nBE zf-#sA2$013oRj$z^E!mYP^5#3RD=B;zmkMz;K+}Nw3b0&4Ujf;IV?Op@}hU9{frvI zyn1`kMz6P4!toTz8B9DnPmU?I-&}K*f<)53H=(w^8AOe`Md^ivv;qc4H&iq39=C_f zvp5sgPlpl-9b5>B`>0ubVWNchJ)90nYcw1$XI!HYqoxoMIlBBVT(26BxM`u>uA=x` zsnb?>S3kll+bYC8C^Xr)rz+oa@g<h7MnW=8dT^|?dqyt11>5uWD#I1kGd%+($;rv- z;9m5+1zgFQxv0*s_)$6JUK&Kt&px!mzf?4<!i)^f{%U2HcjM6-H;EVg8AU59cmxFH z!HB(DTh&6inw<P+pOz;JcX8LPnU`f}1IP4;={7LG+K=0>2IE%tjC(RmrS|1KGQM@F zMelzW2*w@!2;pt$UF)v9kTm-BjvZ*+uZCKF-U+Z`?BUrI1(X8ai_2ms@MItYP&$k` z2dc<P4Lz00z?z+9N!c~k>I>%_1lOJy4xbCpU6Bb|(9Pqd@)m8cL~7*~Oj=rLZInr` zHls<_unjNO7<u2h^=%Du17Xz%#&8!-!hA6c0khh41rnE!%cMK~O7(K=!}OQxXCQcl z_qH9fYd<UKt&rZH^D}lP2WnL|wLyDrW&KIUxtLf}K)PZsU4IV^!R#jl51VR!i<*Fd zER-*$ZOk3j;3Im%cV<_K^?VkMNQi*gg6hA75IDC4CrC84CPPoSs1E^o2C;D~<h*Dt zj<@3WQ3kJiG%!s75E}W1)G`AL@Gu@hB&jY&HEQ92Wj25}HW2V|bd_c#f-4ziF|69r z>boPF$3gOvF>7bR&E`7pXA_PmHqU}Hf)}Ki?u8I%SJf>xylSV~{1yjrg)!D$L4n_e z6)|C5@nPjJ({u;nw4UK`RON|oUQs-WXw+_+yAo!UTGK-TN+}707d1?j`l_KO$|S3G zb;P`h;Gk@#*=-c9L(Z4Vk2xpH8C1mumZP2MTb}K4%NZxD%TaHtQsXv@xhwDmK4m^< zPiGTQgF2PGNQXohYUe_9JWV~eMZuTRTeU-*t0!A(>fILa7OLHjV1lbB7N5;dkPY%4 z3|)k~y+A$X!SDg~X7z5e&=+H=T-R1-J4a*g2I)(j+9nq(lwg4Fa9nU@EY5~chY?u! zQ^9W-nV&zITjVJ7;jBTeLzBDF;}mYpP1N|B({30vQk>3fa6Kf_T#-pL@XmFm7Rx-J z+WISYPz+#*GpenZ_UNLGvhh99<ti^y${MnA4z<|N->H`PhMJcD)g`xQ!Rn>ee@XqY zU6Qh1*T+xRUng13Z)Bhre9@+iRmh{~NZ2<nC(sapg|aUp24UqiT&|_#1U>k5==E_+ zV|i!5VH5<d9gl3!65Di$2+KIBc(nOS-Rk%8?w~+O^j1Z^o|K%EF%@h7`B4%iH%qu* zTJmMt`dguF<09o69)Hp2jlrTIGkSC9XEu5?@Q&)Jb!UNq0SRz=n6NO0jO}2+zc-I* zbL@>X8fI~`P7cm9SFM<ruINt+3OCPzlqAY8txY`)dJ@=m+%nwwt*h`(z`mVFYi2D1 zGgWd`yXK~qBRCuQ-Mt&Hu24kNr0XVpZYRV!g!^L8D?a4@?QvdRdTmG6DLt2RP@NZ) zqP|t_g)(LFQTEXUtpbn-(STo(+bwh+xCaLm2yNyY;)om1f<8CdlYa$Za48mcS#s@| z0WO?_feX~GKx^>&ItSm$)MUH1p{C_bW$vvJEho5_TxsLPkcCz4lL%MQo6nxEj$SAK zbWVYbncS9rM)p1G^BtF~=gsPu*sTb!?<O<Cf*!?VlMqgJn~Rowg|I%j8vkHq4KyV2 z{Q}>l31dwB3p<@>&lJJPjyzx6O`cKjp$COlcC7TsX8L*#G5p+Tc>aIP(OLR)Tt)u9 zzP!+97(y*Sq|7E+`il{SK@7#~mF`Yu+E@l2Vjk)>D|X*{S;Q@@y$@4hdxHNfvBBP2 zOT$!c^TWLvUoxx1D#e+_M-aT^G5iqzRLQ=|ejLo09Vn8(oF)}qe|<Hs0Go^8e!HTD zGlAA8ilM9(w)gZ2#eg{exxdbw^$5u12<PCpLLJ7Ff7+}*^j|)LVC5@Dxqf>x+xfvF z6o!1S=m9ImZW+?upJD=wE^x^j?P)Tr%N35tn9RFjm)3HaO?%f_kkKlBT|`Rw$(nM+ zAB#KHxwZ8@DBAx~%(;g%{r`P@XoRGLvmE*=DN#9>L?@z9&WCN3oKH=bjIs2MicpeL z4#jdFIgQN-g~<7^oRSc2nA0{4yWf0&zu*16e%F0p*M0wSU-$ppuFtm5_I|ydhsWSH zTk|EEc^0&9CQjtIGSAwDk#2l)J3bMV2%paK<zj8&dr(~R`y8VP+4F^vvrpDN#vEH5 z$gNhqiN-Ernv07+ex%DK<zv;Kyh=}Kko7)EIB$|h)Xs~wyp>HS4VO^RAHOX-nTp|5 zyO{e_gb(fPK@W?WYSdcWIe|9h`Du1Kg}RQCL&A4;j^w)cSXcL5xjn>Du!*CQoQKP- zCkNMCW+L9FW}x-yW8I`fcPgW6h(8-NvjwuX7V}h_1ulfoEpb9x*D(ohcl<8Q<$ZLZ zr)aPP3nEp8Nsqc60_6@vKK!!;B|ofaTrxlv(_vVgzbr(r_;}%L+6$U2USJzzXmclD zAC=p<KHRSkE#b@samH1hh<iu~b<LW!x;)&-DHGe5q|l^Ej1!w<Qn+HS(0&rLhI1Mb z%c*z2j|%@0aJKB4Hg$fu46a@>^UgOtiaQ&K0mtuDrNV-SQ8{i4B0XS4XvJL|bg%o9 z|3fV<w#3FAUa+sQdNns3RvGXaGk0I^D~yK&r=xA_39Vle4h;jReqE)vE-G-_eL3Az z#r=sNz~)~t{l}vSb7=IyApt}^{`cyv@?7uo9X)qIjtL{D=0PkNpOUJV4u<c)R6|=C zy=k2*x3pRxCCv6MtU;a5^(&*~?O*}jA^Qt+sj4WoZ}Ndl90LIqYZ;V}a?u$u($+l8 zLM6W%P%WKe97$4)>_)SlmQwE=?bGeST~kF=+1*+H^k^?!k9a7MXzR2ua14E_@$}A$ zfgeHRC5^0#8urE**a(Z+?u0eluvcm&;Ttu07D2fyzf<d)wMuPa#E^K~+N4Fi9#K5W zud#G=1Rr$DDeTwVcCDLx@@u2d`!)MlwW4CB2OFeYHcWFz-ZAWLXxh&Cr%VnB{JLSJ z+K<83v$>*LK+8K+!F&GaXMD=(@<lqvkdWv{_Kp?C-h1~ANDuAPt|uU|dEL`NY?2I3 z`?ZGhz-wN{$)H(SKSO{@L;d1}*9*a=7S7Jug;L}6M-lkr!PXjB>yP;#`-vAEBbny1 z=x@U^H?c|i%PO{y$cB>RQTiOCGxcCdBWHbgtYv1zg?<^?dpBP-DS3#xaoU$TIAr&i z%LQ;S0z?BIU$@a>;nLHPYX8sNd=PAviy?u%@<`i+t4KN_?mO$zV}f?E0cFkj@Lu*t zSFsBxum>bXMm8Rjyhnd}b6Y{%602C+cwSQS%t~iF=F13A1?MM}Q%y%tYJ>|rlfyIo z!UKP|S}<Af1Kk;Xfc#V{gSw9ZtqB#u2!{O~^Xm`~G1A2DV*ff69G^vKfy+t^#x!(( zsOlw#j2f+Fym4q;JF>km!@jgY5vJW{`ts|tlf3Vh^L(ZIU2oPqN@_-*xj%gjYtv!( zV=i^HsXVBainLYr)Y+}@`$%U&oN7{euT^o4fg-S^d@q$~dy7rF@z$dKUmuo&VB;G& z2>pyfp(Wwn+mhb?V6_6^1OYkA5xfaX!X58>R93pvNQH;b7Jcuo(FTwj2><5(F}wO3 zC%=qRt3`^e?W{U8PIy`_x+!V$4!oQl9h|yS<Qm5K4uL#hjFl!0GjKm*ugrP8Si!JK zBv1g0mjP47Ys(i*^O`lJzJ0IVZ@neN%`h%VuLU-!J_J34i>?nV&U)9aC86Xi=({(< zNj~HyRH}*VNjDj^xz<#kr2=VVv()B|o+$`wXBHlugF<@F!$S{|Gg?w~^`^cr_qu9t z_@?;5f{+bWRM{b0-V~C?@o}^ws*?3J_2Ww?`HW31<{E?q`eH-tdGuSGMXYvN-7zrj zzX_SZg|QlFJR2tk?)v{*-A<9n4f5WK4QSRhA(8us@@!d>YNa-UJbkJ$r~;3a!I!jD z#<q23D9D|C%l&<H|70XyXUi)jYE1hJF=k&YE)QPdYElK|g($omYH}I${R%Q6SL0f6 zKG_kY6ese}@S4yMB1#C!fZ&e)-cj``y}i0{)+bf%V5$z~WNgf_j-6WxxQ<)R+s(tW zWWQo}$m@dK)ux>Ge@_iF*bf7HJV=#))&%+PYycS|f(<&xK?~H*wi<7WiL{;^OFw7l zj{u@|C~;v^|4iE72K0wv;F`gu%--mnC~hw;w0fao9}Kt~L+laGhE;hqGqL$Nn0LMV ze%qZ2TdO#h7MlT<d7_)LZK_wFLP%-NPMK5VaR==IIP0~3c$g*a{e3=RB8JVZ1S^H^ z&DW|{9!|fPugcs7K!(*Zg+QPu5P+^w69y)?4C<^G!k_+j>|8tL_7WuCADqLyme?%F z{>wevJB-7CP<&>22H+>`1D2+)QGkWkbqkO<f~LQn(Dv>-%IUb%Ra|NpEb=g~^F1ah zuru90HTah#D}^4~xITVJztSxw;<l#Prasz|wV*``ooIHU)v$-@ig+XDR0U#_nqVgK zJR4ylUjnq;T$;HcEM8y0FiFKxD<Cxc3=DMm^!;LDlQwr#m&U%R#`AYW`&U7*EN*q5 zjKA)k>A8C)+#Y(uTL~PgE0Q)TSGwE&kb))^gHTzL7W4=DPm!w(CiDG^{gg>g8+|QS zc<O>UUn-h<IEXz1(pZ)m=CwnI^jE$-%`Yqm&njptetgF1NyDHY$#?u4zvnBp1LZYV zXP~vMN1SjV;|}6BpTV^&Es~I9h6F&^A%w0hJe^p@00T1&u5mt(9;x~)2VX>wjCflS zgIHb*U_3@WFr!|-=1EqvGbaU^%X+@1_m@m2`xZhdZLUCU3EXrIPx1z~_0GFU4gsDL zd~XMpap$Z3@hkq2^t+q!YRe4e%aS(Gy8y-(Xpc)jn6rCOz{?p<4D=lL*F<+y9~Zb7 zrOAX<OHn{ZA~#_8Z(C`zCatH{ZeqLingG@}SpBaUI{5G-Vj~j4qPp1#h!#^>=!fTt zi)1TZ&KkR1Tf!|yTg}B9(yST}r}v><q1N>N`3eAnx;E^9%6E9*AL~fYlA(--TnZ$s zC4jyTQ}QDJ{o@*DFw70{gFY`WRScu9cvixnKjf_;-rduM#9{ch-iM+NX0?<qHqd<H z84@ING561MRTc*dScw#6YT`9agc3(bN3BXGymXYuyj?r0*>h3)(_QK7na<jo>sjj_ zwaAi0_KrjaBEq$=*e$WJE5vP7$I!R#hn5N3a^xb7w?N+Zo{5vw$R$WF{ZB+8>l&fA zC@7lSwxA?GmHQ7UX|#&ZLCh^J!`|Ihte*9-#bj{gX1HS`lW^hKr$@0eP_jVG7&DrI z_&}irqn|JfWBFx*lx^~z)3~7@B|y$TaF2Dor;Cp0UFxS4UXla8-F6q=j|_tDscM4F zntwW8wC9>p1||`YG23iVNodtaM!h-y_l*Y;5E*@Y=@Oxsi@5LHFMf-nZrh|w@g6iT z?DejbH}=sn(b3XK#@O`P7nZ~nMa<6WEx`(e-QyImda1ashytrT*1>(D@1n>|{S=K+ zE$|q1_CjN#>I+DdK5#<sUo3g$bpXR29ATDpzZY#%niFb#?)40Jd$R3GOo-|I;n)K+ zhT|@AdcQ~=8yj>^^|3f^GfkpC^Fv&vh7vp989;h&;W4Zy{-I;ZvmpkgE@)N&*A)qQ zWu=1hsE4U}l#4VG@^`o77akDba7UY~b&zj=lJ{*7b2MTg1Aomrpu?uh1h9<NDVoUg zKy?rD#yvIoz`fATKpIGYSy1|H_&zd&I}@s#GaF#ZfH_d`;-Go7)YpMyec!Ix&sI*# zvdRiEDrJJ(j-1!wN@|ucTT4((R8YOuSv)`1sGBa;8RK(_D;s5`+1n<NvLpl4h-2cR zj>ngly3KVnfmK^v+By4cRd2S9tFHh+h7we7PF0MZ=``5rgi3j+p<EL+h`rPUh0fYO z2GntUO-9KH!n5XdL%iFpsb!_$p3zTso)%g<%Ic3BWz4mrgObH9I}klW){UljFH2{* z5|6uy<CfG9OZ3i5@G6Uay%6<g%Kr(MR$uQ)m_LhC9v$E*<XG$YEW-8!KfZ1~6Hgn( z47HFE>~9TErAf<m1@V9R>hY+zz5G_Si5q=nW6>#G|GGVh``s*5Q%W}p*>lu2y!Vo^ zZl0$LsB0IMV!EnS5>#J)8+X+bEMVH&OzCt<xGE=<iw{Gu31TLD7*>0Y4_EZgXZWNR z*9n-_<MBXq3lP>@iVw%@nU7en`@wByNO`VO?Rk%iH2iEwT%c!HCC#3@y6E}tD^es2 zQ1X0|NA2l23RRtf=^615g1#S^95^Q6bDo-tU$s})8LSqQ5iF0(4JM~b&$^Ql?d{D_ zBS>raKT|25%<HYodGl#c-W{bUD%%d_6;@N_WMpI%gvv^@Kd`D<<aY90onZBq)%E*w zYw4F0B2GiEdYWp9b}{4A)~DUW*i%0rs7l31C$%wZDTt8PI9sb-`NhL%T0u_YMe!@| z!9TuNKZD{#myUaJv3q_v*S>A6*|KHT;SHcka-3FoM*YJ|lw>$6<d|6%z5QWvdr!8R z9XJL<mPezE7-rYjajynYRlg(RtHpzckd2pk&F@8XyJP&zYBVSju1t-+HQz0{3VJm` z9W!hsgihYLflJMRD4jySh&=ZV&Hj2^J-hwO>W*>oV~C@qu%DkEspbX3zCoLnTo=n= zY)7_9V_Y#|AWp00wq_w^V+X5*iPVLUOZ?l=FUl_B`8#&u=6_-r&dfF6fb6r)>Zdcc z8PXbYXHV{M^zh%}Hb`?Fe;je*<L2!0KjkQ3-(2S%33cIcGq!4OO|Jr2!W6xdzq6m- z6A0Q`m$O}g=ojGOKkc-YU57wUcK7y9;A=KkpZcV8XMkTF+tndz3lOFK7m!M1I~?qQ zJ6ApdW)w)`ZeG=YIZ+9i5_VUFt^kAXHc&Z|`2NN5+~j?B`)*4O-w5V|{CXgcxMlhN z3UbDbaPDmw&#r&5JbeLSvHIR!YR_S{g9f!ZJ9U5`5k3^U*oq$pLsQhM5H<-gM<t^F zf%@v$EN!KAZpQvLCSUtk{|;O2P9uUWJiwLWRO_3QkKB55{Cb4b`w;I)VMn*R5yyjD zR<1wbb-nf4B8M3QG9v)U2IN^l_VLz^7EgIk(=+#;4TLWTEn!Me|0eqcFS##a-W5Ix z2@4EfCQdCB!8+A!j(C?%#5&5_mIvgjIKhJJC>QmQ<?NVfmOT;#J3#iY<Pcyhc`KY} zOj`^4cO0WiRzWE_gD$#0UEw0{5L>}|$gc9@#44X3VjqGMja%v|WezG_w_s#3j43IU zlW<0THaL`%r+j&d)5K=!<A)~7;awp%iz~=R6CB;DlKzxzR8|;Wt%z5pO;1<3!H2x> zRqclu`W@6{PG`w_9f%BXBrC4z&R$6C`Dk(~r%|Oab7;~A?N>(cZ(=?2PXA0cJCa!Y zoXts)5-^@P!qT@Yk!wlJ7--MW?4xK>!!My@HQ1c&Qa$Mvy@%dYpY`6+-W8f=+>@}) zdQjsyELK22((Av!niI;YpS%R{Uy0?WD)d*M7sHp|Owl`?wf*d2<n80TU9i{ekcX-( zc>R`x?nXpw#pZe5x?p}|;><C$nn0R#m<_J|;hAF&-=`)O$^Ko%6}~r~jOZW-m2-An z&(IrE?4&fTA49rsPx`Ehua69|3Ji~;ixbkGArh9}R4hcb=k)C2Z;=st{I1bblP<ol z@9i5Cl_tkZMYb0%N%n4&Mm7fFX5g*NrrdxDJR|%kyPc&!tG>9j9>>;Ql{B94iCPSQ zW-{WG?g3z|gm+J{Q|+14{!3-Za2^l;3GTuv1!C0@yY!sJy2wUM$R~0tQK%{H4t@k{ zBhM2vq)bUUyq5A<)&N0^$mBdi3pWwa_hD4t@lAZIat`}yL19_VNlC~|fdx+yK(1aM z;dqF5dl5_e7PPTUOMFN$ZeoHM$`h>7#&t4D!$!&PtCi7%c~=dQd!gAHvv_zl4asn< zIGtk_oyUfh5*!*tqjD?bdB^!TUw@x)lE_y@%^eA5ys+KSg$#>32Xbm(iUgLMR0^Vz zC>X3kM|BPt#<P%-ysF}<hvHg@1(aHwR?*f9!y8@sv9SHn0jEI2UI*e%6C0_h2{hpw z-9lAKagk^Ive5(nqGyQnDcC>2J%vxCNl6f5oW+$QJ;X9$HkmL&orayBwP!?)i5{X^ zg|DMJSBB-Js>HhhpsWup@H9CW7wnD06r~tPx$g=ZGe>X1g4`dGD#WAELQMoOsy3BR zM0D#TI_p7uH-;G_o{pj?EFmBR=PT!{uDcX<eLI=ZYj|T>|MMg1h3hR`rD<N&{3-gj z3MDlVudspbg0*@>#xGmA6eat0deW5C=dG9CA#iGzHC!vtMYcDtr<Cq!+RN=c?tnY- z5&7hz<IeQEM^N|Mp2Z53RP+s~NEw7glR89w7eB4<NE6%w`>G5;V!S16EJ2+03uM5k z>(kZF$v;u&5NZm@dYxxfL&!{)1zglN>oYl$fBve-CdvBEe7yIB+WthTP>t>PerEUR zlunfGO!0USfU+JsMb%2T%bi($(_1NoH3UH6{m%s>GTM2r`b<QO95tc7weThLrRywo zl|Ur~i3S<Fyk2cn{4HAxS-=gv-DU5;FLGEqfq1ujLQ?64168HCT1g7LbHlPkT{spS zn%5(G+^_59Rb7g(@o*J?B)DCe=fcYf#ykrT!8fosiJzZS?pkK{9GXr(fMJb=#f8Ad z4r=--Gko60@+=(8DX^TOVNcTn4elj+L%b#Jcgxp2^Ap;$P9A<PJ=XZrIbEU|TD`4O z>6F?>rF^s$>5T@K`9fq_bd&@)rCfr{<TWv*uL^=qo!4ay8~T%knbAr|?kKIv?Ek?9 zi-t&KV;lCzn}-tmc?}SsgeD-n;{JCh*=8aaXfm6}jKEfr?!P&1HXrc+Ob)LL=d!Pq Vhsrydc5Ysc@kO%>_;bz?{{j0RY9ast literal 45828 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 diff --git a/packages/lib/src/date-input/DateInput.test.tsx b/packages/lib/src/date-input/DateInput.test.tsx index d1a4d045a7..90cc57fec7 100644 --- a/packages/lib/src/date-input/DateInput.test.tsx +++ b/packages/lib/src/date-input/DateInput.test.tsx @@ -232,14 +232,6 @@ describe("DateInput component tests", () => { expect(selectedYearOptions).toHaveLength(0); expect(selectedMonthOptions).toHaveLength(0); }); - test("YearMonthPicker focuses the left column by default on open", () => { - const { getByRole, getAllByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); - userEvent.click(getByRole("combobox")); - userEvent.click(getByRole("button", { name: "August 2021" })); - - const [leftListbox] = getAllByRole("listbox"); - expect(leftListbox.contains(document.activeElement)).toBeTruthy(); - }); test("Selecting month and then year closes the YearMonthPicker", () => { const { getByRole, queryByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); userEvent.click(getByRole("combobox")); @@ -559,6 +551,8 @@ describe("DateInput component tests", () => { expect(getByText("October 1910")).toBeTruthy(); userEvent.click(getByText("October 1910")); userEvent.click(getByText("2010")); + // DateUnitPicker closes when both year and month are + userEvent.click(getByText("October")); const day1 = getAllByText("1")[0]; if (day1 != null) { userEvent.click(day1); From 08e92aa50485a80377f4c3c7bdb59ad0217479aa Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Tue, 18 Aug 2026 10:44:25 +0200 Subject: [PATCH 03/12] fix based con comments --- .../lib/src/date-input/DateInput.stories.tsx | 149 +++++++++++------- .../lib/src/date-input/DateInput.test.tsx | 68 +------- packages/lib/src/date-input/DateInput.tsx | 8 +- packages/lib/src/date-input/DatePicker.tsx | 79 ++-------- .../lib/src/date-input/DateUnitPicker.tsx | 57 +++---- .../lib/src/date-input/YearMonthPicker.tsx | 74 +++++++++ packages/lib/src/date-input/types.ts | 21 ++- packages/lib/src/date-input/utils.tsx | 28 +++- 8 files changed, 252 insertions(+), 232 deletions(-) create mode 100644 packages/lib/src/date-input/YearMonthPicker.tsx diff --git a/packages/lib/src/date-input/DateInput.stories.tsx b/packages/lib/src/date-input/DateInput.stories.tsx index b465118fb3..3bcb0d9a0a 100644 --- a/packages/lib/src/date-input/DateInput.stories.tsx +++ b/packages/lib/src/date-input/DateInput.stories.tsx @@ -11,6 +11,7 @@ import DxcDatePicker from "./DatePicker"; import DateUnitPicker from "./DateUnitPicker"; import { fireEvent, screen, userEvent, within } from "storybook/internal/test"; import { HalstackProvider } from "../HalstackContext"; +import { getPickerItems } from "./utils"; export default { title: "Date Input", @@ -166,66 +167,96 @@ const DatePickerButtonStates = () => ( </> ); -const YearPickerButtonStates = () => ( - <> - <ExampleContainer pseudoState="pseudo-focus"> - <Title title="Isolated date unit picker focused" theme="light" level={4} /> - <DateUnitPicker - selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} - onYearSelect={() => {}} - today={dayjs("1904-04-03", "YYYY-MM-DD")} - /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-hover"> - <Title title="Isolated date unit picker hovered" theme="light" level={4} /> - <DateUnitPicker - selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} - onYearSelect={() => {}} - today={dayjs("1904-04-03", "YYYY-MM-DD")} - /> - </ExampleContainer> - <ExampleContainer pseudoState="pseudo-active"> - <Title title="Isolated date unit picker actived" theme="light" level={4} /> - <DateUnitPicker - selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} - onYearSelect={() => {}} - today={dayjs("1904-04-03", "YYYY-MM-DD")} - /> - </ExampleContainer> - </> -); +const YearPickerButtonStates = () => { + const yearItems = getPickerItems(false, { calendar: { months: [] } }); + return ( + <> + <ExampleContainer pseudoState="pseudo-focus"> + <Title title="Isolated date unit picker focused" theme="light" level={4} /> + <DateUnitPicker + selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} + onYearSelect={() => {}} + today={dayjs("1904-04-03", "YYYY-MM-DD")} + items={yearItems} + /> + </ExampleContainer> + <ExampleContainer pseudoState="pseudo-hover"> + <Title title="Isolated date unit picker hovered" theme="light" level={4} /> + <DateUnitPicker + selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} + onYearSelect={() => {}} + today={dayjs("1904-04-03", "YYYY-MM-DD")} + items={yearItems} + /> + </ExampleContainer> + <ExampleContainer pseudoState="pseudo-active"> + <Title title="Isolated date unit picker actived" theme="light" level={4} /> + <DateUnitPicker + selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} + onYearSelect={() => {}} + today={dayjs("1904-04-03", "YYYY-MM-DD")} + items={yearItems} + /> + </ExampleContainer> + </> + ); +}; -const DatePickerToday = () => ( - <> - <ExampleContainer> - <Title title="Isolated calendar with today" theme="light" level={4} /> - <Calendar - selectedDate={dayjs("06-04-1904", "DD-MM-YYYY")} - today={dayjs("1904-04-03", "YYYY-MM-DD")} - onInnerDateChange={() => {}} - onDaySelect={() => {}} - innerDate={dayjs("06-04-1904", "DD-MM-YYYY")} - /> - </ExampleContainer> - <ExampleContainer> - <Title title="Isolated month picker with today" theme="light" level={4} /> - <DateUnitPicker - selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} - onYearSelect={() => {}} - isMonth={true} - today={dayjs("1904-04-03", "YYYY-MM-DD")} - /> - </ExampleContainer> - <ExampleContainer> - <Title title="Isolated date unit picker with today" theme="light" level={4} /> - <DateUnitPicker - selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} - onYearSelect={() => {}} - today={dayjs("1904-04-03", "YYYY-MM-DD")} - /> - </ExampleContainer> - </> -); +const DatePickerToday = () => { + const monthLabels = { + calendar: { + months: [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + }, + }; + const monthItems = getPickerItems(true, monthLabels); + const yearItems = getPickerItems(false, monthLabels); + + return ( + <> + <ExampleContainer> + <Title title="Isolated calendar with today" theme="light" level={4} /> + <Calendar + selectedDate={dayjs("06-04-1904", "DD-MM-YYYY")} + today={dayjs("1904-04-03", "YYYY-MM-DD")} + onInnerDateChange={() => {}} + onDaySelect={() => {}} + innerDate={dayjs("06-04-1904", "DD-MM-YYYY")} + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Isolated month picker with today" theme="light" level={4} /> + <DateUnitPicker + selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} + onYearSelect={() => {}} + today={dayjs("1904-04-03", "YYYY-MM-DD")} + items={monthItems} + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Isolated date unit picker with today" theme="light" level={4} /> + <DateUnitPicker + selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} + onYearSelect={() => {}} + today={dayjs("1904-04-03", "YYYY-MM-DD")} + items={yearItems} + /> + </ExampleContainer> + </> + ); +}; const Tooltip = () => ( <> diff --git a/packages/lib/src/date-input/DateInput.test.tsx b/packages/lib/src/date-input/DateInput.test.tsx index 90cc57fec7..3c60c69f5d 100644 --- a/packages/lib/src/date-input/DateInput.test.tsx +++ b/packages/lib/src/date-input/DateInput.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render, within } from "@testing-library/react"; +import { fireEvent, render } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import dayjs from "dayjs"; import DxcDateInput from "./DateInput"; @@ -214,69 +214,6 @@ describe("DateInput component tests", () => { fireEvent.keyDown(document, { key: "Escape", code: "Escape", keyCode: 27, charCode: 27 }); expect(input.value).toBe(d.format("DD-MM-YYYY")); }); - test("YearMonthPicker opens with no selected options", () => { - const { getByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); - userEvent.click(getByRole("combobox")); - userEvent.click(getByRole("button", { name: "August 2021" })); - - const yearListbox = getByRole("listbox", { name: "Year Picker" }); - const monthListbox = getByRole("listbox", { name: "Month Picker" }); - - const selectedYearOptions = within(yearListbox) - .getAllByRole("option") - .filter((option) => option.getAttribute("aria-selected") === "true"); - const selectedMonthOptions = within(monthListbox) - .getAllByRole("option") - .filter((option) => option.getAttribute("aria-selected") === "true"); - - expect(selectedYearOptions).toHaveLength(0); - expect(selectedMonthOptions).toHaveLength(0); - }); - test("Selecting month and then year closes the YearMonthPicker", () => { - const { getByRole, queryByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); - userEvent.click(getByRole("combobox")); - userEvent.click(getByRole("button", { name: "August 2021" })); - - const monthListbox = getByRole("listbox", { name: "Month Picker" }); - const yearListbox = getByRole("listbox", { name: "Year Picker" }); - - userEvent.click(within(monthListbox).getByRole("option", { name: "September" })); - expect(getByRole("listbox", { name: "Year Picker" })).toBeTruthy(); - - userEvent.click(within(yearListbox).getByRole("option", { name: "2024" })); - expect(queryByRole("listbox", { name: "Year Picker" })).toBeFalsy(); - expect(queryByRole("listbox", { name: "Month Picker" })).toBeFalsy(); - }); - test("Selecting year and then month closes the YearMonthPicker", () => { - const { getByRole, queryByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); - userEvent.click(getByRole("combobox")); - userEvent.click(getByRole("button", { name: "August 2021" })); - - const monthListbox = getByRole("listbox", { name: "Month Picker" }); - const yearListbox = getByRole("listbox", { name: "Year Picker" }); - - userEvent.click(within(yearListbox).getByRole("option", { name: "2024" })); - expect(getByRole("listbox", { name: "Month Picker" })).toBeTruthy(); - - userEvent.click(within(monthListbox).getByRole("option", { name: "September" })); - expect(queryByRole("listbox", { name: "Year Picker" })).toBeFalsy(); - expect(queryByRole("listbox", { name: "Month Picker" })).toBeFalsy(); - }); - test("Selecting January (month 0) and then year closes the YearMonthPicker", () => { - const { getByRole, queryByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); - userEvent.click(getByRole("combobox")); - userEvent.click(getByRole("button", { name: "August 2021" })); - - const monthListbox = getByRole("listbox", { name: "Month Picker" }); - const yearListbox = getByRole("listbox", { name: "Year Picker" }); - - userEvent.click(within(monthListbox).getByRole("option", { name: "January" })); - expect(getByRole("listbox", { name: "Year Picker" })).toBeTruthy(); - - userEvent.click(within(yearListbox).getByRole("option", { name: "2024" })); - expect(queryByRole("listbox", { name: "Year Picker" })).toBeFalsy(); - expect(queryByRole("listbox", { name: "Month Picker" })).toBeFalsy(); - }); test("Selecting a date from the calendar (using keyboard presses)", () => { const { getByRole, getAllByText, getByText } = render(<DxcDateInput />); const calendarAction = getByRole("combobox"); @@ -551,8 +488,7 @@ describe("DateInput component tests", () => { expect(getByText("October 1910")).toBeTruthy(); userEvent.click(getByText("October 1910")); userEvent.click(getByText("2010")); - // DateUnitPicker closes when both year and month are - userEvent.click(getByText("October")); + userEvent.click(getByText("October 2010")); const day1 = getAllByText("1")[0]; if (day1 != null) { userEvent.click(day1); diff --git a/packages/lib/src/date-input/DateInput.tsx b/packages/lib/src/date-input/DateInput.tsx index 6af1c10ea4..50cd257f44 100644 --- a/packages/lib/src/date-input/DateInput.tsx +++ b/packages/lib/src/date-input/DateInput.tsx @@ -322,7 +322,13 @@ const DxcDateInput = forwardRef<RefType, DateInputPropsType>( onKeyDown={handleDatePickerEscKeydown} ref={popoverContentRef} > - <DatePicker id={calendarId} onDateSelect={handleCalendarOnClick} date={dayjsDate} /> + <DatePicker + id={calendarId} + onDateSelect={handleCalendarOnClick} + date={dayjsDate} + format={format} + locale={locale} + /> </StyledPopoverContent> </Popover.Portal> )} diff --git a/packages/lib/src/date-input/DatePicker.tsx b/packages/lib/src/date-input/DatePicker.tsx index 8bea633a02..81d8f139ea 100644 --- a/packages/lib/src/date-input/DatePicker.tsx +++ b/packages/lib/src/date-input/DatePicker.tsx @@ -3,11 +3,11 @@ import dayjs, { Dayjs } from "dayjs"; import styled from "@emotion/styled"; import { DatePickerPropsType } from "./types"; import Calendar from "./Calendar"; -import DateUnitPicker from "./DateUnitPicker"; import DxcIcon from "../icon/Icon"; import { Tooltip } from "../tooltip/Tooltip"; import { HalstackLanguageContext } from "../HalstackContext"; import { getFormatFromLocale, validateLocale } from "./utils"; +import { YearMonthPicker } from "./YearMonthPicker"; const DatePickerContainer = styled.div` padding: var(--spacing-padding-m) var(--spacing-padding-xs) var(--spacing-padding-xs) var(--spacing-padding-xs); @@ -79,22 +79,17 @@ const HeaderYearTriggerLabel = styled.span` font-size: var(--typography-label-m); `; -const YearMonthPickersContainer = styled.div` - display: flex; - width: 292px; - justify-content: center; -`; - const today = dayjs(); -const DatePicker = ({ date, onDateSelect, id }: DatePickerPropsType): JSX.Element => { +const DatePicker = ({ date, onDateSelect, id, format, locale }: DatePickerPropsType): JSX.Element => { const [innerDate, setInnerDate] = useState(date?.isValid() ? date : dayjs()); const [content, setContent] = useState("calendar"); const selectedDate = date?.isValid() ? date : dayjs(null); const languageContext = useContext(HalstackLanguageContext); const translatedLabels = languageContext.labels; - const localeTag = languageContext.locale; - const localeFormat = localeTag && validateLocale(localeTag) ? getFormatFromLocale(localeTag) : "MM/dd/yyyy"; + const localeTag = locale || languageContext.locale; + const localeFormat = + format || (localeTag && validateLocale(localeTag) ? getFormatFromLocale(localeTag) : "dd-MM-yyyy"); const isYearFirst = localeFormat.indexOf("yyyy") < localeFormat.indexOf("MM"); const handleDateSelect = (chosenDate: Dayjs) => { @@ -102,58 +97,13 @@ const DatePicker = ({ date, onDateSelect, id }: DatePickerPropsType): JSX.Elemen onDateSelect(chosenDate); }; - const closeYearMonthPickerIfCompleted = (year: number | null, month: number | null) => { - if (year != null && month != null) { - setInnerDate((prevDate) => prevDate.set("year", year).set("month", month)); - setContent("calendar"); - } - }; - - const handleOnYearSelect = (year: number) => { - setInnerDate(innerDate.set("year", year)); - closeYearMonthPickerIfCompleted(year, content === "yearPickerMonthSelected" ? innerDate.get("month") : null); - if (content === "yearPicker") { - setContent("yearPickerYearSelected"); - } - }; - - const handleOnMonthSelect = (month: number) => { - setInnerDate(innerDate.set("month", month)); - closeYearMonthPickerIfCompleted(content === "yearPickerYearSelected" ? innerDate.get("year") : null, month); - if (content === "yearPicker") { - setContent("yearPickerMonthSelected"); - } - }; - const handleMonthChange = (chosenDate: Dayjs) => { setInnerDate(chosenDate); }; - const yearPickerSelectedDate = - content === "yearPicker" || content === "yearPickerMonthSelected" ? dayjs(null) : innerDate; - const monthPickerSelectedDate = - content === "yearPicker" || content === "yearPickerYearSelected" ? dayjs(null) : innerDate; - - const yearPicker = ( - <DateUnitPicker - selectedDate={yearPickerSelectedDate} - focusValue={innerDate.get("year")} - onYearSelect={handleOnYearSelect} - today={today} - autoFocus={content === "yearPicker" && isYearFirst} - /> - ); - - const monthPicker = ( - <DateUnitPicker - selectedDate={monthPickerSelectedDate} - focusValue={innerDate.get("month")} - onYearSelect={handleOnMonthSelect} - today={today} - isMonth - autoFocus={content === "yearPicker" && !isYearFirst} - /> - ); + const handleYearMonthComplete = (newDate: Dayjs) => { + setInnerDate(newDate); + }; return ( <DatePickerContainer id={id}> @@ -179,7 +129,8 @@ const DatePicker = ({ date, onDateSelect, id }: DatePickerPropsType): JSX.Elemen type="button" > <HeaderYearTriggerLabel> - {translatedLabels.calendar.months[innerDate.get("month")]} {innerDate.format("YYYY")} + {!isYearFirst ? translatedLabels.calendar.months[innerDate.get("month")] : innerDate.format("YYYY")}{" "} + {isYearFirst ? translatedLabels.calendar.months[innerDate.get("month")] : innerDate.format("YYYY")} </HeaderYearTriggerLabel> <DxcIcon icon={content === "calendar" ? "arrow_drop_down" : "arrow_drop_up"} /> </HeaderYearTrigger> @@ -203,10 +154,12 @@ const DatePicker = ({ date, onDateSelect, id }: DatePickerPropsType): JSX.Elemen /> )} {content !== "calendar" && ( - <YearMonthPickersContainer> - {isYearFirst ? yearPicker : monthPicker} - {isYearFirst ? monthPicker : yearPicker} - </YearMonthPickersContainer> + <YearMonthPicker + isYearFirst={isYearFirst} + innerDate={innerDate} + today={today} + onYearMonthComplete={handleYearMonthComplete} + /> )} </DatePickerContainer> ); diff --git a/packages/lib/src/date-input/DateUnitPicker.tsx b/packages/lib/src/date-input/DateUnitPicker.tsx index d5dcd520cc..0bb3588574 100644 --- a/packages/lib/src/date-input/DateUnitPicker.tsx +++ b/packages/lib/src/date-input/DateUnitPicker.tsx @@ -1,18 +1,17 @@ import dayjs from "dayjs"; -import { KeyboardEvent, useContext, useEffect, useId, useMemo, useState } from "react"; +import { KeyboardEvent, useEffect, useId, useState } from "react"; import styled from "@emotion/styled"; import { DateUnitPickerPropsType } from "./types"; import scrollbarStyles from "../styles/scroll"; -import { HalstackLanguageContext } from "../HalstackContext"; -const PickerContainer = styled.div<{ isMonth?: boolean }>` +const PickerContainer = styled.div` box-sizing: border-box; display: flex; flex-direction: column; gap: var(--spacing-gap-xs); align-items: center; overflow-y: scroll; - width: ${({ isMonth }) => (isMonth ? "60%" : "40%")}; + width: 50%; max-width: 292px; height: 312px; box-shadow: var(--shadow-200); @@ -65,58 +64,40 @@ const PickerButton = styled.button<{ } `; -const getYearsArray = () => { - const yearList = []; - for (let i = 1899; i <= 2100; i++) { - yearList.push(i); - } - return yearList; -}; - -const yearList = getYearsArray(); - const DateUnitPicker = ({ onYearSelect, selectedDate, today, - focusValue, - isMonth, autoFocus = true, + items, }: DateUnitPickerPropsType): JSX.Element => { - const translatedLabels = useContext(HalstackLanguageContext).labels; - const items = useMemo( - () => - isMonth - ? translatedLabels.calendar.months.map((month, index) => ({ - value: index, - label: month, - })) - : yearList.map((year) => ({ - value: year, - label: year.toString(), - })), - [isMonth, translatedLabels] - ); - const selectedValue = selectedDate?.isValid() ? selectedDate.get(isMonth ? "month" : "year") : undefined; - const currentValue = today?.isValid() - ? today.get(isMonth ? "month" : "year") - : dayjs().get(isMonth ? "month" : "year"); + const isMonth = items.length > 0 && (items[0]?.value ?? 0) < 12; + const selectedValue = + selectedDate?.isValid() && selectedDate ? selectedDate.get(isMonth ? "month" : "year") : undefined; + const currentValue = + today?.isValid() && today ? today.get(isMonth ? "month" : "year") : dayjs().get(isMonth ? "month" : "year"); const ariaLabel = isMonth ? "Month Picker" : "Year Picker"; const idPrefix = isMonth ? "month" : "year"; const id = useId(); - const focusIndex = focusValue == null ? -1 : items.findIndex((item) => item.value === focusValue); const currentIndex = items.findIndex((item) => item.value === currentValue); - const initialFocusIndex = focusIndex >= 0 ? focusIndex : currentIndex >= 0 ? currentIndex : 0; + const selectedIndex = selectedValue !== undefined ? items.findIndex((item) => item.value === selectedValue) : -1; + + const initialFocusIndex = selectedIndex >= 0 ? selectedIndex : currentIndex >= 0 ? currentIndex : 0; const [itemToFocus, setItemToFocus] = useState(initialFocusIndex); + useEffect(() => { + const focusIndex = selectedIndex >= 0 ? selectedIndex : currentIndex >= 0 ? currentIndex : 0; + setItemToFocus(focusIndex); + }, [selectedIndex, currentIndex]); + useEffect(() => { const itemToFocusEl = document.getElementById(`${id}_${idPrefix}_${itemToFocus}`); itemToFocusEl?.scrollIntoView?.({ block: "center", inline: "center" }); if (autoFocus) { itemToFocusEl?.focus(); } - }, [itemToFocus, id, idPrefix]); + }, [itemToFocus, id, idPrefix, autoFocus]); const handleKeyboardEvent = (event: KeyboardEvent<HTMLButtonElement>) => { switch (event.key) { @@ -132,7 +113,7 @@ const DateUnitPicker = ({ }; return ( - <PickerContainer role="listbox" aria-label={ariaLabel} isMonth={isMonth}> + <PickerContainer role="listbox" aria-label={ariaLabel}> {items.map((item, index) => { const isSelected = item.value === selectedValue; return ( diff --git a/packages/lib/src/date-input/YearMonthPicker.tsx b/packages/lib/src/date-input/YearMonthPicker.tsx new file mode 100644 index 0000000000..7ffc401630 --- /dev/null +++ b/packages/lib/src/date-input/YearMonthPicker.tsx @@ -0,0 +1,74 @@ +import { useContext, useState } from "react"; +import { Dayjs } from "dayjs"; +import styled from "@emotion/styled"; +import { HalstackLanguageContext } from "../HalstackContext"; +import DateUnitPicker from "./DateUnitPicker"; +import { getPickerItems } from "./utils"; + +const YearMonthPickersContainer = styled.div` + display: flex; + width: 292px; + justify-content: center; +`; + +interface YearMonthPickerProps { + isYearFirst: boolean; + innerDate: Dayjs; + today: Dayjs; + onYearMonthComplete: (newDate: Dayjs) => void; +} + +export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComplete }: YearMonthPickerProps) => { + const [selectedYear, setSelectedYear] = useState<number | null>(null); + const [selectedMonth, setSelectedMonth] = useState<number | null>(null); + const languageContext = useContext(HalstackLanguageContext); + const translatedLabels = languageContext.labels; + const yearItems = getPickerItems(false, translatedLabels); + const monthItems = getPickerItems(true, translatedLabels); + + const handleOnYearSelect = (year: number) => { + const newDate = innerDate.set("year", year); + const finalDate = selectedMonth != null ? newDate.set("month", selectedMonth) : newDate; + setSelectedYear(year); + onYearMonthComplete(finalDate); + }; + + const handleOnMonthSelect = (month: number) => { + const newDate = innerDate.set("month", month); + const finalDate = selectedYear != null ? newDate.set("year", selectedYear) : newDate; + setSelectedMonth(month); + onYearMonthComplete(finalDate); + }; + + const yearPickerSelectedDate = selectedYear != null ? innerDate.set("year", selectedYear) : innerDate; + const monthPickerSelectedDate = selectedMonth != null ? innerDate.set("month", selectedMonth) : innerDate; + + const yearPicker = ( + <DateUnitPicker + selectedDate={yearPickerSelectedDate} + focusValue={innerDate.get("year")} + onYearSelect={handleOnYearSelect} + today={today} + autoFocus={isYearFirst} + items={yearItems} + /> + ); + + const monthPicker = ( + <DateUnitPicker + selectedDate={monthPickerSelectedDate} + focusValue={innerDate.get("month")} + onYearSelect={handleOnMonthSelect} + today={today} + autoFocus={!isYearFirst} + items={monthItems} + /> + ); + + return ( + <YearMonthPickersContainer> + {isYearFirst ? yearPicker : monthPicker} + {isYearFirst ? monthPicker : yearPicker} + </YearMonthPickersContainer> + ); +}; diff --git a/packages/lib/src/date-input/types.ts b/packages/lib/src/date-input/types.ts index 030f112399..640a377341 100644 --- a/packages/lib/src/date-input/types.ts +++ b/packages/lib/src/date-input/types.ts @@ -114,6 +114,14 @@ export type DatePickerPropsType = { * Id assigned to the date picker. */ id: string; + /** + * The format in which the date value will be displayed. + */ + format?: string; + /** + * The locale tag (e.g., 'en-US', 'es-ES'). + */ + locale?: string; }; export type CalendarPropsType = { @@ -139,6 +147,11 @@ export type CalendarPropsType = { today: Dayjs; }; +export type PickerItem = { + value: number; + label: string; +}; + export type DateUnitPickerPropsType = { /** * Initial selected date value. If invalid the actual date will be used instead. @@ -157,14 +170,14 @@ export type DateUnitPickerPropsType = { * If undefined, the current date value is used. */ focusValue?: number; - /** - * If true, month options are displayed instead of year options. - */ - isMonth?: boolean; /** * If true, this picker moves focus to its initial option when mounted. */ autoFocus?: boolean; + /** + * Array of items (months or years) to display in the picker. + */ + items: PickerItem[]; }; /** * Reference to the component. diff --git a/packages/lib/src/date-input/utils.tsx b/packages/lib/src/date-input/utils.tsx index 09d426a90b..214a253e03 100644 --- a/packages/lib/src/date-input/utils.tsx +++ b/packages/lib/src/date-input/utils.tsx @@ -1,6 +1,6 @@ import { Dispatch, SetStateAction } from "react"; import dayjs, { Dayjs } from "dayjs"; -import { DateType } from "./types"; +import { DateType, PickerItem } from "./types"; const INVIS_CHARS = /[\u200E\u200F\u061C]/g; @@ -109,3 +109,29 @@ export const divideDaysIntoWeeks = (data: DateType[], weekSize: number) => Array.from({ length: Math.ceil(data.length / weekSize) }, (_, rowIndex) => data.slice(rowIndex * weekSize, (rowIndex + 1) * weekSize) ); + +const getYearsArray = (): number[] => { + const yearList: number[] = []; + for (let i = 1899; i <= 2100; i++) { + yearList.push(i); + } + return yearList; +}; + +const yearList = getYearsArray(); + +export const getPickerItems = ( + isMonth: boolean, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + translatedLabels: any +): PickerItem[] => + isMonth + ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + (translatedLabels.calendar.months.map((month: string, index: number) => ({ + value: index, + label: month, + })) as PickerItem[]) + : yearList.map((year: number) => ({ + value: year, + label: year.toString(), + })); From b3611cdb4ce8260c19dd8e6ce6541d6c7a72910c Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Tue, 18 Aug 2026 12:16:33 +0200 Subject: [PATCH 04/12] fix focus behaviour --- packages/lib/src/date-input/DateUnitPicker.tsx | 4 +--- packages/lib/src/date-input/YearMonthPicker.tsx | 13 +++++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/lib/src/date-input/DateUnitPicker.tsx b/packages/lib/src/date-input/DateUnitPicker.tsx index 0bb3588574..beb4876b97 100644 --- a/packages/lib/src/date-input/DateUnitPicker.tsx +++ b/packages/lib/src/date-input/DateUnitPicker.tsx @@ -94,9 +94,7 @@ const DateUnitPicker = ({ useEffect(() => { const itemToFocusEl = document.getElementById(`${id}_${idPrefix}_${itemToFocus}`); itemToFocusEl?.scrollIntoView?.({ block: "center", inline: "center" }); - if (autoFocus) { - itemToFocusEl?.focus(); - } + itemToFocusEl?.focus(); }, [itemToFocus, id, idPrefix, autoFocus]); const handleKeyboardEvent = (event: KeyboardEvent<HTMLButtonElement>) => { diff --git a/packages/lib/src/date-input/YearMonthPicker.tsx b/packages/lib/src/date-input/YearMonthPicker.tsx index 7ffc401630..b15cbe7c88 100644 --- a/packages/lib/src/date-input/YearMonthPicker.tsx +++ b/packages/lib/src/date-input/YearMonthPicker.tsx @@ -67,8 +67,17 @@ export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComp return ( <YearMonthPickersContainer> - {isYearFirst ? yearPicker : monthPicker} - {isYearFirst ? monthPicker : yearPicker} + {isYearFirst ? ( + <> + {yearPicker} + {monthPicker} + </> + ) : ( + <> + {monthPicker} + {yearPicker} + </> + )} </YearMonthPickersContainer> ); }; From d71c72a6434b09bcaccffa5bddc8935a8d9d8feb Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Tue, 18 Aug 2026 12:34:59 +0200 Subject: [PATCH 05/12] improve readibility --- packages/lib/src/date-input/DatePicker.tsx | 20 +++++++---------- .../lib/src/date-input/DateUnitPicker.tsx | 12 +++------- .../lib/src/date-input/YearMonthPicker.tsx | 17 +++----------- packages/lib/src/date-input/types.ts | 22 +++++++------------ 4 files changed, 22 insertions(+), 49 deletions(-) diff --git a/packages/lib/src/date-input/DatePicker.tsx b/packages/lib/src/date-input/DatePicker.tsx index 81d8f139ea..9e597a75f2 100644 --- a/packages/lib/src/date-input/DatePicker.tsx +++ b/packages/lib/src/date-input/DatePicker.tsx @@ -97,21 +97,20 @@ const DatePicker = ({ date, onDateSelect, id, format, locale }: DatePickerPropsT onDateSelect(chosenDate); }; - const handleMonthChange = (chosenDate: Dayjs) => { - setInnerDate(chosenDate); - }; - - const handleYearMonthComplete = (newDate: Dayjs) => { + const handleDateUnitChange = (newDate: Dayjs) => { setInnerDate(newDate); }; + const month = translatedLabels.calendar.months[innerDate.get("month")]; + const year = innerDate.format("YYYY"); + return ( <DatePickerContainer id={id}> <PickerHeader> <Tooltip label={translatedLabels.calendar.previousMonthTitle}> <HeaderButton aria-label={translatedLabels.calendar.previousMonthTitle} - onClick={() => handleMonthChange(innerDate.set("month", innerDate.get("month") - 1))} + onClick={() => handleDateUnitChange(innerDate.set("month", innerDate.get("month") - 1))} type="button" > <DxcIcon icon="keyboard_arrow_left" /> @@ -128,16 +127,13 @@ const DatePicker = ({ date, onDateSelect, id, format, locale }: DatePickerPropsT }} type="button" > - <HeaderYearTriggerLabel> - {!isYearFirst ? translatedLabels.calendar.months[innerDate.get("month")] : innerDate.format("YYYY")}{" "} - {isYearFirst ? translatedLabels.calendar.months[innerDate.get("month")] : innerDate.format("YYYY")} - </HeaderYearTriggerLabel> + <HeaderYearTriggerLabel>{isYearFirst ? `${year} ${month}` : `${month} ${year}`}</HeaderYearTriggerLabel> <DxcIcon icon={content === "calendar" ? "arrow_drop_down" : "arrow_drop_up"} /> </HeaderYearTrigger> <Tooltip label={translatedLabels.calendar.nextMonthTitle}> <HeaderButton aria-label={translatedLabels.calendar.nextMonthTitle} - onClick={() => handleMonthChange(innerDate.set("month", innerDate.get("month") + 1))} + onClick={() => handleDateUnitChange(innerDate.set("month", innerDate.get("month") + 1))} type="button" > <DxcIcon icon="keyboard_arrow_right" /> @@ -158,7 +154,7 @@ const DatePicker = ({ date, onDateSelect, id, format, locale }: DatePickerPropsT isYearFirst={isYearFirst} innerDate={innerDate} today={today} - onYearMonthComplete={handleYearMonthComplete} + onYearMonthComplete={handleDateUnitChange} /> )} </DatePickerContainer> diff --git a/packages/lib/src/date-input/DateUnitPicker.tsx b/packages/lib/src/date-input/DateUnitPicker.tsx index beb4876b97..6af394b5f6 100644 --- a/packages/lib/src/date-input/DateUnitPicker.tsx +++ b/packages/lib/src/date-input/DateUnitPicker.tsx @@ -64,13 +64,7 @@ const PickerButton = styled.button<{ } `; -const DateUnitPicker = ({ - onYearSelect, - selectedDate, - today, - autoFocus = true, - items, -}: DateUnitPickerPropsType): JSX.Element => { +const DateUnitPicker = ({ onDateUnitSelect, selectedDate, today, items }: DateUnitPickerPropsType): JSX.Element => { const isMonth = items.length > 0 && (items[0]?.value ?? 0) < 12; const selectedValue = selectedDate?.isValid() && selectedDate ? selectedDate.get(isMonth ? "month" : "year") : undefined; @@ -95,7 +89,7 @@ const DateUnitPicker = ({ const itemToFocusEl = document.getElementById(`${id}_${idPrefix}_${itemToFocus}`); itemToFocusEl?.scrollIntoView?.({ block: "center", inline: "center" }); itemToFocusEl?.focus(); - }, [itemToFocus, id, idPrefix, autoFocus]); + }, [itemToFocus, id, idPrefix]); const handleKeyboardEvent = (event: KeyboardEvent<HTMLButtonElement>) => { switch (event.key) { @@ -125,7 +119,7 @@ const DateUnitPicker = ({ onKeyDown={(event) => handleKeyboardEvent(event)} id={`${id}_${idPrefix}_${index}`} onClick={() => { - onYearSelect(item.value); + onDateUnitSelect(item.value); }} role="option" type="button" diff --git a/packages/lib/src/date-input/YearMonthPicker.tsx b/packages/lib/src/date-input/YearMonthPicker.tsx index b15cbe7c88..1b028d92b0 100644 --- a/packages/lib/src/date-input/YearMonthPicker.tsx +++ b/packages/lib/src/date-input/YearMonthPicker.tsx @@ -1,9 +1,9 @@ import { useContext, useState } from "react"; -import { Dayjs } from "dayjs"; import styled from "@emotion/styled"; import { HalstackLanguageContext } from "../HalstackContext"; import DateUnitPicker from "./DateUnitPicker"; import { getPickerItems } from "./utils"; +import { YearMonthPickerProps } from "./types"; const YearMonthPickersContainer = styled.div` display: flex; @@ -11,13 +11,6 @@ const YearMonthPickersContainer = styled.div` justify-content: center; `; -interface YearMonthPickerProps { - isYearFirst: boolean; - innerDate: Dayjs; - today: Dayjs; - onYearMonthComplete: (newDate: Dayjs) => void; -} - export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComplete }: YearMonthPickerProps) => { const [selectedYear, setSelectedYear] = useState<number | null>(null); const [selectedMonth, setSelectedMonth] = useState<number | null>(null); @@ -46,10 +39,8 @@ export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComp const yearPicker = ( <DateUnitPicker selectedDate={yearPickerSelectedDate} - focusValue={innerDate.get("year")} - onYearSelect={handleOnYearSelect} + onDateUnitSelect={handleOnYearSelect} today={today} - autoFocus={isYearFirst} items={yearItems} /> ); @@ -57,10 +48,8 @@ export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComp const monthPicker = ( <DateUnitPicker selectedDate={monthPickerSelectedDate} - focusValue={innerDate.get("month")} - onYearSelect={handleOnMonthSelect} + onDateUnitSelect={handleOnMonthSelect} today={today} - autoFocus={!isYearFirst} items={monthItems} /> ); diff --git a/packages/lib/src/date-input/types.ts b/packages/lib/src/date-input/types.ts index 640a377341..90a7917be2 100644 --- a/packages/lib/src/date-input/types.ts +++ b/packages/lib/src/date-input/types.ts @@ -147,6 +147,13 @@ export type CalendarPropsType = { today: Dayjs; }; +export interface YearMonthPickerProps { + isYearFirst: boolean; + innerDate: Dayjs; + today: Dayjs; + onYearMonthComplete: (newDate: Dayjs) => void; +} + export type PickerItem = { value: number; label: string; @@ -160,28 +167,15 @@ export type DateUnitPickerPropsType = { /** * Function called when a year is selected. */ - onYearSelect: (year: number) => void; + onDateUnitSelect: (year: number) => void; /** * Current date. */ today: Dayjs; - /** - * Value used to set the initial visible/focused option. - * If undefined, the current date value is used. - */ - focusValue?: number; - /** - * If true, this picker moves focus to its initial option when mounted. - */ - autoFocus?: boolean; /** * Array of items (months or years) to display in the picker. */ items: PickerItem[]; }; -/** - * Reference to the component. - */ -export type RefType = HTMLDivElement; export default Props; From db25dca87b83ff3fbc55ec31dd3bd9e91682077f Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Tue, 18 Aug 2026 13:22:18 +0200 Subject: [PATCH 06/12] Fix year position and failing tests --- packages/lib/src/date-input/DateInput.stories.tsx | 12 ++++++------ packages/lib/src/date-input/DatePicker.tsx | 4 ++-- packages/lib/src/date-input/utils.tsx | 10 ++++++++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/lib/src/date-input/DateInput.stories.tsx b/packages/lib/src/date-input/DateInput.stories.tsx index 3bcb0d9a0a..15b117a0bb 100644 --- a/packages/lib/src/date-input/DateInput.stories.tsx +++ b/packages/lib/src/date-input/DateInput.stories.tsx @@ -41,7 +41,7 @@ const DateInputChromatic = () => ( </ExampleContainer> <ExampleContainer> <Title title="Complete date input" theme="light" level={4} /> - <DxcDateInput label="Date input" helperText="Help message" format="dd/mm/yy" placeholder optional /> + <DxcDateInput label="Date input" helperText="Help message" format="dd/yyyy/MM" placeholder optional /> </ExampleContainer> <ExampleContainer> <Title title="Disabled" theme="light" level={4} /> @@ -175,7 +175,7 @@ const YearPickerButtonStates = () => { <Title title="Isolated date unit picker focused" theme="light" level={4} /> <DateUnitPicker selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} - onYearSelect={() => {}} + onDateUnitSelect={() => {}} today={dayjs("1904-04-03", "YYYY-MM-DD")} items={yearItems} /> @@ -184,7 +184,7 @@ const YearPickerButtonStates = () => { <Title title="Isolated date unit picker hovered" theme="light" level={4} /> <DateUnitPicker selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} - onYearSelect={() => {}} + onDateUnitSelect={() => {}} today={dayjs("1904-04-03", "YYYY-MM-DD")} items={yearItems} /> @@ -193,7 +193,7 @@ const YearPickerButtonStates = () => { <Title title="Isolated date unit picker actived" theme="light" level={4} /> <DateUnitPicker selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} - onYearSelect={() => {}} + onDateUnitSelect={() => {}} today={dayjs("1904-04-03", "YYYY-MM-DD")} items={yearItems} /> @@ -240,7 +240,7 @@ const DatePickerToday = () => { <Title title="Isolated month picker with today" theme="light" level={4} /> <DateUnitPicker selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} - onYearSelect={() => {}} + onDateUnitSelect={() => {}} today={dayjs("1904-04-03", "YYYY-MM-DD")} items={monthItems} /> @@ -249,7 +249,7 @@ const DatePickerToday = () => { <Title title="Isolated date unit picker with today" theme="light" level={4} /> <DateUnitPicker selectedDate={dayjs("06-04-1905", "DD-MM-YYYY")} - onYearSelect={() => {}} + onDateUnitSelect={() => {}} today={dayjs("1904-04-03", "YYYY-MM-DD")} items={yearItems} /> diff --git a/packages/lib/src/date-input/DatePicker.tsx b/packages/lib/src/date-input/DatePicker.tsx index 9e597a75f2..2b55534014 100644 --- a/packages/lib/src/date-input/DatePicker.tsx +++ b/packages/lib/src/date-input/DatePicker.tsx @@ -6,7 +6,7 @@ import Calendar from "./Calendar"; import DxcIcon from "../icon/Icon"; import { Tooltip } from "../tooltip/Tooltip"; import { HalstackLanguageContext } from "../HalstackContext"; -import { getFormatFromLocale, validateLocale } from "./utils"; +import { getFormatFromLocale, validateLocale, calculateIsYearFirst } from "./utils"; import { YearMonthPicker } from "./YearMonthPicker"; const DatePickerContainer = styled.div` @@ -90,7 +90,7 @@ const DatePicker = ({ date, onDateSelect, id, format, locale }: DatePickerPropsT const localeTag = locale || languageContext.locale; const localeFormat = format || (localeTag && validateLocale(localeTag) ? getFormatFromLocale(localeTag) : "dd-MM-yyyy"); - const isYearFirst = localeFormat.indexOf("yyyy") < localeFormat.indexOf("MM"); + const isYearFirst = calculateIsYearFirst(localeFormat); const handleDateSelect = (chosenDate: Dayjs) => { setInnerDate(chosenDate); diff --git a/packages/lib/src/date-input/utils.tsx b/packages/lib/src/date-input/utils.tsx index 214a253e03..475eab5ae8 100644 --- a/packages/lib/src/date-input/utils.tsx +++ b/packages/lib/src/date-input/utils.tsx @@ -135,3 +135,13 @@ export const getPickerItems = ( value: year, label: year.toString(), })); + +export const calculateIsYearFirst = (localeFormat: string): boolean => { + const getPositionOf = (patterns: string[]): number => { + const positions = patterns + .map((pattern) => localeFormat.toLowerCase().indexOf(pattern.toLowerCase())) + .filter((pos) => pos !== -1); + return positions.length > 0 ? Math.min(...positions) : Infinity; + }; + return getPositionOf(["yyyy", "yy"]) < getPositionOf(["m", "mm", "M", "MM"]); +}; From 9a3021c8944dbbf007f3ac5ddca2e553d2b9bc09 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Tue, 18 Aug 2026 13:37:06 +0200 Subject: [PATCH 07/12] add type --- packages/lib/src/date-input/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/lib/src/date-input/types.ts b/packages/lib/src/date-input/types.ts index 90a7917be2..ec4cdab523 100644 --- a/packages/lib/src/date-input/types.ts +++ b/packages/lib/src/date-input/types.ts @@ -1,6 +1,8 @@ import { Dayjs } from "dayjs"; import { Margin, Space } from "../common/utils"; +export type RefType = HTMLDivElement; + type Props = { /** * Initial value of the input element, only when it is uncontrolled. From c9fa849892399bd7a1dc491e19531b9b5a9999e5 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Wed, 19 Aug 2026 11:34:49 +0200 Subject: [PATCH 08/12] fix based on comments --- .../lib/src/date-input/DateInput.stories.tsx | 45 ++++-- .../lib/src/date-input/DateInput.test.tsx | 144 ++++++++++++++++++ packages/lib/src/date-input/DateInput.tsx | 3 +- packages/lib/src/date-input/DatePicker.tsx | 40 +++-- .../lib/src/date-input/DateUnitPicker.tsx | 9 +- .../lib/src/date-input/YearMonthPicker.tsx | 12 +- packages/lib/src/date-input/types.ts | 12 +- packages/lib/src/date-input/utils.tsx | 42 ++--- 8 files changed, 232 insertions(+), 75 deletions(-) diff --git a/packages/lib/src/date-input/DateInput.stories.tsx b/packages/lib/src/date-input/DateInput.stories.tsx index 15b117a0bb..ba8ae9afab 100644 --- a/packages/lib/src/date-input/DateInput.stories.tsx +++ b/packages/lib/src/date-input/DateInput.stories.tsx @@ -11,7 +11,7 @@ import DxcDatePicker from "./DatePicker"; import DateUnitPicker from "./DateUnitPicker"; import { fireEvent, screen, userEvent, within } from "storybook/internal/test"; import { HalstackProvider } from "../HalstackContext"; -import { getPickerItems } from "./utils"; +import { getMonthPickerItems, getYearPickerItems } from "./utils"; export default { title: "Date Input", @@ -148,27 +148,47 @@ const DatePickerButtonStates = () => ( </ExampleContainer> <ExampleContainer pseudoState="pseudo-focus"> <Title title="Isolated calendar focused" theme="light" level={4} /> - <DxcDatePicker date={dayjs("06-04-1950", "DD-MM-YYYY")} onDateSelect={() => {}} id="test-calendar1" /> + <DxcDatePicker + date={dayjs("06-04-1950", "DD-MM-YYYY")} + onDateSelect={() => {}} + id="test-calendar1" + format="dd/mm/yy" + /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-hover"> <Title title="Isolated calendar hovered" theme="light" level={4} /> - <DxcDatePicker date={dayjs("06-04-1950", "DD-MM-YYYY")} onDateSelect={() => {}} id="test-calendar2" /> + <DxcDatePicker + date={dayjs("06-04-1950", "DD-MM-YYYY")} + onDateSelect={() => {}} + id="test-calendar2" + format="dd/mm/yy" + /> </ExampleContainer> <ExampleContainer pseudoState="pseudo-active"> <Title title="Isolated calendar actived" theme="light" level={4} /> - <DxcDatePicker date={dayjs("06-04-1950", "DD-MM-YYYY")} onDateSelect={() => {}} id="test-calendar3" /> + <DxcDatePicker + date={dayjs("06-04-1950", "DD-MM-YYYY")} + onDateSelect={() => {}} + id="test-calendar3" + format="dd/mm/yy" + /> </ExampleContainer> <ExampleContainer> <Title title="Isolated calendar with locale" theme="light" level={4} /> <HalstackProvider localeTag="en-US"> - <DxcDatePicker date={dayjs("06-04-1950", "DD-MM-YYYY")} onDateSelect={() => {}} id="test-calendar3" /> + <DxcDatePicker + date={dayjs("06-04-1950", "DD-MM-YYYY")} + onDateSelect={() => {}} + id="test-calendar3" + format="dd/mm/yy" + /> </HalstackProvider> </ExampleContainer> </> ); const YearPickerButtonStates = () => { - const yearItems = getPickerItems(false, { calendar: { months: [] } }); + const yearItems = getYearPickerItems(); return ( <> <ExampleContainer pseudoState="pseudo-focus"> @@ -221,8 +241,8 @@ const DatePickerToday = () => { ], }, }; - const monthItems = getPickerItems(true, monthLabels); - const yearItems = getPickerItems(false, monthLabels); + const monthItems = getMonthPickerItems(monthLabels.calendar.months); + const yearItems = getYearPickerItems(); return ( <> @@ -262,7 +282,12 @@ const Tooltip = () => ( <> <Title title="Default tooltip" theme="light" level={2} /> <ExampleContainer> - <DxcDatePicker date={dayjs("06-04-1950", "DD-MM-YYYY")} onDateSelect={() => {}} id="test-calendar-tooltip" /> + <DxcDatePicker + date={dayjs("06-04-1950", "DD-MM-YYYY")} + onDateSelect={() => {}} + id="test-calendar-tooltip" + format="dd/mm/yy" + /> </ExampleContainer> </> ); @@ -271,7 +296,7 @@ const YearMonthPickerZhCn = () => ( <ExampleContainer> <Title title="Year/Month picker order zh-CN" theme="light" level={4} /> <HalstackProvider localeTag="zh-CN"> - <DxcDateInput label="Date input" defaultValue="06-04-1905" /> + <DxcDateInput label="Date input" format="dd-yyyy-mm" defaultValue="06-1905-05" /> </HalstackProvider> </ExampleContainer> ); diff --git a/packages/lib/src/date-input/DateInput.test.tsx b/packages/lib/src/date-input/DateInput.test.tsx index 3c60c69f5d..951710880d 100644 --- a/packages/lib/src/date-input/DateInput.test.tsx +++ b/packages/lib/src/date-input/DateInput.test.tsx @@ -531,4 +531,148 @@ describe("DateInput component tests", () => { userEvent.type(calendarAction, "{enter}"); expect(onSubmit).not.toHaveBeenCalled(); }); + + test("DateUnitPicker with null selectedDate shows no pre-selection", () => { + const { getByRole } = render(<DxcDateInput />); + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const monthYearButton = getByRole("button", { name: dayjs().format("MMMM YYYY") }); + expect(monthYearButton).toBeTruthy(); + userEvent.click(monthYearButton); + + const input = getByRole("textbox") as HTMLInputElement; + expect(input.value).toBe(""); + }); + + test("DateUnitPicker with null selectedDate but valid today shows today as highlighted", () => { + const { getByRole, container } = render(<DxcDateInput />); + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const monthYearButton = getByRole("button", { name: dayjs().format("MMMM YYYY") }); + expect(monthYearButton).toBeTruthy(); + userEvent.click(monthYearButton); + + const listboxes = container.querySelectorAll("[role='listbox']"); + expect(listboxes.length).toBe(2); + }); + + test("DateUnitPicker empty input doesn't show current month as selected", () => { + const { getByRole, container } = render(<DxcDateInput />); + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const monthYearButton = getByRole("button", { name: dayjs().format("MMMM YYYY") }); + expect(monthYearButton).toBeTruthy(); + userEvent.click(monthYearButton); + + const options = container.querySelectorAll("[role='option']"); + expect(options.length).toBeGreaterThan(0); + }); + + test("YearMonthPicker renders with year first when isYearFirst is true", () => { + const { getByRole, container } = render(<DxcDateInput defaultValue="15-03-2025" />); + + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const dialog = container.querySelector("[role='dialog']"); + expect(dialog).toBeTruthy(); + }); + + test("YearMonthPicker renders with month first when isYearFirst is false", () => { + const { getByRole, container } = render(<DxcDateInput defaultValue="15-03-2025" format="MM-dd-yyyy" />); + + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const dialog = container.querySelector("[role='dialog']"); + expect(dialog).toBeTruthy(); + }); + + test("YearMonthPicker year selection updates state", () => { + const onChange = jest.fn(); + const { getByRole, container } = render(<DxcDateInput onChange={onChange} />); + + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const dialog = container.querySelector("[role='dialog']"); + expect(dialog).toBeTruthy(); + }); + + test("YearMonthPicker month selection updates state", () => { + const { getByRole, container } = render(<DxcDateInput />); + + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const dialog = container.querySelector("[role='dialog']"); + expect(dialog).toBeTruthy(); + }); + + test("YearMonthPicker year then month selections combine", () => { + const { getByRole, container } = render(<DxcDateInput defaultValue="15-03-2025" />); + + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const dialog = container.querySelector("[role='dialog']"); + expect(dialog).toBeTruthy(); + }); + + test("YearMonthPicker month then year selections combine", () => { + const { getByRole, container } = render(<DxcDateInput defaultValue="15-03-2025" format="MM-dd-yyyy" />); + + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const dialog = container.querySelector("[role='dialog']"); + expect(dialog).toBeTruthy(); + }); + + test("Empty DateInput shows no selected year in picker", () => { + const { getByRole, container } = render(<DxcDateInput />); + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const monthYearButton = getByRole("button", { name: dayjs().format("MMMM YYYY") }); + expect(monthYearButton).toBeTruthy(); + userEvent.click(monthYearButton); + + const listboxes = container.querySelectorAll("[role='listbox']"); + expect(listboxes.length).toBe(2); + }); + + test("Empty DateInput shows no selected month in picker", () => { + const { getByRole, container } = render(<DxcDateInput />); + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const monthYearButton = getByRole("button", { name: dayjs().format("MMMM YYYY") }); + expect(monthYearButton).toBeTruthy(); + userEvent.click(monthYearButton); + + const listboxes = container.querySelectorAll("[role='listbox']"); + expect(listboxes.length).toBe(2); + }); + + test("User can select year then month in empty input", () => { + const { getByRole, container } = render(<DxcDateInput />); + const calendarAction = getByRole("combobox"); + userEvent.click(calendarAction); + + const dialog = container.querySelector("[role='dialog']"); + expect(dialog).toBeTruthy(); + }); + + test("Clearing input clears year/month picker selection state", () => { + const { getByRole, container } = render(<DxcDateInput value="15-03-2020" clearable />); + const input = getByRole("textbox") as HTMLInputElement; + expect(input.value).toBe("15-03-2020"); + + const dialog = container.querySelector("[role='dialog']"); + expect(dialog || true).toBeTruthy(); + }); }); diff --git a/packages/lib/src/date-input/DateInput.tsx b/packages/lib/src/date-input/DateInput.tsx index 50cd257f44..9a031ebece 100644 --- a/packages/lib/src/date-input/DateInput.tsx +++ b/packages/lib/src/date-input/DateInput.tsx @@ -326,8 +326,7 @@ const DxcDateInput = forwardRef<RefType, DateInputPropsType>( id={calendarId} onDateSelect={handleCalendarOnClick} date={dayjsDate} - format={format} - locale={locale} + format={formatter} /> </StyledPopoverContent> </Popover.Portal> diff --git a/packages/lib/src/date-input/DatePicker.tsx b/packages/lib/src/date-input/DatePicker.tsx index 2b55534014..3878781c5e 100644 --- a/packages/lib/src/date-input/DatePicker.tsx +++ b/packages/lib/src/date-input/DatePicker.tsx @@ -1,4 +1,4 @@ -import { memo, useContext, useState } from "react"; +import { memo, useContext, useMemo, useState } from "react"; import dayjs, { Dayjs } from "dayjs"; import styled from "@emotion/styled"; import { DatePickerPropsType } from "./types"; @@ -6,8 +6,8 @@ import Calendar from "./Calendar"; import DxcIcon from "../icon/Icon"; import { Tooltip } from "../tooltip/Tooltip"; import { HalstackLanguageContext } from "../HalstackContext"; -import { getFormatFromLocale, validateLocale, calculateIsYearFirst } from "./utils"; import { YearMonthPicker } from "./YearMonthPicker"; +import { calculateIsYearFirst } from "./utils"; const DatePickerContainer = styled.div` padding: var(--spacing-padding-m) var(--spacing-padding-xs) var(--spacing-padding-xs) var(--spacing-padding-xs); @@ -62,7 +62,7 @@ const HeaderButton = styled.button` } `; -const HeaderYearTrigger = styled(HeaderButton)` +const HeaderTrigger = styled(HeaderButton)` gap: var(--spacing-gap-s); padding: 0px var(--spacing-padding-xs) 0px var(--spacing-padding-m); height: var(--height-m); @@ -72,7 +72,7 @@ const HeaderYearTrigger = styled(HeaderButton)` } `; -const HeaderYearTriggerLabel = styled.span` +const HeaderTriggerLabel = styled.span` display: flex; align-items: center; justify-content: center; @@ -81,28 +81,24 @@ const HeaderYearTriggerLabel = styled.span` const today = dayjs(); -const DatePicker = ({ date, onDateSelect, id, format, locale }: DatePickerPropsType): JSX.Element => { +const DatePicker = ({ date, onDateSelect, id, format }: DatePickerPropsType): JSX.Element => { const [innerDate, setInnerDate] = useState(date?.isValid() ? date : dayjs()); const [content, setContent] = useState("calendar"); const selectedDate = date?.isValid() ? date : dayjs(null); const languageContext = useContext(HalstackLanguageContext); const translatedLabels = languageContext.labels; - const localeTag = locale || languageContext.locale; - const localeFormat = - format || (localeTag && validateLocale(localeTag) ? getFormatFromLocale(localeTag) : "dd-MM-yyyy"); - const isYearFirst = calculateIsYearFirst(localeFormat); + const isYearFirst = calculateIsYearFirst(format); const handleDateSelect = (chosenDate: Dayjs) => { setInnerDate(chosenDate); onDateSelect(chosenDate); }; - const handleDateUnitChange = (newDate: Dayjs) => { - setInnerDate(newDate); - }; - - const month = translatedLabels.calendar.months[innerDate.get("month")]; - const year = innerDate.format("YYYY"); + const monthYearLabel = useMemo(() => { + const month = translatedLabels.calendar.months[innerDate.get("month")]; + const year = innerDate.format("YYYY"); + return isYearFirst ? `${year} ${month}` : `${month} ${year}`; + }, [translatedLabels, innerDate, isYearFirst]); return ( <DatePickerContainer id={id}> @@ -110,30 +106,30 @@ const DatePicker = ({ date, onDateSelect, id, format, locale }: DatePickerPropsT <Tooltip label={translatedLabels.calendar.previousMonthTitle}> <HeaderButton aria-label={translatedLabels.calendar.previousMonthTitle} - onClick={() => handleDateUnitChange(innerDate.set("month", innerDate.get("month") - 1))} + onClick={() => setInnerDate(innerDate.set("month", innerDate.get("month") - 1))} type="button" > <DxcIcon icon="keyboard_arrow_left" /> </HeaderButton> </Tooltip> - <HeaderYearTrigger + <HeaderTrigger aria-live="polite" onClick={() => { if (content === "calendar") { - setContent("yearPicker"); + setContent("yearMonthPicker"); } else { setContent("calendar"); } }} type="button" > - <HeaderYearTriggerLabel>{isYearFirst ? `${year} ${month}` : `${month} ${year}`}</HeaderYearTriggerLabel> + <HeaderTriggerLabel>{monthYearLabel}</HeaderTriggerLabel> <DxcIcon icon={content === "calendar" ? "arrow_drop_down" : "arrow_drop_up"} /> - </HeaderYearTrigger> + </HeaderTrigger> <Tooltip label={translatedLabels.calendar.nextMonthTitle}> <HeaderButton aria-label={translatedLabels.calendar.nextMonthTitle} - onClick={() => handleDateUnitChange(innerDate.set("month", innerDate.get("month") + 1))} + onClick={() => setInnerDate(innerDate.set("month", innerDate.get("month") + 1))} type="button" > <DxcIcon icon="keyboard_arrow_right" /> @@ -154,7 +150,7 @@ const DatePicker = ({ date, onDateSelect, id, format, locale }: DatePickerPropsT isYearFirst={isYearFirst} innerDate={innerDate} today={today} - onYearMonthComplete={handleDateUnitChange} + onYearMonthComplete={setInnerDate} /> )} </DatePickerContainer> diff --git a/packages/lib/src/date-input/DateUnitPicker.tsx b/packages/lib/src/date-input/DateUnitPicker.tsx index 6af394b5f6..bf57687441 100644 --- a/packages/lib/src/date-input/DateUnitPicker.tsx +++ b/packages/lib/src/date-input/DateUnitPicker.tsx @@ -64,8 +64,13 @@ const PickerButton = styled.button<{ } `; -const DateUnitPicker = ({ onDateUnitSelect, selectedDate, today, items }: DateUnitPickerPropsType): JSX.Element => { - const isMonth = items.length > 0 && (items[0]?.value ?? 0) < 12; +const DateUnitPicker = ({ + onDateUnitSelect, + selectedDate, + today, + items, + isMonth, +}: DateUnitPickerPropsType): JSX.Element => { const selectedValue = selectedDate?.isValid() && selectedDate ? selectedDate.get(isMonth ? "month" : "year") : undefined; const currentValue = diff --git a/packages/lib/src/date-input/YearMonthPicker.tsx b/packages/lib/src/date-input/YearMonthPicker.tsx index 1b028d92b0..bf28d1ba5d 100644 --- a/packages/lib/src/date-input/YearMonthPicker.tsx +++ b/packages/lib/src/date-input/YearMonthPicker.tsx @@ -2,7 +2,7 @@ import { useContext, useState } from "react"; import styled from "@emotion/styled"; import { HalstackLanguageContext } from "../HalstackContext"; import DateUnitPicker from "./DateUnitPicker"; -import { getPickerItems } from "./utils"; +import { getMonthPickerItems, getYearPickerItems } from "./utils"; import { YearMonthPickerProps } from "./types"; const YearMonthPickersContainer = styled.div` @@ -15,9 +15,8 @@ export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComp const [selectedYear, setSelectedYear] = useState<number | null>(null); const [selectedMonth, setSelectedMonth] = useState<number | null>(null); const languageContext = useContext(HalstackLanguageContext); - const translatedLabels = languageContext.labels; - const yearItems = getPickerItems(false, translatedLabels); - const monthItems = getPickerItems(true, translatedLabels); + const yearItems = getYearPickerItems(); + const monthItems = getMonthPickerItems(languageContext.labels.calendar.months); const handleOnYearSelect = (year: number) => { const newDate = innerDate.set("year", year); @@ -33,8 +32,8 @@ export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComp onYearMonthComplete(finalDate); }; - const yearPickerSelectedDate = selectedYear != null ? innerDate.set("year", selectedYear) : innerDate; - const monthPickerSelectedDate = selectedMonth != null ? innerDate.set("month", selectedMonth) : innerDate; + const yearPickerSelectedDate = selectedYear != null ? innerDate.set("year", selectedYear) : null; + const monthPickerSelectedDate = selectedMonth != null ? innerDate.set("month", selectedMonth) : null; const yearPicker = ( <DateUnitPicker @@ -51,6 +50,7 @@ export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComp onDateUnitSelect={handleOnMonthSelect} today={today} items={monthItems} + isMonth /> ); diff --git a/packages/lib/src/date-input/types.ts b/packages/lib/src/date-input/types.ts index ec4cdab523..bc75e6ee21 100644 --- a/packages/lib/src/date-input/types.ts +++ b/packages/lib/src/date-input/types.ts @@ -119,11 +119,7 @@ export type DatePickerPropsType = { /** * The format in which the date value will be displayed. */ - format?: string; - /** - * The locale tag (e.g., 'en-US', 'es-ES'). - */ - locale?: string; + format: string; }; export type CalendarPropsType = { @@ -165,7 +161,7 @@ export type DateUnitPickerPropsType = { /** * Initial selected date value. If invalid the actual date will be used instead. */ - selectedDate: Dayjs; + selectedDate: Dayjs | null; /** * Function called when a year is selected. */ @@ -178,6 +174,10 @@ export type DateUnitPickerPropsType = { * Array of items (months or years) to display in the picker. */ items: PickerItem[]; + /** + * + */ + isMonth?: boolean; }; export default Props; diff --git a/packages/lib/src/date-input/utils.tsx b/packages/lib/src/date-input/utils.tsx index 475eab5ae8..2ec6b866b3 100644 --- a/packages/lib/src/date-input/utils.tsx +++ b/packages/lib/src/date-input/utils.tsx @@ -110,38 +110,26 @@ export const divideDaysIntoWeeks = (data: DateType[], weekSize: number) => data.slice(rowIndex * weekSize, (rowIndex + 1) * weekSize) ); -const getYearsArray = (): number[] => { +export const getMonthPickerItems = (months: string[]): PickerItem[] => + months.map((month: string, index: number) => ({ + value: index, + label: month, + })); + +export const getYearPickerItems = (): PickerItem[] => { const yearList: number[] = []; for (let i = 1899; i <= 2100; i++) { yearList.push(i); } - return yearList; + return yearList.map((year: number) => ({ + value: year, + label: year.toString(), + })); }; -const yearList = getYearsArray(); - -export const getPickerItems = ( - isMonth: boolean, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - translatedLabels: any -): PickerItem[] => - isMonth - ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - (translatedLabels.calendar.months.map((month: string, index: number) => ({ - value: index, - label: month, - })) as PickerItem[]) - : yearList.map((year: number) => ({ - value: year, - label: year.toString(), - })); - export const calculateIsYearFirst = (localeFormat: string): boolean => { - const getPositionOf = (patterns: string[]): number => { - const positions = patterns - .map((pattern) => localeFormat.toLowerCase().indexOf(pattern.toLowerCase())) - .filter((pos) => pos !== -1); - return positions.length > 0 ? Math.min(...positions) : Infinity; - }; - return getPositionOf(["yyyy", "yy"]) < getPositionOf(["m", "mm", "M", "MM"]); + const lower = localeFormat.toLowerCase(); + const yearIndices = [lower.indexOf("yyyy"), lower.indexOf("yy")].filter((i) => i !== -1); + const monthIndices = [lower.indexOf("m"), lower.indexOf("mm")].filter((i) => i !== -1); + return Math.min(...yearIndices) < Math.min(...monthIndices); }; From fccab24a1c144f7f635b4e6d964d7607051bbe9f Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Wed, 19 Aug 2026 13:38:34 +0200 Subject: [PATCH 09/12] utils fixes --- packages/lib/src/date-input/utils.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/lib/src/date-input/utils.tsx b/packages/lib/src/date-input/utils.tsx index 2ec6b866b3..21c5c16f25 100644 --- a/packages/lib/src/date-input/utils.tsx +++ b/packages/lib/src/date-input/utils.tsx @@ -3,6 +3,8 @@ import dayjs, { Dayjs } from "dayjs"; import { DateType, PickerItem } from "./types"; const INVIS_CHARS = /[\u200E\u200F\u061C]/g; +const MIN_YEAR = 1899; +const MAX_YEAR = 2100; export const getValueForPicker = (value: string, format: string) => dayjs(value, format.toUpperCase(), true); @@ -118,7 +120,7 @@ export const getMonthPickerItems = (months: string[]): PickerItem[] => export const getYearPickerItems = (): PickerItem[] => { const yearList: number[] = []; - for (let i = 1899; i <= 2100; i++) { + for (let i = MIN_YEAR; i <= MAX_YEAR; i++) { yearList.push(i); } return yearList.map((year: number) => ({ @@ -129,7 +131,7 @@ export const getYearPickerItems = (): PickerItem[] => { export const calculateIsYearFirst = (localeFormat: string): boolean => { const lower = localeFormat.toLowerCase(); - const yearIndices = [lower.indexOf("yyyy"), lower.indexOf("yy")].filter((i) => i !== -1); - const monthIndices = [lower.indexOf("m"), lower.indexOf("mm")].filter((i) => i !== -1); - return Math.min(...yearIndices) < Math.min(...monthIndices); + const yearIndices = lower.indexOf("y"); + const monthIndices = lower.indexOf("m"); + return yearIndices < monthIndices; }; From f7eaf5c0b330fb326a98a0eacd246614aabd968b Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso <pfelguerosogalguera@gmail.com> Date: Thu, 20 Aug 2026 12:36:21 +0200 Subject: [PATCH 10/12] fix based on comments --- .../lib/src/date-input/DateInput.stories.tsx | 2 +- packages/lib/src/date-input/DatePicker.tsx | 2 +- packages/lib/src/date-input/DateUnitPicker.tsx | 17 +++++++---------- packages/lib/src/date-input/YearMonthPicker.tsx | 15 +++++++-------- packages/lib/src/date-input/types.ts | 4 ++-- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/lib/src/date-input/DateInput.stories.tsx b/packages/lib/src/date-input/DateInput.stories.tsx index ba8ae9afab..fe112bc3fb 100644 --- a/packages/lib/src/date-input/DateInput.stories.tsx +++ b/packages/lib/src/date-input/DateInput.stories.tsx @@ -296,7 +296,7 @@ const YearMonthPickerZhCn = () => ( <ExampleContainer> <Title title="Year/Month picker order zh-CN" theme="light" level={4} /> <HalstackProvider localeTag="zh-CN"> - <DxcDateInput label="Date input" format="dd-yyyy-mm" defaultValue="06-1905-05" /> + <DxcDateInput label="Date input" format="dd-mm" defaultValue="06-1905-05" /> </HalstackProvider> </ExampleContainer> ); diff --git a/packages/lib/src/date-input/DatePicker.tsx b/packages/lib/src/date-input/DatePicker.tsx index 3878781c5e..ef8d648828 100644 --- a/packages/lib/src/date-input/DatePicker.tsx +++ b/packages/lib/src/date-input/DatePicker.tsx @@ -148,7 +148,7 @@ const DatePicker = ({ date, onDateSelect, id, format }: DatePickerPropsType): JS {content !== "calendar" && ( <YearMonthPicker isYearFirst={isYearFirst} - innerDate={innerDate} + innerDate={selectedDate} today={today} onYearMonthComplete={setInnerDate} /> diff --git a/packages/lib/src/date-input/DateUnitPicker.tsx b/packages/lib/src/date-input/DateUnitPicker.tsx index bf57687441..520865b5d2 100644 --- a/packages/lib/src/date-input/DateUnitPicker.tsx +++ b/packages/lib/src/date-input/DateUnitPicker.tsx @@ -69,14 +69,11 @@ const DateUnitPicker = ({ selectedDate, today, items, - isMonth, + unit = "year", }: DateUnitPickerPropsType): JSX.Element => { - const selectedValue = - selectedDate?.isValid() && selectedDate ? selectedDate.get(isMonth ? "month" : "year") : undefined; - const currentValue = - today?.isValid() && today ? today.get(isMonth ? "month" : "year") : dayjs().get(isMonth ? "month" : "year"); - const ariaLabel = isMonth ? "Month Picker" : "Year Picker"; - const idPrefix = isMonth ? "month" : "year"; + const selectedValue = selectedDate?.isValid() && selectedDate ? selectedDate.get(unit) : undefined; + const currentValue = today?.isValid() && today ? today.get(unit) : dayjs().get(unit); + const ariaLabel = `${unit.charAt(0).toUpperCase() + unit.slice(1)} Picker`; const id = useId(); const currentIndex = items.findIndex((item) => item.value === currentValue); @@ -91,10 +88,10 @@ const DateUnitPicker = ({ }, [selectedIndex, currentIndex]); useEffect(() => { - const itemToFocusEl = document.getElementById(`${id}_${idPrefix}_${itemToFocus}`); + const itemToFocusEl = document.getElementById(`${id}_${unit}_${itemToFocus}`); itemToFocusEl?.scrollIntoView?.({ block: "center", inline: "center" }); itemToFocusEl?.focus(); - }, [itemToFocus, id, idPrefix]); + }, [itemToFocus, id, unit]); const handleKeyboardEvent = (event: KeyboardEvent<HTMLButtonElement>) => { switch (event.key) { @@ -122,7 +119,7 @@ const DateUnitPicker = ({ tabIndex={itemToFocus === index ? 0 : -1} isCurrent={item.value === currentValue} onKeyDown={(event) => handleKeyboardEvent(event)} - id={`${id}_${idPrefix}_${index}`} + id={`${id}_${unit}_${index}`} onClick={() => { onDateUnitSelect(item.value); }} diff --git a/packages/lib/src/date-input/YearMonthPicker.tsx b/packages/lib/src/date-input/YearMonthPicker.tsx index bf28d1ba5d..41cdeaec81 100644 --- a/packages/lib/src/date-input/YearMonthPicker.tsx +++ b/packages/lib/src/date-input/YearMonthPicker.tsx @@ -12,9 +12,10 @@ const YearMonthPickersContainer = styled.div` `; export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComplete }: YearMonthPickerProps) => { - const [selectedYear, setSelectedYear] = useState<number | null>(null); - const [selectedMonth, setSelectedMonth] = useState<number | null>(null); + const [selectedYear, setSelectedYear] = useState<number | null>(innerDate?.get("year") || null); + const [selectedMonth, setSelectedMonth] = useState<number | null>(innerDate?.get("month") || null); const languageContext = useContext(HalstackLanguageContext); + const yearItems = getYearPickerItems(); const monthItems = getMonthPickerItems(languageContext.labels.calendar.months); @@ -32,25 +33,23 @@ export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComp onYearMonthComplete(finalDate); }; - const yearPickerSelectedDate = selectedYear != null ? innerDate.set("year", selectedYear) : null; - const monthPickerSelectedDate = selectedMonth != null ? innerDate.set("month", selectedMonth) : null; - const yearPicker = ( <DateUnitPicker - selectedDate={yearPickerSelectedDate} + selectedDate={innerDate} onDateUnitSelect={handleOnYearSelect} today={today} items={yearItems} + unit="year" /> ); const monthPicker = ( <DateUnitPicker - selectedDate={monthPickerSelectedDate} + selectedDate={innerDate} onDateUnitSelect={handleOnMonthSelect} today={today} items={monthItems} - isMonth + unit="month" /> ); diff --git a/packages/lib/src/date-input/types.ts b/packages/lib/src/date-input/types.ts index bc75e6ee21..16fb6ee3ff 100644 --- a/packages/lib/src/date-input/types.ts +++ b/packages/lib/src/date-input/types.ts @@ -175,9 +175,9 @@ export type DateUnitPickerPropsType = { */ items: PickerItem[]; /** - * + * Date unit to display in the picker (month or year). */ - isMonth?: boolean; + unit?: "month" | "year"; }; export default Props; From c40d9e42b58f806c23189af45ee8695e7c9c763f Mon Sep 17 00:00:00 2001 From: jialecl <jialestrabajos@gmail.com> Date: Thu, 20 Aug 2026 14:26:44 +0200 Subject: [PATCH 11/12] Fixed issues related to focus and selection and added tests --- .../lib/src/date-input/DateInput.stories.tsx | 2 +- .../lib/src/date-input/DateInput.test.tsx | 150 +++--------------- packages/lib/src/date-input/DatePicker.tsx | 4 +- .../lib/src/date-input/DateUnitPicker.tsx | 22 +-- .../lib/src/date-input/YearMonthPicker.tsx | 14 +- packages/lib/src/date-input/types.ts | 2 +- 6 files changed, 43 insertions(+), 151 deletions(-) diff --git a/packages/lib/src/date-input/DateInput.stories.tsx b/packages/lib/src/date-input/DateInput.stories.tsx index fe112bc3fb..ba8ae9afab 100644 --- a/packages/lib/src/date-input/DateInput.stories.tsx +++ b/packages/lib/src/date-input/DateInput.stories.tsx @@ -296,7 +296,7 @@ const YearMonthPickerZhCn = () => ( <ExampleContainer> <Title title="Year/Month picker order zh-CN" theme="light" level={4} /> <HalstackProvider localeTag="zh-CN"> - <DxcDateInput label="Date input" format="dd-mm" defaultValue="06-1905-05" /> + <DxcDateInput label="Date input" format="dd-yyyy-mm" defaultValue="06-1905-05" /> </HalstackProvider> </ExampleContainer> ); diff --git a/packages/lib/src/date-input/DateInput.test.tsx b/packages/lib/src/date-input/DateInput.test.tsx index 951710880d..9dd3a17feb 100644 --- a/packages/lib/src/date-input/DateInput.test.tsx +++ b/packages/lib/src/date-input/DateInput.test.tsx @@ -1,5 +1,6 @@ import { fireEvent, render } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import "@testing-library/jest-dom"; import dayjs from "dayjs"; import DxcDateInput from "./DateInput"; import MockDOMRect from "../../test/mocks/domRectMock"; @@ -201,7 +202,7 @@ describe("DateInput component tests", () => { }); expect(input.value).toBe(d.format("DD-MM-YYYY")); }); - test("Selecting a year from the calendar year picker", () => { + test("Selecting a year and month from the calendar year picker", () => { const { getByText, getByRole } = render(<DxcDateInput format="dd-mm-yyyy" defaultValue="10-08-2021" />); const input = getByRole("textbox") as HTMLInputElement; const calendarAction = getByRole("combobox"); @@ -210,9 +211,11 @@ describe("DateInput component tests", () => { userEvent.click(getByText(d.format("MMMM YYYY"))); expect(getByText("2024")).toBeTruthy(); userEvent.click(getByText("2024")); - userEvent.click(getByText(d.set("year", 2024).format("MMMM YYYY"))); + expect(getByText("February")).toBeTruthy(); + userEvent.click(getByText("February")); + userEvent.click(getByText(d.set("year", 2024).set("month", 1).format("MMMM YYYY"))); fireEvent.keyDown(document, { key: "Escape", code: "Escape", keyCode: 27, charCode: 27 }); - expect(input.value).toBe(d.format("DD-MM-YYYY")); + expect(input.value).toBe(d.set("year", 2024).set("month", 1).format("DD-MM-YYYY")); }); test("Selecting a date from the calendar (using keyboard presses)", () => { const { getByRole, getAllByText, getByText } = render(<DxcDateInput />); @@ -531,148 +534,43 @@ describe("DateInput component tests", () => { userEvent.type(calendarAction, "{enter}"); expect(onSubmit).not.toHaveBeenCalled(); }); - - test("DateUnitPicker with null selectedDate shows no pre-selection", () => { - const { getByRole } = render(<DxcDateInput />); - const calendarAction = getByRole("combobox"); - userEvent.click(calendarAction); - - const monthYearButton = getByRole("button", { name: dayjs().format("MMMM YYYY") }); - expect(monthYearButton).toBeTruthy(); - userEvent.click(monthYearButton); - - const input = getByRole("textbox") as HTMLInputElement; - expect(input.value).toBe(""); - }); - - test("DateUnitPicker with null selectedDate but valid today shows today as highlighted", () => { - const { getByRole, container } = render(<DxcDateInput />); - const calendarAction = getByRole("combobox"); - userEvent.click(calendarAction); - - const monthYearButton = getByRole("button", { name: dayjs().format("MMMM YYYY") }); - expect(monthYearButton).toBeTruthy(); - userEvent.click(monthYearButton); - - const listboxes = container.querySelectorAll("[role='listbox']"); - expect(listboxes.length).toBe(2); - }); - - test("DateUnitPicker empty input doesn't show current month as selected", () => { - const { getByRole, container } = render(<DxcDateInput />); - const calendarAction = getByRole("combobox"); - userEvent.click(calendarAction); - - const monthYearButton = getByRole("button", { name: dayjs().format("MMMM YYYY") }); - expect(monthYearButton).toBeTruthy(); - userEvent.click(monthYearButton); - - const options = container.querySelectorAll("[role='option']"); - expect(options.length).toBeGreaterThan(0); - }); - - test("YearMonthPicker renders with year first when isYearFirst is true", () => { - const { getByRole, container } = render(<DxcDateInput defaultValue="15-03-2025" />); - - const calendarAction = getByRole("combobox"); - userEvent.click(calendarAction); - - const dialog = container.querySelector("[role='dialog']"); - expect(dialog).toBeTruthy(); - }); - - test("YearMonthPicker renders with month first when isYearFirst is false", () => { - const { getByRole, container } = render(<DxcDateInput defaultValue="15-03-2025" format="MM-dd-yyyy" />); - - const calendarAction = getByRole("combobox"); - userEvent.click(calendarAction); - - const dialog = container.querySelector("[role='dialog']"); - expect(dialog).toBeTruthy(); - }); - - test("YearMonthPicker year selection updates state", () => { - const onChange = jest.fn(); - const { getByRole, container } = render(<DxcDateInput onChange={onChange} />); - - const calendarAction = getByRole("combobox"); - userEvent.click(calendarAction); - - const dialog = container.querySelector("[role='dialog']"); - expect(dialog).toBeTruthy(); - }); - - test("YearMonthPicker month selection updates state", () => { - const { getByRole, container } = render(<DxcDateInput />); - - const calendarAction = getByRole("combobox"); - userEvent.click(calendarAction); - - const dialog = container.querySelector("[role='dialog']"); - expect(dialog).toBeTruthy(); - }); - - test("YearMonthPicker year then month selections combine", () => { - const { getByRole, container } = render(<DxcDateInput defaultValue="15-03-2025" />); - + test("Two listboxes are shown", () => { + const { getByRole, container } = render(<DxcDateInput defaultValue="15-03-2020" />); const calendarAction = getByRole("combobox"); userEvent.click(calendarAction); - const dialog = container.querySelector("[role='dialog']"); - expect(dialog).toBeTruthy(); - }); - - test("YearMonthPicker month then year selections combine", () => { - const { getByRole, container } = render(<DxcDateInput defaultValue="15-03-2025" format="MM-dd-yyyy" />); - - const calendarAction = getByRole("combobox"); - userEvent.click(calendarAction); - - const dialog = container.querySelector("[role='dialog']"); - expect(dialog).toBeTruthy(); - }); - - test("Empty DateInput shows no selected year in picker", () => { - const { getByRole, container } = render(<DxcDateInput />); - const calendarAction = getByRole("combobox"); - userEvent.click(calendarAction); - - const monthYearButton = getByRole("button", { name: dayjs().format("MMMM YYYY") }); + const monthYearButton = getByRole("button", { name: "March 2020" }); expect(monthYearButton).toBeTruthy(); userEvent.click(monthYearButton); const listboxes = container.querySelectorAll("[role='listbox']"); expect(listboxes.length).toBe(2); }); - - test("Empty DateInput shows no selected month in picker", () => { - const { getByRole, container } = render(<DxcDateInput />); + test("Calendar header formatting", () => { + const { getByRole, container } = render(<DxcDateInput defaultValue="2020/03/15" format="yyyy/MM/dd" />); const calendarAction = getByRole("combobox"); userEvent.click(calendarAction); - const monthYearButton = getByRole("button", { name: dayjs().format("MMMM YYYY") }); + const monthYearButton = getByRole("button", { name: "2020 March" }); expect(monthYearButton).toBeTruthy(); userEvent.click(monthYearButton); const listboxes = container.querySelectorAll("[role='listbox']"); expect(listboxes.length).toBe(2); + expect(listboxes[0]).toHaveTextContent("2019"); + expect(listboxes[1]).toHaveTextContent("February"); }); - - test("User can select year then month in empty input", () => { - const { getByRole, container } = render(<DxcDateInput />); + // listbox keyboard control + test("Listbox keyboard control", () => { + const { getByRole, getAllByRole } = render(<DxcDateInput defaultValue="15-03-2020" />); const calendarAction = getByRole("combobox"); userEvent.click(calendarAction); - - const dialog = container.querySelector("[role='dialog']"); - expect(dialog).toBeTruthy(); - }); - - test("Clearing input clears year/month picker selection state", () => { - const { getByRole, container } = render(<DxcDateInput value="15-03-2020" clearable />); - const input = getByRole("textbox") as HTMLInputElement; - expect(input.value).toBe("15-03-2020"); - - const dialog = container.querySelector("[role='dialog']"); - expect(dialog || true).toBeTruthy(); + expect(getByRole("button", { name: "March 2020" })).toBeTruthy(); + userEvent.click(getByRole("button", { name: "March 2020" })); + const yearListbox = getAllByRole("listbox")[1]; + expect(yearListbox).toBeTruthy(); + userEvent.keyboard("{ArrowDown}"); + userEvent.keyboard("{Enter}"); + expect(getByRole("button", { name: "March 2021" })).toBeTruthy(); }); }); diff --git a/packages/lib/src/date-input/DatePicker.tsx b/packages/lib/src/date-input/DatePicker.tsx index ef8d648828..b6eaf29b35 100644 --- a/packages/lib/src/date-input/DatePicker.tsx +++ b/packages/lib/src/date-input/DatePicker.tsx @@ -148,9 +148,9 @@ const DatePicker = ({ date, onDateSelect, id, format }: DatePickerPropsType): JS {content !== "calendar" && ( <YearMonthPicker isYearFirst={isYearFirst} - innerDate={selectedDate} + selectedDate={selectedDate} today={today} - onYearMonthComplete={setInnerDate} + onYearMonthComplete={handleDateSelect} /> )} </DatePickerContainer> diff --git a/packages/lib/src/date-input/DateUnitPicker.tsx b/packages/lib/src/date-input/DateUnitPicker.tsx index 520865b5d2..f8247d68ec 100644 --- a/packages/lib/src/date-input/DateUnitPicker.tsx +++ b/packages/lib/src/date-input/DateUnitPicker.tsx @@ -76,16 +76,7 @@ const DateUnitPicker = ({ const ariaLabel = `${unit.charAt(0).toUpperCase() + unit.slice(1)} Picker`; const id = useId(); - const currentIndex = items.findIndex((item) => item.value === currentValue); - const selectedIndex = selectedValue !== undefined ? items.findIndex((item) => item.value === selectedValue) : -1; - - const initialFocusIndex = selectedIndex >= 0 ? selectedIndex : currentIndex >= 0 ? currentIndex : 0; - const [itemToFocus, setItemToFocus] = useState(initialFocusIndex); - - useEffect(() => { - const focusIndex = selectedIndex >= 0 ? selectedIndex : currentIndex >= 0 ? currentIndex : 0; - setItemToFocus(focusIndex); - }, [selectedIndex, currentIndex]); + const [itemToFocus, setItemToFocus] = useState(selectedDate ? selectedDate.get(unit) : dayjs().get(unit)); useEffect(() => { const itemToFocusEl = document.getElementById(`${id}_${unit}_${itemToFocus}`); @@ -99,7 +90,10 @@ const DateUnitPicker = ({ setItemToFocus((prev) => (prev > 0 ? prev - 1 : prev)); break; case "ArrowDown": - setItemToFocus((prev) => (prev < items.length - 1 ? prev + 1 : prev)); + setItemToFocus((prev) => { + const lastValue = items[items.length - 1]?.value; + return lastValue !== undefined && prev < lastValue ? prev + 1 : prev; + }); break; default: break; @@ -108,7 +102,7 @@ const DateUnitPicker = ({ return ( <PickerContainer role="listbox" aria-label={ariaLabel}> - {items.map((item, index) => { + {items.map((item) => { const isSelected = item.value === selectedValue; return ( <PickerButton @@ -116,10 +110,10 @@ const DateUnitPicker = ({ key={item.value} selected={isSelected} aria-selected={isSelected} - tabIndex={itemToFocus === index ? 0 : -1} + tabIndex={itemToFocus === item.value ? 0 : -1} isCurrent={item.value === currentValue} onKeyDown={(event) => handleKeyboardEvent(event)} - id={`${id}_${unit}_${index}`} + id={`${id}_${unit}_${item.value}`} onClick={() => { onDateUnitSelect(item.value); }} diff --git a/packages/lib/src/date-input/YearMonthPicker.tsx b/packages/lib/src/date-input/YearMonthPicker.tsx index 41cdeaec81..40e653aa49 100644 --- a/packages/lib/src/date-input/YearMonthPicker.tsx +++ b/packages/lib/src/date-input/YearMonthPicker.tsx @@ -11,23 +11,23 @@ const YearMonthPickersContainer = styled.div` justify-content: center; `; -export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComplete }: YearMonthPickerProps) => { - const [selectedYear, setSelectedYear] = useState<number | null>(innerDate?.get("year") || null); - const [selectedMonth, setSelectedMonth] = useState<number | null>(innerDate?.get("month") || null); +export const YearMonthPicker = ({ isYearFirst, selectedDate, today, onYearMonthComplete }: YearMonthPickerProps) => { + const [selectedYear, setSelectedYear] = useState<number | null>(selectedDate?.get("year") || null); + const [selectedMonth, setSelectedMonth] = useState<number | null>(selectedDate?.get("month") || null); const languageContext = useContext(HalstackLanguageContext); const yearItems = getYearPickerItems(); const monthItems = getMonthPickerItems(languageContext.labels.calendar.months); const handleOnYearSelect = (year: number) => { - const newDate = innerDate.set("year", year); + const newDate = selectedDate.set("year", year); const finalDate = selectedMonth != null ? newDate.set("month", selectedMonth) : newDate; setSelectedYear(year); onYearMonthComplete(finalDate); }; const handleOnMonthSelect = (month: number) => { - const newDate = innerDate.set("month", month); + const newDate = selectedDate.set("month", month); const finalDate = selectedYear != null ? newDate.set("year", selectedYear) : newDate; setSelectedMonth(month); onYearMonthComplete(finalDate); @@ -35,7 +35,7 @@ export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComp const yearPicker = ( <DateUnitPicker - selectedDate={innerDate} + selectedDate={selectedDate} onDateUnitSelect={handleOnYearSelect} today={today} items={yearItems} @@ -45,7 +45,7 @@ export const YearMonthPicker = ({ isYearFirst, innerDate, today, onYearMonthComp const monthPicker = ( <DateUnitPicker - selectedDate={innerDate} + selectedDate={selectedDate} onDateUnitSelect={handleOnMonthSelect} today={today} items={monthItems} diff --git a/packages/lib/src/date-input/types.ts b/packages/lib/src/date-input/types.ts index 16fb6ee3ff..a9bdf8f9ed 100644 --- a/packages/lib/src/date-input/types.ts +++ b/packages/lib/src/date-input/types.ts @@ -147,7 +147,7 @@ export type CalendarPropsType = { export interface YearMonthPickerProps { isYearFirst: boolean; - innerDate: Dayjs; + selectedDate: Dayjs; today: Dayjs; onYearMonthComplete: (newDate: Dayjs) => void; } From 12904964a811ac618f42fe990deab68608cf3358 Mon Sep 17 00:00:00 2001 From: jialecl <jialestrabajos@gmail.com> Date: Thu, 20 Aug 2026 14:42:50 +0200 Subject: [PATCH 12/12] missing unit in storybook test --- packages/lib/src/date-input/DateInput.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/lib/src/date-input/DateInput.stories.tsx b/packages/lib/src/date-input/DateInput.stories.tsx index ba8ae9afab..821993c92f 100644 --- a/packages/lib/src/date-input/DateInput.stories.tsx +++ b/packages/lib/src/date-input/DateInput.stories.tsx @@ -263,6 +263,7 @@ const DatePickerToday = () => { onDateUnitSelect={() => {}} today={dayjs("1904-04-03", "YYYY-MM-DD")} items={monthItems} + unit="month" /> </ExampleContainer> <ExampleContainer>