video-flow-b/hooks/useTypewriterText.ts
2025-10-22 20:24:01 +08:00

63 lines
2.2 KiB
TypeScript

import { useEffect, useMemo, useState } from 'react';
/**
* Renders a typewriter-style text that cycles through a list of strings.
* It types each string character-by-character, pauses, clears, then proceeds to the next.
* @param {string[]} strings - The list of sentences to cycle through.
* @param {{ typingMs?: number; pauseMs?: number; resetMs?: number }} [options] - Timing options.
* @returns {string} - The current text to display.
*/
export function useTypewriterText(
strings: string[],
options?: { typingMs?: number; pauseMs?: number; resetMs?: number }
): string {
const typingMs = options?.typingMs ?? 40;
const pauseMs = options?.pauseMs ?? 1200;
const resetMs = options?.resetMs ?? 300;
const [text, setText] = useState('');
const [listIndex, setListIndex] = useState(0);
const [charIndex, setCharIndex] = useState(0);
const [phase, setPhase] = useState<'typing' | 'pausing' | 'clearing'>('typing');
const key = useMemo(() => (strings && strings.length ? strings.join('|') : ''), [strings]);
useEffect(() => {
setText('');
setListIndex(0);
setCharIndex(0);
setPhase('typing');
}, [key]);
useEffect(() => {
const list = strings && strings.length ? strings : ['Describe the story you want to make...'];
const full = list[listIndex % list.length] ?? '';
let timer: number | undefined;
if (phase === 'typing') {
if (charIndex <= full.length) {
setText(full.slice(0, charIndex));
timer = window.setTimeout(() => setCharIndex(charIndex + 1), typingMs);
} else {
setPhase('pausing');
}
} else if (phase === 'pausing') {
timer = window.setTimeout(() => setPhase('clearing'), pauseMs);
} else {
setText('');
timer = window.setTimeout(() => {
setListIndex((listIndex + 1) % list.length);
setCharIndex(0);
setPhase('typing');
}, resetMs);
}
return () => {
if (timer) window.clearTimeout(timer);
};
}, [strings, listIndex, charIndex, phase, typingMs, pauseMs, resetMs]);
return text;
}