'use client';
import { forwardRef, HTMLAttributes, useEffect, useState, useCallback } from 'react';
export interface TypingTextProps extends Omit, 'children'> {
text: string;
speed?: number;
delay?: number;
showCursor?: boolean;
cursorStyle?: 'block' | 'line' | 'underscore';
variant?: 'default' | 'terminal' | 'hacker' | 'cyber' | 'ghost';
loop?: boolean;
loopDelay?: number;
deleteSpeed?: number;
onComplete?: () => void;
}
const variantStyles = {
default: '',
terminal: 'text-green-400 drop-shadow-[0_0_5px_#00ff00]',
hacker: 'text-rose-500 drop-shadow-[0_0_5px_#ff0040]',
cyber: 'text-cyan-400 drop-shadow-[0_0_5px_#00ffff]',
ghost: 'text-gray-500 opacity-80',
};
const cursorVariants = {
default: 'bg-current',
terminal: 'bg-green-400 shadow-[0_0_5px_#00ff00]',
hacker: 'bg-rose-500 shadow-[0_0_5px_#ff0040]',
cyber: 'bg-cyan-400 shadow-[0_0_5px_#00ffff]',
ghost: 'bg-gray-500',
};
const cursorSizes = {
block: 'w-[0.6em] h-[1.1em]',
line: 'w-0.5 h-[1.1em]',
underscore: 'w-[0.6em] h-0.5',
};
export const TypingText = forwardRef(
(
{
text,
speed = 50,
delay = 0,
showCursor = true,
cursorStyle = 'block',
variant = 'default',
loop = false,
loopDelay = 2000,
deleteSpeed = 30,
onComplete,
className = '',
...props
},
ref
) => {
const [displayText, setDisplayText] = useState('');
const [isTyping, setIsTyping] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const typeText = useCallback(() => {
setIsTyping(true);
let index = 0;
const typeInterval = setInterval(() => {
if (index < text.length) {
setDisplayText(text.slice(0, index + 1));
index++;
} else {
clearInterval(typeInterval);
setIsTyping(false);
if (loop) {
setTimeout(() => {
setIsDeleting(true);
let deleteIndex = text.length;
const deleteInterval = setInterval(() => {
if (deleteIndex > 0) {
setDisplayText(text.slice(0, deleteIndex - 1));
deleteIndex--;
} else {
clearInterval(deleteInterval);
setIsDeleting(false);
setTimeout(typeText, delay);
}
}, deleteSpeed);
}, loopDelay);
} else {
onComplete?.();
}
}
}, speed);
return () => clearInterval(typeInterval);
}, [text, speed, loop, loopDelay, deleteSpeed, delay, onComplete]);
useEffect(() => {
const timeout = setTimeout(typeText, delay);
return () => clearTimeout(timeout);
}, [typeText, delay]);
const showBlinkingCursor = isTyping || isDeleting || loop;
return (
{displayText}
{showCursor && (
)}
);
}
);
TypingText.displayName = 'TypingText';
export default TypingText;