{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stepper",
  "type": "registry:component",
  "title": "stepper",
  "description": "An animated numeric stepper with plu and minus controls and spring-based roling digit transitions.",
  "dependencies": [
    "motion",
    "react-icons"
  ],
  "files": [
    {
      "path": "components/watermelon/stepper.tsx",
      "type": "registry:component",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\nimport { HiMinus, HiPlus } from 'react-icons/hi';\n\nexport interface StepperProps {\n  value?: number;\n  defaultValue?: number;\n  min?: number;\n  max?: number;\n  onChange?: (val: number) => void;\n}\n\nconst digitVariants = {\n  initial: (dir: number) => ({\n    y: dir > 0 ? 20 : -20,\n    opacity: 0,\n    scale: 0.5,\n    z: 0,\n    filter: 'blur(2px)',\n  }),\n  animate: {\n    y: 0,\n    opacity: 1,\n    scale: 1,\n    z: 10,\n    filter: 'blur(0px)',\n  },\n  exit: (dir: number) => ({\n    y: dir > 0 ? -20 : 20,\n    opacity: 0,\n    scale: 0.5,\n    z: 0,\n    filter: 'blur(2px)',\n  }),\n};\n\nexport function Stepper({\n  value,\n  defaultValue = 0,\n  min = 0,\n  max = 999,\n  onChange,\n}: StepperProps) {\n  const isControlled = value !== undefined;\n  const [internal, setInternal] = React.useState(defaultValue);\n  const [direction, setDirection] = React.useState(0);\n\n  const current = isControlled ? value! : internal;\n  const digits = current.toString().split('');\n\n  const [prevDigits, setPrevDigits] = React.useState<string[]>([]);\n  const [prevTicks, setPrevTicks] = React.useState<number[]>([]);\n\n  const len = digits.length;\n  const lenDiff = len - prevDigits.length;\n\n  const nextTicks = digits.map((digit, i) => {\n    const prevI = i - lenDiff;\n    const prevDigit = prevI >= 0 ? prevDigits[prevI] : undefined;\n    const prevTick = prevI >= 0 ? prevTicks[prevI] : 0;\n\n    return digit !== prevDigit ? (prevTick ?? 0) + 1 : (prevTick ?? 0);\n  });\n\n  if (prevDigits.join(\"\") !== digits.join(\"\")) {\n    setPrevTicks(nextTicks);\n    setPrevDigits(digits);\n  }\n\n  const step = (dir: number) => {\n    const next = Math.min(max, Math.max(min, current + dir));\n    if (next === current) return;\n    setDirection(dir);\n    if (!isControlled) setInternal(next);\n    onChange?.(next);\n  };\n\n  return (\n    <div className=\"flex w-full justify-center\">\n      <div className=\"flex items-center gap-3 rounded-full border-2 border-[#E6E6EF] bg-transparent px-1 py-1 shadow-sm sm:gap-5 dark:border-zinc-800\">\n        <motion.button\n          whileHover={{ scale: 1.05 }}\n          whileTap={{ scale: 0.92 }}\n          transition={{ type: 'spring', stiffness: 300, damping: 22 }}\n          onClick={() => step(-1)}\n          disabled={current <= min}\n          className=\"flex h-11 w-11 shrink-0 cursor-pointer items-center justify-center rounded-full bg-[#F0EFF6] text-[#5A5A63] disabled:opacity-50 sm:h-14 sm:w-14 dark:bg-zinc-800 dark:text-zinc-400\"\n        >\n          <HiMinus className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n        </motion.button>\n\n        <div className=\"relative flex shrink-0 items-center justify-center gap-1 text-xl font-bold text-[#242426] perspective-midrange transform-3d sm:h-8 sm:text-3xl dark:text-white\">\n          {digits.map((digit, index) => (\n            <div\n              key={`${index}-${len}`}\n              className=\"relative w-3 transform-3d sm:h-8 sm:w-4\"\n            >\n              <AnimatePresence\n                mode=\"popLayout\"\n                initial={false}\n                custom={direction}\n              >\n                <motion.span\n                  key={nextTicks[index]}\n                  custom={direction}\n                  variants={digitVariants}\n                  initial=\"initial\"\n                  animate=\"animate\"\n                  exit=\"exit\"\n                  transition={{\n                    type: 'spring',\n                    stiffness: 200,\n                    damping: 16,\n                    mass: 1.2,\n                  }}\n                  className=\"absolute inset-0 flex items-center justify-center tabular-nums\"\n                >\n                  {digit}\n                </motion.span>\n              </AnimatePresence>\n            </div>\n          ))}\n        </div>\n\n        <motion.button\n          whileHover={{ scale: 1.05 }}\n          whileTap={{ scale: 0.92 }}\n          transition={{ type: 'spring', stiffness: 300, damping: 22 }}\n          onClick={() => step(1)}\n          disabled={current >= max}\n          className=\"flex h-11 w-11 shrink-0 cursor-pointer items-center justify-center rounded-full bg-[#F0EFF6] text-[#5A5A63] disabled:opacity-50 sm:h-14 sm:w-14 dark:bg-zinc-800 dark:text-zinc-400\"\n        >\n          <HiPlus className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n        </motion.button>\n      </div>\n    </div>\n  );\n}\n"
    }
  ]
}
