18 lines
4.2 KiB
JSON
18 lines
4.2 KiB
JSON
{
|
|
"$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<string, any>;\n animate?: Record<string, any>;\n transition?: Record<string, any>;\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<SplittingTextProps> = ({\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<React.ReactNode[]>(() => {\n if (Array.isArray(text)) {\n return text.flatMap((line, i) => [\n <React.Fragment key={`line-${i}`}>{line}</React.Fragment>,\n i < text.length - 1 ? <br key={`br-${i}`} /> : null,\n ]);\n }\n\n if (type === 'words') {\n const tokens = text.match(/\\S+\\s*/g) || [];\n return tokens.map((token, i) => (\n <React.Fragment key={i}>{token}</React.Fragment>\n ));\n }\n\n return text\n .split('')\n .map((char, i) => <React.Fragment key={i}>{char}</React.Fragment>);\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<HTMLDivElement>(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 <motion.span\n ref={localRef}\n initial=\"hidden\"\n animate={isInView ? 'visible' : 'hidden'}\n variants={containerVariants}\n {...props}\n >\n {items.map(\n (item, index) =>\n item && (\n <React.Fragment key={index}>\n <motion.span\n key={index}\n variants={itemVariants}\n style={{\n display: 'inline-block',\n whiteSpace:\n type === 'chars'\n ? 'pre'\n : Array.isArray(text)\n ? 'normal'\n : 'normal',\n }}\n >\n {item}\n </motion.span>\n {type === 'words' && ' '}\n </React.Fragment>\n ),\n )}\n </motion.span>\n );\n};\n",
|
|
"type": "registry:ui",
|
|
"target": "components/animate-ui/text/splitting.tsx"
|
|
}
|
|
]
|
|
} |