{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"text-generate-effect","type":"registry:ui","title":"TextGenerateEffect","description":"Typewriter-style text reveal that fades in words one by one with optional blur","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["text"],"registryDependencies":["https://fancy-ui.rama.app/r/internals-dom.json","https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/text-generate-effect/TextGenerateEffect.tsx","content":"\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { useLiveRef } from \"../../internals/dom/use-live-ref\";\nimport { cn } from \"../../utils\";\n\nexport interface TextGenerateEffectProps {\n\twords: string;\n\tfilter?: boolean;\n\tduration?: number;\n\tdelay?: number;\n\tstagger?: number;\n\tclassName?: string;\n}\n\nexport function TextGenerateEffect({\n\twords,\n\tfilter = true,\n\tduration = 0.7,\n\tdelay = 0,\n\tstagger = 200,\n\tclassName,\n}: TextGenerateEffectProps) {\n\tconst wordsArray = words.split(\" \");\n\tconst scopeRef = useRef<HTMLDivElement>(null);\n\t// The reveal reads these when each timeout fires, not when it was scheduled,\n\t// so a prop changed mid-reveal applies to the words still to come.\n\tconst filterRef = useLiveRef(filter);\n\tconst staggerRef = useLiveRef(stagger);\n\n\tuseEffect(() => {\n\t\tconst scope = scopeRef.current;\n\t\tif (!scope) return;\n\t\tconst spans = scope.querySelectorAll<HTMLSpanElement>(\"span\");\n\n\t\tconst timeout = setTimeout(() => {\n\t\t\tspans.forEach((span, index) => {\n\t\t\t\tconst wordTimeout = setTimeout(() => {\n\t\t\t\t\tspan.style.opacity = \"1\";\n\t\t\t\t\tspan.style.filter = filterRef.current ? \"blur(0px)\" : \"none\";\n\t\t\t\t}, index * staggerRef.current);\n\n\t\t\t\t// Store timeout ID for cleanup\n\t\t\t\t(span as HTMLSpanElement & { _tid?: ReturnType<typeof setTimeout> })._tid = wordTimeout;\n\t\t\t});\n\t\t}, delay);\n\n\t\treturn () => {\n\t\t\tclearTimeout(timeout);\n\t\t\tspans.forEach((span) => {\n\t\t\t\tconst tid = (span as HTMLSpanElement & { _tid?: ReturnType<typeof setTimeout> })._tid;\n\t\t\t\tif (tid) clearTimeout(tid);\n\t\t\t});\n\t\t};\n\t\t// Mount-only, mirroring the source's onMount: prop changes after mount\n\t\t// do not restart the reveal sequence.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, []);\n\n\treturn (\n\t\t<div className={cn(\"leading-snug tracking-wide\", className)}>\n\t\t\t<div ref={scopeRef}>\n\t\t\t\t{wordsArray.map((word, idx) => (\n\t\t\t\t\t<span\n\t\t\t\t\t\tkey={word + idx}\n\t\t\t\t\t\tclassName=\"inline-block\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\topacity: 0,\n\t\t\t\t\t\t\tfilter: filter ? \"blur(10px)\" : \"none\",\n\t\t\t\t\t\t\ttransition: `opacity ${duration}s, filter ${duration}s`,\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{word}\n\t\t\t\t\t\t{\"\\u00A0\"}\n\t\t\t\t\t</span>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","type":"registry:ui","target":"@ui/fancy/components/text-generate-effect/TextGenerateEffect.tsx"},{"path":"react/src/components/text-generate-effect/index.ts","content":"\"use client\";\n\nexport { TextGenerateEffect } from \"./TextGenerateEffect\";\nexport type { TextGenerateEffectProps } from \"./TextGenerateEffect\";\n","type":"registry:ui","target":"@ui/fancy/components/text-generate-effect/index.ts"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/text-generate-effect?framework=react"}}