{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "inputs-typewriter",
  "title": "Typewriter inputs",
  "description": "Styled inputs: the Typewriter set. 5 decorative inputs variations (TypewriterInput, CyclePlaceholderInput, CaretInput, GhostInput, MorphLabelInput) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/inputs-typewriter.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Typewriter input family: 5 text inputs built on a typed, animated placeholder. The animation is driven by useEffect plus setInterval (with cleanup) at a fixed interval (no Date.now). Every loop is off under reduced-motion. Colour comes ONLY from tokens. Each wraps a real <input> and passes native props through. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst height: Record<StyledSize, string> = {\n  sm: \"h-8 text-sm\",\n  md: \"h-9 text-sm\",\n  lg: \"h-10 text-base\",\n  xl: \"h-12 text-base\",\n}\n\nconst fieldBase =\n  \"w-full bg-transparent text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\"\n\nconst wrapBase =\n  \"relative inline-flex w-56 max-w-full items-center rounded-lg border border-field-border px-3 transition-colors focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50\"\n\ntype Props = Omit<React.ComponentProps<\"input\">, \"size\"> & { size?: StyledSize }\n\n/* Focus and filled-value tracking (we stop the animation in these two states). */\nfunction useIdle(props: Pick<Props, \"value\" | \"defaultValue\" | \"onChange\" | \"onFocus\" | \"onBlur\">) {\n  const [focused, setFocused] = React.useState(false)\n  const [internal, setInternal] = React.useState(\n    props.defaultValue != null ? String(props.defaultValue) : \"\"\n  )\n  const value = props.value != null ? String(props.value) : internal\n  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setInternal(e.target.value)\n    props.onChange?.(e)\n  }\n  const onFocus = (e: React.FocusEvent<HTMLInputElement>) => {\n    setFocused(true)\n    props.onFocus?.(e)\n  }\n  const onBlur = (e: React.FocusEvent<HTMLInputElement>) => {\n    setFocused(false)\n    props.onBlur?.(e)\n  }\n  const idle = !focused && value.length === 0\n  return { idle, value, handlers: { onChange, onFocus, onBlur } }\n}\n\n/* The loop that types out a single word letter by letter, deletes it, then moves to\n   the next. */\nfunction useTypewriter(words: string[], active: boolean) {\n  const [text, setText] = React.useState(\"\")\n  React.useEffect(() => {\n    if (!active || words.length === 0) {\n      setText(\"\")\n      return\n    }\n    let wordIndex = 0\n    let charIndex = 0\n    let deleting = false\n    const id = setInterval(() => {\n      const word = words[wordIndex % words.length]\n      if (!deleting) {\n        charIndex += 1\n        setText(word.slice(0, charIndex))\n        if (charIndex >= word.length) deleting = true\n      } else {\n        charIndex -= 1\n        setText(word.slice(0, charIndex))\n        if (charIndex <= 0) {\n          deleting = false\n          wordIndex += 1\n        }\n      }\n    }, 110)\n    return () => clearInterval(id)\n  }, [active, words])\n  return text\n}\n\n/* Typewriter: placeholder metni dongu halinde yazilir ve silinir. */\nexport function TypewriterInput({\n  className,\n  size = \"md\",\n  words = [\"Search projects...\", \"Search people...\", \"Search anything...\"],\n  placeholder,\n  ...props\n}: Props & { words?: string[] }) {\n  const reduce = useReducedMotion()\n  const { idle, handlers } = useIdle(props)\n  const active = !reduce && idle\n  const typed = useTypewriter(words, active)\n  return (\n    <span data-slot=\"styled-input\" className={cn(wrapBase, height[size], className)}>\n      <input\n        {...props}\n        {...handlers}\n        placeholder={active ? typed : placeholder}\n        className={cn(fieldBase, \"h-full\")}\n      />\n    </span>\n  )\n}\n\n/* CyclePlaceholder: it cycles between several placeholder strings as whole texts. */\nexport function CyclePlaceholderInput({\n  className,\n  size = \"md\",\n  placeholders = [\"name@example.com\", \"you@work.com\", \"hello@team.io\"],\n  placeholder,\n  ...props\n}: Props & { placeholders?: string[] }) {\n  const reduce = useReducedMotion()\n  const { idle, handlers } = useIdle(props)\n  const active = !reduce && idle && placeholders.length > 0\n  const [index, setIndex] = React.useState(0)\n  React.useEffect(() => {\n    if (!active) return\n    const id = setInterval(() => setIndex((i) => (i + 1) % placeholders.length), 2200)\n    return () => clearInterval(id)\n  }, [active, placeholders])\n  return (\n    <span data-slot=\"styled-input\" className={cn(wrapBase, height[size], className)}>\n      <input\n        {...props}\n        {...handlers}\n        placeholder={active ? placeholders[index] : placeholder}\n        className={cn(fieldBase, \"h-full\")}\n      />\n    </span>\n  )\n}\n\n/* Caret: metinden once yanip sonen token caret blogu. */\nexport function CaretInput({ className, size = \"md\", ...props }: Props) {\n  const reduce = useReducedMotion()\n  const { idle, handlers } = useIdle(props)\n  return (\n    <span data-slot=\"styled-input\" className={cn(wrapBase, \"gap-1.5\", height[size], className)}>\n      <span\n        aria-hidden=\"true\"\n        className={cn(\n          \"h-4 w-0.5 shrink-0 rounded-full bg-primary\",\n          idle && !reduce ? \"animate-pulse motion-reduce:animate-none\" : \"opacity-0\"\n        )}\n      />\n      <input {...props} {...handlers} className={cn(fieldBase, \"h-full\")} />\n    </span>\n  )\n}\n\n/* Ghost: a faint suggestion ghost; accepted with Tab / right arrow. */\nexport function GhostInput({\n  className,\n  size = \"md\",\n  suggestion = \"\",\n  ...props\n}: Props & { suggestion?: string }) {\n  const [value, setValue] = React.useState(props.defaultValue != null ? String(props.defaultValue) : \"\")\n  const current = props.value != null ? String(props.value) : value\n  const showGhost =\n    suggestion.length > 0 &&\n    current.length > 0 &&\n    suggestion.toLowerCase().startsWith(current.toLowerCase()) &&\n    suggestion.length > current.length\n  const remainder = showGhost ? suggestion.slice(current.length) : \"\"\n\n  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setValue(e.target.value)\n    props.onChange?.(e)\n  }\n  const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n    if (showGhost && (e.key === \"Tab\" || e.key === \"ArrowRight\")) {\n      e.preventDefault()\n      setValue(suggestion)\n    }\n    props.onKeyDown?.(e)\n  }\n  return (\n    <span data-slot=\"styled-input\" className={cn(wrapBase, height[size], className)}>\n      <span className=\"relative inline-flex h-full w-full items-center\">\n        <span\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute inset-0 flex items-center whitespace-pre text-muted-foreground/50\"\n        >\n          {current}\n          <span>{remainder}</span>\n        </span>\n        <input\n          {...props}\n          value={current}\n          onChange={onChange}\n          onKeyDown={onKeyDown}\n          className={cn(fieldBase, \"relative h-full\")}\n        />\n      </span>\n    </span>\n  )\n}\n\n/* MorphLabel: odakta / dolu iken etiket kayarak kuculur ve token'a doner. */\nexport function MorphLabelInput({\n  className,\n  size = \"md\",\n  id,\n  label = \"Label\",\n  ...props\n}: Props & { label?: string }) {\n  const autoId = React.useId()\n  const inputId = id ?? autoId\n  return (\n    <span\n      data-slot=\"styled-input\"\n      className={cn(\n        \"relative inline-flex w-56 max-w-full items-center rounded-lg border border-field-border px-3 transition-colors focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50\",\n        height[size],\n        className\n      )}\n    >\n      <input id={inputId} placeholder=\" \" {...props} className={cn(fieldBase, \"peer h-full\")} />\n      <label\n        htmlFor={inputId}\n        className=\"pointer-events-none absolute left-3 top-1/2 origin-left -translate-y-1/2 text-muted-foreground transition-all duration-(--motion-base) ease-(--motion-ease) peer-focus:top-0 peer-focus:-translate-y-1/2 peer-focus:scale-90 peer-focus:bg-background peer-focus:px-1 peer-focus:text-primary peer-[:not(:placeholder-shown)]:top-0 peer-[:not(:placeholder-shown)]:scale-90 peer-[:not(:placeholder-shown)]:bg-background peer-[:not(:placeholder-shown)]:px-1 motion-reduce:transition-none\"\n      >\n        {label}\n      </label>\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/inputs-typewriter.tsx"
    }
  ],
  "categories": [
    "styled",
    "inputs"
  ],
  "type": "registry:component"
}