{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <educlopez93@gmail.com>","css":{},"dependencies":["lucide-react"],"description":"A NumberFlow component for SmoothUI.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Minus, Plus } from \"lucide-react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nconst TENS_PLACE = 10;\nconst HUNDREDS_PLACE = 100;\n\nconst animateDigit = (\n  prevElement: HTMLElement | null,\n  nextElement: HTMLElement | null,\n  isIncreasing: boolean\n) => {\n  if (!prevElement) {\n    return;\n  }\n  if (!nextElement) {\n    return;\n  }\n\n  if (isIncreasing) {\n    prevElement.classList.add(\"slide-out-up\");\n    nextElement.classList.add(\"slide-in-up\");\n  } else {\n    prevElement.classList.add(\"slide-out-down\");\n    nextElement.classList.add(\"slide-in-down\");\n  }\n\n  const handleAnimationEnd = () => {\n    prevElement.classList.remove(\"slide-out-up\", \"slide-out-down\");\n    nextElement.classList.remove(\"slide-in-up\", \"slide-in-down\");\n    prevElement.removeEventListener(\"animationend\", handleAnimationEnd);\n  };\n\n  prevElement.addEventListener(\"animationend\", handleAnimationEnd);\n};\n\nconst getTensValue = (num: number) => Math.floor(num / TENS_PLACE);\nconst getHundredsValue = (num: number) => Math.floor(num / HUNDREDS_PLACE);\n\nexport interface NumberFlowProps {\n  buttonClassName?: string;\n  className?: string;\n  digitClassName?: string;\n  max?: number;\n  min?: number;\n  onChange?: (value: number) => void;\n  value?: number;\n}\n\nexport default function NumberFlow({\n  value: controlledValue,\n  onChange,\n  min = 0,\n  max = 999,\n  className = \"\",\n  digitClassName = \"\",\n  buttonClassName = \"\",\n}: NumberFlowProps) {\n  const [internalValue, setInternalValue] = useState(0);\n  const [prevValue, setPrevValue] = useState(0);\n\n  const value = controlledValue === undefined ? internalValue : controlledValue;\n\n  const prevValueRef = useRef<HTMLElement>(null);\n  const nextValueRef = useRef<HTMLElement>(null);\n  const prevValueTens = useRef<HTMLElement>(null);\n  const nextValueTens = useRef<HTMLElement>(null);\n  const prevValueHunds = useRef<HTMLElement>(null);\n  const nextValueHunds = useRef<HTMLElement>(null);\n\n  const setValue = (val: number) => {\n    if (onChange) {\n      onChange(val);\n    } else {\n      setInternalValue(val);\n    }\n  };\n\n  const add = () => {\n    if (value < max) {\n      setPrevValue(value);\n      setValue(value + 1);\n    }\n  };\n\n  const subtract = () => {\n    if (value > min) {\n      setPrevValue(value);\n      setValue(value - 1);\n    }\n  };\n\n  useEffect(() => {\n    if (prevValueRef.current && nextValueRef.current) {\n      animateDigit(\n        prevValueRef.current,\n        nextValueRef.current,\n        value > prevValue\n      );\n    }\n\n    const currentTens = getTensValue(value);\n    const prevTens = getTensValue(prevValue);\n\n    if (\n      prevValueTens.current &&\n      nextValueTens.current &&\n      currentTens !== prevTens\n    ) {\n      animateDigit(\n        prevValueTens.current,\n        nextValueTens.current,\n        currentTens > prevTens\n      );\n    }\n\n    const currentHundreds = getHundredsValue(value);\n    const prevHundreds = getHundredsValue(prevValue);\n\n    if (\n      prevValueHunds.current &&\n      nextValueHunds.current &&\n      currentHundreds !== prevHundreds\n    ) {\n      animateDigit(\n        prevValueHunds.current,\n        nextValueHunds.current,\n        currentHundreds > prevHundreds\n      );\n    }\n  }, [value, prevValue]);\n\n  return (\n    <div\n      className={cn(\n        \"flex flex-col items-center justify-center gap-8\",\n        className\n      )}\n    >\n      <div className=\"flex items-center gap-2 rounded-xl border bg-background p-4\">\n        <div className={cn(\"flex items-center gap-1\", digitClassName)}>\n          <div\n            className={cn(\n              \"relative h-16 w-12 overflow-hidden rounded-lg border bg-primary\"\n            )}\n          >\n            <span\n              className=\"absolute inset-0 flex items-center justify-center font-semibold text-2xl text-foreground\"\n              ref={prevValueHunds}\n              style={{ transform: \"translateY(-100%)\" }}\n            >\n              {Math.floor(prevValue / HUNDREDS_PLACE)}\n            </span>\n            <span\n              className=\"absolute inset-0 flex items-center justify-center font-semibold text-2xl text-foreground\"\n              ref={nextValueHunds}\n              style={{ transform: \"translateY(0%)\" }}\n            >\n              {Math.floor(value / HUNDREDS_PLACE)}\n            </span>\n          </div>\n          <div\n            className={cn(\n              \"relative h-16 w-12 overflow-hidden rounded-lg border bg-primary\"\n            )}\n          >\n            <span\n              className=\"absolute inset-0 flex items-center justify-center font-semibold text-2xl text-foreground\"\n              ref={prevValueTens}\n              style={{ transform: \"translateY(-100%)\" }}\n            >\n              {Math.floor(prevValue / TENS_PLACE) % TENS_PLACE}\n            </span>\n            <span\n              className=\"absolute inset-0 flex items-center justify-center font-semibold text-2xl text-foreground\"\n              ref={nextValueTens}\n              style={{ transform: \"translateY(0%)\" }}\n            >\n              {Math.floor(value / TENS_PLACE) % TENS_PLACE}\n            </span>\n          </div>\n          <div\n            className={cn(\n              \"relative h-16 w-12 overflow-hidden rounded-lg border bg-primary\"\n            )}\n          >\n            <span\n              className=\"absolute inset-0 flex items-center justify-center font-semibold text-2xl text-foreground\"\n              ref={prevValueRef}\n              style={{ transform: \"translateY(-100%)\" }}\n            >\n              {prevValue % TENS_PLACE}\n            </span>\n            <span\n              className=\"absolute inset-0 flex items-center justify-center font-semibold text-2xl text-foreground\"\n              ref={nextValueRef}\n              style={{ transform: \"translateY(0%)\" }}\n            >\n              {value % TENS_PLACE}\n            </span>\n          </div>\n        </div>\n\n        <div className=\"flex flex-col gap-1\">\n          <button\n            aria-label=\"Increase number\"\n            className={cn(\n              \"relative w-auto cursor-pointer overflow-hidden rounded-md border bg-background p-2 disabled:cursor-not-allowed disabled:opacity-50\",\n              buttonClassName\n            )}\n            disabled={value >= max}\n            onClick={add}\n            type=\"button\"\n          >\n            <Plus className=\"h-3 w-3\" />\n          </button>\n          <button\n            aria-label=\"Decrease number\"\n            className={cn(\n              \"relative w-auto cursor-pointer overflow-hidden rounded-md border bg-background p-2 disabled:cursor-not-allowed disabled:opacity-50\",\n              buttonClassName\n            )}\n            disabled={value <= min}\n            onClick={subtract}\n            type=\"button\"\n          >\n            <Minus className=\"h-3 w-3\" />\n          </button>\n        </div>\n      </div>\n    </div>\n  );\n}\n","path":"index.tsx","target":"components/smoothui/number-flow/index.tsx","type":"registry:ui"}],"name":"number-flow","registryDependencies":[],"title":"Number Flow","type":"registry:ui"}