diff --git a/src/components/action-popover/action-popover-menu-button/action-popover-menu-button.component.tsx b/src/components/action-popover/action-popover-menu-button/action-popover-menu-button.component.tsx index e9d1551b05..32de52052a 100644 --- a/src/components/action-popover/action-popover-menu-button/action-popover-menu-button.component.tsx +++ b/src/components/action-popover/action-popover-menu-button/action-popover-menu-button.component.tsx @@ -12,6 +12,8 @@ import { IconType } from "../../icon"; export type ActionPopoverMenuButtonAria = { "aria-haspopup": string; "aria-label": string; + "aria-labelledby"?: string; + "aria-describedby"?: string; "aria-controls": string; "aria-expanded": string; }; @@ -41,6 +43,7 @@ export const ActionPopoverMenuButton = ({ iconPosition, size, children, + ariaAttributes, ...props }: ActionPopoverMenuButtonProps) => ( @@ -49,6 +52,7 @@ export const ActionPopoverMenuButton = ({ iconType={iconType} iconPosition={iconPosition} size={size} + {...ariaAttributes} {...props} > {children} diff --git a/src/components/action-popover/action-popover-test.stories.tsx b/src/components/action-popover/action-popover-test.stories.tsx index 40c6653e19..9cb9f3137e 100644 --- a/src/components/action-popover/action-popover-test.stories.tsx +++ b/src/components/action-popover/action-popover-test.stories.tsx @@ -8,6 +8,7 @@ import { ActionPopoverItem, ActionPopoverMenu, ActionPopoverProps, + ActionPopoverMenuButton, } from "."; import { FlatTable, @@ -21,7 +22,7 @@ import Box from "../box"; export default { title: "Action Popover/Test", - includeStories: ["Default", "LongMenuExample"], + includeStories: ["Default", "LongMenuExample", "WithAriaAttributes"], parameters: { info: { disable: true }, chromatic: { @@ -800,3 +801,52 @@ export const LongMenuExample = () => { ); }; + +export const WithAriaAttributes = () => { + return ( + <> + + Instead of "actions", this should be the label + +
+ This should be the description + + + example item + + + Instead of "Foo", this should be the label +
+ + This should be the description for Foo + + + ( + + Foo + + )} + > + {}}>foo + + + ); +}; diff --git a/src/components/action-popover/action-popover.component.tsx b/src/components/action-popover/action-popover.component.tsx index d427fe2461..e97b5afdf7 100644 --- a/src/components/action-popover/action-popover.component.tsx +++ b/src/components/action-popover/action-popover.component.tsx @@ -36,6 +36,8 @@ export interface RenderButtonProps { ariaAttributes: { "aria-haspopup": string; "aria-label": string; + "aria-labelledby"?: string; + "aria-describedby"?: string; "aria-controls": string; "aria-expanded": string; }; @@ -62,6 +64,10 @@ export interface ActionPopoverProps extends MarginProps { rightAlignMenu?: boolean; /** Prop to specify an aria-label for the component */ "aria-label"?: string; + /** Prop to specify an aria-labelledby for the component */ + "aria-labelledby"?: string; + /** Prop to specify an aria-describedby for the component */ + "aria-describedby"?: string; } const onOpenDefault = () => {}; @@ -78,6 +84,8 @@ export const ActionPopover = ({ horizontalAlignment = "left", submenuPosition = "left", "aria-label": ariaLabel, + "aria-labelledby": ariaLabelledBy, + "aria-describedby": ariaDescribedBy, ...rest }: ActionPopoverProps) => { const l = useLocale(); @@ -233,6 +241,8 @@ export const ActionPopover = ({ ariaAttributes: { "aria-haspopup": "true", "aria-label": ariaLabel || l.actionPopover.ariaLabel(), + "aria-labelledby": ariaLabelledBy, + "aria-describedby": ariaDescribedBy, "aria-controls": menuID, "aria-expanded": `${isOpen}`, }, @@ -244,6 +254,8 @@ export const ActionPopover = ({ role="button" aria-haspopup="true" aria-label={ariaLabel || l.actionPopover.ariaLabel()} + aria-labelledby={ariaLabelledBy} + aria-describedby={ariaDescribedBy} aria-controls={menuID} aria-expanded={isOpen} tabIndex={isOpen ? -1 : 0} diff --git a/src/components/action-popover/action-popover.mdx b/src/components/action-popover/action-popover.mdx index 58f9f2fa23..b86d812bc7 100644 --- a/src/components/action-popover/action-popover.mdx +++ b/src/components/action-popover/action-popover.mdx @@ -90,7 +90,16 @@ Menu items can also be displayed without icons. ### With custom menu button It is possible to use the `renderButton` prop to override the default button used to trigger the opening and closing of -ActionPopoverMenu. Expand the code example below for how to use this. +ActionPopoverMenu. + +The `renderButton` prop is a function that allows consumers to pass `tabIndex`, `data-element` and the provided aria attribute props to +`ActionPopoverMenuButton` or a custom button component. + +A default `aria-label` will be provided to the button, but this can be overridden by passing the `ariaLabel` prop to the +`ActionPopover` or making use of the `actionPopover.ariaLabel` translation key. Please ensure to set this to `undefined` if your +button contains visible text, as this will prevent screen readers from reading the visible label. + +See the example below for an example of how to use the `renderButton` prop: diff --git a/src/components/action-popover/action-popover.stories.tsx b/src/components/action-popover/action-popover.stories.tsx index 34ed29197f..0c6ad97987 100644 --- a/src/components/action-popover/action-popover.stories.tsx +++ b/src/components/action-popover/action-popover.stories.tsx @@ -219,6 +219,7 @@ export const CustomMenuButton: Story = () => { tabIndex={tabIndex} data-element={dataElement} ariaAttributes={ariaAttributes} + aria-label={undefined} > More @@ -232,6 +233,31 @@ export const CustomMenuButton: Story = () => { Delete + ( + + )} + > + {}}> + Email Invoice + + + {}} icon="delete"> + Delete + + ( {}} data-element={dataElement}> diff --git a/src/components/action-popover/action-popover.test.tsx b/src/components/action-popover/action-popover.test.tsx index 131b86f04f..49dc114f81 100644 --- a/src/components/action-popover/action-popover.test.tsx +++ b/src/components/action-popover/action-popover.test.tsx @@ -140,6 +140,32 @@ test("uses the aria-label prop if provided", () => { expect(screen.getByRole("button")).toHaveAccessibleName("test aria label"); }); +test("renders with the provided aria-labelledby prop", () => { + render( + <> + test label + + example item + + , + ); + expect(screen.getByRole("button")).toHaveAccessibleName("test label"); +}); + +test("renders with the provided aria-describedby prop", () => { + render( + <> + test description + + example item + + , + ); + expect(screen.getByRole("button")).toHaveAccessibleDescription( + "test description", + ); +}); + test("renders with the menu closed by default", () => { render( @@ -1825,7 +1851,7 @@ test("an error is thrown, with appropriate error message, if an invalid element globalConsoleSpy.mockRestore(); }); -test("an error is thrown, with appropriate error message, if a submenu has incorrecr children", async () => { +test("an error is thrown, with appropriate error message, if a submenu has incorrect children", async () => { const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); const globalConsoleSpy = jest @@ -1911,6 +1937,62 @@ describe("when the renderButton prop is passed", () => { expect(menuButton).toHaveAttribute("tabindex", "-1"); }); + + it("renders the menu button with the provided aria-label", () => { + render( + ( + + Foo + + )} + > + foo + , + ); + + const menuButton = screen.getByRole("button"); + expect(menuButton).toBeVisible(); + expect(menuButton).toHaveAccessibleName("test label"); + }); + + it("renders the menu button with the provided aria-labelledby and aria-describedby", () => { + render( + <> + test label + test description + ( + + Foo + + )} + > + {}}>foo + + , + ); + + const menuButton = screen.getByRole("button"); + expect(menuButton).toBeVisible(); + expect(menuButton).toHaveAccessibleName("test label"); + expect(menuButton).toHaveAccessibleDescription("test description"); + }); }); describe("When ActionPopoverMenu contains multiple disabled items", () => {