fix(popover): stronger, centralized active-row highlight (UAT finding)
The roving-focus row in every popover menu was barely distinguishable from the menu surface — an accent fill at only 24%, and the recipe was copy-pasted across 5 consumers. Centralize it in PopoverItem (ACTIVE_HIGHLIGHT, applied to every item) and strengthen it: accent/35 fill + a 1px inset accent ring for a defined edge that reads across all 6 schemes in both themes. Consumers no longer carry the recipe. Uses the now-working `bg-accent/NN` / `ring-accent/NN` utilities (v4 does the opacity modifier via color-mix) instead of the old hand-written color-mix arbitrary value the v3 no-op forced. Verified in-browser: fill + inset ring render on the active item, dark and light.
This commit is contained in:
@@ -124,7 +124,7 @@ export default function AddToPlaylist({
|
||||
// the floating element only). `toggle()` already no-ops while busy, so re-entrancy is
|
||||
// guarded without touching focus; `aria-busy` on the list conveys the pending state.
|
||||
label={playlistName(pl, t)}
|
||||
className="w-full flex items-center gap-2 text-sm px-1.5 py-1.5 rounded-lg text-fg outline-hidden transition hover:bg-card/60 data-active:bg-[color-mix(in_srgb,var(--accent)_24%,transparent)]"
|
||||
className="w-full flex items-center gap-2 text-sm px-1.5 py-1.5 rounded-lg text-fg outline-hidden transition hover:bg-card/60"
|
||||
>
|
||||
<span
|
||||
className={`grid place-items-center w-4 h-4 rounded border ${
|
||||
|
||||
@@ -223,13 +223,18 @@ type ItemRole = "menuitem" | "menuitemradio" | "menuitemcheckbox" | "option";
|
||||
* one exclusive choice, `menuitemcheckbox`+`checked` when several can be on at once,
|
||||
* `option`+`selected` for a listbox choice, plain `menuitem` for an action.
|
||||
*
|
||||
* The active (roving) item carries `data-active` — style the highlight off THAT, not `:focus-visible`:
|
||||
* The active (roving) item carries `data-active`, and the highlight is applied HERE for every item
|
||||
* (see ACTIVE_HIGHLIGHT) — consumers don't add it. It's driven off `data-active`, not `:focus-visible`:
|
||||
* a click-opened menu focuses the first item programmatically, which does NOT reliably trip the
|
||||
* focus-visible heuristic, so a `:focus-visible` highlight can stay invisible until the first arrow
|
||||
* key. The recommended recipe is an accent tint — and note `bg-accent/NN` does NOT work here (the
|
||||
* theme's `--accent` is a hex var, and Tailwind v3 can't inject alpha into it), so use a color-mix
|
||||
* arbitrary value like the app's own glass idiom:
|
||||
* `data-active:bg-[color-mix(in_srgb,var(--accent)_24%,transparent)] data-active:text-fg`. */
|
||||
* key. (Since the Tailwind v4 migration `bg-accent/NN` works — v4 does the opacity modifier via
|
||||
* color-mix — so the recipe no longer needs a hand-written color-mix arbitrary value.) */
|
||||
// Applied to EVERY PopoverItem so the roving-focus row is clearly visible across all schemes: an
|
||||
// accent fill plus a 1px inset accent ring for a defined edge. (The previous per-consumer 24% fill
|
||||
// alone read as barely-there — a real UAT finding, 2026-08-08.)
|
||||
const ACTIVE_HIGHLIGHT =
|
||||
"data-active:bg-accent/35 data-active:text-fg data-active:ring-1 data-active:ring-inset data-active:ring-accent/60";
|
||||
|
||||
export function PopoverItem({
|
||||
children,
|
||||
onSelect,
|
||||
@@ -286,7 +291,7 @@ export function PopoverItem({
|
||||
tabIndex={active ? 0 : -1}
|
||||
data-active={active ? "true" : undefined}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
className={`${ACTIVE_HIGHLIGHT}${className ? ` ${className}` : ""}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
|
||||
@@ -75,7 +75,7 @@ function TagEditorMenu({
|
||||
checked={on}
|
||||
onSelect={() => onToggle(tg.id)}
|
||||
label={tg.name}
|
||||
className={`w-full flex items-center gap-2 text-left text-xs px-2 py-1.5 rounded-md outline-hidden transition hover:bg-card data-active:bg-[color-mix(in_srgb,var(--accent)_24%,transparent)] data-active:text-fg ${
|
||||
className={`w-full flex items-center gap-2 text-left text-xs px-2 py-1.5 rounded-md outline-hidden transition hover:bg-card ${
|
||||
on ? "text-fg" : "text-muted"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function LanguageSwitcher({
|
||||
setOpen(false);
|
||||
}}
|
||||
label={l.label}
|
||||
className="w-full flex items-center justify-between gap-2 text-sm px-2 py-2 rounded-lg text-muted outline-hidden transition hover:text-fg hover:bg-card data-active:bg-[color-mix(in_srgb,var(--accent)_24%,transparent)] data-active:text-fg"
|
||||
className="w-full flex items-center justify-between gap-2 text-sm px-2 py-2 rounded-lg text-muted outline-hidden transition hover:text-fg hover:bg-card"
|
||||
>
|
||||
<span>{l.label}</span>
|
||||
{l.code === value && <Check className="w-4 h-4 text-accent" />}
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function ViewSwitcher<T extends string>({
|
||||
setOpen(false);
|
||||
}}
|
||||
label={o.label}
|
||||
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-lg text-sm text-left outline-hidden transition data-active:bg-[color-mix(in_srgb,var(--accent)_24%,transparent)] ${
|
||||
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-lg text-sm text-left outline-hidden transition ${
|
||||
on ? "text-accent" : "text-fg hover:bg-card"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -197,7 +197,7 @@ function FilterFunnel<T>({
|
||||
onApply(col.key, on ? arr.filter((x) => x !== o.value) : [...arr, o.value])
|
||||
}
|
||||
label={o.label}
|
||||
className={`flex w-full items-center gap-2 text-left text-xs px-2 py-1.5 rounded-md outline-hidden transition data-active:bg-[color-mix(in_srgb,var(--accent)_24%,transparent)] ${
|
||||
className={`flex w-full items-center gap-2 text-left text-xs px-2 py-1.5 rounded-md outline-hidden transition ${
|
||||
on ? "text-fg" : "text-muted hover:bg-card"
|
||||
}`}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user