{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "ElasticSlider-JS-TW",
	"title": "ElasticSlider",
	"description": "Slider handle stretches elastically then snaps with spring physics.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "ElasticSlider/ElasticSlider.jsx",
			"content": "import { animate, motion, useMotionValue, useMotionValueEvent, useTransform } from 'motion/react';\nimport { useEffect, useRef, useState } from 'react';\n\nconst MAX_OVERFLOW = 50;\n\nexport default function ElasticSlider({\n  defaultValue = 50,\n  startingValue = 0,\n  maxValue = 100,\n  className = '',\n  isStepped = false,\n  stepSize = 1,\n  leftIcon = <>-</>,\n  rightIcon = <>+</>\n}) {\n  return (\n    <div className={`flex flex-col items-center justify-center gap-4 w-48 ${className}`}>\n      <Slider\n        defaultValue={defaultValue}\n        startingValue={startingValue}\n        maxValue={maxValue}\n        isStepped={isStepped}\n        stepSize={stepSize}\n        leftIcon={leftIcon}\n        rightIcon={rightIcon}\n      />\n    </div>\n  );\n}\n\nfunction Slider({ defaultValue, startingValue, maxValue, isStepped, stepSize, leftIcon, rightIcon }) {\n  const [value, setValue] = useState(defaultValue);\n  const sliderRef = useRef(null);\n  const [region, setRegion] = useState('middle');\n  const clientX = useMotionValue(0);\n  const overflow = useMotionValue(0);\n  const scale = useMotionValue(1);\n\n  useEffect(() => {\n    setValue(defaultValue);\n  }, [defaultValue]);\n\n  useMotionValueEvent(clientX, 'change', latest => {\n    if (sliderRef.current) {\n      const { left, right } = sliderRef.current.getBoundingClientRect();\n      let newValue;\n\n      if (latest < left) {\n        setRegion('left');\n        newValue = left - latest;\n      } else if (latest > right) {\n        setRegion('right');\n        newValue = latest - right;\n      } else {\n        setRegion('middle');\n        newValue = 0;\n      }\n\n      overflow.jump(decay(newValue, MAX_OVERFLOW));\n    }\n  });\n\n  const handlePointerMove = e => {\n    if (e.buttons > 0 && sliderRef.current) {\n      const { left, width } = sliderRef.current.getBoundingClientRect();\n      let newValue = startingValue + ((e.clientX - left) / width) * (maxValue - startingValue);\n\n      if (isStepped) {\n        newValue = Math.round(newValue / stepSize) * stepSize;\n      }\n\n      newValue = Math.min(Math.max(newValue, startingValue), maxValue);\n      setValue(newValue);\n      clientX.jump(e.clientX);\n    }\n  };\n\n  const handlePointerDown = e => {\n    handlePointerMove(e);\n    e.currentTarget.setPointerCapture(e.pointerId);\n  };\n\n  const handlePointerUp = () => {\n    animate(overflow, 0, { type: 'spring', bounce: 0.5 });\n  };\n\n  const getRangePercentage = () => {\n    const totalRange = maxValue - startingValue;\n    if (totalRange === 0) return 0;\n    return ((value - startingValue) / totalRange) * 100;\n  };\n\n  return (\n    <>\n      <motion.div\n        onHoverStart={() => animate(scale, 1.2)}\n        onHoverEnd={() => animate(scale, 1)}\n        onTouchStart={() => animate(scale, 1.2)}\n        onTouchEnd={() => animate(scale, 1)}\n        style={{\n          scale,\n          opacity: useTransform(scale, [1, 1.2], [0.7, 1])\n        }}\n        className=\"flex w-full touch-none select-none items-center justify-center gap-4\"\n      >\n        <motion.div\n          animate={{\n            scale: region === 'left' ? [1, 1.4, 1] : 1,\n            transition: { duration: 0.25 }\n          }}\n          style={{\n            x: useTransform(() => (region === 'left' ? -overflow.get() / scale.get() : 0))\n          }}\n        >\n          {leftIcon}\n        </motion.div>\n\n        <div\n          ref={sliderRef}\n          className=\"relative flex w-full max-w-xs flex-grow cursor-grab touch-none select-none items-center py-4\"\n          onPointerMove={handlePointerMove}\n          onPointerDown={handlePointerDown}\n          onPointerUp={handlePointerUp}\n          onPointerCancel={handlePointerUp}\n          onLostPointerCapture={handlePointerUp}\n        >\n          <motion.div\n            style={{\n              scaleX: useTransform(() => {\n                if (sliderRef.current) {\n                  const { width } = sliderRef.current.getBoundingClientRect();\n                  return 1 + overflow.get() / width;\n                }\n              }),\n              scaleY: useTransform(overflow, [0, MAX_OVERFLOW], [1, 0.8]),\n              transformOrigin: useTransform(() => {\n                if (sliderRef.current) {\n                  const { left, width } = sliderRef.current.getBoundingClientRect();\n                  return clientX.get() < left + width / 2 ? 'right' : 'left';\n                }\n              }),\n              height: useTransform(scale, [1, 1.2], [6, 12]),\n              marginTop: useTransform(scale, [1, 1.2], [0, -3]),\n              marginBottom: useTransform(scale, [1, 1.2], [0, -3])\n            }}\n            className=\"flex flex-grow\"\n          >\n            <div className=\"relative h-full flex-grow overflow-hidden rounded-full bg-gray-400\">\n              <div className=\"absolute h-full bg-gray-500 rounded-full\" style={{ width: `${getRangePercentage()}%` }} />\n            </div>\n          </motion.div>\n        </div>\n\n        <motion.div\n          animate={{\n            scale: region === 'right' ? [1, 1.4, 1] : 1,\n            transition: { duration: 0.25 }\n          }}\n          style={{\n            x: useTransform(() => (region === 'right' ? overflow.get() / scale.get() : 0))\n          }}\n        >\n          {rightIcon}\n        </motion.div>\n      </motion.div>\n      <p className=\"absolute text-gray-400 transform -translate-y-4 text-xs font-medium tracking-wide\">\n        {Math.round(value)}\n      </p>\n    </>\n  );\n}\n\nfunction decay(value, max) {\n  if (max === 0) {\n    return 0;\n  }\n\n  const entry = value / max;\n  const sigmoid = 2 * (1 / (1 + Math.exp(-entry)) - 0.5);\n\n  return sigmoid * max;\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}