"use client"; import { cn } from "@/lib/utils"; import { motion, MotionProps } from "framer-motion"; import { useEffect, useRef, useState } from "react"; interface TypingAnimationProps extends MotionProps { children: string; className?: string; duration?: number; delay?: number; as?: React.ElementType; startOnView?: boolean; } export function TypingAnimation({ children, className, duration = 50, delay = 0, as: Component = "div", startOnView = true, ...props }: TypingAnimationProps) { const MotionComponent = motion.create(Component, { forwardMotionProps: true, }); const [displayedText, setDisplayedText] = useState(""); const [started, setStarted] = useState(false); const elementRef = useRef(null); useEffect(() => { if (!startOnView) { const startTimeout = setTimeout(() => { setStarted(true); }, delay); return () => clearTimeout(startTimeout); } const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setTimeout(() => { setStarted(true); }, delay); observer.disconnect(); } }, { threshold: 0.1 } ); if (elementRef.current) { observer.observe(elementRef.current); } return () => observer.disconnect(); }, [delay, startOnView]); useEffect(() => { if (!started) return; let i = 0; const typingEffect = setInterval(() => { if (i < children.length) { setDisplayedText(children.substring(0, i + 1)); i++; } else { clearInterval(typingEffect); } }, duration); return () => { clearInterval(typingEffect); }; }, [children, duration, started]); return ( {displayedText} ); }