{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "typewriter-text",
  "type": "registry:ui",
  "title": "Typewriter Text",
  "description": "Types out text character-by-character with a blinking cursor. Classic hero headline effect.",
  "dependencies": [
    "@saasflare/ui"
  ],
  "files": [
    {
      "path": "components/ui/typewriter-text.tsx",
      "type": "registry:ui",
      "content": "// @toreview\n\"use client\"\n\n/**\n * @fileoverview TypewriterText primitive — word-by-word text reveal animation with\n * a blinking cursor effect. Supports configurable word delay, skip animation,\n * and completion callback. Part of the Saasflare base component layer.\n * @module packages/ui/components/ui/typewriter-text\n * @layer core\n *\n * @component\n * @example\n * import { TypewriterText } from '@saasflare/ui';\n * <TypewriterText text=\"Hello, welcome to Saasflare.\" wordDelay={60} />\n */\n\n// =============================================================================\n// TYPEWRITER TEXT\n// components/ui/typewriter-text.tsx\n//\n// Word-by-word reveal animation with blinking cursor\n// =============================================================================\n\nimport { useState, useEffect, useRef } from 'react';\nimport { cn } from '@saasflare/ui';\nimport { useSaasflareProps, type SaasflareComponentProps } from '@saasflare/ui';\n\ninterface TypewriterTextProps extends SaasflareComponentProps {\n  text: string;\n  /** Delay between each word in ms (default: 40) */\n  wordDelay?: number;\n  /** Skip animation and show full text immediately */\n  skipAnimation?: boolean;\n  /** Callback when animation completes */\n  onComplete?: () => void;\n  /** Additional class names */\n  className?: string;\n}\n\n/**\n * Typewriter component that reveals text word-by-word\n */\nexport function TypewriterText({\n  text,\n  wordDelay = 40,\n  skipAnimation = false,\n  onComplete,\n  className,\n  animated,\n}: TypewriterTextProps) {\n  const sf = useSaasflareProps({ animated });\n  const effectiveSkip = skipAnimation || !sf.animated;\n\n  const [displayedWords, setDisplayedWords] = useState<string[]>([]);\n  const [isComplete, setIsComplete] = useState(effectiveSkip);\n  const words = useRef(text.split(' '));\n  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  // Latest-ref: an inline `onComplete` changes identity every parent render —\n  // keying the reveal effect on it would restart the animation from word one.\n  const onCompleteRef = useRef(onComplete);\n  useEffect(() => {\n    onCompleteRef.current = onComplete;\n  });\n\n  useEffect(() => {\n    // Reset when text changes\n    words.current = text.split(' ');\n\n    if (effectiveSkip) {\n      setDisplayedWords(words.current);\n      setIsComplete(true);\n      return;\n    }\n\n    setDisplayedWords([]);\n    setIsComplete(false);\n\n    let currentIndex = 0;\n\n    const revealNextWord = () => {\n      if (currentIndex < words.current.length) {\n        setDisplayedWords(words.current.slice(0, currentIndex + 1));\n        currentIndex++;\n        timeoutRef.current = setTimeout(revealNextWord, wordDelay);\n      } else {\n        setIsComplete(true);\n        onCompleteRef.current?.();\n      }\n    };\n\n    // Start animation after a brief delay\n    timeoutRef.current = setTimeout(revealNextWord, 100);\n\n    return () => {\n      if (timeoutRef.current) {\n        clearTimeout(timeoutRef.current);\n      }\n    };\n  }, [text, wordDelay, effectiveSkip]);\n\n  return (\n    <span\n      data-slot=\"typewriter-text\"\n      data-animated={String(sf.animated)}\n      className={cn('inline', className)}\n    >\n      {displayedWords.join(' ')}\n      {!isComplete && (\n        <span className=\"inline-block w-0.5 h-4 bg-primary ml-0.5 animate-pulse\" />\n      )}\n    </span>\n  );\n}\n",
      "target": "components/ui/typewriter-text.tsx"
    }
  ],
  "$meta": {
    "source": "https://ui.saasflare.io/r/typewriter-text.json"
  }
}
