since3-website/modules/landingPage/ui/components/pricing/animated-number.tsx
2026-09-25 00:45:33 +02:00

49 lines
1.3 KiB
TypeScript

"use client"
import { useEffect, useRef, useState } from "react"
import { animate } from "motion/react"
/**
* Number that rolls smoothly to its new value. React renders only the first
* value; every later update is written by the animation, so the text never
* flashes to the final value before rolling.
* `format` must be a stable function (defined at module level).
*/
export function AnimatedNumber({
value,
format,
className,
}: {
value: number
format: (value: number) => string
className?: string
}) {
const ref = useRef<HTMLSpanElement>(null)
const displayed = useRef(value)
const [initial] = useState(value)
useEffect(() => {
const el = ref.current
if (!el || displayed.current === value) return
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
displayed.current = value
el.textContent = format(value)
return
}
const controls = animate(displayed.current, value, {
duration: 0.7,
ease: [0.16, 1, 0.3, 1],
onUpdate: (latest) => {
displayed.current = latest
el.textContent = format(latest)
},
})
return () => controls.stop()
}, [value, format])
return (
<span ref={ref} className={className}>
{format(initial)}
</span>
)
}