fix(filters): make the whole filter option row clickable

The padding around an enum filter's checkbox/radio carried the hover cue but
swallowed the click: the only element that toggles a Mantine Checkbox is its
native <label>, which wraps its own text and nothing else. The row's padding
and the gutter beside the input square lie outside it, so styling those on
`root` produced an area that looked interactive and was not.

Stretch a `::before` over the relatively-positioned root. The pseudo-element
belongs to the label's own box, so a click anywhere in the row lands on the
label and toggles the input. `cursor: pointer` moves to the root for the same
reason -- the affordance should cover exactly what is clickable.
This commit is contained in:
Nathnael
2026-08-20 09:05:31 +00:00
parent 87ec7cec07
commit 54cf0c2944

View File

@@ -11,19 +11,29 @@ import type { FilterBodyProps } from "./TextBody";
const SEARCH_THRESHOLD = 8;
/**
* Stretches the Checkbox/Radio's native <label> across the full popover
* width and pads it, so the clickable/tappable area is the whole row —
* not just the ~14px input square — plus a hover cue. `body`/`labelWrapper`
* are Mantine's part names for this; `cursor: pointer` on the row (not just
* the input) makes the affordance visible before you even click.
* Whole-row hit target. The only element that toggles a Mantine Checkbox /
* Radio is its native <label>, and that label wraps its own text and nothing
* else — the row's padding and the gutter beside the input square lie
* OUTSIDE it. Styling those on `root` therefore bought a hover cue over an
* area that swallowed the click.
*
* The fix is a `::before` stretched over the (relatively positioned) root:
* that pseudo-element is part of the label's own box, so a click anywhere in
* the row hits the label and toggles the input. `cursor: pointer` goes on the
* root for the same reason — the affordance must cover what is clickable.
*/
const ROW_STYLES = {
root: { padding: "10px 10px", borderRadius: 6 },
root: { position: "relative" as const, padding: "10px 10px", borderRadius: 6, cursor: "pointer" },
body: { alignItems: "center" as const },
labelWrapper: { flex: 1 },
label: { cursor: "pointer", paddingLeft: 8 },
};
const ROW_CLASSES = {
root: "hover:bg-gray-100 transition-colors",
label: "before:absolute before:inset-0 before:content-['']",
};
function OptionLabel({ label, count }: { label: string; count?: number }) {
return (
<Group justify="space-between" wrap="nowrap" gap="sm">
@@ -96,7 +106,7 @@ export function EnumBody({ def, value, onChange, onClose }: FilterBodyProps<Enum
// just the tiny checkbox square) toggles the option too.
label={<OptionLabel label={o.label} count={def.counts?.[o.value]} />}
styles={ROW_STYLES}
classNames={{ root: "hover:bg-gray-100 transition-colors" }}
classNames={ROW_CLASSES}
/>
))}
</Stack>
@@ -115,7 +125,7 @@ export function EnumBody({ def, value, onChange, onClose }: FilterBodyProps<Enum
size="sm"
label={<OptionLabel label={o.label} count={def.counts?.[o.value]} />}
styles={ROW_STYLES}
classNames={{ root: "hover:bg-gray-100 transition-colors" }}
classNames={ROW_CLASSES}
/>
))}
</Stack>