diff --git a/frontend/src/components/AnchoredPopover.tsx b/frontend/src/components/AnchoredPopover.tsx index ddf2cee1..21287f54 100644 --- a/frontend/src/components/AnchoredPopover.tsx +++ b/frontend/src/components/AnchoredPopover.tsx @@ -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} diff --git a/frontend/src/components/tableHeader.tsx b/frontend/src/components/tableHeader.tsx index 9e9f5f6c..e40b187a 100644 --- a/frontend/src/components/tableHeader.tsx +++ b/frontend/src/components/tableHeader.tsx @@ -156,6 +156,7 @@ function FilterFunnel({ 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({ 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 ${