Skip to content

Commit 2d975de

Browse files
committed
fix(react): LangToggle a11y isues and BEM formatting
1 parent 1f428b5 commit 2d975de

File tree

3 files changed

+144
-4
lines changed

3 files changed

+144
-4
lines changed

.changeset/angry-spoons-provide.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ilo-org/react": patch
3+
---
4+
5+
**LanguageToggle:** Fixes some accessibility issues

packages/react/src/components/LanguageToggle/LanguageToggle.tsx

+15-4
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ const LanguageToggle = forwardRef<HTMLDivElement, LanguageToggleProps>(
4242
) => {
4343
const { prefix } = useGlobalSettings();
4444

45-
const [isCtxMenuOpen, setIsCtxMenuOpen] = useState(true);
45+
// Context menu is hidden by default
46+
const [isCtxMenuOpen, setIsCtxMenuOpen] = useState(false);
4647

4748
const containerRef = useRef<HTMLButtonElement>(null);
4849
const contextMenuRef = useRef<HTMLDivElement>(null);
@@ -83,9 +84,14 @@ const LanguageToggle = forwardRef<HTMLDivElement, LanguageToggleProps>(
8384
return (
8485
<div
8586
ref={ref}
86-
className={classNames(baseClass, className, `${baseClass}--${theme}`, {
87-
[`${baseClass}--open`]: isCtxMenuOpen,
88-
})}
87+
className={classNames(
88+
baseClass,
89+
className,
90+
`${baseClass}__theme__${theme}`,
91+
{
92+
[`${baseClass}__open`]: isCtxMenuOpen,
93+
}
94+
)}
8995
{...rest}
9096
>
9197
<button
@@ -94,16 +100,21 @@ const LanguageToggle = forwardRef<HTMLDivElement, LanguageToggleProps>(
94100
onClick={() => {
95101
setIsCtxMenuOpen(!isCtxMenuOpen);
96102
}}
103+
aria-controls={`${baseClass}--context-menu`}
104+
aria-expanded={isCtxMenuOpen}
97105
>
98106
{!hideIcon && <span className={`${baseClass}--icon`} />}
99107
<span className={`${baseClass}--action`}>{language}</span>
100108
<span className={`${baseClass}--arrow`} />
101109
</button>
102110
{options && (
103111
<div
112+
role="menu"
113+
id={`${baseClass}--context-menu`}
104114
className={classNames(`${baseClass}--context-menu`, {
105115
[`${baseClass}--context-menu__open`]: isCtxMenuOpen,
106116
})}
117+
hidden={!isCtxMenuOpen}
107118
ref={contextMenuRef}
108119
>
109120
<ContextMenu links={options} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { render, screen, fireEvent } from "@testing-library/react";
2+
import {
3+
LanguageToggle,
4+
LanguageToggleProps,
5+
} from "../src/components/LanguageToggle";
6+
import { describe, it, expect } from "vitest";
7+
import { LinkProps } from "../src/components/Link";
8+
9+
const defaultProps: LanguageToggleProps = {
10+
language: "English",
11+
options: [
12+
{
13+
label: "Spanish",
14+
url: "https://www.ilo.org",
15+
},
16+
{
17+
label: "German",
18+
url: "https://www.ilo.org",
19+
},
20+
{
21+
label: "French",
22+
url: "https://www.ilo.org",
23+
},
24+
],
25+
theme: "dark",
26+
hideIcon: false,
27+
};
28+
29+
describe("Language Toggle", () => {
30+
it("renders the language label correctly", () => {
31+
render(<LanguageToggle {...defaultProps} />);
32+
const toggleButton = screen.getByRole("button", {
33+
name: defaultProps.language,
34+
});
35+
expect(toggleButton).toHaveTextContent(defaultProps.language);
36+
});
37+
38+
it("should not render the icon when hideicon is false", () => {
39+
const { container } = render(
40+
<LanguageToggle {...defaultProps} hideIcon={false} />
41+
);
42+
const icon = container.querySelector(".ilo--language-toggle--icon");
43+
expect(icon).toBeInTheDocument();
44+
});
45+
46+
it("has the correct theme applied", () => {
47+
const { container } = render(<LanguageToggle {...defaultProps} />);
48+
const outerDiv = container.firstChild;
49+
expect(outerDiv).toHaveClass(
50+
`ilo--language-toggle__theme__${defaultProps.theme}`
51+
);
52+
});
53+
54+
it("menu is hidden by default", () => {
55+
const { container } = render(<LanguageToggle {...defaultProps} />);
56+
const contextMenu = container.querySelector(
57+
".ilo--language-toggle--context-menu"
58+
);
59+
expect(contextMenu).not.toBeVisible();
60+
});
61+
62+
it("menu opens when button is clicked", () => {
63+
const { container } = render(<LanguageToggle {...defaultProps} />);
64+
const toggleButton = container.querySelector("button");
65+
const contextMenu = container.querySelector(
66+
".ilo--language-toggle--context-menu"
67+
);
68+
fireEvent.click(toggleButton as HTMLButtonElement);
69+
expect(contextMenu).toBeVisible();
70+
});
71+
72+
it("menu closes when button is clicked again", () => {
73+
const { container } = render(<LanguageToggle {...defaultProps} />);
74+
const toggleButton = container.querySelector("button");
75+
const contextMenu = container.querySelector(
76+
".ilo--language-toggle--context-menu"
77+
);
78+
fireEvent.click(toggleButton as HTMLButtonElement);
79+
fireEvent.click(toggleButton as HTMLButtonElement);
80+
expect(contextMenu).not.toBeVisible();
81+
});
82+
83+
it("renders the correct number of language options", () => {
84+
const { container } = render(<LanguageToggle {...defaultProps} />);
85+
const toggleButton = container.querySelector("button");
86+
fireEvent.click(toggleButton as HTMLButtonElement);
87+
if (defaultProps.options) {
88+
const links = screen.getAllByRole("link");
89+
expect(links).toHaveLength(defaultProps.options.length);
90+
}
91+
});
92+
93+
it("ensures each language option has a valid href", () => {
94+
if (defaultProps.options) {
95+
const { container } = render(<LanguageToggle {...defaultProps} />);
96+
const toggleButton = container.querySelector("button");
97+
fireEvent.click(toggleButton as HTMLButtonElement);
98+
const links = screen.getAllByRole("link");
99+
links.forEach((link, index) => {
100+
const options = defaultProps.options as LinkProps[];
101+
const href = options[index].href as string;
102+
expect(link).toHaveAttribute("href", href);
103+
});
104+
}
105+
});
106+
107+
it("has correct accessibility attributes (aria-expanded & hidden)", () => {
108+
const { container } = render(<LanguageToggle {...defaultProps} />);
109+
const toggleButton = container.querySelector("button");
110+
const contextMenu = container.querySelector(
111+
".ilo--language-toggle--context-menu"
112+
);
113+
expect(toggleButton).toHaveAttribute("aria-expanded", "false");
114+
expect(contextMenu).toHaveAttribute("hidden");
115+
116+
fireEvent.click(toggleButton as HTMLButtonElement);
117+
expect(toggleButton).toHaveAttribute("aria-expanded", "true");
118+
expect(contextMenu).not.toHaveAttribute("hidden");
119+
120+
fireEvent.click(toggleButton as HTMLButtonElement);
121+
expect(toggleButton).toHaveAttribute("aria-expanded", "false");
122+
expect(contextMenu).toHaveAttribute("hidden");
123+
});
124+
});

0 commit comments

Comments
 (0)