Skip to content

Commit 603ec29

Browse files
committed
feat: adding keyboard navigation to Tabs component for accessibility purpose
1 parent f28b3ab commit 603ec29

File tree

1 file changed

+48
-2
lines changed

1 file changed

+48
-2
lines changed

src/Tabs.tsx

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,14 @@ export const Tabs = memo(
8383
return index === -1 ? 0 : index;
8484
};
8585

86+
const buttonRefs = React.useRef<Array<HTMLButtonElement | null>>([]);
87+
8688
const [selectedTabIndex, setSelectedTabIndex] = useState<number>(getSelectedTabIndex);
8789

8890
useEffect(() => {
8991
if (selectedTabId === undefined) {
9092
return;
9193
}
92-
9394
setSelectedTabIndex(getSelectedTabIndex());
9495
}, [selectedTabId]);
9596

@@ -104,6 +105,45 @@ export const Tabs = memo(
104105
}
105106
});
106107

108+
const onKeyboardNavigation = (
109+
event: React.KeyboardEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLUListElement>
110+
) => {
111+
let targetIndex = selectedTabIndex;
112+
switch (event.key) {
113+
case "ArrowRight":
114+
targetIndex = selectedTabIndex < tabs.length - 1 ? selectedTabIndex + 1 : 0;
115+
break;
116+
case "ArrowLeft":
117+
targetIndex = selectedTabIndex === 0 ? tabs.length - 1 : selectedTabIndex - 1;
118+
break;
119+
case "Home":
120+
targetIndex = 0;
121+
break;
122+
case "End":
123+
targetIndex = tabs.length - 1;
124+
break;
125+
}
126+
setSelectedTabIndex(targetIndex);
127+
};
128+
129+
React.useEffect(() => {
130+
const targetTabButton = buttonRefs.current[selectedTabIndex];
131+
if (targetTabButton) {
132+
targetTabButton.focus();
133+
}
134+
}, [selectedTabIndex]);
135+
136+
React.useEffect(() => {
137+
if (selectedTabId === undefined) {
138+
onTabChange?.({
139+
tabIndex: selectedTabIndex,
140+
"tab": tabs[selectedTabIndex]
141+
});
142+
} else {
143+
onTabChange(tabs[selectedTabIndex].tabId);
144+
}
145+
}, [selectedTabIndex]);
146+
107147
const { getPanelId, getTabId } = (function useClosure() {
108148
const id = useId();
109149

@@ -124,10 +164,16 @@ export const Tabs = memo(
124164
style={style}
125165
{...rest}
126166
>
127-
<ul className={fr.cx("fr-tabs__list")} role="tablist" aria-label={label}>
167+
<ul
168+
className={fr.cx("fr-tabs__list")}
169+
role="tablist"
170+
aria-label={label}
171+
onKeyDownCapture={e => onKeyboardNavigation(e)}
172+
>
128173
{tabs.map(({ label, iconId }, tabIndex) => (
129174
<li key={label + (iconId ?? "")} role="presentation">
130175
<button
176+
ref={button => (buttonRefs.current[tabIndex] = button)}
131177
id={getTabId(tabIndex)}
132178
className={cx(
133179
fr.cx("fr-tabs__tab", iconId, "fr-tabs__tab--icon-left"),

0 commit comments

Comments
 (0)