{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "checkbox", "title": "Checkbox", "description": "Hand-drawn checkbox with tick animation.", "dependencies": [ "roughjs" ], "registryDependencies": [ "https://www.bydefaulthuman.fun/r/use-rough.json", "https://www.bydefaulthuman.fun/r/crumble-theme.json", "https://www.bydefaulthuman.fun/r/rough-lib.json", "utils" ], "files": [ { "path": "registry/new-york/ui/checkbox.tsx", "content": "\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useRef,\n type ChangeEvent,\n} from \"react\";\nimport { useRough } from \"@/hooks/use-rough\";\nimport {\n randomSeed,\n resolveRoughVars,\n stableSeed,\n type CrumbleColorProps,\n type CrumbleTheme,\n} from \"@/lib/rough\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface CheckboxProps extends CrumbleColorProps {\n checked?: boolean;\n className?: string;\n defaultChecked?: boolean;\n disabled?: boolean;\n id?: string;\n label?: string;\n onChange?: (checked: boolean) => void;\n theme?: CrumbleTheme;\n}\n\nconst SIZE = 20;\n\nexport function Checkbox({\n checked,\n className,\n defaultChecked,\n disabled,\n fill,\n id,\n label,\n onChange,\n stroke,\n strokeMuted,\n theme: themeProp,\n}: CheckboxProps) {\n const internalChecked = useRef(defaultChecked ?? false);\n const inputId =\n id ?? `checkbox-${label?.toLowerCase().replace(/\\s+/g, \"-\") ?? \"field\"}`;\n const roughStyle = resolveRoughVars({ stroke, strokeMuted, fill });\n const {\n animateOnHover,\n drawLine,\n drawRect,\n svgRef,\n } = useRough({\n stableId: inputId,\n theme: themeProp,\n variant: \"interactive\",\n });\n\n const draw = useCallback(\n (isOn: boolean, reseed = false) => {\n const svg = svgRef.current;\n if (!svg) return;\n\n svg.replaceChildren();\n\n const baseOptions = {\n fill: \"none\",\n seed: reseed ? randomSeed() : undefined,\n stroke: disabled ? \"var(--cr-stroke-muted)\" : \"var(--cr-stroke)\",\n };\n\n const box = drawRect(1, 1, SIZE - 2, SIZE - 2, baseOptions);\n if (box) svg.appendChild(box);\n\n if (isOn) {\n const tickOptions = {\n ...baseOptions,\n seed: reseed ? randomSeed() : stableSeed(`${inputId}-tick`),\n strokeWidth: 1.8,\n };\n const left = drawLine(3, SIZE / 2 + 1, SIZE / 2 - 1, SIZE - 4, tickOptions);\n const right = drawLine(\n SIZE / 2 - 1,\n SIZE - 4,\n SIZE - 3,\n 3,\n {\n ...tickOptions,\n seed: reseed ? randomSeed() : stableSeed(`${inputId}-tick-r`),\n },\n );\n if (left) svg.appendChild(left);\n if (right) svg.appendChild(right);\n }\n },\n [disabled, drawLine, drawRect, inputId, svgRef],\n );\n\n useEffect(() => {\n draw(checked ?? internalChecked.current);\n }, [checked, draw]);\n\n const handleChange = (event: ChangeEvent) => {\n internalChecked.current = event.target.checked;\n draw(event.target.checked);\n onChange?.(event.target.checked);\n };\n\n return (\n {\n if (!disabled && animateOnHover) {\n draw(checked ?? internalChecked.current, true);\n }\n }}\n onMouseLeave={() => {\n if (!disabled && animateOnHover) {\n draw(checked ?? internalChecked.current, false);\n }\n }}\n >\n
\n \n \n
\n {label ? {label} : null}\n \n );\n}\n", "type": "registry:ui", "target": "components/crumble/ui/checkbox.tsx" } ], "type": "registry:ui" }