From addb3d1b327cbf390b3a8cf08aace0599973a0a1 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 27 Sep 2026 16:30:40 +0200 Subject: [PATCH] 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. --- .../src/components/ui/SidePanel.drawer.dom.test.tsx | 10 +++++++--- frontend/src/components/ui/SidePanel.tsx | 7 +++++-- frontend/src/i18n/locales/en/sidebar.json | 2 -- frontend/src/i18n/locales/hu/sidebar.json | 2 -- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/ui/SidePanel.drawer.dom.test.tsx b/frontend/src/components/ui/SidePanel.drawer.dom.test.tsx index 66f2f7e3..9d3c35ef 100644 --- a/frontend/src/components/ui/SidePanel.drawer.dom.test.tsx +++ b/frontend/src/components/ui/SidePanel.drawer.dom.test.tsx @@ -95,8 +95,12 @@ describe("SidePanel · the tab under an open card", () => { disconnect() {} } as unknown as typeof ResizeObserver; 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( + '[aria-hidden="true"][aria-label="sidebar.unpinNamed:Filters"]', + )!; expect(tab.tabIndex).toBe(-1); + expect(screen.getAllByRole("button", { name: "sidebar.unpinNamed:Filters" })).toHaveLength(1); cleanup(); mount(true); 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(onToggleCollapse).not.toHaveBeenCalled(); // 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" }); expect(screen.queryByRole("dialog")).toBeNull(); }); @@ -129,7 +133,7 @@ describe("SidePanel · on touch", () => { stubMedia({ coarse: true, narrow: false, wide: true }); const { onToggleCollapse } = mount(); 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(screen.queryByRole("dialog")).toBeNull(); }); diff --git a/frontend/src/components/ui/SidePanel.tsx b/frontend/src/components/ui/SidePanel.tsx index 14eb1767..bb56ba01 100644 --- a/frontend/src/components/ui/SidePanel.tsx +++ b/frontend/src/components/ui/SidePanel.tsx @@ -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} + aria-hidden={expanded || undefined} aria-label={ drawerMode ? 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. */}
{pinRoom && ( { diff --git a/frontend/src/i18n/locales/en/sidebar.json b/frontend/src/i18n/locales/en/sidebar.json index 617acd97..b25e3437 100644 --- a/frontend/src/i18n/locales/en/sidebar.json +++ b/frontend/src/i18n/locales/en/sidebar.json @@ -10,8 +10,6 @@ "hideWidget": "Hide widget", "expand": "Expand", "collapse": "Collapse", - "pin": "Pin panel open", - "unpin": "Unpin panel", "pinNamed": "Pin {{title}} open", "unpinNamed": "Unpin {{title}}", "openPanel": "Open {{title}}", diff --git a/frontend/src/i18n/locales/hu/sidebar.json b/frontend/src/i18n/locales/hu/sidebar.json index d75de425..70d70934 100644 --- a/frontend/src/i18n/locales/hu/sidebar.json +++ b/frontend/src/i18n/locales/hu/sidebar.json @@ -10,8 +10,6 @@ "hideWidget": "Modul elrejtése", "expand": "Kibontá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", "unpinNamed": "{{title}} rögzítésének feloldása", "openPanel": "{{title}} megnyitása",