{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "primitive-typewriter",
  "type": "registry:ui",
  "title": "Typewriter",
  "description": "Reveal short text character by character while preserving complete accessible text.",
  "dependencies": [
    "motion"
  ],
  "categories": [
    "primitive",
    "polish-effects"
  ],
  "docs": "https://motion-lexicon.pages.dev/en/primitives/typewriter/",
  "files": [
    {
      "path": "src/registry/primitives/typewriter.tsx",
      "type": "registry:ui",
      "target": "components/motion-lexicon/primitives/typewriter.tsx",
      "content": "\"use client\";\n\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { useEffect, useMemo, useState } from \"react\";\n\nexport type TypewriterPrimitiveProps = {\n  text: string;\n  duration?: number;\n  characters?: number;\n  caret?: boolean;\n  className?: string;\n};\n\nexport function TypewriterPrimitive({\n  text,\n  duration = 1.2,\n  characters,\n  caret = true,\n  className,\n}: TypewriterPrimitiveProps) {\n  const reduceMotion = useReducedMotion();\n  const content = useMemo(() => text.slice(0, characters ?? text.length), [characters, text]);\n  const [visible, setVisible] = useState(reduceMotion ? content.length : 0);\n\n  useEffect(() => {\n    if (reduceMotion) {\n      setVisible(content.length);\n      return;\n    }\n    setVisible(0);\n    const interval = Math.max(24, (duration * 1000) / Math.max(1, content.length));\n    const timer = window.setInterval(() => {\n      setVisible((current) => {\n        if (current >= content.length) {\n          window.clearInterval(timer);\n          return current;\n        }\n        return current + 1;\n      });\n    }, interval);\n    return () => window.clearInterval(timer);\n  }, [content, duration, reduceMotion]);\n\n  return (\n    <span className={className} aria-label={content}>\n      <span aria-hidden>{content.slice(0, visible)}</span>\n      {caret ? (\n        <motion.span\n          aria-hidden\n          className=\"ml-[1px] inline-block h-[1em] w-px bg-current align-[-0.12em]\"\n          animate={{ opacity: reduceMotion ? 1 : [1, 0, 1] }}\n          transition={{ duration: 0.8, ease: \"linear\", repeat: reduceMotion ? 0 : Infinity }}\n        />\n      ) : null}\n    </span>\n  );\n}\n"
    }
  ]
}
