feat(a11y): R9 focus foundation — glass shadow fix, focus-visible ring, highlight/selected split

S1 — recipe foundation:
- Move .glass/.glass-card/.glass-menu into @layer components so a JSX shadow-* utility overrides the
  glass box-shadow again; the ~9 dead shadow-* on glass surfaces (modals, menus, save-bar, chat) now
  paint their intended drop-shadow. Perf-mode and :hover overrides stay unlayered so they still win.
- Add a .selectable recipe: the hover tint menu/list rows already use, PLUS the app-wide
  focus-visible ring that was missing on non-input controls (the biggest a11y gap).
- Split the popover roving highlight from the selected state (AnchoredPopover): a selected/checked
  PopoverItem now gets a ring ONLY, so its resting paint (bg-accent fill / text-accent / checkbox)
  survives being highlighted instead of downgrading to a 35% tint or being recoloured. Fixes the 4
  review findings deferred from R10-S0 generically; drops the leftover per-consumer data-active ring
  in tableHeader.

S2 — worst offenders:
- Account-menu trigger + switch/add/sign-out rows adopt .selectable (visible keyboard focus).
- Saved-views rows: group-focus-within reveals the hover-only action buttons for keyboard users;
  aria-current on the active row; focus-visible ring on every button.
- The 3 invisible-focus inputs get an accent focus ring: SearchBar + ShareDialog via focus-within on
  the glass pill/row, AboutCell via focus-visible on the trigger.

Deferred to a later R9 sprint: the full unification of all ~14 selection idioms, a shared <NavItem>,
and .is-current/.chip recipes (no consumer yet in this pass).

Verified: tsc/eslint/prettier/knip/vitest (79) green; browser computed-style checks confirm the glass
shadow now wins, the focus outlines render (2px accent), and the split renders (selected ring-only,
unselected fill).
This commit is contained in:
2026-08-08 04:27:51 +02:00
parent 280ba87f25
commit 51f9c8d0ed
8 changed files with 108 additions and 78 deletions
+8 -7
View File
@@ -288,14 +288,15 @@ function ViewRow({
{...attributes}
{...listeners}
title={t("views.reorder")}
className="p-1 text-muted/60 hover:text-muted cursor-grab touch-none shrink-0"
className="p-1 text-muted/60 hover:text-muted cursor-grab touch-none shrink-0 rounded-sm outline-hidden focus-visible:outline-2 focus-visible:outline-accent/60"
>
<GripVertical className="w-3.5 h-3.5" />
</button>
<button
onClick={onApply}
title={t("views.apply")}
className={`flex-1 min-w-0 text-left text-xs py-1 truncate ${
aria-current={active ? "true" : undefined}
className={`flex-1 min-w-0 text-left text-xs py-1 truncate rounded-sm outline-hidden focus-visible:outline-2 focus-visible:outline-accent/60 ${
active ? "text-accent font-medium" : "text-fg"
}`}
>
@@ -305,10 +306,10 @@ function ViewRow({
<button
onClick={onToggleDefault}
title={view.is_default ? t("views.isDefault") : t("views.makeDefault")}
className={`p-1 rounded transition ${
className={`p-1 rounded outline-hidden transition focus-visible:outline-2 focus-visible:outline-accent/60 ${
view.is_default
? "text-accent"
: "text-muted opacity-0 group-hover:opacity-100 hover:text-accent"
: "text-muted opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 hover:text-accent"
}`}
>
<Star className={`w-3.5 h-3.5 ${view.is_default ? "fill-current" : ""}`} />
@@ -316,21 +317,21 @@ function ViewRow({
<button
onClick={onShare}
title={t("views.share")}
className="p-1 rounded-sm text-muted opacity-0 group-hover:opacity-100 hover:text-fg transition"
className="p-1 rounded-sm text-muted opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 hover:text-fg transition outline-hidden focus-visible:outline-2 focus-visible:outline-accent/60"
>
<Share2 className="w-3.5 h-3.5" />
</button>
<button
onClick={onStartRename}
title={t("views.rename")}
className="p-1 rounded-sm text-muted opacity-0 group-hover:opacity-100 hover:text-fg transition"
className="p-1 rounded-sm text-muted opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 hover:text-fg transition outline-hidden focus-visible:outline-2 focus-visible:outline-accent/60"
>
<Pencil className="w-3.5 h-3.5" />
</button>
<button
onClick={onDelete}
title={t("views.delete")}
className="p-1 rounded-sm text-muted opacity-0 group-hover:opacity-100 hover:text-red-400 transition"
className="p-1 rounded-sm text-muted opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 hover:text-red-400 transition outline-hidden focus-visible:outline-2 focus-visible:outline-accent/60"
>
<Trash2 className="w-3.5 h-3.5" />
</button>