fix(popover): R10 weak-ring — checked rows get the full highlight fill

A hovered/roving CHECKED row got only a thin ring (R9 split), reading as a weak
afterthought next to an unchecked row's vivid accent fill. Now selected rows get the
SAME bg-accent/35 fill + a STRONGER ring (ring-2 accent/80 vs the unselected ring-1
accent/60); the child checkmark/checkbox stays legible on the tint. The one idiom that
DID rely on the R9 ring-only — the table-filter options, whose selected item paints its
own solid bg-accent ROW — opts back in via a new `keepSelectedPaint` PopoverItem prop,
so its solid paint isn't downgraded to a 35% tint. Scopes the R9 exception to the one
menu that needs it instead of penalising every checkbox menu.
This commit is contained in:
2026-08-09 17:46:13 +02:00
parent eee7443e6f
commit 5c5c045ed9
2 changed files with 25 additions and 8 deletions
+23 -8
View File
@@ -250,15 +250,20 @@ type ItemRole = "menuitem" | "menuitemradio" | "menuitemcheckbox" | "option";
* focus-visible heuristic, so a `:focus-visible` highlight can stay invisible until the first arrow
* 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.) */
// The roving-focus highlight, SPLIT by selection state (R9). A NON-selected row gets the familiar
// accent fill + inset ring. A SELECTED/checked row gets ring-ONLY, so the consumer's own resting
// selected paint (a bg-accent fill, text-accent, a checkbox) SURVIVES being highlighted instead of
// being downgraded to a 35% tint or having its text recoloured (the R10-S0 review findings). The two
// can't be merged onto one recipe: the fill and the selected paint both drive background/color on the
// same element, so the highlight must yield to the selected state rather than overwrite it.
// The roving-focus highlight. A hovered/roving row gets the SAME strong accent fill whether or not it
// is selected — a checked row no longer reads as a weak ring-only afterthought (R10 weak-ring fix). The
// selected state stays legible ON the fill because these menus mark "selected" with a CHILD element (a
// checkmark / checkbox) that paints over the row tint; the selected row just gets a stronger ring
// (ring-2, accent/80) than an unselected one (ring-1, accent/60) to keep the two apart.
// EXCEPTION: a menu whose SELECTED item paints its OWN row background (a solid bg-accent — the
// table-filter options) passes `keepSelectedPaint`, so the highlight yields to ring-only there; a fill
// would downgrade that solid paint to a 35% tint (the R9 concern, now scoped to just that idiom).
const HIGHLIGHT_UNSELECTED =
"data-active:bg-accent/35 data-active:text-fg data-active:ring-1 data-active:ring-inset data-active:ring-accent/60";
const HIGHLIGHT_SELECTED = "data-active:ring-2 data-active:ring-inset data-active:ring-accent/70";
const HIGHLIGHT_SELECTED =
"data-active:bg-accent/35 data-active:text-fg data-active:ring-2 data-active:ring-inset data-active:ring-accent/80";
const HIGHLIGHT_SELECTED_RING_ONLY =
"data-active:ring-2 data-active:ring-inset data-active:ring-accent/70";
export function PopoverItem({
children,
@@ -266,6 +271,7 @@ export function PopoverItem({
role = "menuitem",
checked,
selected,
keepSelectedPaint,
disabled,
className,
/** Typeahead match string; defaults to the text child. Set explicitly when children aren't plain text. */
@@ -277,6 +283,9 @@ export function PopoverItem({
role?: ItemRole;
checked?: boolean;
selected?: boolean;
/** Set when the SELECTED item paints its OWN row background (a solid bg-accent, not a child checkmark):
* the active highlight then yields to ring-only so the fill doesn't downgrade that solid paint. */
keepSelectedPaint?: boolean;
disabled?: boolean;
className?: string;
label?: string;
@@ -327,7 +336,13 @@ export function PopoverItem({
tabIndex={focusActive ? 0 : -1}
data-active={visualActive ? "true" : undefined}
disabled={disabled}
className={`outline-hidden ${checked || selected ? HIGHLIGHT_SELECTED : HIGHLIGHT_UNSELECTED}${className ? ` ${className}` : ""}`}
className={`outline-hidden ${
checked || selected
? keepSelectedPaint
? HIGHLIGHT_SELECTED_RING_ONLY
: HIGHLIGHT_SELECTED
: HIGHLIGHT_UNSELECTED
}${className ? ` ${className}` : ""}`}
>
{children}
</button>
+2
View File
@@ -156,6 +156,7 @@ function FilterFunnel<T>({
<PopoverItem
role="menuitemradio"
checked={!filterOn}
keepSelectedPaint
onSelect={() => onApply(col.key, "")}
label={t("datatable.all")}
className={`text-left text-xs px-2 py-1.5 rounded-md outline-hidden transition ${
@@ -169,6 +170,7 @@ function FilterFunnel<T>({
key={o.value}
role="menuitemradio"
checked={value === o.value}
keepSelectedPaint
onSelect={() => onApply(col.key, o.value)}
label={o.label}
className={`text-left text-xs px-2 py-1.5 rounded-md outline-hidden transition ${