Skip to content

feat(responsive-vertical-menu): allows custom aria-label for menu launcher #7367

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/vertical-menu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type { VerticalMenuTriggerProps } from "./vertical-menu-trigger/vertical-

export {
ResponsiveVerticalMenu,
ResponsiveVerticalMenuDivider,
ResponsiveVerticalMenuItem,
ResponsiveVerticalMenuProvider,
} from "./responsive-vertical-menu";
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,57 @@ export const Default = (props: Partial<ResponsiveVerticalMenuProps>) => {
};
Default.storyName = "Default";

export const WithoutGlobalHeader = (
props: Partial<ResponsiveVerticalMenuProps>,
) => {
return (
<ResponsiveVerticalMenuProvider>
<ResponsiveVerticalMenu {...props}>
<ResponsiveVerticalMenuItem icon="home" id="home" label="Home" />
<ResponsiveVerticalMenuDivider />
<ResponsiveVerticalMenuItem
customIcon={<CustomAccountingIcon />}
id="accounting"
label="Accounting"
>
<ResponsiveVerticalMenuItem id="summary" label="Summary" />
<ResponsiveVerticalMenuItem
id="sales-invoices"
label="Sales Invoices"
>
<ResponsiveVerticalMenuItem
id="quotes-and-estimates"
label="Quotes & Estimates"
href="#"
/>
<ResponsiveVerticalMenuItem
id="new-sales-invoice"
label="New Sales Invoice"
href="#"
/>
<ResponsiveVerticalMenuItem
id="sales-summary"
label="Sales Summary"
href="#"
/>
</ResponsiveVerticalMenuItem>
<ResponsiveVerticalMenuItem id="contacts" label="Contacts">
<ResponsiveVerticalMenuItem
id="nested-menu-item-1"
label="Nested Menu Item 1"
/>
</ResponsiveVerticalMenuItem>
<ResponsiveVerticalMenuItem
id="products-and-services"
label="Products & Services"
/>
</ResponsiveVerticalMenuItem>
</ResponsiveVerticalMenu>
</ResponsiveVerticalMenuProvider>
);
};
WithoutGlobalHeader.storyName = "Without Global Header";

