import TextType from '.
/TextType';
<TextType
text={["Text typing effect", "for your websites", "Happy coding!"]}
typingSpeed={75}
pauseDuration={1500}
showCursor={true}
cursorCharacter="|"
/>
"use client";
import { useEffect, useRef, useState, createElement, useMemo, useCallback } from
"react";
import { gsap } from "gsap";
import "./[Link]";
const TextType = ({
text,
as: Component = "div",
typingSpeed = 50,
initialDelay = 0,
pauseDuration = 2000,
deletingSpeed = 30,
loop = true,
className = "",
showCursor = true,
hideCursorWhileTyping = false,
cursorCharacter = "|",
cursorClassName = "",
cursorBlinkDuration = 0.5,
textColors = [],
variableSpeed,
onSentenceComplete,
startOnVisible = false,
reverseMode = false,
...props
}) => {
const [displayedText, setDisplayedText] = useState("");
const [currentCharIndex, setCurrentCharIndex] = useState(0);
const [isDeleting, setIsDeleting] = useState(false);
const [currentTextIndex, setCurrentTextIndex] = useState(0);
const [isVisible, setIsVisible] = useState(!startOnVisible);
const cursorRef = useRef(null);
const containerRef = useRef(null);
const textArray = useMemo(() => ([Link](text) ? text : [text]), [text]);
const getRandomSpeed = useCallback(() => {
if (!variableSpeed) return typingSpeed;
const { min, max } = variableSpeed;
return [Link]() * (max - min) + min;
}, [variableSpeed, typingSpeed]);
const getCurrentTextColor = () => {
if ([Link] === 0) return "#ffffff";
return textColors[currentTextIndex % [Link]];
};
useEffect(() => {
if (!startOnVisible || ![Link]) return;
const observer = new IntersectionObserver(
(entries) => {
[Link]((entry) => {
if ([Link]) {
setIsVisible(true);
}
});
},
{ threshold: 0.1 }
);
[Link]([Link]);
return () => [Link]();
}, [startOnVisible]);
useEffect(() => {
if (showCursor && [Link]) {
[Link]([Link], { opacity: 1 });
[Link]([Link], {
opacity: 0,
duration: cursorBlinkDuration,
repeat: -1,
yoyo: true,
ease: "[Link]",
});
}
}, [showCursor, cursorBlinkDuration]);
useEffect(() => {
if (!isVisible) return;
let timeout;
const currentText = textArray[currentTextIndex];
const processedText = reverseMode
? [Link]("").reverse().join("")
: currentText;
const executeTypingAnimation = () => {
if (isDeleting) {
if (displayedText === "") {
setIsDeleting(false);
if (currentTextIndex === [Link] - 1 && !loop) {
return;
}
if (onSentenceComplete) {
onSentenceComplete(textArray[currentTextIndex], currentTextIndex);
}
setCurrentTextIndex((prev) => (prev + 1) % [Link]);
setCurrentCharIndex(0);
timeout = setTimeout(() => { }, pauseDuration);
} else {
timeout = setTimeout(() => {
setDisplayedText((prev) => [Link](0, -1));
}, deletingSpeed);
}
} else {
if (currentCharIndex < [Link]) {
timeout = setTimeout(
() => {
setDisplayedText(
(prev) => prev + processedText[currentCharIndex]
);
setCurrentCharIndex((prev) => prev + 1);
},
variableSpeed ? getRandomSpeed() : typingSpeed
);
} else if ([Link] > 1) {
timeout = setTimeout(() => {
setIsDeleting(true);
}, pauseDuration);
}
}
};
if (currentCharIndex === 0 && !isDeleting && displayedText === "") {
timeout = setTimeout(executeTypingAnimation, initialDelay);
} else {
executeTypingAnimation();
}
return () => clearTimeout(timeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
currentCharIndex,
displayedText,
isDeleting,
typingSpeed,
deletingSpeed,
pauseDuration,
textArray,
currentTextIndex,
loop,
initialDelay,
isVisible,
reverseMode,
variableSpeed,
onSentenceComplete,
]);
const shouldHideCursor =
hideCursorWhileTyping &&
(currentCharIndex < textArray[currentTextIndex].length || isDeleting);
return createElement(
Component,
{
ref: containerRef,
className: `text-type ${className}`,
...props,
},
<span
className="text-type__content"
style={{ color: getCurrentTextColor() }}
>
{displayedText}
</span>,
showCursor && (
<span
ref={cursorRef}
className={`text-type__cursor ${cursorClassName} ${shouldHideCursor ?
"text-type__cursor--hidden" : ""}`}
>
{cursorCharacter}
</span>
)
);
};
export default TextType;