"use client"; import { useState, useEffect, useRef, useCallback } from "react"; import { AnimateNumber } from "motion-number"; interface TextNumberCountProps { value: number; format?: Omit & { notation?: Exclude< Intl.NumberFormatOptions["notation"], "scientific" | "engineering" >; }; locales?: Intl.LocalesArgument; className?: string; suffix?: string; prefix?: string; animateOnScroll?: boolean; startFrom?: number; duration?: number; threshold?: number; } const TextNumberCount = ({ value, format, locales = "en-US", className = "", suffix, prefix, animateOnScroll = false, startFrom, duration = 2, threshold = 0.5, }: TextNumberCountProps) => { const initialValue = animateOnScroll ? (startFrom ?? 0) : value; const [displayValue, setDisplayValue] = useState(initialValue); const [hasAnimated, setHasAnimated] = useState(false); const containerRef = useRef(null); const observerRef = useRef(null); const handleIntersection = useCallback( (entries: IntersectionObserverEntry[]) => { const [entry] = entries; if (entry?.isIntersecting && !hasAnimated) { setDisplayValue(value); setHasAnimated(true); observerRef.current?.disconnect(); } }, [value, hasAnimated] ); useEffect(() => { if (!animateOnScroll || hasAnimated || typeof window === "undefined") { return; } const element = containerRef.current; if (!element) return; observerRef.current = new IntersectionObserver(handleIntersection, { threshold: Math.min(Math.max(threshold, 0), 1), rootMargin: "0px", }); observerRef.current.observe(element); return () => { observerRef.current?.disconnect(); }; }, [animateOnScroll, hasAnimated, threshold, handleIntersection]); useEffect(() => { if (!animateOnScroll) { setDisplayValue(value); } }, [value, animateOnScroll]); const animateProps = animateOnScroll ? { animate: { duration } } : {}; const content = ( {displayValue} ); if (animateOnScroll) { return {content}; } return content; }; export default TextNumberCount;