{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "splitting-text", "type": "registry:ui", "title": "Splitting Text", "description": "A text component that dynamically simulates a splitting animation, progressively revealing characters as if typed in real-time.", "dependencies": [ "motion" ], "files": [ { "path": "registry/text/splitting/index.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport {\n motion,\n type Variants,\n type TargetAndTransition,\n type HTMLMotionProps,\n useInView,\n type UseInViewOptions,\n} from 'motion/react';\n\ntype DefaultSplittingTextProps = {\n motionVariants?: {\n initial?: Record;\n animate?: Record;\n transition?: Record;\n stagger?: number;\n };\n inView?: boolean;\n inViewMargin?: UseInViewOptions['margin'];\n inViewOnce?: boolean;\n delay?: number;\n} & HTMLMotionProps<'div'>;\n\ntype CharsOrWordsSplittingTextProps = DefaultSplittingTextProps & {\n type?: 'chars' | 'words';\n text: string;\n};\n\ntype LinesSplittingTextProps = DefaultSplittingTextProps & {\n type: 'lines';\n text: string[];\n};\n\ntype SplittingTextProps =\n | CharsOrWordsSplittingTextProps\n | LinesSplittingTextProps;\n\nconst defaultItemVariant: Variants = {\n hidden: { x: 150, opacity: 0 },\n visible: {\n x: 0,\n opacity: 1,\n transition: { duration: 0.7, ease: 'easeOut' },\n },\n};\n\nexport const SplittingText: React.FC = ({\n ref,\n text,\n type = 'chars',\n motionVariants = {},\n inView = false,\n inViewMargin = '0px',\n inViewOnce = true,\n delay = 0,\n ...props\n}) => {\n const items = React.useMemo(() => {\n if (Array.isArray(text)) {\n return text.flatMap((line, i) => [\n {line},\n i < text.length - 1 ?
: null,\n ]);\n }\n\n if (type === 'words') {\n const tokens = text.match(/\\S+\\s*/g) || [];\n return tokens.map((token, i) => (\n {token}\n ));\n }\n\n return text\n .split('')\n .map((char, i) => {char});\n }, [text, type]);\n\n const containerVariants: Variants = {\n hidden: {},\n visible: {\n transition: {\n delayChildren: delay / 1000,\n staggerChildren:\n motionVariants.stagger ??\n (type === 'chars' ? 0.05 : type === 'words' ? 0.2 : 0.3),\n },\n },\n };\n\n const itemVariants: Variants = {\n hidden: {\n ...defaultItemVariant.hidden,\n ...(motionVariants.initial || {}),\n },\n visible: {\n ...defaultItemVariant.visible,\n ...(motionVariants.animate || {}),\n transition: {\n ...((defaultItemVariant.visible as TargetAndTransition).transition ||\n {}),\n ...(motionVariants.transition || {}),\n },\n },\n };\n\n const localRef = React.useRef(null);\n React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);\n\n const inViewResult = useInView(localRef, {\n once: inViewOnce,\n margin: inViewMargin,\n });\n const isInView = !inView || inViewResult;\n\n return (\n \n {items.map(\n (item, index) =>\n item && (\n \n \n {item}\n \n {type === 'words' && ' '}\n \n ),\n )}\n \n );\n};\n", "type": "registry:ui", "target": "components/animate-ui/text/splitting.tsx" } ] }