{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "BlurText-TS-TW", "type": "registry:block", "title": "BlurText", "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", "dependencies": [ "motion" ], "files": [ { "path": "public/ts/tailwind/src/ts-tailwind/TextAnimations/BlurText/BlurText.tsx", "content": "import { motion, Transition, Easing } from 'motion/react';\r\nimport { useEffect, useRef, useState, useMemo } from 'react';\r\n\r\ntype BlurTextProps = {\r\n text?: string;\r\n delay?: number;\r\n className?: string;\r\n animateBy?: 'words' | 'letters';\r\n direction?: 'top' | 'bottom';\r\n threshold?: number;\r\n rootMargin?: string;\r\n animationFrom?: Record;\r\n animationTo?: Array>;\r\n easing?: Easing | Easing[];\r\n onAnimationComplete?: () => void;\r\n stepDuration?: number;\r\n};\r\n\r\nconst buildKeyframes = (\r\n from: Record,\r\n steps: Array>\r\n): Record> => {\r\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\r\n\r\n const keyframes: Record> = {};\r\n keys.forEach(k => {\r\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\r\n });\r\n return keyframes;\r\n};\r\n\r\nconst BlurText: React.FC = ({\r\n text = '',\r\n delay = 200,\r\n className = '',\r\n animateBy = 'words',\r\n direction = 'top',\r\n threshold = 0.1,\r\n rootMargin = '0px',\r\n animationFrom,\r\n animationTo,\r\n easing = (t: number) => t,\r\n onAnimationComplete,\r\n stepDuration = 0.35\r\n}) => {\r\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\r\n const [inView, setInView] = useState(false);\r\n const ref = useRef(null);\r\n\r\n useEffect(() => {\r\n if (!ref.current) return;\r\n const observer = new IntersectionObserver(\r\n ([entry]) => {\r\n if (entry.isIntersecting) {\r\n setInView(true);\r\n observer.unobserve(ref.current as Element);\r\n }\r\n },\r\n { threshold, rootMargin }\r\n );\r\n observer.observe(ref.current);\r\n return () => observer.disconnect();\r\n }, [threshold, rootMargin]);\r\n\r\n const defaultFrom = useMemo(\r\n () =>\r\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\r\n [direction]\r\n );\r\n\r\n const defaultTo = useMemo(\r\n () => [\r\n {\r\n filter: 'blur(5px)',\r\n opacity: 0.5,\r\n y: direction === 'top' ? 5 : -5\r\n },\r\n { filter: 'blur(0px)', opacity: 1, y: 0 }\r\n ],\r\n [direction]\r\n );\r\n\r\n const fromSnapshot = animationFrom ?? defaultFrom;\r\n const toSnapshots = animationTo ?? defaultTo;\r\n\r\n const stepCount = toSnapshots.length + 1;\r\n const totalDuration = stepDuration * (stepCount - 1);\r\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\r\n\r\n return (\r\n

\r\n {elements.map((segment, index) => {\r\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\r\n\r\n const spanTransition: Transition = {\r\n duration: totalDuration,\r\n times,\r\n delay: (index * delay) / 1000,\r\n ease: easing\r\n };\r\n\r\n return (\r\n \r\n {segment === ' ' ? '\\u00A0' : segment}\r\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\r\n \r\n );\r\n })}\r\n

\r\n );\r\n};\r\n\r\nexport default BlurText;\r\n", "type": "registry:component" } ] }