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:
@@ -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>
|
||||
|
||||
@@ -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 ${
|
||||
|
||||
Reference in New Issue
Block a user