{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"motion-primitives-text-effect","type":"registry:component","title":"Text Effect","description":"Tailark motion primitives text-effect component","dependencies":["motion"],"registryDependencies":["@tailark/core-utils"],"files":[{"path":"core/motion-primitives/text-effect.tsx","type":"registry:component","target":"@ui/motion-primitives/text-effect.tsx","content":"'use client'\nimport { cn } from '@/lib/utils'\nimport { LazyMotion, domAnimation, m, AnimatePresence, type TargetAndTransition, type Transition, type Variant, type Variants } 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    children: string\n    per?: PerType\n    as?: keyof React.JSX.IntrinsicElements\n    variants?: {\n        container?: Variants\n        item?: Variants\n    }\n    className?: string\n    preset?: PresetType\n    delay?: number\n    speedReveal?: number\n    speedSegment?: number\n    trigger?: boolean\n    onAnimationComplete?: () => void\n    onAnimationStart?: () => void\n    segmentWrapperClassName?: string\n    containerTransition?: Transition\n    segmentTransition?: Transition\n    style?: React.CSSProperties\n}\n\nconst defaultStaggerTimes: Record<PerType, number> = {\n    char: 0.03,\n    word: 0.05,\n    line: 0.1,\n}\n\nconst defaultContainerVariants: Variants = {\n    hidden: { opacity: 0 },\n    visible: {\n        opacity: 1,\n        transition: {\n            staggerChildren: 0.05,\n        },\n    },\n    exit: {\n        transition: { staggerChildren: 0.05, staggerDirection: -1 },\n    },\n}\n\nconst defaultItemVariants: Variants = {\n    hidden: { opacity: 0 },\n    visible: {\n        opacity: 1,\n    },\n    exit: { opacity: 0 },\n}\n\nconst presetVariants: Record<PresetType, { container: Variants; item: Variants }> = {\n    blur: {\n        container: defaultContainerVariants,\n        item: {\n            hidden: { opacity: 0, filter: 'blur(12px)' },\n            visible: { opacity: 1, filter: 'blur(0px)' },\n            exit: { opacity: 0, filter: 'blur(12px)' },\n        },\n    },\n    'fade-in-blur': {\n        container: defaultContainerVariants,\n        item: {\n            hidden: { opacity: 0, y: 20, filter: 'blur(12px)' },\n            visible: { opacity: 1, y: 0, filter: 'blur(0px)' },\n            exit: { opacity: 0, y: 20, filter: 'blur(12px)' },\n        },\n    },\n    scale: {\n        container: defaultContainerVariants,\n        item: {\n            hidden: { opacity: 0, scale: 0 },\n            visible: { opacity: 1, scale: 1 },\n            exit: { opacity: 0, scale: 0 },\n        },\n    },\n    fade: {\n        container: defaultContainerVariants,\n        item: {\n            hidden: { opacity: 0 },\n            visible: { opacity: 1 },\n            exit: { opacity: 0 },\n        },\n    },\n    slide: {\n        container: defaultContainerVariants,\n        item: {\n            hidden: { opacity: 0, y: 20 },\n            visible: { opacity: 1, y: 0 },\n            exit: { opacity: 0, y: 20 },\n        },\n    },\n}\n\nconst AnimationComponent: React.FC<{\n    segment: string\n    variants: Variants\n    per: 'line' | 'word' | 'char'\n    segmentWrapperClassName?: string\n}> = React.memo(({ segment, variants, per, segmentWrapperClassName }) => {\n    const content =\n        per === 'line' ? (\n            <m.span\n                variants={variants}\n                className=\"block\">\n                {segment}\n            </m.span>\n        ) : per === 'word' ? (\n            <m.span\n                aria-hidden=\"true\"\n                variants={variants}\n                className=\"inline-block whitespace-pre\">\n                {segment}\n            </m.span>\n        ) : (\n            <m.span className=\"inline-block whitespace-pre\">\n                {segment.split('').map((char, charIndex) => (\n                    <m.span\n                        key={`char-${charIndex}`}\n                        aria-hidden=\"true\"\n                        variants={variants}\n                        className=\"inline-block whitespace-pre\">\n                        {char}\n                    </m.span>\n                ))}\n            </m.span>\n        )\n\n    if (!segmentWrapperClassName) {\n        return content\n    }\n\n    const defaultWrapperClassName = per === 'line' ? 'block' : 'inline-block'\n\n    return <span className={cn(defaultWrapperClassName, segmentWrapperClassName)}>{content}</span>\n})\n\nAnimationComponent.displayName = 'AnimationComponent'\n\nconst splitText = (text: string, per: 'line' | 'word' | 'char') => {\n    if (per === 'line') return text.split('\\n')\n    return text.split(/(\\s+)/)\n}\n\nconst hasTransition = (variant: Variant): variant is TargetAndTransition & { transition?: Transition } => {\n    return typeof variant === 'object' && variant !== null && 'transition' in variant\n}\n\nconst createVariantsWithTransition = (baseVariants: Variants, transition?: Transition & { exit?: Transition }): Variants => {\n    if (!transition) return baseVariants\n\n    const { ...mainTransition } = transition\n\n    return {\n        ...baseVariants,\n        visible: {\n            ...baseVariants.visible,\n            transition: {\n                ...(hasTransition(baseVariants.visible) ? baseVariants.visible.transition : {}),\n                ...mainTransition,\n            },\n        },\n        exit: {\n            ...baseVariants.exit,\n            transition: {\n                ...(hasTransition(baseVariants.exit) ? baseVariants.exit.transition : {}),\n                ...mainTransition,\n                staggerDirection: -1,\n            },\n        },\n    }\n}\n\nexport function TextEffect({ children, per = 'word', as = 'p', variants, className, preset = 'fade', delay = 0, speedReveal = 1, speedSegment = 1, trigger = true, onAnimationComplete, onAnimationStart, segmentWrapperClassName, containerTransition, segmentTransition, style }: TextEffectProps) {\n    const segments = splitText(children, per)\n    const MotionTag = m[as as keyof typeof m] as typeof m.div\n\n    const baseVariants = preset ? presetVariants[preset] : { container: defaultContainerVariants, item: defaultItemVariants }\n\n    const stagger = defaultStaggerTimes[per] / speedReveal\n\n    const baseDuration = 0.3 / speedSegment\n\n    const customStagger = hasTransition(variants?.container?.visible ?? {}) ? (variants?.container?.visible as TargetAndTransition).transition?.staggerChildren : undefined\n\n    const customDelay = hasTransition(variants?.container?.visible ?? {}) ? (variants?.container?.visible as TargetAndTransition).transition?.delayChildren : undefined\n\n    const computedVariants = {\n        container: createVariantsWithTransition(variants?.container || baseVariants.container, {\n            staggerChildren: customStagger ?? stagger,\n            delayChildren: customDelay ?? delay,\n            ...containerTransition,\n            exit: {\n                staggerChildren: customStagger ?? stagger,\n                staggerDirection: -1,\n            },\n        }),\n        item: createVariantsWithTransition(variants?.item || baseVariants.item, {\n            duration: baseDuration,\n            ...segmentTransition,\n        }),\n    }\n\n    return (\n        <LazyMotion features={domAnimation}>\n        <AnimatePresence mode=\"popLayout\">\n            {trigger && (\n                <MotionTag\n                    initial=\"hidden\"\n                    animate=\"visible\"\n                    exit=\"exit\"\n                    variants={computedVariants.container}\n                    className={className}\n                    onAnimationComplete={onAnimationComplete}\n                    onAnimationStart={onAnimationStart}\n                    style={style}>\n                    {per !== 'line' ? <span className=\"sr-only\">{children}</span> : null}\n                    {(() => {\n                        let offset = 0\n                        return segments.map((segment) => {\n                            const start = offset\n                            offset += segment.length\n                            return (\n                                <AnimationComponent\n                                    key={`${per}-${start}-${segment}`}\n                                    segment={segment}\n                                    variants={computedVariants.item}\n                                    per={per}\n                                    segmentWrapperClassName={segmentWrapperClassName}\n                                />\n                            )\n                        })\n                    })()}\n                </MotionTag>\n            )}\n        </AnimatePresence>\n        </LazyMotion>\n    )\n}\n"}]}