From 9878f7eb912d731a5b5230e7d537618d1c41051b Mon Sep 17 00:00:00 2001 From: Dylan Kilgore Date: Thu, 4 May 2023 13:03:26 -0700 Subject: [PATCH 1/3] fix: select: ensure aria attributes target the intended element --- src/components/Dropdown/Dropdown.tsx | 22 +++++ src/components/Dropdown/Dropdown.types.ts | 6 ++ src/components/Select/Select.tsx | 2 +- .../Select/__snapshots__/Select.test.tsx.snap | 90 ++++++++----------- 4 files changed, 65 insertions(+), 55 deletions(-) diff --git a/src/components/Dropdown/Dropdown.tsx b/src/components/Dropdown/Dropdown.tsx index cd16629b4..18fbb2936 100644 --- a/src/components/Dropdown/Dropdown.tsx +++ b/src/components/Dropdown/Dropdown.tsx @@ -40,6 +40,7 @@ export const Dropdown: FC = React.memo( React.forwardRef( ( { + ariaRef, children, classNames, closeOnDropdownClick = true, @@ -243,6 +244,27 @@ export const Dropdown: FC = React.memo( if (child.props.id && dropdownReferenceId !== child.props.id) { setReferenceElementId(child.props.id); } + // If there's an ariaRef, apply the a11y attributes to it, rather than the immediate child. + if (ariaRef && ariaRef.current) { + ariaRef.current.setAttribute('aria-controls', dropdownId); + ariaRef.current.setAttribute('aria-expanded', `${mergedVisible}`); + ariaRef.current.setAttribute('aria-haspopup', 'true'); + + if (!ariaRef.current.role) { + ariaRef.current.setAttribute('role', 'button'); + } + + return cloneElement(child, { + ...{ + [TRIGGER_TO_HANDLER_MAP_ON_ENTER[trigger]]: toggle(true), + }, + id: dropdownReferenceId, + onClick: handleReferenceClick, + onKeyDown: handleReferenceKeyDown, + className: referenceWrapperClassNames, + }); + } + return cloneElement(child, { ...{ [TRIGGER_TO_HANDLER_MAP_ON_ENTER[trigger]]: toggle(true), diff --git a/src/components/Dropdown/Dropdown.types.ts b/src/components/Dropdown/Dropdown.types.ts index 276f2e28f..74cf16c91 100644 --- a/src/components/Dropdown/Dropdown.types.ts +++ b/src/components/Dropdown/Dropdown.types.ts @@ -16,6 +16,12 @@ export const TRIGGER_TO_HANDLER_MAP_ON_LEAVE = { }; export interface DropdownProps { + /** + * The ref of element that should implement the following props: + * 'aria-controls', 'aria-expanded', 'aria-haspopup', 'role' + * @default child + */ + ariaRef?: React.MutableRefObject; /** * Class names of the main wrapper */ diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index 50a5f312b..e0b4dde59 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -722,6 +722,7 @@ export const Select: FC = React.forwardRef( {/* When Dropdown is hidden, place Pills outside the reference element */} {!dropdownVisible && showPills() ? getPills() : null} = React.forwardRef( onVisibleChange={(isVisible) => setDropdownVisibility(isVisible)} overlay={isLoading ? spinner : } showDropdown={showDropdown} - tabIndex={-1} // Defer focus to the TextInput visible={ dropdownVisible && (showEmptyDropdown || diff --git a/src/components/Select/__snapshots__/Select.test.tsx.snap b/src/components/Select/__snapshots__/Select.test.tsx.snap index 346ddc830..29a681bcc 100644 --- a/src/components/Select/__snapshots__/Select.test.tsx.snap +++ b/src/components/Select/__snapshots__/Select.test.tsx.snap @@ -9,13 +9,8 @@ exports[`Select Renders with default value 1`] = ` class="select-dropdown-main-wrapper main-wrapper" >