'use client';
import {
motion,
VariantLabels,
Target,
TargetAndTransition,
Transition,
} from 'motion/react';
export type TextRollProps = {
children: string;
duration?: number;
getEnterDelay?: (index: number) => number;
getExitDelay?: (index: number) => number;
className?: string;
transition?: Transition;
variants?: {
enter: {
initial: Target | VariantLabels | boolean;
animate: TargetAndTransition | VariantLabels;
};
exit: {
initial: Target | VariantLabels | boolean;
animate: TargetAndTransition | VariantLabels;
};
};
onAnimationComplete?: () => void;
};
export function TextRoll({
children,
duration = 0.5,
getEnterDelay = (i) => i * 0.1,
getExitDelay = (i) => i * 0.1 + 0.2,
className,
transition = { ease: 'easeIn' },
variants,
onAnimationComplete,
}: TextRollProps) {
const defaultVariants = {
enter: {
initial: { rotateX: 0 },
animate: { rotateX: 90 },
},
exit: {
initial: { rotateX: 90 },
animate: { rotateX: 0 },
},
} as const;
const letters = children.split('');
return (
<span className={className}>
{letters.map((letter, i) => {
return (
<span
key={i}
className='relative inline-block [perspective:10000px] [transform-style:preserve-3d] [width:auto]'
aria-hidden='true'
>
<motion.span
className='absolute inline-block [backface-visibility:hidden] [transform-origin:50%_25%]'
initial={
variants?.enter?.initial ?? defaultVariants.enter.initial
}
animate={
variants?.enter?.animate ?? defaultVariants.enter.animate
}
transition={{
...transition,
duration,
delay: getEnterDelay(i),
}}
>
{letter === ' ' ? '\u00A0' : letter}
</motion.span>
<motion.span
className='absolute inline-block [backface-visibility:hidden] [transform-origin:50%_100%]'
initial={variants?.exit?.initial ?? defaultVariants.exit.initial}
animate={variants?.exit?.animate ?? defaultVariants.exit.animate}
transition={{
...transition,
duration,
delay: getExitDelay(i),
}}
onAnimationComplete={
letters.length === i + 1 ? onAnimationComplete : undefined
}
>
{letter === ' ' ? '\u00A0' : letter}
</motion.span>
<span className='invisible'>
{letter === ' ' ? '\u00A0' : letter}
</span>
</span>
);
})}
<span className='sr-only'>{children}</span>
</span>
);
}