fix(ui): make row selection checkboxes always visible at muted opacity (#2093)

* fix(ui): make row selection checkboxes always visible at muted opacity

User feedback: bulk-select checkboxes hidden until hover are "sjukt
pilligt": an invisible 16px target forces a precise hover-then-aim per
row, and bulk selection is a primary workflow on list pages.

New CHECKBOX_REVEAL_CLASS in dry-table.tsx: rest at opacity-50, solid on
row hover, focus, coarse pointers, and checked state. Applied to the 8
selection-checkbox sites (transaction inbox, skattekonto inbox, journal
list, invoices, supplier invoices, orders, pending, invoice inbox
workspace). Also fixes a touch bug: TransactionInboxCard and
SkattekontoInboxCard lacked pointer-coarse fallback, leaving unselected
checkboxes permanently invisible on touch devices.

Row action controls (chevrons, quiet links) keep HOVER_REVEAL_CLASS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015n8vUx9Nukr8mHC7CVNF7y

* fix(ui): meet WCAG 3:1 on resting selection checkboxes via border-foreground

Skeptic finding: the checkbox primitive's border-input is ~1.4:1 against
the page, so the new opacity-50 resting state composited to ~1.2:1,
under the 3:1 non-text contrast minimum design.md commits to. Putting
border-foreground unconditionally on the 8 reveal checkboxes lands the
resting border at >=3.4:1 in every theme (light 3.41:1, dark 4.24:1,
white-label palettes similar) and the hover/solid state at ~17:1.

The border class lives at call sites, not in CHECKBOX_REVEAL_CLASS,
because the constant is sometimes applied to a borderless wrapper
(InvoiceInboxWorkspace); documented in dry-table.tsx.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015n8vUx9Nukr8mHC7CVNF7y

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-09-01 10:39:34 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 05dce83a2b
commit 5f81c0638c
9 changed files with 48 additions and 33 deletions
+16
View File
@@ -27,6 +27,22 @@ export const QUIET_LINK_CLASS =
export const HOVER_REVEAL_CLASS =
'opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 focus-within:opacity-100 pointer-coarse:opacity-100'
// Row SELECTION checkboxes are not hover-reveal controls: bulk-select is a
// primary workflow on list pages, and an invisible 16px target forces a
// precise hover-then-aim per row (user feedback: "sjukt pilligt"). They rest
// always-visible at muted opacity and go solid on hover/focus/coarse
// pointers; call sites force opacity-100 when checked. Action controls
// (chevrons, buttons) keep HOVER_REVEAL_CLASS above.
//
// Call sites must also put `border-foreground` (unconditionally) on the
// Checkbox itself: the primitive's border-input is ~1.4:1 against the page,
// which composited at opacity-50 lands ~1.2:1, under the WCAG 3:1 non-text
// minimum. border-foreground composites to >=3.4:1 at rest in every theme.
// It lives at the call site, not in this constant, because the constant is
// sometimes applied to a borderless WRAPPER around the checkbox.
export const CHECKBOX_REVEAL_CLASS =
'opacity-50 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 pointer-coarse:opacity-100'
// Animated row expansion (concept vwrap/vinner): grid-rows 0fr -> 1fr on
// mount; the global reduced-motion rule collapses the transition.
export function RowFoldout({ children }: { children: React.ReactNode }) {