diff --git a/skills/carbon-react/components/date-input.md b/skills/carbon-react/components/date-input.md index 0e374d1488..1f1ee536b0 100644 --- a/skills/carbon-react/components/date-input.md +++ b/skills/carbon-react/components/date-input.md @@ -39,8 +39,6 @@ description: Carbon DateInput component props and usage examples. | dangerouslySetInnerHTML | { __html: string \| TrustedHTML; } \| undefined | No | | | | | | | datatype | string \| undefined | No | | | | | | | dateFormatOverride | string \| undefined | No | | | | Date format string to be applied to the date inputs | | -| datePickerAriaLabel | string \| undefined | No | | | | Prop to specify the aria-label attribute of the date picker | | -| datePickerAriaLabelledBy | string \| undefined | No | | | | Prop to specify the aria-labelledby attribute of the date picker | | | defaultChecked | boolean \| undefined | No | | | | | | | dir | string \| undefined | No | | | | | | | disabled | boolean \| undefined | No | | | | If true, the component will be disabled | | @@ -283,9 +281,11 @@ description: Carbon DateInput component props and usage examples. | unselectable | "off" \| "on" \| undefined | No | | | | | | | validationIconId | string \| undefined | No | | | | Id of the validation icon | | | validationMessagePositionTop | boolean \| undefined | No | | | | Render the ValidationMessage above the Textbox input when validationRedesignOptIn flag is set | | +| variant | "typical" \| "legacy" \| undefined | No | | | | Date input presentation. Typical is the default; legacy retains its icon trigger. | | | vocab | string \| undefined | No | | | | | | | warning | string \| boolean \| undefined | No | | | | Indicate that warning has occurred. | | | width | string \| number \| undefined | No | | | | | | +| yearRangeOffset | number \| undefined | No | | | | Number of years shown either side of the bounds (`minDate`/`maxDate`, or `pickerProps.startMonth`/`endMonth`) or the anchor year in the year selector dropdown. Defaults to 10. | | | data-element | string \| undefined | No | | | | Identifier used for testing purposes, applied to the root element of the component. | | | data-role | string \| undefined | No | | | | Identifier used for testing purposes, applied to the root element of the component. | | | aria-activedescendant | string \| undefined | No | | | | Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application. | | @@ -340,6 +340,8 @@ description: Carbon DateInput component props and usage examples. | aria-valuenow | number \| undefined | No | | | | Defines the current value for a range widget. | | | aria-valuetext | string \| undefined | No | | | | Defines the human readable text alternative of aria-valuenow for a range widget. | | | adaptiveLabelBreakpoint | number \| undefined | No | | Yes | `adaptiveLabelBreakpoint` has been deprecated, the functionality will no longer work. | | | +| datePickerAriaLabel | string \| undefined | No | | Yes | The dialog label is now always derived from the locale (`date.ariaLabels.chooseDate`). Remove this prop; it has no effect. | | | +| datePickerAriaLabelledBy | string \| undefined | No | | Yes | The dialog label is now always derived from the locale (`date.ariaLabels.chooseDate`). Remove this prop; it has no effect. | | | | fieldHelp | React.ReactNode | No | | Yes | `fieldHelp` has been deprecated, `inputHint` should be used instead. [Legacy] Help content to be displayed under an input. | | | | helpAriaLabel | string \| undefined | No | | Yes | `helpAriaLabel` has been deprecated, the functionality will no longer work. | | | | info | string \| boolean \| undefined | No | | Yes | `info` has been deprecated, the functionality will no longer work. | | | @@ -363,17 +365,29 @@ description: Carbon DateInput component props and usage examples. ```tsx () => { - const [state, setState] = useState("04/04/2019"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; + const [typicalValue, setTypicalValue] = useState(storyDateValue); + const [legacyValue, setLegacyValue] = useState(storyDateValue); + return ( - + + setTypicalValue(event.target.value.formattedValue)} + /> + setLegacyValue(event.target.value.formattedValue)} + /> + ); } ``` @@ -385,19 +399,7 @@ description: Carbon DateInput component props and usage examples. ```tsx () => { - const [state, setState] = useState("04/04/2019"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; - return ( - - ); + return ; } ``` @@ -408,23 +410,16 @@ description: Carbon DateInput component props and usage examples. ```tsx () => { - const [state, setState] = useState("01/10/2016"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; return ( - <> + {(["small", "medium", "large"] as const).map((size) => ( - + ))} - + ); } ``` @@ -436,11 +431,7 @@ description: Carbon DateInput component props and usage examples. ```tsx () => { - const [state, setState] = useState("01/10/2016"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; - return ; + return ; } ``` @@ -451,11 +442,7 @@ description: Carbon DateInput component props and usage examples. ```tsx () => { - const [state, setState] = useState("01/10/2016"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; - return ; + return ; } ``` @@ -466,90 +453,107 @@ description: Carbon DateInput component props and usage examples. ```tsx () => { - const [state, setState] = useState(""); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); + const [typicalValue, setTypicalValue] = useState(""); + const [legacyValue, setLegacyValue] = useState(""); + + const setValues = (value: string) => { + setTypicalValue(value); + setLegacyValue(value); }; + return ( <> - - + - + + + setTypicalValue(event.target.value.formattedValue) + } + allowEmptyValue + /> + + setLegacyValue(event.target.value.formattedValue) + } + allowEmptyValue + /> + ); } ``` -### Disabled Dates +### Disabled Dates in Calendar **Render** ```tsx -({ onChange, ...args }: DateInputProps) => { - const [state, setState] = useState("04/04/2019"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; +() => { + const isWeekend = (day: Date) => [0, 6].includes(day.getDay()); + return ( - { - setValue(ev); - onChange?.(ev); - }} - /> + + + Saturdays and Sundays are disabled in both calendars. + + + ); } ``` -### Disabled Dates using pickerProps +### Disabled Dates Outside Min/Max Range **Render** ```tsx () => { - const [state, setState] = useState("04/04/2019"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; - - const isWeekend = (day: Date) => [0, 6].includes(day.getDay()); - return ( - + + + Days outside the minDate/maxDate range are disabled in the calendar + below: only dates from 10th to 20th June 2024 are selectable. The year + and month selectors themselves remain enabled so users can still + navigate to, and view, months outside the selectable range. + + + ); } ``` @@ -561,19 +565,7 @@ description: Carbon DateInput component props and usage examples. ```tsx () => { - const [state, setState] = useState("01/10/2016"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; - return ( - - ); + return ; } ``` @@ -584,59 +576,83 @@ description: Carbon DateInput component props and usage examples. ```tsx () => { - const [state, setState] = useState("01/10/2016"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; - return ( - - ); + return ; } ``` -### With Field Help +### With Error **Render** ```tsx () => { - const [state, setState] = useState("01/10/2016"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; return ( - + + + + ); } ``` -### With Disabled Portal +### With Caution **Render** ```tsx -() => { - const [state, setState] = useState("01/10/2016"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; - return ( - - ); -} +() => ( + + + + +) +``` + + +### Without Portal (Deprecated) + +**Render** + +```tsx +() => ( + + + Deprecated compatibility behavior: disablePortal renders the calendar in + the Date Input's local DOM tree instead of through a portal. Existing + legacy and typical consumers remain supported, but new implementations + should not adopt this prop. + + + +) ``` @@ -646,11 +662,7 @@ description: Carbon DateInput component props and usage examples. ```tsx () => { - const [state, setState] = useState("01/10/2016"); - const setValue = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; - return ; + return ; } ``` @@ -661,16 +673,17 @@ description: Carbon DateInput component props and usage examples. ```tsx () => { - const [state, setState] = useState("2022-04-05"); - const handleChange = (ev: DateChangeEvent) => { - setState(ev.target.value.formattedValue); - }; - const [state2, setState2] = useState("2022-04-05"); - const handleChange2 = (ev: DateChangeEvent) => { - setState2(ev.target.value.formattedValue); - }; return ( - + + + Locale values are merged with en-GB. Applications should supply approved + translations for their users through I18nProvider; omitted accessibility + labels fall back to en-GB. + "de-DE", @@ -683,11 +696,7 @@ description: Carbon DateInput component props and usage examples. }, }} > - + - + + "pl-PL", + date: { + dateFnsLocale: () => pl, + ariaLabels: { + previousMonthButton: () => "Poprzedni miesiąc", + nextMonthButton: () => "Następny miesiąc", + }, + }, + }} + > + + + "en-GB", + date: { + dateFnsLocale: () => enGB, + ariaLabels: { + previousMonthButton: () => "Previous month", + nextMonthButton: () => "Next month", + }, + }, + }} + > + @@ -733,7 +775,16 @@ description: Carbon DateInput component props and usage examples. }; return ( - + + + Both fields use the German locale. The first uses the locale translation + key, so it displays YYYY-MM-DD. The second passes dateFormatOverride + directly, so that prop takes precedence and displays DD/MM/YYYY. + "de-DE", @@ -749,7 +800,8 @@ description: Carbon DateInput component props and usage examples. > { handleChangeKey(ev); @@ -760,7 +812,8 @@ description: Carbon DateInput component props and usage examples. { handleChangeProp(ev); diff --git a/skills/carbon-react/components/date-range.md b/skills/carbon-react/components/date-range.md index df6fa438cf..20e008b368 100644 --- a/skills/carbon-react/components/date-range.md +++ b/skills/carbon-react/components/date-range.md @@ -13,51 +13,51 @@ description: Carbon DateRange component props and usage examples. - Props interface: `DateRangeProps` ## Props -| Name | Type | Required | Literals | Description | Default | -| --- | --- | --- | --- | --- | --- | -| onChange | (ev: DateRangeChangeEvent) => void | Yes | | Specify a callback triggered on change | | -| value | string[] | Yes | | An array containing the value of startDate and endDate | | -| dateFormatOverride | string \| undefined | No | | Date format string to be applied to the date inputs | | -| datePickerEndAriaLabel | string \| undefined | No | | Prop to specify the aria-label attribute of the end date picker | | -| datePickerEndAriaLabelledBy | string \| undefined | No | | Prop to specify the aria-labelledby attribute of the end date picker | | -| datePickerStartAriaLabel | string \| undefined | No | | Prop to specify the aria-label attribute of the start date picker | | -| datePickerStartAriaLabelledBy | string \| undefined | No | | Prop to specify the aria-labelledby attribute of the start date picker | | -| endDateProps | Omit, "required"> \| undefined | No | | Props for the child end Date component | {} | -| endError | string \| boolean \| undefined | No | | Indicate that error has occurred on end date. Pass string to display icon, tooltip and red border. Pass true boolean to only display red border. | | -| endInfo | string \| boolean \| undefined | No | | [Legacy] Indicate additional information for end date. Pass string to display icon, tooltip and blue border. Pass true boolean to only display blue border. | | -| endLabel | string \| undefined | No | | Optional label for endDate field | | -| endRef | React.ForwardedRef \| undefined | No | | A React ref to pass to the second of the two Date Input fields | | -| endWarning | string \| boolean \| undefined | No | | Indicate that warning has occurred on end date. Pass string to display icon, tooltip and orange border. Pass true boolean to only display orange border. | | -| id | string \| undefined | No | | An optional string prop to provide an id to the component | | -| labelsInline | boolean \| undefined | No | | [Legacy] Display labels inline | | -| m | ResponsiveValue \| undefined | No | | Margin on top, left, bottom and right | | -| margin | ResponsiveValue \| undefined | No | | Margin on top, left, bottom and right | | -| marginBottom | ResponsiveValue \| undefined | No | | Margin on bottom | | -| marginLeft | ResponsiveValue \| undefined | No | | Margin on left | | -| marginRight | ResponsiveValue \| undefined | No | | Margin on right | | -| marginTop | ResponsiveValue \| undefined | No | | Margin on top | | -| marginX | ResponsiveValue \| undefined | No | | Margin on left and right | | -| marginY | ResponsiveValue \| undefined | No | | Margin on top and bottom | | -| mb | ResponsiveValue \| undefined | No | | Margin on bottom | | -| ml | ResponsiveValue \| undefined | No | | Margin on left | | -| mr | ResponsiveValue \| undefined | No | | Margin on right | | -| mt | ResponsiveValue \| undefined | No | | Margin on top | | -| mx | ResponsiveValue \| undefined | No | | Margin on left and right | | -| my | ResponsiveValue \| undefined | No | | Margin on top and bottom | | -| name | string \| undefined | No | | An optional string prop to provide a name to the component | | -| onBlur | ((ev: DateRangeChangeEvent) => void) \| undefined | No | | Specify a callback triggered on blur | | -| required | boolean \| undefined | No | | Flag to configure component as mandatory. | | -| startDateProps | Omit, "required"> \| undefined | No | | Props for the child start Date component | {} | -| startError | string \| boolean \| undefined | No | | Indicate that error has occurred on start date. Pass string to display icon, tooltip and red border. Pass true boolean to only display red border. | | -| startInfo | string \| boolean \| undefined | No | | [Legacy] Indicate additional information for start date. Pass string to display icon, tooltip and blue border. Pass true boolean to only display blue border. | | -| startLabel | string \| undefined | No | | Optional label for startDate field | | -| startRef | React.ForwardedRef \| undefined | No | | A React ref to pass to the first of the two Date Input fields | | -| startWarning | string \| boolean \| undefined | No | | Indicate that warning has occurred on start date. Pass string to display icon, tooltip and orange border. Pass true boolean to only display orange border. | | -| tooltipPosition | "left" \| "right" \| "bottom" \| "top" \| undefined | No | | [Legacy] Overrides the default tooltip position | | -| validationMessagePositionTop | boolean \| undefined | No | | Render the ValidationMessage above the Date inputs when validationRedesignOptIn flag is set | true | -| validationOnLabel | boolean \| undefined | No | | [Legacy] When true, validation icons will be placed on labels instead of being placed on the inputs | | -| data-element | string \| undefined | No | | Identifier used for testing purposes, applied to the root element of the component. | | -| data-role | string \| undefined | No | | Identifier used for testing purposes, applied to the root element of the component. | | +| Name | Type | Required | Literals | Deprecated | Deprecation reason | Description | Default | +| --- | --- | --- | --- | --- | --- | --- | --- | +| onChange | (ev: DateRangeChangeEvent) => void | Yes | | | | Specify a callback triggered on change | | +| value | string[] | Yes | | | | An array containing the value of startDate and endDate | | +| dateFormatOverride | string \| undefined | No | | | | Date format string to be applied to the date inputs | | +| endDateProps | Omit, "required"> \| undefined | No | | | | Props for the child end Date component | {} | +| endError | string \| boolean \| undefined | No | | | | Indicate that error has occurred on end date. Pass string to display icon, tooltip and red border. Pass true boolean to only display red border. | | +| endInfo | string \| boolean \| undefined | No | | | | [Legacy] Indicate additional information for end date. Pass string to display icon, tooltip and blue border. Pass true boolean to only display blue border. | | +| endLabel | string \| undefined | No | | | | Optional label for endDate field | | +| endRef | React.ForwardedRef \| undefined | No | | | | A React ref to pass to the second of the two Date Input fields | | +| endWarning | string \| boolean \| undefined | No | | | | Indicate that warning has occurred on end date. Pass string to display icon, tooltip and orange border. Pass true boolean to only display orange border. | | +| id | string \| undefined | No | | | | An optional string prop to provide an id to the component | | +| labelsInline | boolean \| undefined | No | | | | [Legacy] Display labels inline | | +| m | ResponsiveValue \| undefined | No | | | | Margin on top, left, bottom and right | | +| margin | ResponsiveValue \| undefined | No | | | | Margin on top, left, bottom and right | | +| marginBottom | ResponsiveValue \| undefined | No | | | | Margin on bottom | | +| marginLeft | ResponsiveValue \| undefined | No | | | | Margin on left | | +| marginRight | ResponsiveValue \| undefined | No | | | | Margin on right | | +| marginTop | ResponsiveValue \| undefined | No | | | | Margin on top | | +| marginX | ResponsiveValue \| undefined | No | | | | Margin on left and right | | +| marginY | ResponsiveValue \| undefined | No | | | | Margin on top and bottom | | +| mb | ResponsiveValue \| undefined | No | | | | Margin on bottom | | +| ml | ResponsiveValue \| undefined | No | | | | Margin on left | | +| mr | ResponsiveValue \| undefined | No | | | | Margin on right | | +| mt | ResponsiveValue \| undefined | No | | | | Margin on top | | +| mx | ResponsiveValue \| undefined | No | | | | Margin on left and right | | +| my | ResponsiveValue \| undefined | No | | | | Margin on top and bottom | | +| name | string \| undefined | No | | | | An optional string prop to provide a name to the component | | +| onBlur | ((ev: DateRangeChangeEvent) => void) \| undefined | No | | | | Specify a callback triggered on blur | | +| required | boolean \| undefined | No | | | | Flag to configure component as mandatory. | | +| startDateProps | Omit, "required"> \| undefined | No | | | | Props for the child start Date component | {} | +| startError | string \| boolean \| undefined | No | | | | Indicate that error has occurred on start date. Pass string to display icon, tooltip and red border. Pass true boolean to only display red border. | | +| startInfo | string \| boolean \| undefined | No | | | | [Legacy] Indicate additional information for start date. Pass string to display icon, tooltip and blue border. Pass true boolean to only display blue border. | | +| startLabel | string \| undefined | No | | | | Optional label for startDate field | | +| startRef | React.ForwardedRef \| undefined | No | | | | A React ref to pass to the first of the two Date Input fields | | +| startWarning | string \| boolean \| undefined | No | | | | Indicate that warning has occurred on start date. Pass string to display icon, tooltip and orange border. Pass true boolean to only display orange border. | | +| tooltipPosition | "left" \| "right" \| "bottom" \| "top" \| undefined | No | | | | [Legacy] Overrides the default tooltip position | | +| validationMessagePositionTop | boolean \| undefined | No | | | | Render the ValidationMessage above the Date inputs when validationRedesignOptIn flag is set | true | +| validationOnLabel | boolean \| undefined | No | | | | [Legacy] When true, validation icons will be placed on labels instead of being placed on the inputs | | +| data-element | string \| undefined | No | | | | Identifier used for testing purposes, applied to the root element of the component. | | +| data-role | string \| undefined | No | | | | Identifier used for testing purposes, applied to the root element of the component. | | +| datePickerEndAriaLabel | string \| undefined | No | | Yes | The dialog label is now always derived from the locale (`date.ariaLabels.chooseDate`). Remove this prop; it has no effect. | | | +| datePickerEndAriaLabelledBy | string \| undefined | No | | Yes | The dialog label is now always derived from the locale (`date.ariaLabels.chooseDate`). Remove this prop; it has no effect. | | | +| datePickerStartAriaLabel | string \| undefined | No | | Yes | The dialog label is now always derived from the locale (`date.ariaLabels.chooseDate`). Remove this prop; it has no effect. | | | +| datePickerStartAriaLabelledBy | string \| undefined | No | | Yes | The dialog label is now always derived from the locale (`date.ariaLabels.chooseDate`). Remove this prop; it has no effect. | | | ## Examples ### Default diff --git a/src/__internal__/__stories__/z-index-interaction.stories.tsx b/src/__internal__/__stories__/z-index-interaction.stories.tsx index ce4b2f6fb9..81d56576a0 100644 --- a/src/__internal__/__stories__/z-index-interaction.stories.tsx +++ b/src/__internal__/__stories__/z-index-interaction.stories.tsx @@ -74,8 +74,10 @@ export const DateInputInDialog: StoryObj = { await expect(modal).toHaveAttribute("data-state", "open"); }); - const icon = await screen.findByTestId("icon"); - await userEvent.click(icon); + const calendarButton = await screen.findByRole("button", { + name: "Open calendar", + }); + await userEvent.click(calendarButton); const datePicker = await screen.findByTestId("date-picker"); expect(datePicker).toBeVisible(); @@ -458,11 +460,10 @@ export const DateInPopoverContainer: StoryObj = { play: async () => { if (!allowInteractions()) return; - const icon = document.querySelector( - '[data-component="icon"][data-element="calendar"]', - ); - - if (icon) await userEvent.click(icon); + const calendarButton = await screen.findByRole("button", { + name: "Open calendar", + }); + await userEvent.click(calendarButton); const datePicker = await screen.findByTestId("date-picker"); expect(datePicker).toBeVisible(); diff --git a/src/__internal__/input/input-style-overrides.style.ts b/src/__internal__/input/input-style-overrides.style.ts index 17b527db36..bc32bcef44 100644 --- a/src/__internal__/input/input-style-overrides.style.ts +++ b/src/__internal__/input/input-style-overrides.style.ts @@ -7,8 +7,21 @@ import addFocusStyling from "../../style/utils/add-focus-styling"; export const dateStyleOverrides = css` .date & { .input-text-container input { - padding: 0 0 0 12px; - margin-right: -12px; + padding-inline-end: 0; + } + } + + .date-typical [data-role="date-input-wrapper"] & { + &:focus-within { + box-shadow: none; + outline: none; + } + + .input-text-container input:focus { + border-radius: var(--global-radius-action-m) 0 0 + var(--global-radius-action-m); + ${addFocusStyling()} + z-index: 2; } } `; @@ -144,7 +157,6 @@ const searchNewBaseStyles = css` &:focus-within:has(:focus:not(button)) { box-shadow: none; - -webkit-box-shadow: none; } .input-text-container input[type="search"]:focus { @@ -331,7 +343,6 @@ export const passwordStyleOverrides = css` [data-component="password"] & { &:focus-within { box-shadow: none !important; - -webkit-box-shadow: none !important; outline: none !important; } diff --git a/src/__internal__/label/label.style.ts b/src/__internal__/label/label.style.ts index edbb074a05..8f5056e02d 100644 --- a/src/__internal__/label/label.style.ts +++ b/src/__internal__/label/label.style.ts @@ -44,16 +44,14 @@ const StyledLabel = styled.label` ${({ $isRequired, $size, $disabled }) => $isRequired && css` - display: inline-flex; - align-items: center; - ::after { - content: "*"; color: ${$disabled ? "var(--input-labelset-label-disabled)" : "var(--input-labelset-label-required)"}; + content: "*"; + display: inline-block; font: ${getFontToken($size)}; - margin-left: 4px; + margin-inline-start: 4px; } `} @@ -65,7 +63,7 @@ const StyledLabel = styled.label` .fieldset-required-input & { ::after { content: ""; - margin-left: 0; + margin-inline-start: 0; } } `; diff --git a/src/__internal__/label/label.test.tsx b/src/__internal__/label/label.test.tsx index 3bde6a4f6b..a896ea9cf6 100644 --- a/src/__internal__/label/label.test.tsx +++ b/src/__internal__/label/label.test.tsx @@ -120,6 +120,24 @@ test("should apply medium font to required indicator by default", () => { ); }); +test("should apply small font to label and required indicator", () => { + render( + , + ); + + expect(screen.getByText("Small Required")).toHaveStyleRule( + "font", + "var(--global-font-static-comp-medium-s)", + ); + expect(screen.getByText("Small Required")).toHaveStyleRule( + "font", + "var(--global-font-static-comp-medium-s)", + { modifier: "::after" }, + ); +}); + test("should apply disabled colour and set aria-disabled when `disabled` prop is true", () => { render( , ); @@ -793,7 +891,7 @@ describe("when the `locale` is 'de-DE'", () => { date: { ariaLabels, dateFnsLocale: () => deLocale }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -854,7 +952,7 @@ describe("when the `locale` is 'de-DE'", () => { }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -902,7 +1000,7 @@ describe("when the `locale` is 'es'", () => { date: { ariaLabels, dateFnsLocale: () => esLocale }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -963,7 +1061,7 @@ describe("when the `locale` is 'es'", () => { }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1011,7 +1109,7 @@ describe("when the `locale` is 'en-ZA'", () => { date: { ariaLabels, dateFnsLocale: () => enZALocale }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1072,7 +1170,7 @@ describe("when the `locale` is 'en-ZA'", () => { }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1120,7 +1218,7 @@ describe("when the `locale` is 'fr-FR'", () => { date: { ariaLabels, dateFnsLocale: () => frLocale }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1181,7 +1279,7 @@ describe("when the `locale` is 'fr-FR'", () => { }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1229,7 +1327,7 @@ describe("when the `locale` is 'fr-CA'", () => { date: { ariaLabels, dateFnsLocale: () => frCALocale }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1290,7 +1388,7 @@ describe("when the `locale` is 'fr-CA'", () => { }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1338,7 +1436,7 @@ describe("when the `locale` is 'en-CA'", () => { date: { ariaLabels, dateFnsLocale: () => enCALocale }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1399,7 +1497,7 @@ describe("when the `locale` is 'en-CA'", () => { }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1447,7 +1545,7 @@ describe("when the `locale` is 'en-US'", () => { date: { ariaLabels, dateFnsLocale: () => enUSLocale }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1508,7 +1606,7 @@ describe("when the `locale` is 'en-US'", () => { }, }} > - {}} value="2019-04-05" /> + {}} value="2019-04-05" /> , ); @@ -1597,7 +1695,7 @@ test("should call `onChange` with expected parameters but not update the input v test("should render the validation message when an `error` is passed a string value and the user hovers the mouse over the input", () => { render( - {}} value="" @@ -1611,7 +1709,7 @@ test("should render the validation message when an `error` is passed a string va }); test("should not render the validation message when `error` is passed a boolean value", () => { - render( {}} value="" error />); + render( {}} value="" error />); const input = screen.getByRole("textbox"); expect(input).toHaveAttribute("aria-invalid", "true"); @@ -1620,7 +1718,7 @@ test("should not render the validation message when `error` is passed a boolean test("should render the validation message when an `warning` is passed a string value and the user hovers the mouse over the input", () => { render( - {}} value="" @@ -1634,7 +1732,9 @@ test("should render the validation message when an `warning` is passed a string }); test("should not render the validation message when `warning` is passed a boolean value", () => { - render( {}} value="" warning />); + render( + {}} value="" warning />, + ); const input = screen.getByRole("textbox"); expect(input).toHaveAttribute("aria-invalid", "false"); @@ -1646,7 +1746,7 @@ test("should call `onPickerOpen` callback when the user opens the DatePicker and const onPickerOpen = jest.fn(); const onPickerClose = jest.fn(); render( - {}} onPickerOpen={onPickerOpen} @@ -1663,6 +1763,29 @@ test("should call `onPickerOpen` callback when the user opens the DatePicker and expect(onPickerClose).toHaveBeenCalled(); }); +test("should close the legacy DatePicker with its close button and return focus to the input", async () => { + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + const onPickerClose = jest.fn(); + render( + {}} + onPickerClose={onPickerClose} + value="010122" + />, + ); + + const input = screen.getByRole("textbox", { name: "label" }); + await user.click(screen.getByTestId("input-icon-toggle")); + await user.click(screen.getByRole("button", { name: "Close" })); + + expect( + screen.queryByRole("button", { name: "Close" }), + ).not.toBeInTheDocument(); + expect(input).toHaveFocus(); + expect(onPickerClose).toHaveBeenCalledTimes(1); +}); + test("should select the correct date when the locale is overridden and a date is typed into the input", async () => { const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); const onChange = jest.fn(); @@ -1678,7 +1801,7 @@ test("should select the correct date when the locale is overridden and a date is }, }} > - + , ); const input = screen.getByRole("textbox"); @@ -1687,8 +1810,12 @@ test("should select the correct date when the locale is overridden and a date is const calendarIcon = screen.getByTestId("input-icon-toggle"); await user.click(calendarIcon); - const caption = screen.getByRole("status"); - expect(caption).toHaveTextContent("April 2019"); + expect( + screen.getByRole("combobox", { name: "Choose the month" }), + ).toHaveValue("3"); + expect(screen.getByRole("combobox", { name: "Choose the year" })).toHaveValue( + "2019", + ); }); // coverage @@ -1697,7 +1824,7 @@ test("should correctly focus when disablePortal is false and Tab is used to navi const onFocus = jest.fn(); const onClick = jest.fn(); render( - {}} value="2019-04-04" @@ -1713,9 +1840,12 @@ test("should correctly focus when disablePortal is false and Tab is used to navi await user.tab(); await user.tab(); await user.tab(); + await user.tab(); expect( - screen.getByLabelText("Thursday, April 4th, 2019", { exact: false }), + screen.getByRole("button", { + name: "Thursday, April 4th, 2019, selected", + }), ).toHaveFocus(); }); @@ -1726,7 +1856,7 @@ test("should fire the onPickerClose callback when the input has focus and shift- const onPickerClose = jest.fn(); render( - { - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - const onChange = jest.fn(); - - render( - , - ); - const calendarIcon = screen.getByTestId("input-icon-toggle"); - await user.click(calendarIcon); - await user.tab(); - const calendar = await screen.findByRole("grid"); - - expect(calendar).toBeVisible(); - expect( - screen.getByRole("button", { name: "Previous month" }), - ).toHaveFocus(); - }, -); diff --git a/src/components/flat-table/flat-table.test.tsx b/src/components/flat-table/flat-table.test.tsx index 9b474a5420..ba7e1ccc50 100644 --- a/src/components/flat-table/flat-table.test.tsx +++ b/src/components/flat-table/flat-table.test.tsx @@ -1,6 +1,12 @@ import React from "react"; -import { act, render, screen, waitFor } from "@testing-library/react"; +import { + act, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import FlatTable from "./flat-table.component"; @@ -1378,20 +1384,14 @@ test("when a DateInput is opened in an expandable row and FlatTable has sticky f , ); - const preventDefaultSpy = jest.spyOn( - KeyboardEvent.prototype, - "preventDefault", - ); - const flatTableRow = screen.getByTestId("flat-table-row-0"); + const table = screen.getByRole("table"); await user.click(flatTableRow); - await user.keyboard("{PageDown}"); - await user.keyboard("{PageUp}"); - await user.keyboard("{Home}"); - await user.keyboard("{End}"); - - expect(preventDefaultSpy).not.toHaveBeenCalled(); + expect(fireEvent.keyDown(table, { key: "PageDown" })).toBe(true); + expect(fireEvent.keyDown(table, { key: "PageUp" })).toBe(true); + expect(fireEvent.keyDown(table, { key: "Home" })).toBe(true); + expect(fireEvent.keyDown(table, { key: "End" })).toBe(true); const calendarIcon = screen.getByTestId("input-icon-toggle"); await user.click(calendarIcon); @@ -1400,11 +1400,8 @@ test("when a DateInput is opened in an expandable row and FlatTable has sticky f expect(screen.getByRole("grid")).toBeVisible(); }); - await user.keyboard("{PageDown}"); - await user.keyboard("{PageUp}"); - await user.keyboard("{Home}"); - await user.keyboard("{End}"); - - expect(preventDefaultSpy).toHaveBeenCalled(); - preventDefaultSpy.mockRestore(); + expect(fireEvent.keyDown(table, { key: "PageDown" })).toBe(false); + expect(fireEvent.keyDown(table, { key: "PageUp" })).toBe(false); + expect(fireEvent.keyDown(table, { key: "Home" })).toBe(false); + expect(fireEvent.keyDown(table, { key: "End" })).toBe(false); }); diff --git a/src/components/form/form-interaction.stories.tsx b/src/components/form/form-interaction.stories.tsx index 643544fb1f..bd1d75dc30 100644 --- a/src/components/form/form-interaction.stories.tsx +++ b/src/components/form/form-interaction.stories.tsx @@ -123,6 +123,9 @@ export const FocusManagement: Story = { const datepickerInput = canvas.getByRole("textbox", { name: /date picker/i, }); + const datepickerButton = canvas.getByRole("button", { + name: /open calendar/i, + }); const textarea = canvas.getByRole("textbox", { name: /textbox 3/i }); const checkboxes = canvas.getAllByRole("checkbox", { name: /checkbox/i }); const tertiaryButton = canvas.getByRole("button", { name: /tertiary/i }); @@ -144,6 +147,9 @@ export const FocusManagement: Story = { await userEvent.tab(); await expect(datepickerInput).toHaveFocus(); + await userEvent.tab(); + await expect(datepickerButton).toHaveFocus(); + await userEvent.tab(); await expect(textarea).toHaveFocus(); diff --git a/src/components/i18n-provider/i18n-provider.test.tsx b/src/components/i18n-provider/i18n-provider.test.tsx index 912ec02dd7..5045f8ce3c 100644 --- a/src/components/i18n-provider/i18n-provider.test.tsx +++ b/src/components/i18n-provider/i18n-provider.test.tsx @@ -17,6 +17,20 @@ const TestComponent = () => { ); }; +const DateAriaLabels = () => { + const { ariaLabels } = useLocale().date; + + return ( + <> + {ariaLabels.previousMonthButton()} + {ariaLabels.openCalendarButton?.()} + {ariaLabels.chooseMonth?.()} + {ariaLabels.chooseYear?.()} + {ariaLabels.closeButton?.()} + + ); +}; + it("defaults to the 'en-gb' locale when no other locale is specified", () => { render( @@ -39,6 +53,20 @@ it("should support overriding the default locale with 'de-DE'", () => { expect(localeText).not.toHaveTextContent("en-GB"); }); +it("falls back to en-GB for date aria labels omitted by another locale", () => { + render( + + + , + ); + + expect(screen.getByText("Vorheriger Monat")).toBeVisible(); + expect(screen.getByText("Open calendar")).toBeVisible(); + expect(screen.getByText("Choose the month")).toBeVisible(); + expect(screen.getByText("Choose the year")).toBeVisible(); + expect(screen.getByText("Close")).toBeVisible(); +}); + it("should support overriding the default locale with 'en-CA'", () => { render( diff --git a/src/locales/en-gb.ts b/src/locales/en-gb.ts index b4010c86b0..a1b91f22b9 100644 --- a/src/locales/en-gb.ts +++ b/src/locales/en-gb.ts @@ -46,6 +46,12 @@ const enGB: Locale = { ariaLabels: { previousMonthButton: () => "Previous month", nextMonthButton: () => "Next month", + openCalendarButton: () => "Open calendar", + chooseMonth: () => "Choose the month", + chooseYear: () => "Choose the year", + closeButton: () => "Close", + openCalendarDescription: () => "Instructions on how to use", + chooseDate: () => "Choose a date", }, dateFormatOverride: undefined, }, diff --git a/src/locales/locale.ts b/src/locales/locale.ts index 344aef86b8..534d355ab8 100644 --- a/src/locales/locale.ts +++ b/src/locales/locale.ts @@ -34,6 +34,18 @@ interface Locale { ariaLabels: { previousMonthButton: () => string; nextMonthButton: () => string; + /** Optional in locale overrides; I18nProvider supplies the en-GB fallback. */ + openCalendarButton?: () => string; + /** Optional in locale overrides; I18nProvider supplies the en-GB fallback. */ + chooseMonth?: () => string; + /** Optional in locale overrides; I18nProvider supplies the en-GB fallback. */ + chooseYear?: () => string; + /** Optional in locale overrides; I18nProvider supplies the en-GB fallback. */ + closeButton?: () => string; + /** Optional in locale overrides; I18nProvider supplies the en-GB fallback. */ + openCalendarDescription?: () => string; + /** Optional in locale overrides; I18nProvider supplies the en-GB fallback. */ + chooseDate?: () => string; }; dateFormatOverride?: string; };