diff --git a/src/components/DatePicker/DatePicker.mdx b/src/components/DatePicker/DatePicker.mdx index def6e0fbda..4a4b514129 100644 --- a/src/components/DatePicker/DatePicker.mdx +++ b/src/components/DatePicker/DatePicker.mdx @@ -10,10 +10,10 @@ import Table from "../Table/Table"; # DatePicker -| Component Version | DS Version | -| ----------------- | ---------- | -| Added | `0.24.0` | -| Latest | `2.1.0` | +| Component Version | DS Version | +| ----------------- | ----------- | +| Added | `0.24.0` | +| Latest | `Prerelease | ## Table of Contents @@ -63,10 +63,11 @@ attribute. If a `helperTextFrom` or `helperTextTo` is passed in addition to a ge `helperText` for the entire `DatePicker`, then the `aria-describedby` attribute of the `` element will be "[general-helperText-id] [input-helperText-id]". -When `showLabel` is set to false, the single `` element's `aria-label` -attribute is set to the required `labelText` value. In the "date range" mode, -when `showLabel` is set to false, the `
`'s `` will have the -`labelText` visually hidden. +The `DatePicker`'s `` has an `aria-label` that tells screen reader users how +to access the calendar. Additionally, when `showLabel` is set to false, the single +`` element's `aria-label` attribute contains the required `labelText` value. +In the "date range" mode, when `showLabel` is set to false, the `
`'s +`` will have the `labelText` visually hidden. Resources: diff --git a/src/components/DatePicker/DatePicker.test.tsx b/src/components/DatePicker/DatePicker.test.tsx index aeb91df863..458589b584 100644 --- a/src/components/DatePicker/DatePicker.test.tsx +++ b/src/components/DatePicker/DatePicker.test.tsx @@ -105,7 +105,10 @@ describe("DatePicker", () => { ); expect(input).toBeInTheDocument(); - expect(input).not.toHaveAttribute("aria-label"); + expect(input).toHaveAttribute( + "aria-label", + "Press tab to access the calendar." + ); // Date format based on component specification yyyy-mm-dd. expect(date).toEqual(`${year}-${month}-${day}`); expect(screen.getByDisplayValue(date)).toBeInTheDocument(); @@ -596,20 +599,21 @@ describe("DatePicker", () => { // Helper text for the "To" input expect(screen.getByText(/Note for the 'to' field./i)).toBeInTheDocument(); - const fromInput = screen.getByRole("textbox", { name: "From" }); - const toInput = screen.getByRole("textbox", { name: "To" }); + const inputs = screen.getAllByRole("textbox", { + name: "Press tab to access the calendar.", + }); // The `fromInput` should have an `aria-describedby` value of both the id of // the `helperText` and the id of the `helperTextFrom` in that order - from // more general to more specific. - expect(fromInput).toHaveAttribute( + expect(inputs[0]).toHaveAttribute( "aria-describedby", "datePicker-helper-text datePicker-start-helperText" ); // The `toInput` should have an `aria-describedby` value of both the id of // the `helperText` and the id of the `helperTextTo` in that order - from // more general to more specific. - expect(toInput).toHaveAttribute( + expect(inputs[1]).toHaveAttribute( "aria-describedby", "datePicker-helper-text datePicker-end-helperText" ); @@ -645,8 +649,11 @@ describe("DatePicker", () => { /> ); // Both input fields are disabled. - expect(screen.getByLabelText(/From/i)).toHaveAttribute("disabled"); - expect(screen.getByLabelText(/To/i)).toHaveAttribute("disabled"); + let inputs = screen.getAllByLabelText( + "Press tab to access the calendar." + ); + expect(inputs[0]).toHaveAttribute("disabled"); + expect(inputs[1]).toHaveAttribute("disabled"); rerender( { isDateRange /> ); + + inputs = screen.getAllByLabelText("Press tab to access the calendar."); // Both input fields are required. // The "Required" text is only displayed once in the `legend`. expect(screen.getAllByText(/Required/i)).toHaveLength(1); - expect(screen.getByLabelText(/From/i)).toHaveAttribute("required"); - expect(screen.getByLabelText(/To/i)).toHaveAttribute("required"); + expect(inputs[0]).toHaveAttribute("required"); + expect(inputs[1]).toHaveAttribute("required"); }); // Note: Have to add initial dates so that the snapshot tests always @@ -750,15 +759,16 @@ describe("DatePicker", () => { labelText="Select the date range you want to visit NYPL" /> ); - const fromInput = screen.getByLabelText(/From/i); - const toInput = screen.getByLabelText(/To/i); + const inputs = screen.getAllByLabelText( + "Press tab to access the calendar." + ); - expect(fromInput).toHaveValue("1988-03-02"); - expect(toInput).toHaveValue("1988-03-28"); + expect(inputs[0]).toHaveValue("1988-03-02"); + expect(inputs[1]).toHaveValue("1988-03-28"); // expect(screen.getAllByDisplayValue(date)).toHaveLength(2); // Let's select a new day. - userEvent.click(fromInput); + userEvent.click(inputs[0]); // The popup displays. Select a new day. const newDateFrom = 5; const newDateTo = 25; @@ -771,7 +781,7 @@ describe("DatePicker", () => { // expect(screen.getAllByDisplayValue(date)).toHaveLength(1); // Now select the "To" date. - userEvent.click(toInput); + userEvent.click(inputs[1]); // The popup displays. userEvent.click(screen.getByText(newDateTo)); diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index 20133618dd..b985e07b83 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -159,6 +159,7 @@ const CustomTextInput = forwardRef( return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
` element. When `showLabel` is set to false, the `` element's `aria-label` attribute is set to the -required `labelText` value. +required `labelText` value. Some components that use `TextInput`, like `DatePicker`, +give the `` an `aria-label` regardless of whether `showLabel` is true or false. +This happens when interacting with the element is not as obvious to those using screen +readers and more information is necessary. The `helperText` and the `invalidText` are associated with the `` element through the `aria-describedby` attribute. diff --git a/src/components/TextInput/TextInput.tsx b/src/components/TextInput/TextInput.tsx index aec2ea2b79..f007edf09d 100644 --- a/src/components/TextInput/TextInput.tsx +++ b/src/components/TextInput/TextInput.tsx @@ -44,6 +44,8 @@ export const TextInputFormats = { export type TextInputVariants = "default" | "searchBar" | "searchBarSelect"; export interface InputProps { + /** FOR INTERNAL DS USE ONLY: Adds an aria-label or appends to an existing aria-label for screen readers.*/ + additionalAriaLabel?: string; /** FOR INTERNAL DS USE ONLY: additional helper text id(s) to be used for the input's `aria-describedby` value. * If more than one, separate each with a space */ additionalHelperTextIds?: string; @@ -129,6 +131,7 @@ export const TextInput = chakra( forwardRef( (props, ref: React.Ref) => { const { + additionalAriaLabel, additionalHelperTextIds, className, defaultValue, @@ -200,6 +203,7 @@ export const TextInput = chakra( } const ariaAttributes = getAriaAttrs({ + additionalAriaLabel, footnote, id, labelText, diff --git a/src/utils/utils.ts b/src/utils/utils.ts index bc81046384..b9d2f9b8b5 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -48,6 +48,7 @@ export const getStorybookHrefProps = (pageCount: number) => { }; interface GetAriaAttrsProps { + additionalAriaLabel?: string; footnote: HelperErrorTextType; id: string; labelText: HelperErrorTextType; @@ -60,6 +61,7 @@ interface GetAriaAttrsProps { * `aria-describedby` attributes, based on the label and footnote values. */ export const getAriaAttrs = ({ + additionalAriaLabel, footnote, id, labelText, @@ -84,6 +86,12 @@ export const getAriaAttrs = ({ additionalHelperTextIds ? additionalHelperTextIds + " " : "" }${id}-helperText`; } + + if (additionalAriaLabel) { + ariaAttributes["aria-label"] + ? (ariaAttributes["aria-label"] += ` ${additionalAriaLabel}`) + : (ariaAttributes["aria-label"] = additionalAriaLabel); + } return ariaAttributes; };