import { useSprings, animated, SpringConfig } from '@react-spring/web'; import { useEffect, useRef, useState } from 'react'; interface SplitTextProps { text?: string; className?: string; delay?: number; animationFrom?: { opacity: number; transform: string }; animationTo?: { opacity: number; transform: string }; easing?: SpringConfig['easing']; threshold?: number; rootMargin?: string; textAlign?: 'left' | 'right' | 'center' | 'justify' | 'start' | 'end'; onLetterAnimationComplete?: () => void; } const SplitText: React.FC = ({ text = '', className = '', delay = 100, animationFrom = { opacity: 0, transform: 'translate3d(0,40px,0)' }, animationTo = { opacity: 1, transform: 'translate3d(0,0,0)' }, easing = (t: number) => t, threshold = 0.1, rootMargin = '-100px', textAlign = 'center', onLetterAnimationComplete, }) => { const words = text.split(' ').map((word) => word.split('')); const letters = words.flat(); const [inView, setInView] = useState(false); const ref = useRef(null); const animatedCount = useRef(0); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setInView(true); if (ref.current) { observer.unobserve(ref.current); } } }, { threshold, rootMargin }, ); if (ref.current) { observer.observe(ref.current); } return () => observer.disconnect(); }, [threshold, rootMargin]); const springs = useSprings( letters.length, letters.map((_, i) => ({ from: animationFrom, to: inView ? async (next: (props: any) => Promise) => { await next(animationTo); animatedCount.current += 1; if (animatedCount.current === letters.length && onLetterAnimationComplete) { onLetterAnimationComplete(); } } : animationFrom, delay: i * delay, config: { easing }, })), ); return (

{words.map((word, wordIndex) => ( {word.map((letter, letterIndex) => { const index = words.slice(0, wordIndex).reduce((acc, w) => acc + w.length, 0) + letterIndex; return ( {letter} ); })}   ))}

); }; export default SplitText;