export const WithFullIcons = (props: Partial<ResponsiveVerticalMenuProps>) => {
return (
<GlobalHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import {
} from "./responsive-vertical-menu.style";

import Box from "../../box";

import Modal from "../../modal";

import useIsAboveBreakpoint from "../../../hooks/__internal__/useIsAboveBreakpoint";
import useMediaQuery from "../../../hooks/useMediaQuery";

Expand All @@ -26,6 +26,7 @@ import tagComponent, {
} from "../../../__internal__/utils/helpers/tags";
import { DepthProvider } from "./__internal__/depth.context";
import { MenuFocusProvider } from "./__internal__/focus.context";
import useLocale from "../../../hooks/__internal__/useLocale";

export interface ResponsiveVerticalMenuProps extends TagProps {
/** The content of the menu */
Expand All @@ -45,6 +46,7 @@ const BaseMenu = ({
width,
...rest
}: ResponsiveVerticalMenuProps) => {
const locale = useLocale();
const {
activeMenuItem,
buttonRef,
Expand Down Expand Up @@ -164,6 +166,9 @@ const BaseMenu = ({
<div ref={containerRef}>
<StyledButton
active={active}
aria-controls="responsive-vertical-menu-primary"
aria-expanded={active}
aria-label={locale.verticalMenu.ariaLabels?.responsiveMenuLauncher()}
buttonType="tertiary"
data-component="responsive-vertical-menu-launcher"
data-role="responsive-vertical-menu-launcher"
Expand All @@ -183,7 +188,7 @@ const BaseMenu = ({
p={1}
>
<StyledCloseButton
aria-label="close-menu"
aria-label={locale.verticalMenu.ariaLabels?.responsiveMenuCloseButton()}
data-component="responsive-vertical-menu-close"
data-role="responsive-vertical-menu-close"
iconType="close"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Meta, ArgTypes, Canvas } from "@storybook/blocks";
import TranslationKeysTable from "../../../../.storybook/utils/translation-keys-table";

import * as ResponsiveVerticalMenuStories from "./responsive-vertical-menu.stories";
import * as ResponsiveVerticalMenuDividerStories from "./responsive-vertical-menu-divider/responsive-vertical-menu-divider.stories";
Expand Down Expand Up @@ -213,3 +214,20 @@ You can pass any valid `ReactNode` to the `label` prop of the `ResponsiveVertica
### ResponsiveVerticalMenuItem

<ArgTypes of={ResponsiveVerticalMenuItemStories} />

## Translation keys

The following keys are available to override the translations for this component by passing in a custom locale object
to the [i18nProvider](../?path=/docs/documentation-i18n--docs).

<TranslationKeysTable
translationData={[
{
name: "verticalMenu.ariaLabels.responsiveMenuLauncher",
description:
"An accessible label for the launcher of the `ResponsiveVerticalMenu`.",
type: "func",
returnType: "string",
},
]}
/>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import useIsAboveBreakpoint from "../../../hooks/__internal__/useIsAboveBreakpoint";
import useMediaQuery from "../../../hooks/useMediaQuery";
import guid from "../../../__internal__/utils/helpers/guid";
import I18nProvider from "../../../components/i18n-provider";

jest.mock("../../../hooks/__internal__/useIsAboveBreakpoint");
jest.mock("../../../hooks/useMediaQuery");
Expand Down Expand Up @@ -1304,3 +1305,60 @@ test("nested menu items are correctly justified when icons are present", async (
const nestedMenu = screen.getByTestId("menu-item-2-nested-menu-wrapper");
expect(nestedMenu).toHaveStyleRule("justify-content", "flex-end");
});

test("correct aria-label values are set", async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
mockUseIsAboveBreakpoint.mockReturnValue(false);

render(
<I18nProvider
locale={{
locale: () => "en-GB",
verticalMenu: {
ariaLabels: {
responsiveMenuLauncher: () => "Test menu launcher",
responsiveMenuCloseButton: () => "Test product menu close button",
},
},
}}
>
<ResponsiveVerticalMenuProvider>
<ResponsiveVerticalMenu>
<ResponsiveVerticalMenuItem
data-role="menu-item-1"
icon="home"
id="menu-item-1"
label="Menu Item 1"
href="https://example.com"
>
<ResponsiveVerticalMenuItem
data-role="menu-item-2"
icon="business"
id="menu-item-2"
label="Menu Item 2"
>
<ResponsiveVerticalMenuItem
data-role="menu-item-3"
icon="analysis"
id="menu-item-3"
label="Menu Item 3"
/>
</ResponsiveVerticalMenuItem>
</ResponsiveVerticalMenuItem>
</ResponsiveVerticalMenu>
</ResponsiveVerticalMenuProvider>
</I18nProvider>,
);

const launcherButton = screen.getByTestId(
"responsive-vertical-menu-launcher",
);
expect(launcherButton).toHaveAttribute("aria-label", "Test menu launcher");
await user.click(launcherButton);

const closeButton = screen.getByTestId("responsive-vertical-menu-close");
expect(closeButton).toHaveAttribute(
"aria-label",
"Test product menu close button",
);
});
6 changes: 6 additions & 0 deletions src/locales/en-gb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,12 @@ const enGB: Locale = {
neutral: () => "Information",
notification: () => "Notification",
},
verticalMenu: {
ariaLabels: {
responsiveMenuLauncher: () => "Product menu",
responsiveMenuCloseButton: () => "Close product menu",
},
},
verticalMenuFullScreen: {
ariaLabels: {
close: () => "Close",
Expand Down
6 changes: 6 additions & 0 deletions src/locales/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ interface Locale {
neutral: () => string;
notification: () => string;
};
verticalMenu: {
ariaLabels: {
responsiveMenuLauncher: () => string;
responsiveMenuCloseButton: () => string;
};
};
verticalMenuFullScreen: {
ariaLabels: {
close: () => string;
Expand Down
Loading