{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"text-effect","type":"registry:component","description":"Text effect component.","files":[{"path":"components/ui/text-effect.tsx","type":"registry:component","content":"\"use client\";\nimport { cn } from \"@/lib/utils\";\nimport type {\n\tTargetAndTransition,\n\tTransition,\n\tVariant,\n\tVariants,\n} from \"motion/react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport React from \"react\";\n\nexport type PresetType = \"blur\" | \"fade-in-blur\" | \"scale\" | \"fade\" | \"slide\";\n\nexport type PerType = \"word\" | \"char\" | \"line\";\n\nexport type TextEffectProps = {\n\tchildren: string;\n\tper?: PerType;\n\tas?: keyof React.JSX.IntrinsicElements;\n\tvariants?: {\n\t\tcontainer?: Variants;\n\t\titem?: Variants;\n\t};\n\tclassName?: string;\n\tpreset?: PresetType;\n\tdelay?: number;\n\tspeedReveal?: number;\n\tspeedSegment?: number;\n\ttrigger?: boolean;\n\tonAnimationComplete?: () => void;\n\tonAnimationStart?: () => void;\n\tsegmentWrapperClassName?: string;\n\tcontainerTransition?: Transition;\n\tsegmentTransition?: Transition;\n\tstyle?: React.CSSProperties;\n};\n\nconst defaultStaggerTimes: Record<PerType, number> = {\n\tchar: 0.03,\n\tword: 0.05,\n\tline: 0.1,\n};\n\nconst WHITESPACE_SPLIT_REGEX = /(\\s+)/;\n\nconst defaultContainerVariants: Variants = {\n\thidden: { opacity: 0 },\n\tvisible: {\n\t\topacity: 1,\n\t\ttransition: {\n\t\t\tstaggerChildren: 0.05,\n\t\t},\n\t},\n\texit: {\n\t\ttransition: { staggerChildren: 0.05, staggerDirection: -1 },\n\t},\n};\n\nconst defaultItemVariants: Variants = {\n\thidden: { opacity: 0 },\n\tvisible: {\n\t\topacity: 1,\n\t},\n\texit: { opacity: 0 },\n};\n\nconst presetVariants: Record<\n\tPresetType,\n\t{ container: Variants; item: Variants }\n> = {\n\tblur: {\n\t\tcontainer: defaultContainerVariants,\n\t\titem: {\n\t\t\thidden: { opacity: 0, filter: \"blur(12px)\" },\n\t\t\tvisible: { opacity: 1, filter: \"blur(0px)\" },\n\t\t\texit: { opacity: 0, filter: \"blur(12px)\" },\n\t\t},\n\t},\n\t\"fade-in-blur\": {\n\t\tcontainer: defaultContainerVariants,\n\t\titem: {\n\t\t\thidden: { opacity: 0, y: 20, filter: \"blur(12px)\" },\n\t\t\tvisible: { opacity: 1, y: 0, filter: \"blur(0px)\" },\n\t\t\texit: { opacity: 0, y: 20, filter: \"blur(12px)\" },\n\t\t},\n\t},\n\tscale: {\n\t\tcontainer: defaultContainerVariants,\n\t\titem: {\n\t\t\thidden: { opacity: 0, scale: 0 },\n\t\t\tvisible: { opacity: 1, scale: 1 },\n\t\t\texit: { opacity: 0, scale: 0 },\n\t\t},\n\t},\n\tfade: {\n\t\tcontainer: defaultContainerVariants,\n\t\titem: {\n\t\t\thidden: { opacity: 0 },\n\t\t\tvisible: { opacity: 1 },\n\t\t\texit: { opacity: 0 },\n\t\t},\n\t},\n\tslide: {\n\t\tcontainer: defaultContainerVariants,\n\t\titem: {\n\t\t\thidden: { opacity: 0, y: 20 },\n\t\t\tvisible: { opacity: 1, y: 0 },\n\t\t\texit: { opacity: 0, y: 20 },\n\t\t},\n\t},\n};\n\nconst AnimationComponent: React.FC<{\n\tsegment: string;\n\tvariants: Variants;\n\tper: \"line\" | \"word\" | \"char\";\n\tsegmentWrapperClassName?: string;\n}> = React.memo(({ segment, variants, per, segmentWrapperClassName }) => {\n\tlet content: React.ReactNode;\n\n\tif (per === \"line\") {\n\t\tcontent = (\n\t\t\t<motion.span className=\"block\" variants={variants}>\n\t\t\t\t{segment}\n\t\t\t</motion.span>\n\t\t);\n\t} else if (per === \"word\") {\n\t\tcontent = (\n\t\t\t<motion.span\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\tclassName=\"inline-block whitespace-pre\"\n\t\t\t\tvariants={variants}\n\t\t\t>\n\t\t\t\t{segment}\n\t\t\t</motion.span>\n\t\t);\n\t} else {\n\t\tcontent = (\n\t\t\t<motion.span className=\"inline-block whitespace-pre\">\n\t\t\t\t{segment.split(\"\").map((char, charIndex) => (\n\t\t\t\t\t<motion.span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclassName=\"inline-block whitespace-pre\"\n\t\t\t\t\t\tkey={`char-${charIndex}`}\n\t\t\t\t\t\tvariants={variants}\n\t\t\t\t\t>\n\t\t\t\t\t\t{char}\n\t\t\t\t\t</motion.span>\n\t\t\t\t))}\n\t\t\t</motion.span>\n\t\t);\n\t}\n\n\tif (!segmentWrapperClassName) {\n\t\treturn content;\n\t}\n\n\tconst defaultWrapperClassName = per === \"line\" ? \"block\" : \"inline-block\";\n\n\treturn (\n\t\t<span className={cn(defaultWrapperClassName, segmentWrapperClassName)}>\n\t\t\t{content}\n\t\t</span>\n\t);\n});\n\nAnimationComponent.displayName = \"AnimationComponent\";\n\nconst splitText = (text: string, per: PerType) => {\n\tif (per === \"line\") {\n\t\treturn text.split(\"\\n\");\n\t}\n\treturn text.split(WHITESPACE_SPLIT_REGEX);\n};\n\nconst hasTransition = (\n\tvariant?: Variant\n): variant is TargetAndTransition & { transition?: Transition } => {\n\tif (!variant) {\n\t\treturn false;\n\t}\n\treturn typeof variant === \"object\" && \"transition\" in variant;\n};\n\nconst createVariantsWithTransition = (\n\tbaseVariants: Variants,\n\ttransition?: Transition & { exit?: Transition }\n): Variants => {\n\tif (!transition) {\n\t\treturn baseVariants;\n\t}\n\n\tconst { exit: _, ...mainTransition } = transition;\n\n\treturn {\n\t\t...baseVariants,\n\t\tvisible: {\n\t\t\t...baseVariants.visible,\n\t\t\ttransition: {\n\t\t\t\t...(hasTransition(baseVariants.visible)\n\t\t\t\t\t? baseVariants.visible.transition\n\t\t\t\t\t: {}),\n\t\t\t\t...mainTransition,\n\t\t\t},\n\t\t},\n\t\texit: {\n\t\t\t...baseVariants.exit,\n\t\t\ttransition: {\n\t\t\t\t...(hasTransition(baseVariants.exit)\n\t\t\t\t\t? baseVariants.exit.transition\n\t\t\t\t\t: {}),\n\t\t\t\t...mainTransition,\n\t\t\t\tstaggerDirection: -1,\n\t\t\t},\n\t\t},\n\t};\n};\n\nexport function TextEffect({\n\tchildren,\n\tper = \"word\",\n\tas = \"p\",\n\tvariants,\n\tclassName,\n\tpreset = \"fade\",\n\tdelay = 0,\n\tspeedReveal = 1,\n\tspeedSegment = 1,\n\ttrigger = true,\n\tonAnimationComplete,\n\tonAnimationStart,\n\tsegmentWrapperClassName,\n\tcontainerTransition,\n\tsegmentTransition,\n\tstyle,\n}: TextEffectProps) {\n\tconst segments = splitText(children, per);\n\tconst MotionTag = motion[as as keyof typeof motion] as typeof motion.div;\n\n\tconst baseVariants = preset\n\t\t? presetVariants[preset]\n\t\t: { container: defaultContainerVariants, item: defaultItemVariants };\n\n\tconst stagger = defaultStaggerTimes[per] / speedReveal;\n\n\tconst baseDuration = 0.3 / speedSegment;\n\n\tconst customStagger = hasTransition(variants?.container?.visible ?? {})\n\t\t? (variants?.container?.visible as TargetAndTransition).transition\n\t\t\t\t?.staggerChildren\n\t\t: undefined;\n\n\tconst customDelay = hasTransition(variants?.container?.visible ?? {})\n\t\t? (variants?.container?.visible as TargetAndTransition).transition\n\t\t\t\t?.delayChildren\n\t\t: undefined;\n\n\tconst computedVariants = {\n\t\tcontainer: createVariantsWithTransition(\n\t\t\tvariants?.container || baseVariants.container,\n\t\t\t{\n\t\t\t\tstaggerChildren: customStagger ?? stagger,\n\t\t\t\tdelayChildren: customDelay ?? delay,\n\t\t\t\t...containerTransition,\n\t\t\t\texit: {\n\t\t\t\t\tstaggerChildren: customStagger ?? stagger,\n\t\t\t\t\tstaggerDirection: -1,\n\t\t\t\t},\n\t\t\t}\n\t\t),\n\t\titem: createVariantsWithTransition(variants?.item || baseVariants.item, {\n\t\t\tduration: baseDuration,\n\t\t\t...segmentTransition,\n\t\t}),\n\t};\n\n\treturn (\n\t\t<AnimatePresence mode=\"popLayout\">\n\t\t\t{trigger && (\n\t\t\t\t<MotionTag\n\t\t\t\t\tanimate=\"visible\"\n\t\t\t\t\tclassName={className}\n\t\t\t\t\texit=\"exit\"\n\t\t\t\t\tinitial=\"hidden\"\n\t\t\t\t\tonAnimationComplete={onAnimationComplete}\n\t\t\t\t\tonAnimationStart={onAnimationStart}\n\t\t\t\t\tstyle={style}\n\t\t\t\t\tvariants={computedVariants.container}\n\t\t\t\t>\n\t\t\t\t\t{per !== \"line\" ? <span className=\"sr-only\">{children}</span> : null}\n\t\t\t\t\t{segments.map((segment, index) => (\n\t\t\t\t\t\t<AnimationComponent\n\t\t\t\t\t\t\tkey={`${per}-${index}-${segment}`}\n\t\t\t\t\t\t\tper={per}\n\t\t\t\t\t\t\tsegment={segment}\n\t\t\t\t\t\t\tsegmentWrapperClassName={segmentWrapperClassName}\n\t\t\t\t\t\t\tvariants={computedVariants.item}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</MotionTag>\n\t\t\t)}\n\t\t</AnimatePresence>\n\t);\n}\n"}],"dependencies":["motion"]}