Skip to content

Commit 1ee9e22

Browse files
committed
feat: aria for accordion
1 parent fd6d380 commit 1ee9e22

4 files changed

Lines changed: 157 additions & 2 deletions

File tree

src/Display/Accordion/Accordion.test.tsx

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,99 @@ describe('<Accordion /> click behavior', () => {
8787
expect(panelContent2).toBeVisible();
8888
});
8989
});
90+
91+
describe('<Accordion /> accessibility', () => {
92+
test('container should have proper ARIA attributes', () => {
93+
const { container } = render(
94+
<Accordion>
95+
<Accordion.Panel label="label" content="content" />
96+
</Accordion>
97+
);
98+
99+
const accordionContainer = container.querySelector('.aries-accordion');
100+
expect(accordionContainer).toHaveAttribute('role', 'region');
101+
expect(accordionContainer).toHaveAttribute(
102+
'aria-label',
103+
'Accordion Container'
104+
);
105+
});
106+
107+
test('panel wrapper should have proper ARIA attributes', () => {
108+
const { container } = render(
109+
<Accordion>
110+
<Accordion.Panel label="label" content="content" />
111+
</Accordion>
112+
);
113+
114+
const panelWrapper = container.querySelector('.panel-wrapper');
115+
expect(panelWrapper).toHaveAttribute('role', 'tab');
116+
expect(panelWrapper).toHaveAttribute('aria-expanded', 'false');
117+
});
118+
119+
test('label wrapper should have proper ARIA attributes', () => {
120+
const { container } = render(
121+
<Accordion>
122+
<Accordion.Panel label="label" content="content" />
123+
</Accordion>
124+
);
125+
126+
const labelWrapper = container.querySelector('.label-wrapper');
127+
expect(labelWrapper).toHaveAttribute('role', 'button');
128+
expect(labelWrapper).toHaveAttribute('aria-expanded', 'false');
129+
expect(labelWrapper).toHaveAttribute('aria-controls');
130+
expect(labelWrapper).toHaveAttribute('id');
131+
});
132+
133+
test('content wrapper should have proper ARIA attributes', () => {
134+
const { container } = render(
135+
<Accordion>
136+
<Accordion.Panel label="label" content="content" />
137+
</Accordion>
138+
);
139+
140+
const contentWrapper = container.querySelector('.content-wrapper');
141+
expect(contentWrapper).toHaveAttribute('role', 'region');
142+
expect(contentWrapper).toHaveAttribute('id');
143+
expect(contentWrapper).toHaveAttribute('aria-labelledby');
144+
});
145+
146+
test('ARIA attributes should correctly reference each other', () => {
147+
const { container } = render(
148+
<Accordion>
149+
<Accordion.Panel label="label" content="content" />
150+
</Accordion>
151+
);
152+
153+
const labelWrapper = container.querySelector('.label-wrapper');
154+
const contentWrapper = container.querySelector('.content-wrapper');
155+
156+
const labelId = labelWrapper.getAttribute('id');
157+
const contentId = contentWrapper.getAttribute('id');
158+
159+
expect(labelWrapper).toHaveAttribute('aria-controls', contentId);
160+
expect(contentWrapper).toHaveAttribute('aria-labelledby', labelId);
161+
});
162+
163+
test('aria-expanded should update when panel is toggled', () => {
164+
const { container, getByText } = render(
165+
<Accordion>
166+
<Accordion.Panel label="label" content="content" />
167+
</Accordion>
168+
);
169+
170+
const panelWrapper = container.querySelector('.panel-wrapper');
171+
const labelWrapper = container.querySelector('.label-wrapper');
172+
expect(panelWrapper).toHaveAttribute('aria-expanded', 'false');
173+
expect(labelWrapper).toHaveAttribute('aria-expanded', 'false');
174+
175+
userEvent.click(getByText('label'));
176+
177+
expect(panelWrapper).toHaveAttribute('aria-expanded', 'true');
178+
expect(labelWrapper).toHaveAttribute('aria-expanded', 'true');
179+
180+
userEvent.click(getByText('label'));
181+
182+
expect(panelWrapper).toHaveAttribute('aria-expanded', 'false');
183+
expect(labelWrapper).toHaveAttribute('aria-expanded', 'false');
184+
});
185+
});

