Files
HealthLog/src/components/ui/checkbox.tsx
T
Marc-André Bombeck 582db8a538 feat(export): one grouped scope picker, on both surfaces that choose a scope
The export panel rendered twenty flat switches behind a collapsed disclosure,
and the share-link form rendered none at all — it posted whatever the create
schema defaulted to. Both now mount the same picker: twelve named groups, a
count on each, the leaf grid one chevron away, and a fenced tier below a
separator.

The fence is mechanical, not a label. The sensitive tier has no group
checkbox, so no single control anywhere in the picker can turn on more than
one of mood, cycle, family history, PHQ-9, GAD-7, WHO-5 or SCI at a time.
There is no select-all. The shipped template contains none of them. And the
scope line under the button restates every sensitive inclusion by name rather
than folding it into a count, because a number cannot be checked at a glance
and "incl. Mood, PHQ-9" can.

First run opens expanded with the named standard template applied and the
fenced tier visibly empty, so the user presses Generate having seen twelve
named groups. Every later run opens collapsed carrying their own last
selection, which makes the waiting-room path two interactions.

Only leaves are ever stored. A group's checkbox is derived from its leaves, so
the two cannot disagree, and switching a group off and back on inside one
session restores the pattern instead of flooding it.

The cycle export card is deleted. It posted empty groups under a docblock
claiming every other section was off, and empty groups resolved to the
defaults — so a "cycle only" download carried weight, blood pressure, pulse,
oxygen saturation, body fat, body composition, the medication list, adherence,
labs, allergies and family history. Cycle is a fenced leaf in the one picker
now, reachable for the PDF as well as for FHIR, which is more than the card
offered.

The panel gains a 30-day preset and a custom window, and its error line says
what went wrong instead of interpolating a status code.

The Checkbox primitive learns the indeterminate state it already had the Radix
support for. A partly-selected group drawn as unchecked would say "none of
this is included", which on this surface is a false statement about what
leaves the instance.
2026-07-26 00:45:08 +02:00

66 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import * as React from "react";
import { Checkbox as CheckboxPrimitive } from "radix-ui";
import { Check, Minus } from "lucide-react";
import { cn } from "@/lib/utils";
/**
* v1.4.27 R3d MB4 / CF-40 — shadcn-style `<Checkbox>` primitive backed
* by `radix-ui`'s `Checkbox` namespace. Brought in so the Coach
* sources-rail can drop its raw `<input type="checkbox">` for a
* keyboard + touch accessible control that ships proper focus rings
* and a 44 px hit target on mobile.
*
* Mirrors the v1.4.27 shadcn `<Checkbox>` recipe: a Root that draws
* the border + checked-state background, an Indicator slot that
* shows the lucide `<Check>` when the box is checked.
*
* The Indicator also draws a `<Minus>` for the indeterminate state, which
* Radix's Root accepts natively via `checked="indeterminate"`. A group row
* whose leaves are partly selected is a real third state and needs to look
* like one — drawing it as unchecked would say "none of this is included",
* which on this surface is a false statement about what leaves the instance.
*/
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
// The visual box stays 16×16, but the effective touch target is
// 32×32 via the switch's `::before` hit-area trick (v1.4.43 W5-H1):
// `relative` + an absolutely positioned 8 px halo that receives
// pointer events without disturbing layout. 16 px alone sits below
// the WCAG 2.5.8 24 px floor on the measurement/mood row selectors;
// 32 px clears it with margin while staying small enough not to
// blanket neighbouring controls in dense table rows.
"peer border-border/70 relative size-4 shrink-0 rounded border before:absolute before:inset-[-8px] before:content-['']",
"data-[state=checked]:bg-primary data-[state=checked]:border-primary data-[state=checked]:text-background",
"data-[state=indeterminate]:bg-primary data-[state=indeterminate]:border-primary data-[state=indeterminate]:text-background",
"focus-visible:ring-ring/50 focus-visible:ring-2 focus-visible:outline-none",
"disabled:cursor-not-allowed disabled:opacity-50",
"transition-colors",
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current"
>
{props.checked === "indeterminate" ? (
<Minus className="size-3" aria-hidden="true" />
) : (
<Check className="size-3" aria-hidden="true" />
)}
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
);
}
export { Checkbox };