mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
fix(tabs): vertical tabs support for accessible navigation and aria-orientation (#5924)
This commit is contained in:
parent
bc4c982609
commit
5d9a05be01
5
.changeset/ninety-hairs-heal.md
Normal file
5
.changeset/ninety-hairs-heal.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@heroui/tabs": patch
|
||||
---
|
||||
|
||||
Fix vertical tabs to use correct aria-orientation and support Up/Down arrow navigation for accessibility. (#5810)
|
||||
@ -539,4 +539,63 @@ describe("Tabs", () => {
|
||||
expect(newTabButtons[2]).toHaveAttribute("aria-selected", "true");
|
||||
});
|
||||
});
|
||||
|
||||
test("should have correct aria-orientation for vertical tabs", () => {
|
||||
const wrapper = render(
|
||||
<Tabs isVertical aria-label="Vertical tabs test">
|
||||
<Tab key="item1" title="Item 1">
|
||||
<div>Content 1</div>
|
||||
</Tab>
|
||||
<Tab key="item2" title="Item 2">
|
||||
<div>Content 2</div>
|
||||
</Tab>
|
||||
<Tab key="item3" title="Item 3">
|
||||
<div>Content 3</div>
|
||||
</Tab>
|
||||
</Tabs>,
|
||||
);
|
||||
|
||||
const tablist = wrapper.getByRole("tablist");
|
||||
|
||||
expect(tablist).toHaveAttribute("aria-orientation", "vertical");
|
||||
});
|
||||
|
||||
test("should navigate vertical tabs with ArrowUp and ArrowDown keys", async () => {
|
||||
const wrapper = render(
|
||||
<Tabs isVertical aria-label="Vertical tabs keyboard test">
|
||||
<Tab key="item1" title="Item 1">
|
||||
<div>Content 1</div>
|
||||
</Tab>
|
||||
<Tab key="item2" title="Item 2">
|
||||
<div>Content 2</div>
|
||||
</Tab>
|
||||
<Tab key="item3" title="Item 3">
|
||||
<div>Content 3</div>
|
||||
</Tab>
|
||||
</Tabs>,
|
||||
);
|
||||
|
||||
const tab1 = wrapper.getByRole("tab", {name: "Item 1"});
|
||||
const tab2 = wrapper.getByRole("tab", {name: "Item 2"});
|
||||
const tab3 = wrapper.getByRole("tab", {name: "Item 3"});
|
||||
|
||||
act(() => {
|
||||
focus(tab1);
|
||||
});
|
||||
|
||||
await user.keyboard("[ArrowDown]");
|
||||
expect(tab1).toHaveAttribute("aria-selected", "false");
|
||||
expect(tab2).toHaveAttribute("aria-selected", "true");
|
||||
expect(tab3).toHaveAttribute("aria-selected", "false");
|
||||
|
||||
await user.keyboard("[ArrowDown]");
|
||||
expect(tab1).toHaveAttribute("aria-selected", "false");
|
||||
expect(tab2).toHaveAttribute("aria-selected", "false");
|
||||
expect(tab3).toHaveAttribute("aria-selected", "true");
|
||||
|
||||
await user.keyboard("[ArrowUp]");
|
||||
expect(tab1).toHaveAttribute("aria-selected", "false");
|
||||
expect(tab2).toHaveAttribute("aria-selected", "true");
|
||||
expect(tab3).toHaveAttribute("aria-selected", "false");
|
||||
});
|
||||
});
|
||||
|
||||
@ -106,11 +106,19 @@ export function useTabs<T extends object>(originalProps: UseTabsProps<T>) {
|
||||
const disableAnimation =
|
||||
originalProps?.disableAnimation ?? globalContext?.disableAnimation ?? false;
|
||||
|
||||
const placement = (variantProps as Props).placement ?? (isVertical ? "start" : "top");
|
||||
const orientation =
|
||||
isVertical || placement === "start" || placement === "end" ? "vertical" : "horizontal";
|
||||
|
||||
const state = useTabListState<T>({
|
||||
children: children as CollectionChildren<T>,
|
||||
...otherProps,
|
||||
});
|
||||
const {tabListProps} = useTabList<T>(otherProps as AriaTabListProps<T>, state, domRef);
|
||||
const {tabListProps} = useTabList<T>(
|
||||
{...otherProps, orientation} as AriaTabListProps<T>,
|
||||
state,
|
||||
domRef,
|
||||
);
|
||||
|
||||
const slots = useMemo(
|
||||
() =>
|
||||
@ -161,7 +169,6 @@ export function useTabs<T extends object>(originalProps: UseTabsProps<T>) {
|
||||
[baseStyles, otherProps, slots],
|
||||
);
|
||||
|
||||
const placement = (variantProps as Props).placement ?? (isVertical ? "start" : "top");
|
||||
const getWrapperProps: PropGetter = useCallback(
|
||||
(props) => ({
|
||||
"data-slot": "tabWrapper",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user