"use client" import { ElementType } from "react" import { motion, Transition, Variants } from "motion/react" import { cn } from "@/lib/utils" interface TextProps extends React.HTMLAttributes { /** * The content to be displayed and animated */ children: React.ReactNode /** * HTML Tag to render the component as */ as?: ElementType /** * Initial font variation settings */ fromFontVariationSettings: string /** * Target font variation settings to animate to */ toFontVariationSettings: string /** * Animation transition configuration * @default { duration: 1.5, ease: "easeInOut" } */ transition?: Transition /** * Duration of stagger delay between elements in seconds * @default 0.1 */ staggerDuration?: number /** * Direction to stagger animations from * @default "first" */ staggerFrom?: "first" | "last" | "center" | number /** * Delay between animation repeats in seconds * @default 0.1 */ repeatDelay?: number } const BreathingText = ({ children, as = "span", fromFontVariationSettings, toFontVariationSettings, transition = { duration: 1.5, ease: "easeInOut", }, staggerDuration = 0.1, staggerFrom = "first", repeatDelay = 0.1, className, ...props }: TextProps) => { const letterVariants: Variants = { initial: { fontVariationSettings: fromFontVariationSettings }, animate: (i) => ({ fontVariationSettings: toFontVariationSettings, transition: { ...transition, repeat: Infinity, repeatType: "mirror", delay: i * staggerDuration, repeatDelay: repeatDelay, }, }), } const getCustomIndex = (index: number, total: number) => { if (typeof staggerFrom === "number") { return Math.abs(index - staggerFrom) } switch (staggerFrom) { case "first": return index case "last": return total - 1 - index case "center": default: return Math.abs(index - Math.floor(total / 2)) } } const letters = String(children).split("") const ElementTag = as return ( {letters.map((letter: string, i: number) => ( ))} {children} ) } export default BreathingText