{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "typing-text",
  "type": "registry:component",
  "title": "Typing text",
  "description": "A React typewriter that types a phrase, holds, deletes and moves on, with a blinking accent caret. Starts in view and gives screen readers the plain text.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/premium/typing-text.tsx",
      "type": "registry:ui",
      "target": "components/ui/typing-text.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { useInView, useReducedMotion } from \"motion/react\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * A typewriter that types a phrase, holds, deletes and moves to the next,\n * with a blinking caret. Starts when it scrolls into view, and screen\n * readers get the phrases as plain text instead of the keystrokes.\n */\ntype TypingTextProps = {\n  phrases?: string[]\n  /** Milliseconds per typed character. */\n  speed?: number\n  /** Milliseconds to hold a finished phrase. */\n  hold?: number\n  loop?: boolean\n  className?: string\n}\n\nfunction TypingText({ phrases = [\"Build the MVP.\", \"Ship it in 30 days.\", \"Own every line of code.\"], speed = 55, hold = 1400, loop = true, className }: TypingTextProps) {\n  const reduce = useReducedMotion()\n  const ref = React.useRef<HTMLSpanElement>(null)\n  const inView = useInView(ref, { amount: 0.6 })\n  const [i, setI] = React.useState(0)\n  const [n, setN] = React.useState(0)\n  const [deleting, setDeleting] = React.useState(false)\n  const phrase = phrases[i % phrases.length] ?? \"\"\n\n  React.useEffect(() => {\n    if (reduce || !inView) return\n    let t: number\n    if (!deleting && n < phrase.length) t = window.setTimeout(() => setN(n + 1), speed + (phrase[n] === \" \" ? 40 : Math.random() * 40))\n    else if (!deleting && n === phrase.length) {\n      if (!loop && i === phrases.length - 1) return\n      t = window.setTimeout(() => setDeleting(true), hold)\n    } else if (deleting && n > 0) t = window.setTimeout(() => setN(n - 1), speed * 0.45)\n    else {\n      setDeleting(false)\n      setI((x) => (x + 1) % phrases.length)\n    }\n    return () => window.clearTimeout(t)\n  }, [n, deleting, phrase, speed, hold, loop, i, phrases.length, reduce, inView])\n\n  const shown = reduce ? phrase : phrase.slice(0, n)\n  return (\n    <span ref={ref} data-slot=\"typing-text\" className={cn(\"inline-flex items-baseline text-3xl font-medium tracking-[-0.04em] text-fg sm:text-4xl\", className)}>\n      <span className=\"sr-only\">{phrases.join(\" \")}</span>\n      <span aria-hidden className=\"whitespace-pre\">{shown || \"​\"}</span>\n      <span aria-hidden className=\"ml-0.5 inline-block h-[0.95em] w-[0.08em] translate-y-[0.12em] rounded-full bg-accent\" style={{ animation: reduce ? undefined : \"typing-caret 1s steps(1) infinite\" }} />\n      <style>{`@keyframes typing-caret{50%{opacity:0}}`}</style>\n    </span>\n  )\n}\n\nexport { TypingText }\nexport type { TypingTextProps }\n"
    }
  ]
}
