{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scrub-input",
  "type": "registry:ui",
  "title": "Scrub Input",
  "description": "An inline slider input styled as a pill, perfect for adjusting variables smoothly.",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/scrub-input.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport React, { useRef, useState } from \"react\";\nimport { cn } from \"@workspace/ui/lib/utils\";\n\nexport interface ScrubInputProps {\n    value?: number;\n    defaultValue?: number;\n    onChange?: (value: number) => void;\n    label: string;\n    min?: number;\n    max?: number;\n    step?: number;\n    className?: string;\n}\n\nexport function ScrubInput({\n    value: controlledValue,\n    defaultValue = 0,\n    onChange,\n    label,\n    min = 0,\n    max = 100,\n    step = 1,\n    className,\n}: ScrubInputProps) {\n    const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n    const isControlled = controlledValue !== undefined;\n    const value = isControlled ? controlledValue : uncontrolledValue;\n\n    const [isDragging, setIsDragging] = useState(false);\n    const containerRef = useRef<HTMLDivElement>(null);\n\n    const handleValueChange = (newValue: number) => {\n        if (!isControlled) setUncontrolledValue(newValue);\n        onChange?.(newValue);\n    };\n\n    const updateValueFromPointer = (clientX: number) => {\n        if (!containerRef.current) return;\n        const rect = containerRef.current.getBoundingClientRect();\n        const x = clientX - rect.left;\n        let percentage = x / rect.width;\n        percentage = Math.max(0, Math.min(1, percentage));\n\n        let newValue = min + percentage * (max - min);\n        newValue = Math.round(newValue / step) * step;\n\n        // Ensure value stays within bounds due to rounding\n        newValue = Math.max(min, Math.min(max, newValue));\n\n        if (newValue !== value) {\n            handleValueChange(newValue);\n        }\n    };\n\n    const onPointerDown = (e: React.PointerEvent) => {\n        containerRef.current?.setPointerCapture(e.pointerId);\n        setIsDragging(true);\n        updateValueFromPointer(e.clientX);\n    };\n\n    const onPointerMove = (e: React.PointerEvent) => {\n        if (!isDragging) return;\n        updateValueFromPointer(e.clientX);\n    };\n\n    const onPointerUp = (e: React.PointerEvent) => {\n        containerRef.current?.releasePointerCapture(e.pointerId);\n        setIsDragging(false);\n    };\n\n    const percentage = max > min ? ((value - min) / (max - min)) * 100 : 0;\n\n    // Create a minimum width so the fill doesn't look too weird when value is 0\n    const fillWidth = Math.max(20, percentage);\n\n    return (\n        <div\n            ref={containerRef}\n            className={cn(\n                \"relative h-12 w-[280px] rounded-2xl bg-zinc-100 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 select-none touch-none cursor-ew-resize overflow-hidden\",\n                \"transition-transform active:scale-[0.98]\",\n                className\n            )}\n            onPointerDown={onPointerDown}\n            onPointerMove={onPointerMove}\n            onPointerUp={onPointerUp}\n            onPointerCancel={onPointerUp}\n        >\n            {/* The fill / thumb */}\n            <div\n                className={cn(\n                    \"absolute left-0 top-0 h-full rounded-2xl flex items-center justify-end pr-3\",\n                    \"bg-white dark:bg-zinc-800 shadow-[0_2px_8px_rgba(0,0,0,0.1)] dark:shadow-none border-r border-zinc-200 dark:border-zinc-700\",\n                    isDragging ? \"transition-none\" : \"transition-[width] duration-300 ease-out\"\n                )}\n                style={{ width: `${fillWidth}%` }}\n            >\n                {/* Handle mark */}\n                <div className=\"h-5 w-[3px] rounded-full bg-zinc-300 dark:bg-zinc-600\" />\n            </div>\n\n            {/* Label and Value (Always on top to be visible whether the thumb covers them or not) */}\n            <div className=\"absolute inset-0 flex items-center justify-between px-5 pointer-events-none\">\n                <span className=\"text-[15px] tracking-tight font-medium text-black/60 dark:text-white/60\">\n                    {label}\n                </span>\n                <span className=\"text-[16px] tracking-tight font-medium text-black/50 dark:text-white/50\">\n                    {value}\n                </span>\n            </div>\n        </div>\n    );\n}\n"
    }
  ],
  "tailwind": {},
  "cssVars": {},
  "meta": {}
}