{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "typewriter",
  "title": "Typewriter",
  "description": "Types text out character by character with a blinking cursor.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/typewriter/typewriter.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/** Props for {@link Typewriter}. */\nexport type TypewriterProps = React.ComponentPropsWithoutRef<\"span\"> & {\n  /** Show a blinking cursor while typing. Defaults to `true`. */\n  cursor?: boolean;\n  /** Milliseconds between characters. Defaults to `60`. */\n  speed?: number;\n  /** Text typed out character-by-character. */\n  text: string;\n};\n\nfunction usePrefersReducedMotion(): boolean {\n  const [reduced, setReduced] = React.useState(false);\n\n  React.useEffect(() => {\n    if (\n      typeof window === \"undefined\" ||\n      typeof window.matchMedia !== \"function\"\n    ) {\n      return;\n    }\n\n    const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const onChange = (): void => {\n      setReduced(query.matches);\n    };\n\n    onChange();\n    query.addEventListener(\"change\", onChange);\n\n    return () => {\n      query.removeEventListener(\"change\", onChange);\n    };\n  }, []);\n\n  return reduced;\n}\n\n/**\n * Types out text one character at a time with a blinking terminal cursor.\n *\n * Respects `prefers-reduced-motion`: the full text shows at once.\n *\n * @example\n * ```tsx\n * <Typewriter text=\"Hello, world\" />\n * ```\n */\nexport const Typewriter = ({\n  className,\n  cursor = true,\n  ref,\n  speed = 60,\n  text,\n  ...props\n}: TypewriterProps & { ref?: React.Ref<HTMLSpanElement> }) => {\n  const reduced = usePrefersReducedMotion();\n  const [count, setCount] = React.useState(() => (reduced ? text.length : 0));\n  const [animationKey, setAnimationKey] = React.useState({ reduced, text });\n\n  if (animationKey.reduced !== reduced || animationKey.text !== text) {\n    setAnimationKey({ reduced, text });\n    setCount(reduced ? text.length : 0);\n  }\n\n  React.useEffect(() => {\n    if (reduced) {\n      return;\n    }\n\n    const timer = setInterval(() => {\n      setCount((current) => {\n        if (current >= text.length) {\n          clearInterval(timer);\n          return current;\n        }\n        return current + 1;\n      });\n    }, speed);\n\n    return () => {\n      clearInterval(timer);\n    };\n  }, [reduced, speed, text]);\n\n  const typing = count < text.length;\n\n  return (\n    <span aria-label={text} className={cn(className)} ref={ref} {...props}>\n      <span aria-hidden=\"true\">{text.slice(0, count)}</span>\n      {cursor && typing ? (\n        <span\n          aria-hidden=\"true\"\n          className=\"ml-0.5 inline-block w-[1ch] [animation:vllnt-terminal-cursor-blink_1s_steps(1,end)_infinite] motion-reduce:animate-none\"\n        >\n          |\n        </span>\n      ) : undefined}\n    </span>\n  );\n};\nTypewriter.displayName = \"Typewriter\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.2.1",
  "stability": "stable"
}
