'use client'; import { forwardRef, HTMLAttributes } from 'react'; export interface SplitTextProps extends HTMLAttributes { /** The text to split */ children: string; /** Split by: words, chars, or lines */ splitBy?: 'words' | 'chars' | 'lines'; /** Animation delay per unit in ms */ delay?: number; /** Animation duration per unit */ duration?: number; /** Preset animation class */ variant?: 'fade' | 'slide-up' | 'slide-down' | 'scale' | 'stagger'; /** Preserve whitespace for word splits */ preserveSpaces?: boolean; } const variantClasses = { fade: 'animate-[fade-split_var(--duration)_ease_forwards]', 'slide-up': 'animate-[slide-up-split_var(--duration)_ease_forwards]', 'slide-down': 'animate-[slide-down-split_var(--duration)_ease_forwards]', scale: 'animate-[scale-split_var(--duration)_ease_forwards]', stagger: 'animate-[stagger-split_var(--duration)_cubic-bezier(0.175,0.885,0.32,1.275)_forwards]', }; export const SplitText = forwardRef( ( { children, splitBy = 'words', delay = 50, duration = 400, variant = 'fade', preserveSpaces = true, className = '', style, ...props }, ref ) => { let elements: JSX.Element[] = []; let units: string[]; switch (splitBy) { case 'chars': units = children.split(''); break; case 'lines': units = children.split('\n'); break; case 'words': default: units = children.split(' '); break; } elements = units.map((unit, index) => { const isLast = index === units.length - 1; const spaceAfter = splitBy === 'words' && !isLast && preserveSpaces ? ' ' : ''; const lineBreak = splitBy === 'lines' && !isLast ?
: ''; return ( {unit} {spaceAfter} {lineBreak} ); }); return ( {elements} ); } ); SplitText.displayName = 'SplitText'; export default SplitText;