Skip to content

Commit a119a48

Browse files
committed
refactor: enhance tab accessibility and keyboard navigation
1 parent d1f43bd commit a119a48

File tree

3 files changed

+60
-58
lines changed

3 files changed

+60
-58
lines changed

src/TabNavList/TabNode.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,24 @@ const TabNode: React.FC<TabNodeProps> = props => {
6565
[label, icon],
6666
);
6767

68-
const btnRef = React.useRef<HTMLDivElement>(null);
68+
const tabRef = React.useRef<HTMLDivElement>(null);
6969

7070
React.useEffect(() => {
71-
if (focus && btnRef.current) {
72-
btnRef.current.focus();
71+
if (focus && tabRef.current) {
72+
tabRef.current.focus();
7373
}
7474
}, [focus]);
7575

7676
const node: React.ReactElement = (
7777
<div
78+
ref={tabRef}
7879
key={key}
80+
id={id && `${id}-tab-${key}`}
81+
role="tab"
82+
aria-selected={active}
83+
aria-controls={id && `${id}-panel-${key}`}
84+
aria-disabled={disabled}
85+
tabIndex={disabled ? null : active ? 0 : -1}
7986
data-node-key={genDataNodeKey(key)}
8087
className={classNames(tabPrefix, {
8188
[`${tabPrefix}-with-remove`]: removable,
@@ -85,26 +92,19 @@ const TabNode: React.FC<TabNodeProps> = props => {
8592
})}
8693
style={style}
8794
onClick={onInternalClick}
95+
onKeyDown={onKeyDown}
96+
onMouseDown={onMouseDown}
97+
onMouseUp={onMouseUp}
98+
onFocus={onFocus}
99+
onBlur={onBlur}
88100
>
89101
{/* Primary Tab Button */}
90102
<div
91-
ref={btnRef}
92-
role="tab"
93-
aria-selected={active}
94-
id={id && `${id}-tab-${key}`}
95103
className={`${tabPrefix}-btn`}
96-
aria-controls={id && `${id}-panel-${key}`}
97-
aria-disabled={disabled}
98-
tabIndex={disabled ? null : active ? 0 : -1}
99104
onClick={e => {
100105
e.stopPropagation();
101106
onInternalClick(e);
102107
}}
103-
onKeyDown={onKeyDown}
104-
onMouseDown={onMouseDown}
105-
onMouseUp={onMouseUp}
106-
onFocus={onFocus}
107-
onBlur={onBlur}
108108
>
109109
{icon && <span className={`${tabPrefix}-icon`}>{icon}</span>}
110110
{label && labelNode}

tests/__snapshots__/index.test.tsx.snap

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ exports[`Tabs.Basic Normal 1`] = `
55
class="rc-tabs rc-tabs-top"
66
>
77
<div
8+
aria-orientation="horizontal"
89
class="rc-tabs-nav"
910
role="tablist"
1011
>
@@ -16,46 +17,46 @@ exports[`Tabs.Basic Normal 1`] = `
1617
style="transform: translate(0px, 0px);"
1718
>
1819
<div
20+
aria-controls="rc-tabs-test-panel-light"
21+
aria-selected="false"
1922
class="rc-tabs-tab"
2023
data-node-key="light"
24+
id="rc-tabs-test-tab-light"
25+
role="tab"
26+
tabindex="-1"
2127
>
2228
<div
23-
aria-controls="rc-tabs-test-panel-light"
24-
aria-selected="false"
2529
class="rc-tabs-tab-btn"
26-
id="rc-tabs-test-tab-light"
27-
role="tab"
28-
tabindex="-1"
2930
>
3031
light
3132
</div>
3233
</div>
3334
<div
35+
aria-controls="rc-tabs-test-panel-bamboo"
36+
aria-selected="true"
3437
class="rc-tabs-tab rc-tabs-tab-active"
3538
data-node-key="bamboo"
39+
id="rc-tabs-test-tab-bamboo"
40+
role="tab"
41+
tabindex="0"
3642
>
3743
<div
38-
aria-controls="rc-tabs-test-panel-bamboo"
39-
aria-selected="true"
4044
class="rc-tabs-tab-btn"
41-
id="rc-tabs-test-tab-bamboo"
42-
role="tab"
43-
tabindex="0"
4445
>
4546
bamboo
4647
</div>
4748
</div>
4849
<div
50+
aria-controls="rc-tabs-test-panel-cute"
51+
aria-selected="false"
4952
class="rc-tabs-tab"
5053
data-node-key="cute"
54+
id="rc-tabs-test-tab-cute"
55+
role="tab"
56+
tabindex="-1"
5157
>
5258
<div
53-
aria-controls="rc-tabs-test-panel-cute"
54-
aria-selected="false"
5559
class="rc-tabs-tab-btn"
56-
id="rc-tabs-test-tab-cute"
57-
role="tab"
58-
tabindex="-1"
5960
>
6061
cute
6162
</div>
@@ -109,6 +110,7 @@ exports[`Tabs.Basic Skip invalidate children 1`] = `
109110
class="rc-tabs rc-tabs-top"
110111
>
111112
<div
113+
aria-orientation="horizontal"
112114
class="rc-tabs-nav"
113115
role="tablist"
114116
>
@@ -120,16 +122,16 @@ exports[`Tabs.Basic Skip invalidate children 1`] = `
120122
style="transform: translate(0px, 0px);"
121123
>
122124
<div
125+
aria-controls="rc-tabs-test-panel-light"
126+
aria-selected="true"
123127
class="rc-tabs-tab rc-tabs-tab-active"
124128
data-node-key="light"
129+
id="rc-tabs-test-tab-light"
130+
role="tab"
131+
tabindex="0"
125132
>
126133
<div
127-
aria-controls="rc-tabs-test-panel-light"
128-
aria-selected="true"
129134
class="rc-tabs-tab-btn"
130-
id="rc-tabs-test-tab-light"
131-
role="tab"
132-
tabindex="0"
133135
>
134136
light
135137
</div>

tests/accessibility.test.tsx

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,27 +47,27 @@ describe('Tabs.Accessibility', () => {
4747
expect(firstTab).toHaveFocus();
4848

4949
await user.keyboard('{ArrowRight}');
50-
expect(secondTab.parentElement).toHaveClass('rc-tabs-tab-focus');
50+
expect(secondTab).toHaveClass('rc-tabs-tab-focus');
5151

5252
// skip disabled tab
5353
await user.keyboard('{ArrowRight}');
54-
expect(fourthTab.parentElement).toHaveClass('rc-tabs-tab-focus');
54+
expect(fourthTab).toHaveClass('rc-tabs-tab-focus');
5555

5656
// cycle to first tab
5757
await user.keyboard('{ArrowRight}');
58-
expect(firstTab.parentElement).toHaveClass('rc-tabs-tab-focus');
58+
expect(firstTab).toHaveClass('rc-tabs-tab-focus');
5959

6060
// cycle to last tab
6161
await user.keyboard('{ArrowLeft}');
62-
expect(fourthTab.parentElement).toHaveClass('rc-tabs-tab-focus');
62+
expect(fourthTab).toHaveClass('rc-tabs-tab-focus');
6363

6464
// jump to first tab
6565
await user.keyboard('{Home}');
66-
expect(firstTab.parentElement).toHaveClass('rc-tabs-tab-focus');
66+
expect(firstTab).toHaveClass('rc-tabs-tab-focus');
6767

6868
// jump to last tab
6969
await user.keyboard('{End}');
70-
expect(fourthTab.parentElement).toHaveClass('rc-tabs-tab-focus');
70+
expect(fourthTab).toHaveClass('rc-tabs-tab-focus');
7171
});
7272

7373
it('should support vertical keyboard navigation', async () => {
@@ -82,11 +82,11 @@ describe('Tabs.Accessibility', () => {
8282
// move to second tab
8383
await user.keyboard('{ArrowDown}');
8484
const secondTab = getByRole('tab', { name: 'Tab 2' });
85-
expect(secondTab.parentElement).toHaveClass('rc-tabs-tab-focus');
85+
expect(secondTab).toHaveClass('rc-tabs-tab-focus');
8686

8787
// move to first tab
8888
await user.keyboard('{ArrowUp}');
89-
expect(firstTab.parentElement).toHaveClass('rc-tabs-tab-focus');
89+
expect(firstTab).toHaveClass('rc-tabs-tab-focus');
9090
});
9191

9292
it('should activate tab on Enter/Space', async () => {
@@ -122,7 +122,7 @@ describe('Tabs.Accessibility', () => {
122122
await user.keyboard('{ArrowRight}');
123123

124124
const fourthTab = getByRole('tab', { name: 'Tab 4' });
125-
expect(fourthTab.parentElement).toHaveClass('rc-tabs-tab-focus');
125+
expect(fourthTab).toHaveClass('rc-tabs-tab-focus');
126126
});
127127

128128
it('should distinguish between keyboard and mouse navigation', async () => {
@@ -134,19 +134,19 @@ describe('Tabs.Accessibility', () => {
134134

135135
// mouse click should not add focus style
136136
await user.click(secondTab);
137-
expect(secondTab.parentElement).not.toHaveClass('rc-tabs-tab-focus');
137+
expect(secondTab).not.toHaveClass('rc-tabs-tab-focus');
138138

139139
// clear focus
140140
await user.click(document.body);
141141

142142
// keyboard navigation should add focus style
143143
await user.tab();
144144
// default focus active tab
145-
expect(secondTab.parentElement).toHaveClass('rc-tabs-tab-focus');
145+
expect(secondTab).toHaveClass('rc-tabs-tab-focus');
146146

147147
await user.keyboard('{ArrowRight}');
148148
// skip disabled tab
149-
expect(fourthTab.parentElement).toHaveClass('rc-tabs-tab-focus');
149+
expect(fourthTab).toHaveClass('rc-tabs-tab-focus');
150150
});
151151

152152
it('should support keyboard delete tab', async () => {
@@ -194,28 +194,28 @@ describe('Tabs.Accessibility', () => {
194194

195195
// focus to first tab
196196
await user.tab();
197-
const firstTab = getByRole('tab', { name: 'Tab 1' });
197+
const firstTab = getByRole('tab', { name: 'Tab 1 ×' });
198198
expect(firstTab).toHaveFocus();
199199

200200
// delete first tab
201201
await user.keyboard('{Backspace}');
202-
expect(queryByRole('tab', { name: 'Tab 1' })).toBeNull();
202+
expect(queryByRole('tab', { name: 'Tab 1 ×' })).toBeNull();
203203

204204
// focus should move to next tab
205-
const secondTab = getByRole('tab', { name: 'Tab 2' });
205+
const secondTab = getByRole('tab', { name: 'Tab 2 ×' });
206206
expect(secondTab).toHaveFocus();
207207

208208
// delete second tab
209209
await user.keyboard('{Backspace}');
210-
expect(queryByRole('tab', { name: 'Tab 2' })).toBeNull();
210+
expect(queryByRole('tab', { name: 'Tab 2 ×' })).toBeNull();
211211

212212
// focus should move to next tab
213-
const fourthTab = getByRole('tab', { name: 'Tab 4' });
213+
const fourthTab = getByRole('tab', { name: 'Tab 4 ×' });
214214
expect(fourthTab).toHaveFocus();
215215

216216
// keyboard navigation should work
217217
await user.keyboard('{ArrowRight}');
218-
expect(fourthTab.parentElement).toHaveClass('rc-tabs-tab-focus');
218+
expect(fourthTab).toHaveClass('rc-tabs-tab-focus');
219219
});
220220

221221
it('should focus previous tab when deleting the last tab', async () => {
@@ -256,16 +256,16 @@ describe('Tabs.Accessibility', () => {
256256
const { getByRole, queryByRole } = render(<Demo />);
257257

258258
await user.tab();
259-
const lastTab = getByRole('tab', { name: 'Tab 2' });
260-
expect(lastTab).toHaveFocus();
259+
const secondTab = getByRole('tab', { name: 'Tab 2 ×' });
260+
expect(secondTab).toHaveFocus();
261261

262262
await user.keyboard('{Backspace}');
263-
expect(queryByRole('tab', { name: 'Tab 2' })).toBeNull();
263+
expect(queryByRole('tab', { name: 'Tab 2 ×' })).toBeNull();
264264

265265
await user.keyboard('{Delete}');
266-
expect(queryByRole('tab', { name: 'Tab 3' })).toBeNull();
266+
expect(queryByRole('tab', { name: 'Tab 3 ×' })).toBeNull();
267267

268-
const firstTab = getByRole('tab', { name: 'Tab 1' });
268+
const firstTab = getByRole('tab', { name: 'Tab 1 ×' });
269269
expect(firstTab).toHaveFocus();
270270
});
271271
});

0 commit comments

Comments
 (0)