{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sliding-number",
  "type": "registry:ui",
  "title": "Sliding Number",
  "description": "A numeric display component that smoothly animates number changes with a sliding transition effect.",
  "dependencies": [
    "motion",
    "react-use-measure"
  ],
  "files": [
    {
      "path": "registry/text/sliding-number/index.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport {\n  useSpring,\n  useTransform,\n  motion,\n  useInView,\n  type MotionValue,\n  type SpringOptions,\n  type UseInViewOptions,\n} from 'motion/react';\nimport useMeasure from 'react-use-measure';\n\nimport { cn } from '@/lib/utils';\n\ntype SlidingNumberRollerProps = {\n  prevValue: number;\n  value: number;\n  place: number;\n  transition: SpringOptions;\n};\n\nfunction SlidingNumberRoller({\n  prevValue,\n  value,\n  place,\n  transition,\n}: SlidingNumberRollerProps) {\n  const startNumber = Math.floor(prevValue / place) % 10;\n  const targetNumber = Math.floor(value / place) % 10;\n  const animatedValue = useSpring(startNumber, transition);\n\n  React.useEffect(() => {\n    animatedValue.set(targetNumber);\n  }, [targetNumber, animatedValue]);\n\n  const [measureRef, { height }] = useMeasure();\n\n  return (\n    <span\n      ref={measureRef}\n      data-slot=\"sliding-number-roller\"\n      className=\"relative inline-block w-[1ch] overflow-x-visible overflow-y-clip leading-none tabular-nums\"\n    >\n      <span className=\"invisible\">0</span>\n      {Array.from({ length: 10 }, (_, i) => (\n        <SlidingNumberDisplay\n          key={i}\n          motionValue={animatedValue}\n          number={i}\n          height={height}\n          transition={transition}\n        />\n      ))}\n    </span>\n  );\n}\n\ntype SlidingNumberDisplayProps = {\n  motionValue: MotionValue<number>;\n  number: number;\n  height: number;\n  transition: SpringOptions;\n};\n\nfunction SlidingNumberDisplay({\n  motionValue,\n  number,\n  height,\n  transition,\n}: SlidingNumberDisplayProps) {\n  const y = useTransform(motionValue, (latest) => {\n    if (!height) return 0;\n    const currentNumber = latest % 10;\n    const offset = (10 + number - currentNumber) % 10;\n    let translateY = offset * height;\n    if (offset > 5) translateY -= 10 * height;\n    return translateY;\n  });\n\n  if (!height) {\n    return <span className=\"invisible absolute\">{number}</span>;\n  }\n\n  return (\n    <motion.span\n      data-slot=\"sliding-number-display\"\n      style={{ y }}\n      className=\"absolute inset-0 flex items-center justify-center\"\n      transition={{ ...transition, type: 'spring' }}\n    >\n      {number}\n    </motion.span>\n  );\n}\n\ntype SlidingNumberProps = React.ComponentProps<'span'> & {\n  number: number | string;\n  inView?: boolean;\n  inViewMargin?: UseInViewOptions['margin'];\n  inViewOnce?: boolean;\n  padStart?: boolean;\n  decimalSeparator?: string;\n  decimalPlaces?: number;\n  transition?: SpringOptions;\n};\n\nfunction SlidingNumber({\n  ref,\n  number,\n  className,\n  inView = false,\n  inViewMargin = '0px',\n  inViewOnce = true,\n  padStart = false,\n  decimalSeparator = '.',\n  decimalPlaces = 0,\n  transition = {\n    stiffness: 200,\n    damping: 20,\n    mass: 0.4,\n  },\n  ...props\n}: SlidingNumberProps) {\n  const localRef = React.useRef<HTMLSpanElement>(null);\n  React.useImperativeHandle(ref, () => localRef.current!);\n\n  const inViewResult = useInView(localRef, {\n    once: inViewOnce,\n    margin: inViewMargin,\n  });\n  const isInView = !inView || inViewResult;\n\n  const prevNumberRef = React.useRef<number>(0);\n\n  const effectiveNumber = React.useMemo(\n    () => (!isInView ? 0 : Math.abs(Number(number))),\n    [number, isInView],\n  );\n\n  const formatNumber = React.useCallback(\n    (num: number) =>\n      decimalPlaces != null ? num.toFixed(decimalPlaces) : num.toString(),\n    [decimalPlaces],\n  );\n\n  const numberStr = formatNumber(effectiveNumber);\n  const [newIntStrRaw, newDecStrRaw = ''] = numberStr.split('.');\n  const newIntStr =\n    padStart && newIntStrRaw?.length === 1 ? '0' + newIntStrRaw : newIntStrRaw;\n\n  const prevFormatted = formatNumber(prevNumberRef.current);\n  const [prevIntStrRaw = '', prevDecStrRaw = ''] = prevFormatted.split('.');\n  const prevIntStr =\n    padStart && prevIntStrRaw.length === 1\n      ? '0' + prevIntStrRaw\n      : prevIntStrRaw;\n\n  const adjustedPrevInt = React.useMemo(() => {\n    return prevIntStr.length > (newIntStr?.length ?? 0)\n      ? prevIntStr.slice(-(newIntStr?.length ?? 0))\n      : prevIntStr.padStart(newIntStr?.length ?? 0, '0');\n  }, [prevIntStr, newIntStr]);\n\n  const adjustedPrevDec = React.useMemo(() => {\n    if (!newDecStrRaw) return '';\n    return prevDecStrRaw.length > newDecStrRaw.length\n      ? prevDecStrRaw.slice(0, newDecStrRaw.length)\n      : prevDecStrRaw.padEnd(newDecStrRaw.length, '0');\n  }, [prevDecStrRaw, newDecStrRaw]);\n\n  React.useEffect(() => {\n    if (isInView) prevNumberRef.current = effectiveNumber;\n  }, [effectiveNumber, isInView]);\n\n  const intDigitCount = newIntStr?.length ?? 0;\n  const intPlaces = React.useMemo(\n    () =>\n      Array.from({ length: intDigitCount }, (_, i) =>\n        Math.pow(10, intDigitCount - i - 1),\n      ),\n    [intDigitCount],\n  );\n  const decPlaces = React.useMemo(\n    () =>\n      newDecStrRaw\n        ? Array.from({ length: newDecStrRaw.length }, (_, i) =>\n            Math.pow(10, newDecStrRaw.length - i - 1),\n          )\n        : [],\n    [newDecStrRaw],\n  );\n\n  const newDecValue = newDecStrRaw ? parseInt(newDecStrRaw, 10) : 0;\n  const prevDecValue = adjustedPrevDec ? parseInt(adjustedPrevDec, 10) : 0;\n\n  return (\n    <span\n      ref={localRef}\n      data-slot=\"sliding-number\"\n      className={cn('flex items-center', className)}\n      {...props}\n    >\n      {isInView && Number(number) < 0 && <span className=\"mr-1\">-</span>}\n\n      {intPlaces.map((place) => (\n        <SlidingNumberRoller\n          key={`int-${place}`}\n          prevValue={parseInt(adjustedPrevInt, 10)}\n          value={parseInt(newIntStr ?? '0', 10)}\n          place={place}\n          transition={transition}\n        />\n      ))}\n\n      {newDecStrRaw && (\n        <>\n          <span>{decimalSeparator}</span>\n          {decPlaces.map((place) => (\n            <SlidingNumberRoller\n              key={`dec-${place}`}\n              prevValue={prevDecValue}\n              value={newDecValue}\n              place={place}\n              transition={transition}\n            />\n          ))}\n        </>\n      )}\n    </span>\n  );\n}\n\nexport { SlidingNumber, type SlidingNumberProps };\n",
      "type": "registry:ui",
      "target": "components/animate-ui/text/sliding-number.tsx"
    }
  ]
}