fix(a11y): every side-panel pin names its panel; the tab under an open card leaves the tree
The card's own pin and the drawer's still said 'Unpin panel' / 'Pin panel open' after the edge tab learned the title (F6). Naming them too made two buttons share a name, because the invisible tab under an open card was still in the accessibility tree: it is now aria-hidden as well as out of the Tab order. The generic sidebar.pin / unpin keys are gone.
This commit is contained in:
@@ -95,8 +95,12 @@ describe("SidePanel · the tab under an open card", () => {
|
|||||||
disconnect() {}
|
disconnect() {}
|
||||||
} as unknown as typeof ResizeObserver;
|
} as unknown as typeof ResizeObserver;
|
||||||
mount(false); // pinned
|
mount(false); // pinned
|
||||||
const tab = screen.getByRole("button", { name: "sidebar.unpinNamed:Filters", hidden: true });
|
// The card's own pin is the one control by that name; the tab under it is out of the tree too.
|
||||||
|
const tab = document.querySelector<HTMLElement>(
|
||||||
|
'[aria-hidden="true"][aria-label="sidebar.unpinNamed:Filters"]',
|
||||||
|
)!;
|
||||||
expect(tab.tabIndex).toBe(-1);
|
expect(tab.tabIndex).toBe(-1);
|
||||||
|
expect(screen.getAllByRole("button", { name: "sidebar.unpinNamed:Filters" })).toHaveLength(1);
|
||||||
cleanup();
|
cleanup();
|
||||||
mount(true);
|
mount(true);
|
||||||
expect(screen.getByRole("button", { name: "sidebar.pinNamed:Filters" }).tabIndex).toBe(0);
|
expect(screen.getByRole("button", { name: "sidebar.pinNamed:Filters" }).tabIndex).toBe(0);
|
||||||
@@ -113,7 +117,7 @@ describe("SidePanel · on touch", () => {
|
|||||||
expect(dialog.textContent).toContain("panel body");
|
expect(dialog.textContent).toContain("panel body");
|
||||||
expect(onToggleCollapse).not.toHaveBeenCalled();
|
expect(onToggleCollapse).not.toHaveBeenCalled();
|
||||||
// Below 1024 a pinned panel would leave the content no room: no pin in the drawer.
|
// Below 1024 a pinned panel would leave the content no room: no pin in the drawer.
|
||||||
expect(screen.queryByRole("button", { name: "sidebar.pin" })).toBeNull();
|
expect(screen.queryByRole("button", { name: "sidebar.pinNamed:Filters" })).toBeNull();
|
||||||
fireEvent.keyDown(window, { key: "Escape" });
|
fireEvent.keyDown(window, { key: "Escape" });
|
||||||
expect(screen.queryByRole("dialog")).toBeNull();
|
expect(screen.queryByRole("dialog")).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -129,7 +133,7 @@ describe("SidePanel · on touch", () => {
|
|||||||
stubMedia({ coarse: true, narrow: false, wide: true });
|
stubMedia({ coarse: true, narrow: false, wide: true });
|
||||||
const { onToggleCollapse } = mount();
|
const { onToggleCollapse } = mount();
|
||||||
fireEvent.click(screen.getByRole("button", { name: "sidebar.openPanel:Filters" }));
|
fireEvent.click(screen.getByRole("button", { name: "sidebar.openPanel:Filters" }));
|
||||||
fireEvent.click(screen.getByRole("button", { name: "sidebar.pin" }));
|
fireEvent.click(screen.getByRole("button", { name: "sidebar.pinNamed:Filters" }));
|
||||||
expect(onToggleCollapse).toHaveBeenCalledOnce();
|
expect(onToggleCollapse).toHaveBeenCalledOnce();
|
||||||
expect(screen.queryByRole("dialog")).toBeNull();
|
expect(screen.queryByRole("dialog")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -160,7 +160,10 @@ export default function SidePanel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Under an open card the tab is transparent and inert: out of the Tab order AND of the
|
||||||
|
// accessibility tree, where it would repeat the card's own pin button by name.
|
||||||
tabIndex={expanded ? -1 : undefined}
|
tabIndex={expanded ? -1 : undefined}
|
||||||
|
aria-hidden={expanded || undefined}
|
||||||
aria-label={
|
aria-label={
|
||||||
drawerMode
|
drawerMode
|
||||||
? t("sidebar.openPanel", { title })
|
? t("sidebar.openPanel", { title })
|
||||||
@@ -197,7 +200,7 @@ export default function SidePanel({
|
|||||||
Module actions live on their own row below so the title always has room. */}
|
Module actions live on their own row below so the title always has room. */}
|
||||||
<div className="flex items-center gap-1.5 px-3 py-2.5 border-b border-border/60 flex-none">
|
<div className="flex items-center gap-1.5 px-3 py-2.5 border-b border-border/60 flex-none">
|
||||||
<IconButton
|
<IconButton
|
||||||
label={pinned ? t("sidebar.unpin") : t("sidebar.pin")}
|
label={pinned ? t("sidebar.unpinNamed", { title }) : t("sidebar.pinNamed", { title })}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={onToggleCollapse}
|
onClick={onToggleCollapse}
|
||||||
@@ -245,7 +248,7 @@ export default function SidePanel({
|
|||||||
<>
|
<>
|
||||||
{pinRoom && (
|
{pinRoom && (
|
||||||
<IconButton
|
<IconButton
|
||||||
label={t("sidebar.pin")}
|
label={t("sidebar.pinNamed", { title })}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
"hideWidget": "Hide widget",
|
"hideWidget": "Hide widget",
|
||||||
"expand": "Expand",
|
"expand": "Expand",
|
||||||
"collapse": "Collapse",
|
"collapse": "Collapse",
|
||||||
"pin": "Pin panel open",
|
|
||||||
"unpin": "Unpin panel",
|
|
||||||
"pinNamed": "Pin {{title}} open",
|
"pinNamed": "Pin {{title}} open",
|
||||||
"unpinNamed": "Unpin {{title}}",
|
"unpinNamed": "Unpin {{title}}",
|
||||||
"openPanel": "Open {{title}}",
|
"openPanel": "Open {{title}}",
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
"hideWidget": "Modul elrejtése",
|
"hideWidget": "Modul elrejtése",
|
||||||
"expand": "Kibontás",
|
"expand": "Kibontás",
|
||||||
"collapse": "Összecsukás",
|
"collapse": "Összecsukás",
|
||||||
"pin": "Panel rögzítése nyitva",
|
|
||||||
"unpin": "Panel rögzítésének feloldása",
|
|
||||||
"pinNamed": "{{title}} rögzítése nyitva",
|
"pinNamed": "{{title}} rögzítése nyitva",
|
||||||
"unpinNamed": "{{title}} rögzítésének feloldása",
|
"unpinNamed": "{{title}} rögzítésének feloldása",
|
||||||
"openPanel": "{{title}} megnyitása",
|
"openPanel": "{{title}} megnyitása",
|
||||||
|
|||||||
Reference in New Issue
Block a user