diff --git a/CHANGELOG.md b/CHANGELOG.md index b11167ddb1..b99ad6e93c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,10 @@ Currently, this repo is in Prerelease. When it is released, this project will ad - Temporarily renaming `FilterBar`, `MultiSelect`, `MultiSelectGroup`, `useMultiSelect`, and `useFilterBar` Storybook page files so they don't show up in the Storybook sidebar. - Updates the `Slider` component to use appropriate `aria-label` values for the slider thumbs and text input fields. +- Updates `TextInput` so it no longer incorrectly overwrites the `aria-describedby` value to undefined when part of the `DatePicker` component. - Updates `DatePicker` so that focus remains on input after value is changed. - Updates the `FeedbackBox` component to remove the underline on the component's `Privacy Policy` link. +- Updates `DatePicker` to pass a `additionalHelperTextIds` to its `TextInput` if needed so that the `aria-describedby` value can be associated with all relevant `helperText`s. ### Fixes diff --git a/src/components/DatePicker/DatePicker.mdx b/src/components/DatePicker/DatePicker.mdx index e94e483373..236ab94db0 100644 --- a/src/components/DatePicker/DatePicker.mdx +++ b/src/components/DatePicker/DatePicker.mdx @@ -58,6 +58,11 @@ the "date range" mode by wrapping the elements in a `
` element with its own `` label for the group. Note that this is in addition to the two labels that each `` element is associated with. +`helperText` is also associated with the `` elements via the `aria-describedby` +attribute. If a `helperTextFrom` or `helperTextTo` is passed in addition to a general +`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 diff --git a/src/components/DatePicker/DatePicker.test.tsx b/src/components/DatePicker/DatePicker.test.tsx index 726cf1f923..aeb91df863 100644 --- a/src/components/DatePicker/DatePicker.test.tsx +++ b/src/components/DatePicker/DatePicker.test.tsx @@ -216,13 +216,18 @@ describe("DatePicker", () => { /> ); - // When not errored, we expect only the helper text to appear. - expect( - screen.getByLabelText(/Select the date you want to visit NYPL/i) - ).toBeInTheDocument(); expect( screen.getByText("Note that the Library may be closed on Sundays.") ).toBeInTheDocument(); + + // The input should be associated with the helper text via aria-describedby. + const input = screen.getByRole("textbox"); + expect(input).toHaveAttribute( + "aria-describedby", + "datePicker-start-helperText" + ); + + // When not errored, we expect only the helper text to appear. expect( screen.queryByText("Please select a valid date.") ).not.toBeInTheDocument(); @@ -243,6 +248,13 @@ describe("DatePicker", () => { expect( screen.getByText("Please select a valid date.") ).toBeInTheDocument(); + + // The input should be associated with the error text via aria-describedby. + // The error text replaces the original helper text. + expect(input).toHaveAttribute( + "aria-describedby", + "datePicker-start-helperText" + ); }); it("should not render the helper text or invalid text when 'showHelperInvalidText' is false", () => { @@ -569,6 +581,7 @@ describe("DatePicker", () => { isDateRange /> ); + // There are two labels for each input. expect(screen.getByText("From")).toBeInTheDocument(); expect(screen.getByText("To")).toBeInTheDocument(); @@ -582,6 +595,24 @@ describe("DatePicker", () => { ).toBeInTheDocument(); // 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" }); + + // 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( + "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( + "aria-describedby", + "datePicker-helper-text datePicker-end-helperText" + ); }); it("should render different states based on respective props", () => { diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index 7cb85dfbee..20133618dd 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -385,6 +385,7 @@ export const DatePicker = chakra( ...baseCustomTextInputAttrs, helperText: helperTextTo, }; + // These props are used to follow the pattern recommended by // the react-datepicker plugin. startDatePickerAttrs = { @@ -407,6 +408,12 @@ export const DatePicker = chakra( } @@ -425,6 +432,12 @@ export const DatePicker = chakra( } diff --git a/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap b/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap index 0543078755..f01a320444 100644 --- a/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap +++ b/src/components/DatePicker/__snapshots__/DatePicker.test.tsx.snap @@ -445,6 +445,7 @@ exports[`DatePicker Date Range renders the UI snapshot correctly 4`] = ` className="css-79elbk" > ; */ export const WithControls: Story = { args: { + additionalHelperTextIds: undefined, className: undefined, defaultValue: undefined, helperText: "Choose wisely.", @@ -255,6 +256,7 @@ export const HTMLHelperText: Story = { export const Textarea: Story = { args: { + additionalHelperTextIds: undefined, className: undefined, defaultValue: undefined, helperText: "Let it all out.", diff --git a/src/components/TextInput/TextInput.tsx b/src/components/TextInput/TextInput.tsx index 7894597da3..aec2ea2b79 100644 --- a/src/components/TextInput/TextInput.tsx +++ b/src/components/TextInput/TextInput.tsx @@ -44,6 +44,9 @@ export const TextInputFormats = { export type TextInputVariants = "default" | "searchBar" | "searchBarSelect"; export interface InputProps { + /** 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; /** A class name for the TextInput parent div. */ className?: string; /** The starting value of the input field. */ @@ -126,6 +129,7 @@ export const TextInput = chakra( forwardRef( (props, ref: React.Ref) => { const { + additionalHelperTextIds, className, defaultValue, helperText, @@ -200,6 +204,7 @@ export const TextInput = chakra( id, labelText, name: "TextInput", + additionalHelperTextIds, showLabel, }); @@ -256,8 +261,8 @@ export const TextInput = chakra( ref: finalRef, // The `step` attribute is useful for the number type. step: type === "number" ? step : null, - ...ariaAttributes, ...rest, + ...ariaAttributes, }; // For `input` and `textarea`, all attributes are the same but `input` // also needs `type` and `value` to render correctly. diff --git a/src/utils/utils.ts b/src/utils/utils.ts index f4872a8cd1..bc81046384 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -52,6 +52,7 @@ interface GetAriaAttrsProps { id: string; labelText: HelperErrorTextType; name: string; + additionalHelperTextIds?: string; showLabel: boolean; } /** @@ -63,6 +64,7 @@ export const getAriaAttrs = ({ id, labelText, name, + additionalHelperTextIds, showLabel, }: GetAriaAttrsProps): AriaAttributes => { let ariaAttributes: AriaAttributes = {}; @@ -78,9 +80,10 @@ export const getAriaAttrs = ({ ? `${labelText} - ${footnote}` : (labelText as string); } else if (footnote) { - ariaAttributes["aria-describedby"] = `${id}-helperText`; + ariaAttributes["aria-describedby"] = `${ + additionalHelperTextIds ? additionalHelperTextIds + " " : "" + }${id}-helperText`; } - return ariaAttributes; };