"use client"; import {motion, stagger, useAnimate, useInView} from "motion/react"; import * as React from "react"; import {cn} from "@/lib/utilities"; import styles from "./typewriter.module.css"; /** Single word definition consumed by the typewriter components. */ export interface TypewriterWord { /** Word content split into animated characters at render time. @default undefined */ text: string; /** Additional CSS classes merged with each rendered character. @default undefined */ className?: string; } /** Props accepted by {@link TypewriterText} and {@link TypewriterTextSmooth}. */ export interface TypewriterTextProps { /** Ordered list of words rendered by the typewriter animation. @default undefined */ words: ReadonlyArray; /** Additional CSS classes merged with the outer container. @default undefined */ className?: string; /** Additional CSS classes merged with the blinking cursor element. @default undefined */ cursorClassName?: string; } /** * Reveals text one character at a time with a stepped typewriter animation. * * @remarks * - Animated component using the `motion` library * - Renders a `
` element * - Styling via CSS Modules with `--ac-*` custom properties * - Client-side only (`"use client"` directive) * * @example * ```tsx * * ``` * * @see {@link TypewriterTextProps} for available props */ const TypewriterText = React.forwardRef( ({words, className, cursorClassName}: Readonly, ref): React.JSX.Element => { const wordsArray = words.map((word) => ({ ...word, text: [...word.text], })); const [scope, animate] = useAnimate(); const isInView = useInView(scope); React.useEffect(() => { if (!isInView) { return; } animate( "span", { display: "inline-block", opacity: 1, width: "fit-content", }, { duration: 0.3, delay: stagger(0.1), ease: "easeInOut", }, ); }, [animate, isInView]); return (
{wordsArray.map((word, wordIndex) => (
{word.text.map((character, characterIndex) => ( {character} ))}  
))}
); }, ); TypewriterText.displayName = "TypewriterText"; /** * Reveals text with a continuous width-based typewriter sweep animation. * * @remarks * - Animated component using the `motion` library * - Renders a `
` element * - Styling via CSS Modules with `--ac-*` custom properties * - Client-side only (`"use client"` directive) * * @example * ```tsx * * ``` * * @see {@link TypewriterTextProps} for available props */ const TypewriterTextSmooth = React.forwardRef( ({words, className, cursorClassName}: Readonly, ref): React.JSX.Element => { const wordsArray = words.map((word) => ({ ...word, text: [...word.text], })); const renderWords = (): React.JSX.Element => { return (
{wordsArray.map((word, wordIndex) => (
{word.text.map((character, characterIndex) => ( {character} ))}  
))}
); }; return (
{renderWords()}
); }, ); TypewriterTextSmooth.displayName = "TypewriterTextSmooth"; export {TypewriterText, TypewriterTextSmooth};