fix(a11y): chips, grips, the panel tab and Back to top keep the focus somewhere real
- F5: a chip's × pressed from the keyboard hands the focus to the next chip (else the previous one, else what follows) before it unmounts — in DismissibleChip, so all five chip rows get it (`focusNeighbour` in lib/useFocusKeeper). - F4: a drag grip names what it lifts (`common.order.dragNamed`: panel groups, personal views); the two generic "Drag to reorder" keys are gone. - F6: the side panel's edge tab is out of the Tab order while the card covers it, names its panel, and a keyboard pin moves the focus into the card. - F7: Back to top is no Tab stop while hidden, and a keyboard press leaves the focus on the page.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { useRef, useState, type ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Check, Pencil, Pin, PinOff, RotateCcw } from "lucide-react";
|
||||
import { useScrollFade } from "../../lib/useScrollFade";
|
||||
@@ -64,6 +64,7 @@ export default function SidePanel({
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const cardRef = useRef<HTMLElement>(null);
|
||||
const fade = useScrollFade();
|
||||
const peek = useHoverFocusWithin();
|
||||
const drawerMode = useMediaQuery(DRAWER_QUERY, false);
|
||||
@@ -143,16 +144,29 @@ export default function SidePanel({
|
||||
hover that opened the card, and unmounting it while focused would throw keyboard focus to
|
||||
nowhere. It only goes transparent: the card's rounded corner cuts away its own pixels, and
|
||||
this square-cornered tab sits right under that arc, so it would otherwise bleed through.
|
||||
`opacity-0` (not `invisible`/`hidden`) keeps it hoverable and focusable. */}
|
||||
`opacity-0` (not `invisible`/`hidden`) keeps it hoverable, and a focus already on it stays.
|
||||
But no Tab lands on it while the card covers it (E28 review F6): an invisible stop, where the
|
||||
card's own pin says the same. A keyboard pin hands the focus into the card it opened. */}
|
||||
<Pressable
|
||||
{...(drawerMode ? {} : peek.hoverHandlers)}
|
||||
onClick={drawerMode ? () => setDrawerOpen(true) : onToggleCollapse}
|
||||
onClick={
|
||||
drawerMode
|
||||
? () => setDrawerOpen(true)
|
||||
: (e) => {
|
||||
onToggleCollapse();
|
||||
if (e.detail === 0)
|
||||
requestAnimationFrame(() =>
|
||||
cardRef.current?.querySelector<HTMLElement>("button")?.focus(),
|
||||
);
|
||||
}
|
||||
}
|
||||
tabIndex={expanded ? -1 : undefined}
|
||||
aria-label={
|
||||
drawerMode
|
||||
? t("sidebar.openPanel", { title })
|
||||
: pinned
|
||||
? t("sidebar.unpin")
|
||||
: t("sidebar.pin")
|
||||
? t("sidebar.unpinNamed", { title })
|
||||
: t("sidebar.pinNamed", { title })
|
||||
}
|
||||
aria-haspopup={drawerMode ? "dialog" : undefined}
|
||||
className={`${SURFACE.edgeTab} glass-hover absolute top-3 left-0 z-paneltab flex flex-col items-center gap-2 px-1.5 py-3 pointer-coarse:w-11 transition-opacity ${
|
||||
@@ -175,6 +189,7 @@ export default function SidePanel({
|
||||
|
||||
{expanded && (
|
||||
<aside
|
||||
ref={cardRef}
|
||||
{...peek.hoverHandlers}
|
||||
className={`${SURFACE.panel} absolute top-3 bottom-3 left-0 z-panel w-64 flex flex-col overflow-hidden`}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user