UNPKG

@gfazioli/mantine-text-animate

Version:

The TextAnimate component allows you to animate text with various effects.

143 lines (140 loc) 3.98 kB
'use client'; import { useState, useRef, useEffect, useCallback } from 'react'; function useTypewriter(options) { const { value, animate = true, multiline = false, speed = 0.03, delay = 2e3, loop = true, onTypeEnd, onTypeLoop } = options; const textArray = Array.isArray(value) ? value : [value]; const [displayText, setDisplayText] = useState(""); const [completedLines, setCompletedLines] = useState([]); const [currentTextIndex, setCurrentTextIndex] = useState(0); const [isTyping, setIsTyping] = useState(true); const [isDeleting, setIsDeleting] = useState(false); const [isActive, setIsActive] = useState(animate); const timeoutRef = useRef(null); const prevAnimateRef = useRef(animate); const currentFullText = textArray[currentTextIndex]; useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); useEffect(() => { if (!prevAnimateRef.current && animate) { setIsActive(true); } if (prevAnimateRef.current && !animate) { setIsActive(false); setDisplayText(""); setCompletedLines([]); setCurrentTextIndex(0); setIsTyping(true); setIsDeleting(false); } prevAnimateRef.current = animate; }, [animate]); const reset = useCallback(() => { setDisplayText(""); setCompletedLines([]); setCurrentTextIndex(0); setIsTyping(true); setIsDeleting(false); }, []); const start = useCallback(() => { setIsActive(true); }, []); const stop = useCallback(() => { setIsActive(false); }, []); useEffect(() => { if (textArray.length === 0 || !isActive) return; if (isTyping && !isDeleting) { if (displayText.length < currentFullText.length) { timeoutRef.current = setTimeout(() => { setDisplayText(currentFullText.substring(0, displayText.length + 1)); }, speed * 1e3); } else { setIsTyping(false); const isLastText = currentTextIndex === textArray.length - 1; if (multiline) { timeoutRef.current = setTimeout(() => { setCompletedLines((prev) => [...prev, displayText]); setDisplayText(""); if (isLastText && loop) { setCompletedLines([]); setCurrentTextIndex(0); onTypeLoop?.(); } else if (!isLastText) { setCurrentTextIndex((prev) => prev + 1); } if (!isLastText || loop) { setIsTyping(true); } else { onTypeEnd?.(); } }, delay); } else { if ((!isLastText || loop) && !multiline) { timeoutRef.current = setTimeout(() => { setIsDeleting(true); setIsTyping(true); }, delay); } if (isLastText && loop) { onTypeLoop?.(); } if (isLastText && !loop) { onTypeEnd?.(); } } } } if (isTyping && isDeleting) { if (displayText.length > 0) { timeoutRef.current = setTimeout(() => { setDisplayText(displayText.substring(0, displayText.length - 1)); }, speed * 500); } else { setIsDeleting(false); if (currentTextIndex === textArray.length - 1 && !loop) { setCurrentTextIndex(0); } else { setCurrentTextIndex((prev) => (prev + 1) % textArray.length); } } } }, [ displayText, isActive, isTyping, isDeleting, currentFullText, multiline, textArray, currentTextIndex, speed, delay, loop, onTypeEnd, onTypeLoop ]); const outputText = multiline ? [...completedLines, displayText] : displayText; return { text: outputText, isTyping: isTyping && isActive, start, stop, reset }; } export { useTypewriter }; //# sourceMappingURL=use-typewriter.mjs.map