{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "fractional-picker",
  "type": "registry:component",
  "title": "Fractional Picker",
  "description": "A smooth draggable ruler-style picker for selecting numeric values with spring snapping.",
  "dependencies": [
    "framer-motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "components/watermelon/fractional-picker.tsx",
      "type": "registry:component",
      "content": "'use client';\n\nimport { useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport {\n  motion,\n  useMotionValue,\n  useTransform,\n  animate,\n  MotionValue,\n} from 'framer-motion';\nimport { cn } from '@/lib/utils';\n\ninterface RulerItemProps {\n  value: number;\n  x: MotionValue<number>;\n  itemWidth: number;\n  max: number;\n}\n\nfunction RulerItem({ value, x, itemWidth, max }: RulerItemProps) {\n  const distance = useTransform(x, (latest) => {\n    const itemPos = value * itemWidth;\n    return Math.abs(itemPos + latest);\n  });\n\n  const opacity = useTransform(distance, [0, itemWidth], [1, 0.3]);\n  const scale = useTransform(distance, [0, itemWidth * 0.8], [1.1, 0.9]);\n\n  return (\n    <div className=\"flex h-full shrink-0 flex-col\" style={{ width: itemWidth }}>\n      <div className=\"relative flex h-full w-full flex-col items-center justify-end\">\n        <motion.span\n          className=\"text-foreground mb-1 text-4xl font-semibold tabular-nums select-none\"\n          style={{ opacity, scale }}\n        >\n          {value}\n        </motion.span>\n\n        <div className=\"relative flex h-8 w-full items-end\">\n          <div className=\"absolute left-1/2 z-10 h-8 w-[4px] -translate-x-1/2 rounded-t-full bg-neutral-400 dark:bg-neutral-200\" />\n          <div className=\"flex w-full translate-x-1/2 justify-evenly\">\n            {value !== max &&\n              Array.from({ length: 4 }).map((_, i) => (\n                <div\n                  key={`${value}-sub-${i}`}\n                  className=\"h-4 w-[4px] rounded-t-full bg-neutral-200 dark:bg-neutral-600\"\n                />\n              ))}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport function FractionalPicker({\n  min = 0,\n  max = 20,\n  defaultValue = 0,\n  itemWidth = 80,\n  onChange,\n  className,\n}: any) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const [containerWidth, setContainerWidth] = useState(0);\n\n  const x = useMotionValue(-defaultValue * itemWidth);\n  const [activeValue, setActiveValue] = useState(defaultValue);\n\n  const snap = () => {\n    const currentX = x.get();\n    console.log(currentX)\n    const closestValue = Math.round(currentX / itemWidth) * itemWidth;\n    animate(x, closestValue, {\n      type: 'spring',\n      stiffness: 400,\n      damping: 40,\n    });\n  };\n\n  useEffect(() => {\n    return x.on('change', (latest) => {\n      const val = Math.abs(Math.round(latest / itemWidth));\n      if (val !== activeValue && val >= min && val <= max) {\n        setActiveValue(val);\n        onChange?.(val);\n      }\n    });\n  }, [x, itemWidth, activeValue, onChange, min, max]);\n\n  useLayoutEffect(() => {\n    if (!containerRef.current) return;\n\n    const updateWidth = () => {\n      if (containerRef.current) {\n        setContainerWidth(containerRef.current.offsetWidth);\n      }\n    };\n\n    updateWidth();\n    window.addEventListener('resize', updateWidth);\n    return () => window.removeEventListener('resize', updateWidth);\n  }, []);\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn(\n        'bg-background border-border relative max-w-[600px] overflow-hidden rounded-4xl border shadow-sm',\n        className,\n      )}\n      style={{ height: 120 }}\n    >\n      <div className=\"pointer-events-none absolute top-0 left-1/2 z-20 flex -translate-x-1/2 flex-col items-center\">\n        <div\n          className=\"h-6 w-10 rounded-b-xl bg-neutral-200\"\n          style={{ clipPath: 'polygon(0 0, 100% 0, 80% 100%, 20% 100%)' }}\n        />\n        <div className=\"mt-1 h-1.5 w-1.5 rounded-full bg-neutral-200\" />\n      </div>\n\n      <motion.div\n        drag=\"x\"\n        style={{\n          x,\n          paddingLeft: containerWidth / 2 - itemWidth / 2,\n          paddingRight: containerWidth / 2 - itemWidth / 2,\n        }}\n        dragConstraints={{\n          left: -max * itemWidth,\n          right: -min * itemWidth,\n        }}\n        dragElastic={0.1}\n        onDragEnd={snap}\n        className=\"flex h-full cursor-grab items-end active:cursor-grabbing\"\n      >\n        {Array.from({ length: max - min + 1 }, (_, i) => (\n          <RulerItem\n            key={i + min}\n            value={i + min}\n            x={x}\n            itemWidth={itemWidth}\n            max={max}\n          />\n        ))}\n      </motion.div>\n\n      <div className=\"from-background via-background/60 pointer-events-none absolute inset-y-0 left-0 z-10 w-24 bg-gradient-to-r to-transparent\" />\n      <div className=\"from-background via-background/60 pointer-events-none absolute inset-y-0 right-0 z-10 w-24 bg-gradient-to-l to-transparent\" />\n    </div>\n  );\n}\n"
    }
  ]
}