src/Display/Accordion/Accordion.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,27 @@ const Accordion: Accordion = ({
2929
};
3030

3131
return (
32-
<Container className={classNames('aries-accordion', className)}>
32+
<Container
33+
className={classNames('aries-accordion', className)}
34+
role="region"
35+
aria-label="Accordion Container"
36+
>
3337
{React.Children.map(
3438
children,
3539
(child: React.ReactElement<AccordionPanelProps>, index) => {
3640
const { label, content, ...restChildProps } = child.props;
41+
const headingId = `accordion-heading-${index}`;
42+
const contentId = `accordion-content-${index}`;
43+
3744
return React.cloneElement(child, {
3845
key: index,
3946
label: label,
4047
content: content,
4148
active: currIndex === index,
4249
iconOptions: iconOptions,
4350
onOpen: () => handleOpen(index),
51+
headingId: headingId,
52+
contentId: contentId,
4453
...restChildProps,
4554
});
4655
}

src/Display/Accordion/AccordionPanel.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const AccordionPanel: React.FunctionComponent<Props> = props => {
2121
iconOptions: { activeIcon, inactiveIcon, position },
2222
onOpen,
2323
onClick,
24+
headingId,
25+
contentId,
2426
...restProps
2527
} = props;
2628
const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
@@ -49,12 +51,22 @@ const AccordionPanel: React.FunctionComponent<Props> = props => {
4951
tabIndex={-1}
5052
position={position}
5153
active={active}
54+
role="button"
55+
aria-expanded={active}
56+
aria-controls={contentId}
57+
id={headingId}
5258
>
5359
{position === 'left' && renderIcon()}
5460
<Label>{label}</Label>
5561
{position === 'right' && renderIcon()}
5662
</IconLabelWrapper>
57-
<ContentWrapper className="content-wrapper" active={active}>
63+
<ContentWrapper
64+
className="content-wrapper"
65+
active={active}
66+
role="region"
67+
id={contentId}
68+
aria-labelledby={headingId}
69+
>
5870
<Content position={position}>{content}</Content>
5971
</ContentWrapper>
6072
</PanelWrapper>
@@ -68,6 +80,8 @@ export type Props = React.ComponentPropsWithoutRef<typeof PanelWrapper> & {
6880
iconOptions?: IconOptions;
6981
onOpen?(): void;
7082
onClick?(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
83+
headingId?: string;
84+
contentId?: string;
7185
};
7286

7387
export default AccordionPanel;

src/Display/Accordion/__snapshots__/Accordion.test.tsx.snap

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
exports[`<Accordion /> prop className should match snapshot when class name test is passed 1`] = `
44
<DocumentFragment>
55
<div
6+
aria-label="Accordion Container"
67
class="AccordionStyle__Container-sc-7hk6zs-1 bCUcSS aries-accordion test"
8+
role="region"
79
>
810
<div
911
aria-expanded="false"
@@ -12,7 +14,11 @@ exports[`<Accordion /> prop className should match snapshot when class name test
1214
tabindex="0"
1315
>
1416
<div
17+
aria-controls="accordion-content-0"
18+
aria-expanded="false"
1519
class="AccordionStyle__IconLabelWrapper-sc-7hk6zs-2 kOQHnF label-wrapper"
20+
id="accordion-heading-0"
21+
role="button"
1622
tabindex="-1"
1723
>
1824
<div
@@ -38,7 +44,10 @@ exports[`<Accordion /> prop className should match snapshot when class name test
3844
</div>
3945
</div>
4046
<div
47+
aria-labelledby="accordion-heading-0"
4148
class="AccordionStyle__ContentWrapper-sc-7hk6zs-3 iJedH content-wrapper"
49+
id="accordion-content-0"
50+
role="region"
4251
>
4352
<div
4453
class="AccordionStyle__Content-sc-7hk6zs-4 hAQoeJ"
@@ -54,7 +63,9 @@ exports[`<Accordion /> prop className should match snapshot when class name test
5463
exports[`<Accordion /> prop className should match snapshot when class name undefined is passed 1`] = `
5564
<DocumentFragment>
5665
<div
66+
aria-label="Accordion Container"
5767
class="AccordionStyle__Container-sc-7hk6zs-1 bCUcSS aries-accordion"
68+
role="region"
5869
>
5970
<div
6071
aria-expanded="false"
@@ -63,7 +74,11 @@ exports[`<Accordion /> prop className should match snapshot when class name unde
6374
tabindex="0"
6475
>
6576
<div
77+
aria-controls="accordion-content-0"
78+
aria-expanded="false"
6679
class="AccordionStyle__IconLabelWrapper-sc-7hk6zs-2 kOQHnF label-wrapper"
80+
id="accordion-heading-0"
81+
role="button"
6782
tabindex="-1"
6883
>
6984
<div
@@ -89,7 +104,10 @@ exports[`<Accordion /> prop className should match snapshot when class name unde
89104
</div>
90105
</div>
91106
<div
107+
aria-labelledby="accordion-heading-0"
92108
class="AccordionStyle__ContentWrapper-sc-7hk6zs-3 iJedH content-wrapper"
109+
id="accordion-content-0"
110+
role="region"
93111
>
94112
<div
95113
class="AccordionStyle__Content-sc-7hk6zs-4 hAQoeJ"
@@ -105,7 +123,9 @@ exports[`<Accordion /> prop className should match snapshot when class name unde
105123
exports[`<Accordion.Panel /> prop iconPosition should match snapshot when iconPosition left is passed 1`] = `
106124
<DocumentFragment>
107125
<div
126+
aria-label="Accordion Container"
108127
class="AccordionStyle__Container-sc-7hk6zs-1 bCUcSS aries-accordion"
128+
role="region"
109129
>
110130
<div
111131
aria-expanded="false"
@@ -114,7 +134,11 @@ exports[`<Accordion.Panel /> prop iconPosition should match snapshot when iconPo
114134
tabindex="0"
115135
>
116136
<div
137+
aria-controls="accordion-content-0"
138+
aria-expanded="false"
117139
class="AccordionStyle__IconLabelWrapper-sc-7hk6zs-2 kOQHnF label-wrapper"
140+
id="accordion-heading-0"
141+
role="button"
118142
tabindex="-1"
119143
>
120144
<div
@@ -140,7 +164,10 @@ exports[`<Accordion.Panel /> prop iconPosition should match snapshot when iconPo
140164
</div>
141165
</div>
142166
<div
167+
aria-labelledby="accordion-heading-0"
143168
class="AccordionStyle__ContentWrapper-sc-7hk6zs-3 iJedH content-wrapper"
169+
id="accordion-content-0"
170+
role="region"
144171
>
145172
<div
146173
class="AccordionStyle__Content-sc-7hk6zs-4 hAQoeJ"
@@ -156,7 +183,9 @@ exports[`<Accordion.Panel /> prop iconPosition should match snapshot when iconPo
156183
exports[`<Accordion.Panel /> prop iconPosition should match snapshot when iconPosition right is passed 1`] = `
157184
<DocumentFragment>
158185
<div
186+
aria-label="Accordion Container"
159187
class="AccordionStyle__Container-sc-7hk6zs-1 bCUcSS aries-accordion"
188+
role="region"
160189
>
161190
<div
162191
aria-expanded="false"
@@ -165,7 +194,11 @@ exports[`<Accordion.Panel /> prop iconPosition should match snapshot when iconPo
165194
tabindex="0"
166195
>
167196
<div
197+
aria-controls="accordion-content-0"
198+
aria-expanded="false"
168199
class="AccordionStyle__IconLabelWrapper-sc-7hk6zs-2 bQDRgc label-wrapper"
200+
id="accordion-heading-0"
201+
role="button"
169202
tabindex="-1"
170203
>
171204
<div
@@ -191,7 +224,10 @@ exports[`<Accordion.Panel /> prop iconPosition should match snapshot when iconPo
191224
</div>
192225
</div>
193226
<div
227+
aria-labelledby="accordion-heading-0"
194228
class="AccordionStyle__ContentWrapper-sc-7hk6zs-3 iJedH content-wrapper"
229+
id="accordion-content-0"
230+
role="region"
195231
>
196232
<div
197233
class="AccordionStyle__Content-sc-7hk6zs-4 bBDEAR"

0 commit comments

Comments
 (0)