Fortura/apps/www/public/r/counting-number.json
2025-08-20 04:12:49 -06:00

18 lines
3.4 KiB
JSON

{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "counting-number",
"type": "registry:ui",
"title": "Counting Number",
"description": "A numeric display component that smoothly animates number changes with a counting animation.",
"dependencies": [
"motion"
],
"files": [
{
"path": "registry/text/counting-number/index.tsx",
"content": "'use client';\n\nimport * as React from 'react';\nimport {\n type SpringOptions,\n type UseInViewOptions,\n useInView,\n useMotionValue,\n useSpring,\n} from 'motion/react';\n\ntype CountingNumberProps = React.ComponentProps<'span'> & {\n number: number;\n fromNumber?: number;\n padStart?: boolean;\n inView?: boolean;\n inViewMargin?: UseInViewOptions['margin'];\n inViewOnce?: boolean;\n decimalSeparator?: string;\n transition?: SpringOptions;\n decimalPlaces?: number;\n};\n\nfunction CountingNumber({\n ref,\n number,\n fromNumber = 0,\n padStart = false,\n inView = false,\n inViewMargin = '0px',\n inViewOnce = true,\n decimalSeparator = '.',\n transition = { stiffness: 90, damping: 50 },\n decimalPlaces = 0,\n className,\n ...props\n}: CountingNumberProps) {\n const localRef = React.useRef<HTMLSpanElement>(null);\n React.useImperativeHandle(ref, () => localRef.current as HTMLSpanElement);\n\n const numberStr = number.toString();\n const decimals =\n typeof decimalPlaces === 'number'\n ? decimalPlaces\n : numberStr.includes('.')\n ? (numberStr.split('.')[1]?.length ?? 0)\n : 0;\n\n const motionVal = useMotionValue(fromNumber);\n const springVal = useSpring(motionVal, transition);\n const inViewResult = useInView(localRef, {\n once: inViewOnce,\n margin: inViewMargin,\n });\n const isInView = !inView || inViewResult;\n\n React.useEffect(() => {\n if (isInView) motionVal.set(number);\n }, [isInView, number, motionVal]);\n\n React.useEffect(() => {\n const unsubscribe = springVal.on('change', (latest) => {\n if (localRef.current) {\n let formatted =\n decimals > 0\n ? latest.toFixed(decimals)\n : Math.round(latest).toString();\n\n if (decimals > 0) {\n formatted = formatted.replace('.', decimalSeparator);\n }\n\n if (padStart) {\n const finalIntLength = Math.floor(Math.abs(number)).toString().length;\n const [intPart, fracPart] = formatted.split(decimalSeparator);\n const paddedInt = intPart?.padStart(finalIntLength, '0') ?? '';\n formatted = fracPart\n ? `${paddedInt}${decimalSeparator}${fracPart}`\n : paddedInt;\n }\n\n localRef.current.textContent = formatted;\n }\n });\n return () => unsubscribe();\n }, [springVal, decimals, padStart, number, decimalSeparator]);\n\n const finalIntLength = Math.floor(Math.abs(number)).toString().length;\n const initialText = padStart\n ? '0'.padStart(finalIntLength, '0') +\n (decimals > 0 ? decimalSeparator + '0'.repeat(decimals) : '')\n : '0' + (decimals > 0 ? decimalSeparator + '0'.repeat(decimals) : '');\n\n return (\n <span\n ref={localRef}\n data-slot=\"counting-number\"\n className={className}\n {...props}\n >\n {initialText}\n </span>\n );\n}\n\nexport { CountingNumber, type CountingNumberProps };\n",
"type": "registry:ui",
"target": "components/animate-ui/text/counting-number.tsx"
}
]
}