{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "typewriter",
  "title": "Typewriter",
  "description": "Types and deletes a list of phrases with a blinking cursor.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/new-york/typewriter/typewriter.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { useReducedMotion } from \"motion/react\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface TypewriterProps {\n  /** Words or phrases to type, in order. */\n  words: string[]\n  /** Milliseconds per typed character. Default: 70 */\n  typeSpeed?: number\n  /** Milliseconds per deleted character. Default: 40 */\n  deleteSpeed?: number\n  /** Pause after a word is fully typed, in ms. Default: 1400 */\n  pause?: number\n  /** Cycle through the words forever. Default: true */\n  loop?: boolean\n  /** Show a blinking cursor. Default: true */\n  cursor?: boolean\n  /** Character used for the cursor. Default: \"|\" */\n  cursorChar?: string\n  className?: string\n  cursorClassName?: string\n}\n\nexport function Typewriter({\n  words,\n  typeSpeed = 70,\n  deleteSpeed = 40,\n  pause = 1400,\n  loop = true,\n  cursor = true,\n  cursorChar = \"|\",\n  className,\n  cursorClassName,\n}: TypewriterProps) {\n  const reduced = useReducedMotion()\n  const [index, setIndex] = useState(0)\n  const [text, setText] = useState(\"\")\n  const [deleting, setDeleting] = useState(false)\n\n  const word = words[index % words.length] ?? \"\"\n  const isLast = index === words.length - 1\n\n  useEffect(() => {\n    if (reduced) return\n\n    // Finished typing the current word\n    if (!deleting && text === word) {\n      if (isLast && !loop) return\n      const t = setTimeout(() => setDeleting(true), pause)\n      return () => clearTimeout(t)\n    }\n\n    const t = setTimeout(() => {\n      if (!deleting) {\n        setText(word.slice(0, text.length + 1))\n        return\n      }\n      const next = text.slice(0, -1)\n      setText(next)\n      // Finished deleting: move to the next word\n      if (next === \"\") {\n        setDeleting(false)\n        setIndex((i) => (i + 1) % words.length)\n      }\n    }, deleting ? deleteSpeed : typeSpeed)\n    return () => clearTimeout(t)\n  }, [text, deleting, word, isLast, loop, pause, typeSpeed, deleteSpeed, words.length, reduced])\n\n  return (\n    <span className={cn(\"inline-flex items-baseline\", className)} aria-label={words.join(\", \")}>\n      <span aria-hidden=\"true\" className=\"whitespace-pre\">\n        {reduced ? words[0] : text}\n      </span>\n      {cursor && (\n        <span\n          aria-hidden=\"true\"\n          className={cn(\"ml-0.5 animate-[mc-blink_1s_steps(1)_infinite]\", cursorClassName)}\n        >\n          {cursorChar}\n        </span>\n      )}\n      <style>{`@keyframes mc-blink{50%{opacity:0}}`}</style>\n    </span>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "Docs, live playground and every prop: https://trytweenly.vercel.app/docs/components/typewriter",
  "type": "registry:component"
}