{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "liquid-checkbox",
  "type": "registry:component",
  "title": "Liquid Checkbox",
  "description": "A checkbox that fills with a little pour of liquid before the tick draws in, and drains when you uncheck it. A select-all box can sit part full as a list gets done.",
  "categories": [
    "component",
    "Liquid"
  ],
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/liquid-checkbox.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { Checkbox as CheckboxPrimitive } from \"@base-ui/react/checkbox\";\nimport { animate, motion, useMotionValue, useReducedMotion, useTransform } from \"motion/react\";\nimport { type ReactNode, useEffect, useId, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst TONES = {\n  primary: \"[--liquid:var(--color-primary)] [--liquid-ink:var(--color-primary-foreground)]\",\n  success: \"[--liquid:var(--color-success)] [--liquid-ink:var(--color-white)]\",\n  info: \"[--liquid:var(--color-info)] [--liquid-ink:var(--color-white)]\",\n} as const;\n\nconst SIZES = { sm: 16, default: 20, lg: 24 } as const;\n\nfunction liquid(size: number, level: number, slosh: number, swing: number, offset: number) {\n  const amp = size * 0.11 * Math.sin(Math.PI * Math.min(Math.max(slosh, 0), 1)) * swing;\n  const top = -size * 0.15 + size * 1.3 * (1 - level);\n  const phase = slosh * Math.PI * 2.2 + offset;\n  const wavelength = size * 0.9;\n  let d = `M -1 ${size + 1}`;\n  for (let x = -1; x <= size + 1; x += size / 16) {\n    d += ` L ${x.toFixed(2)} ${(top + amp * Math.sin((x / wavelength) * Math.PI * 2 + phase)).toFixed(2)}`;\n  }\n  return `${d} L ${size + 1} ${size + 1} Z`;\n}\n\nexport interface LiquidCheckboxProps\n  extends Omit<CheckboxPrimitive.Root.Props, \"checked\" | \"defaultChecked\" | \"onCheckedChange\" | \"children\" | \"className\"> {\n  checked?: boolean;\n  defaultChecked?: boolean;\n  onCheckedChange?: (checked: boolean) => void;\n  fill?: number;\n  label?: ReactNode;\n  description?: ReactNode;\n  size?: keyof typeof SIZES;\n  tone?: keyof typeof TONES;\n  strike?: boolean;\n  className?: string;\n}\n\nexport function LiquidCheckbox({\n  checked: checkedProp,\n  defaultChecked = false,\n  onCheckedChange,\n  fill = 0,\n  label,\n  description,\n  size = \"default\",\n  tone = \"primary\",\n  strike = false,\n  disabled,\n  className,\n  ...props\n}: LiquidCheckboxProps) {\n  const [inner, setInner] = useState(defaultChecked);\n  const checked = checkedProp ?? inner;\n  const reduce = useReducedMotion();\n  const s = SIZES[size];\n  const clipId = `liquid-check-${useId().replace(/:/g, \"\")}`;\n  const descriptionId = useId();\n  const partial = !checked && fill > 0 && fill < 1;\n  const target = checked ? 1 : Math.min(Math.max(fill, 0), 1);\n\n  const level = useMotionValue(target);\n  const slosh = useMotionValue(1);\n  const swing = useRef(0);\n  const front = useTransform(() => liquid(s, level.get(), slosh.get(), swing.current, 0));\n  const back = useTransform(() => liquid(s, Math.min(level.get() + 0.07, 1), slosh.get(), swing.current * 0.8, Math.PI * 0.9));\n\n  useEffect(() => {\n    const from = level.get();\n    if (from === target) return;\n    if (reduce) {\n      level.jump(target);\n      return;\n    }\n    swing.current = Math.min(1, Math.abs(target - from) * 1.6);\n    const transition = target > from ? { duration: 0.32, ease: [0.23, 1, 0.32, 1] as const } : { duration: 0.24, ease: [0.45, 0, 0.55, 1] as const };\n    slosh.jump(0);\n    const a = animate(level, target, transition);\n    const b = animate(slosh, 1, transition);\n    return () => {\n      a.stop();\n      b.stop();\n    };\n  }, [target, reduce, level, slosh]);\n\n  const setChecked = (next: boolean) => {\n    if (checkedProp === undefined) setInner(next);\n    onCheckedChange?.(next);\n  };\n\n  const box = (\n    <CheckboxPrimitive.Root\n      aria-describedby={description ? descriptionId : undefined}\n      checked={checked}\n      className={cn(\n        \"group/check relative inline-flex shrink-0 cursor-pointer rounded-[min(calc(var(--box)*0.3),calc(var(--radius)*0.6))] border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none transition-[scale,box-shadow] duration-150 ease-out focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background after:absolute after:-inset-1.5 active:scale-[0.95] data-disabled:cursor-not-allowed data-disabled:opacity-64 dark:bg-input/32\",\n        TONES[tone],\n        !label && className,\n      )}\n      disabled={disabled}\n      indeterminate={partial}\n      onCheckedChange={(next) => setChecked(next)}\n      style={{ width: s, height: s, [\"--box\" as string]: `${s}px` }}\n      {...props}\n    >\n      <svg aria-hidden className=\"absolute -inset-px overflow-visible\" height={s} viewBox={`0 0 ${s} ${s}`} width={s}>\n        <clipPath id={clipId}>\n          <rect height={s} rx={s * 0.3} width={s} />\n        </clipPath>\n        <g clipPath={`url(#${clipId})`}>\n          <motion.path d={back} fill=\"var(--liquid)\" opacity={0.32} />\n          <motion.path d={front} fill=\"var(--liquid)\" />\n        </g>\n        <motion.path\n          animate={{ pathLength: checked ? 1 : 0, opacity: checked ? 1 : 0 }}\n          d={`M ${s * 0.27} ${s * 0.52} L ${s * 0.43} ${s * 0.68} L ${s * 0.74} ${s * 0.35}`}\n          fill=\"none\"\n          initial={false}\n          stroke=\"var(--liquid-ink)\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          strokeWidth={s * 0.1}\n          transition={\n            reduce\n              ? { duration: 0 }\n              : checked\n                ? { pathLength: { delay: 0.08, duration: 0.18, ease: [0.23, 1, 0.32, 1] }, opacity: { delay: 0.08, duration: 0.01 } }\n                : { pathLength: { duration: 0.1 }, opacity: { duration: 0.1 } }\n          }\n        />\n      </svg>\n    </CheckboxPrimitive.Root>\n  );\n\n  if (!label) return box;\n\n  return (\n    <label\n      className={cn(\n        \"flex cursor-pointer items-start gap-3 has-data-disabled:cursor-not-allowed has-data-disabled:opacity-64\",\n        className,\n      )}\n    >\n      <span className=\"flex h-5 items-center\">{box}</span>\n      <span className=\"grid min-w-0 gap-0.5\">\n        <span\n          className={cn(\n            \"text-pretty text-sm transition-colors duration-200\",\n            strike &&\n              \"w-fit bg-[linear-gradient(currentColor,currentColor)] bg-position-[0_58%] bg-no-repeat box-decoration-clone transition-[background-size,color] duration-200 ease-out\",\n            strike && (checked ? \"bg-size-[100%_1px] text-muted-foreground\" : \"bg-size-[0%_1px]\"),\n          )}\n        >\n          {label}\n        </span>\n        {description && (\n          <span className=\"text-muted-foreground text-xs\" id={descriptionId}>\n            {description}\n          </span>\n        )}\n      </span>\n    </label>\n  );\n}\n"
    }
  ]
}