{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "liquid-switch",
  "type": "registry:component",
  "title": "Liquid Switch",
  "description": "A toggle whose thumb stretches as it crosses, trails two droplets behind it and swallows them back up the moment it lands.",
  "categories": [
    "component",
    "Liquid"
  ],
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/liquid-switch.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { Switch as SwitchPrimitive } from \"@base-ui/react/switch\";\nimport { motion, useMotionValue, useReducedMotion, useSpring, useTransform, useVelocity } from \"motion/react\";\nimport { type ReactNode, useEffect, useId, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst SIZES = {\n  sm: { w: 34, h: 20, pad: 2, blur: 3 },\n  default: { w: 44, h: 26, pad: 3, blur: 3.5 },\n  lg: { w: 58, h: 34, pad: 3, blur: 4.5 },\n} as const;\n\nconst TONES = {\n  primary: \"data-checked:bg-primary\",\n  success: \"data-checked:bg-success\",\n  info: \"data-checked:bg-info\",\n  destructive: \"data-checked:bg-destructive\",\n} as const;\n\nexport interface LiquidSwitchProps\n  extends Omit<SwitchPrimitive.Root.Props, \"children\" | \"checked\" | \"defaultChecked\" | \"onCheckedChange\"> {\n  checked?: boolean;\n  defaultChecked?: boolean;\n  onCheckedChange?: (checked: boolean) => void;\n  size?: keyof typeof SIZES;\n  tone?: keyof typeof TONES;\n  label?: ReactNode;\n  description?: ReactNode;\n}\n\nexport function LiquidSwitch({\n  checked: checkedProp,\n  defaultChecked = false,\n  onCheckedChange,\n  size = \"default\",\n  tone = \"primary\",\n  label,\n  description,\n  className,\n  disabled,\n  ...props\n}: LiquidSwitchProps) {\n  const [inner, setInner] = useState(defaultChecked);\n  const checked = checkedProp ?? inner;\n  const reduce = useReducedMotion();\n  const filterId = `liquid-switch-${useId().replace(/:/g, \"\")}`;\n  const descriptionId = useId();\n  const { w, h, pad, blur } = SIZES[size];\n  const thumb = h - pad * 2;\n  const travel = w - h;\n\n  const target = useMotionValue(checked ? travel : 0);\n  const x = useSpring(target, { stiffness: 560, damping: 34, mass: 0.9 });\n  const trailA = useSpring(target, { stiffness: 300, damping: 26 });\n  const trailB = useSpring(target, { stiffness: 170, damping: 20 });\n  const velocity = useVelocity(x);\n  const scaleX = useTransform(velocity, [-1400, 0, 1400], [1.3, 1, 1.3], { clamp: true });\n  const scaleY = useTransform(velocity, [-1400, 0, 1400], [0.86, 1, 0.86], { clamp: true });\n\n  useEffect(() => {\n    const next = checked ? travel : 0;\n    if (reduce) {\n      for (const value of [target, x, trailA, trailB]) value.jump(next);\n    } else {\n      target.set(next);\n    }\n  }, [checked, travel, reduce, target, x, trailA, trailB]);\n\n  const setChecked = (next: boolean) => {\n    if (checkedProp === undefined) setInner(next);\n    onCheckedChange?.(next);\n  };\n\n  const blob = cn(\n    \"absolute top-1/2 left-0 rounded-full bg-white transition-colors duration-150\",\n    tone === \"primary\" && \"group-data-checked/switch:bg-primary-foreground\",\n  );\n\n  const control = (\n    <SwitchPrimitive.Root\n      aria-describedby={description ? descriptionId : undefined}\n      checked={checked}\n      className={cn(\n        \"group/switch relative isolate inline-flex shrink-0 cursor-pointer rounded-full bg-input outline-none after:absolute after:-inset-x-1 after:-inset-y-2 transition-[background-color,box-shadow] duration-150 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-disabled:cursor-not-allowed data-disabled:opacity-64\",\n        TONES[tone],\n        !label && className,\n      )}\n      disabled={disabled}\n      onCheckedChange={setChecked}\n      style={{ width: w, height: h }}\n      {...props}\n    >\n      <svg aria-hidden className=\"absolute size-0\">\n        <filter colorInterpolationFilters=\"sRGB\" height=\"300%\" id={filterId} width=\"300%\" x=\"-100%\" y=\"-100%\">\n          <feGaussianBlur in=\"SourceGraphic\" result=\"blur\" stdDeviation={blur} />\n          <feColorMatrix in=\"blur\" result=\"goo\" values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -8\" />\n          <feComposite in=\"SourceGraphic\" in2=\"goo\" operator=\"atop\" />\n        </filter>\n      </svg>\n      <span aria-hidden className=\"pointer-events-none absolute inset-0 drop-shadow-sm\">\n        <span className=\"absolute inset-y-0\" style={{ left: pad, right: pad, filter: `url(#${filterId})` }}>\n        <motion.span\n          className={blob}\n          style={{ x: trailB, width: thumb * 0.6, height: thumb * 0.6, marginLeft: thumb * 0.2, marginTop: -thumb * 0.3 }}\n        />\n        <motion.span\n          className={blob}\n          style={{ x: trailA, width: thumb * 0.84, height: thumb * 0.84, marginLeft: thumb * 0.08, marginTop: -thumb * 0.42 }}\n        />\n        <motion.span\n          className={cn(blob, \"origin-center\")}\n          style={{ x, scaleX, scaleY, width: thumb, height: thumb, marginTop: -thumb / 2 }}\n        >\n          <span className=\"absolute inset-0 rounded-full bg-inherit transition-[scale] duration-200 group-active/switch:scale-x-[1.18] group-active/switch:scale-y-90\" />\n        </motion.span>\n        </span>\n      </span>\n    </SwitchPrimitive.Root>\n  );\n\n  if (!label) return control;\n\n  return (\n    <label\n      className={cn(\n        \"flex cursor-pointer items-start justify-between gap-4 has-data-disabled:cursor-not-allowed has-data-disabled:opacity-64\",\n        className,\n      )}\n    >\n      <span className=\"grid gap-0.5\">\n        <span className=\"font-medium text-sm\">{label}</span>\n        {description && (\n          <span className=\"text-muted-foreground text-xs\" id={descriptionId}>\n            {description}\n          </span>\n        )}\n      </span>\n      {control}\n    </label>\n  );\n}\n"
    }
  ]
}