Skip to content

Commit

Permalink
feat: Provide accessible name to menu in button-dropdown (#1403)
Browse files Browse the repository at this point in the history
  • Loading branch information
Al-Dani authored Aug 24, 2023
1 parent e032e30 commit 79daf73
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ const renderButtonDropdown = (props: ButtonDropdownProps) => {
return createWrapper(renderResult.container).findButtonDropdown()!;
};

const renderWithTrigger = (props: ButtonDropdownProps, triggerName: string) => {
const renderResult = render(<ButtonDropdown {...props}>{triggerName}</ButtonDropdown>);
return createWrapper(renderResult.container).findButtonDropdown()!;
};

const items: ButtonDropdownProps.Items = [
{ id: 'i1', text: 'item1', description: 'Item 1 description' },
{
Expand Down Expand Up @@ -67,6 +72,68 @@ const items: ButtonDropdownProps.Items = [
expect(groups[0].findAll('[role="menuitem"]').length).toBe(2);
expect(groups[1].findAll('[role="menuitem"]').length).toBe(1);
});

it('auto-labels dropdown with trigger title', () => {
const wrapper = renderWithTrigger({ ...props }, 'Actions');
wrapper.openDropdown();

const menuElement = wrapper.findOpenDropdown()!.find('[role="menu"]')!;
expect(menuElement.getElement()).toHaveAccessibleName('Actions');
});

it('uses aria-label as accessible name for dropdown if presented', () => {
const wrapper = renderWithTrigger({ ...props, ariaLabel: 'Custom label' }, 'Actions');
wrapper.openDropdown();

const menuElement = wrapper.findOpenDropdown()!.find('[role="menu"]')!;
expect(menuElement.getElement()).toHaveAccessibleName('Custom label');
});

it('does not auto-label dropdown with icon trigger', () => {
const wrapper = renderButtonDropdown({ ...props, variant: 'icon' });
wrapper.openDropdown();
const menuElement = wrapper.findOpenDropdown()!.find('[role="menu"]')!;
expect(menuElement.getElement()).not.toHaveAttribute('aria-labelledby');
});
it('passes aria-label to icon trigger', () => {
const wrapper = renderButtonDropdown({ ...props, variant: 'icon', ariaLabel: 'Button trigger' });
wrapper.openDropdown();
const menuElement = wrapper.findOpenDropdown()!.find('[role="menu"]')!;
expect(menuElement.getElement()).toHaveAccessibleName('Button trigger');
});

it('does not auto-label dropdown with inline icon trigger', () => {
const wrapper = renderButtonDropdown({ ...props, variant: 'inline-icon' });
wrapper.openDropdown();
const menuElement = wrapper.findOpenDropdown()!.find('[role="menu"]')!;
expect(menuElement.getElement()).not.toHaveAttribute('aria-labelledby');
});
it('passes aria-label to inline icon trigger', () => {
const wrapper = renderButtonDropdown({ ...props, variant: 'inline-icon', ariaLabel: 'Button trigger' });
wrapper.openDropdown();
const menuElement = wrapper.findOpenDropdown()!.find('[role="menu"]')!;
expect(menuElement.getElement()).toHaveAccessibleName('Button trigger');
});

it('uses aria-label to label dropdown menu in a button with main action', () => {
const wrapper = renderButtonDropdown({
...props,
mainAction: { text: 'Main action', ariaLabel: 'Main action aria label' },
ariaLabel: 'Actions',
});
wrapper.openDropdown();
const menuElement = wrapper.findOpenDropdown()!.find('[role="menu"]')!;
expect(menuElement.getElement()).toHaveAccessibleName('Actions');
});
it('does not auto-label dropdown in a button with main action', () => {
const wrapper = renderButtonDropdown({
...props,
mainAction: { text: 'Main action', ariaLabel: 'Main action aria label' },
});
wrapper.openDropdown();
const menuElement = wrapper.findOpenDropdown()!.find('[role="menu"]')!;
expect(menuElement.getElement()).not.toHaveAccessibleName();
});
});
});

Expand Down
9 changes: 7 additions & 2 deletions src/button-dropdown/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ const InternalButtonDropdown = React.forwardRef(
},
};

const triggerId = useUniqueId('awsui-button-dropdown__trigger');

const triggerHasBadge = () => {
const flatItems = items.flatMap(item => {
if ('items' in item) {
Expand Down Expand Up @@ -215,7 +217,7 @@ const InternalButtonDropdown = React.forwardRef(
} else {
trigger = (
<div className={styles['dropdown-trigger']}>
<InternalButton ref={triggerRef} {...baseTriggerProps} badge={triggerHasBadge()}>
<InternalButton ref={triggerRef} id={triggerId} {...baseTriggerProps} badge={triggerHasBadge()}>
{children}
</InternalButton>
</div>
Expand All @@ -225,6 +227,8 @@ const InternalButtonDropdown = React.forwardRef(
const hasHeader = title || description;
const headerId = useUniqueId('awsui-button-dropdown__header');

const shouldLabelWithTrigger = !ariaLabel && !mainAction && variant !== 'icon' && variant !== 'inline-icon';

const { loadingButtonCount } = useFunnel();
useEffect(() => {
if (loading) {
Expand Down Expand Up @@ -284,7 +288,8 @@ const InternalButtonDropdown = React.forwardRef(
position="static"
role="menu"
decreaseTopMargin={true}
ariaLabelledby={hasHeader ? headerId : undefined}
ariaLabel={ariaLabel}
ariaLabelledby={hasHeader ? headerId : shouldLabelWithTrigger ? triggerId : undefined}
statusType="finished"
>
<ItemsList
Expand Down
3 changes: 3 additions & 0 deletions src/internal/components/options-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface OptionsListProps extends BaseComponentProps {
onMouseMove?: (itemIndex: number) => void;
position?: React.CSSProperties['position'];
role?: 'listbox' | 'list' | 'menu';
ariaLabel?: string;
ariaLabelledby?: string;
ariaDescribedby?: string;
decreaseTopMargin?: boolean;
Expand Down Expand Up @@ -66,6 +67,7 @@ const OptionsList = (
position = 'relative',
role = 'listbox',
decreaseTopMargin = false,
ariaLabel,
ariaLabelledby,
ariaDescribedby,
...restProps
Expand Down Expand Up @@ -113,6 +115,7 @@ const OptionsList = (
onBlur={event => fireNonCancelableEvent(onBlur, { relatedTarget: event.relatedTarget })}
onFocus={() => fireNonCancelableEvent(onFocus)}
tabIndex={-1}
aria-label={ariaLabel}
aria-labelledby={ariaLabelledby}
aria-describedby={ariaDescribedby}
>
Expand Down

0 comments on commit 79daf73

Please sign in to comment.