spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
1'use client';23import { useEffect, useRef, useState } from 'react';4import { fmt } from '@/lib/format';56/**7 * Tweens between values ONLY when `value` changes after mount (a real update arrived). No idle motion.8 * The first render prints the SSR value verbatim so there is no hydration flicker.9 */10export function AnimatedNumber({ value, digits = 1, duration = 600, className = '', style, ...rest }: { value: number | null | undefined; digits?: number; duration?: number; className?: string; style?: React.CSSProperties } & React.AriaAttributes) {11 const [display, setDisplay] = useState<number | null | undefined>(value);12 const fromRef = useRef<number | null | undefined>(value);13 const rafRef = useRef<number | null>(null);1415 useEffect(() => {16 const from = fromRef.current;17 if (value == null || from == null || value === from || typeof window === 'undefined' || window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {18 fromRef.current = value;19 setDisplay(value);20 return;21 }22 const start = performance.now();23 const f = from;24 const step = (t: number) => {25 const p = Math.min(1, (t - start) / duration);26 const e = 1 - (1 - p) ** 3; // ease-out cubic27 setDisplay(f + (value - f) * e);28 if (p < 1) rafRef.current = requestAnimationFrame(step);29 else fromRef.current = value;30 };31 rafRef.current = requestAnimationFrame(step);32 return () => {33 if (rafRef.current) cancelAnimationFrame(rafRef.current);34 fromRef.current = value;35 };36 }, [value, duration]);3738 return (39 <span className={`num ${className}`} style={style} {...rest}>40 {fmt(display, digits)}41 </span>42 );43}44