59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
|
|
|
import { Checkbox as SubframeCheckbox } from "~/ui/components/Checkbox"
|
|
import { cn } from "~/lib/utils"
|
|
|
|
// Subframe migration (strangler shim):
|
|
// Keeps the original shadcn/Radix `Checkbox` API (`React.ComponentProps<typeof
|
|
// CheckboxPrimitive.Root>`) but renders the Subframe design-system Checkbox behind it.
|
|
//
|
|
// Pitfalls handled here:
|
|
// - Subframe's Checkbox is ALWAYS controlled (no uncontrolled/`defaultChecked` path), so
|
|
// the shim owns the checked state with `useState` and only defers to the caller when
|
|
// `checked` is supplied — preserving controlled + uncontrolled behavior.
|
|
// - Radix `CheckedState` is `boolean | "indeterminate"`. Subframe splits this into separate
|
|
// `checked` (boolean) + `indeterminate` (boolean) props, so the shim maps them apart and
|
|
// re-assembles the `CheckedState` back into `onCheckedChange`.
|
|
// - The bare checkbox renders just the box (no label), so Subframe's label slot is hidden by
|
|
// default; a caller can still override `hideLabel`.
|
|
function Checkbox({
|
|
className,
|
|
checked,
|
|
defaultChecked,
|
|
onCheckedChange,
|
|
...props
|
|
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
|
const isControlled = checked !== undefined
|
|
const [internalChecked, setInternalChecked] = React.useState<CheckboxPrimitive.CheckedState>(
|
|
defaultChecked ?? false
|
|
)
|
|
|
|
const currentChecked = isControlled ? checked : internalChecked
|
|
const indeterminate = currentChecked === "indeterminate"
|
|
|
|
const handleCheckedChange = (next: boolean) => {
|
|
// When the box was indeterminate, a click resolves it to checked (Radix semantics).
|
|
const resolved: CheckboxPrimitive.CheckedState = next
|
|
if (!isControlled) {
|
|
setInternalChecked(resolved)
|
|
}
|
|
onCheckedChange?.(resolved)
|
|
}
|
|
|
|
return (
|
|
<SubframeCheckbox
|
|
hideLabel={true}
|
|
{...props}
|
|
checked={indeterminate ? false : Boolean(currentChecked)}
|
|
indeterminate={indeterminate}
|
|
onCheckedChange={handleCheckedChange}
|
|
className={cn(className)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export { Checkbox }
|