{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "search-input",
  "type": "registry:ui",
  "title": "Search Input",
  "description": "Voice-glow powered typing-reactive search composer with animated thinking orbs.",
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "voice-glow@^0.2.1"
  ],
  "registryDependencies": [
    "https://ui.devclubxnst.com/r/orb.json"
  ],
  "cssVars": {
    "theme": {
      "color-line": "var(--line)",
      "color-line-strong": "var(--line-strong)",
      "color-panel": "var(--panel)",
      "color-text": "var(--text)",
      "color-faint": "var(--faint)"
    },
    "light": {
      "line": "rgba(0, 0, 0, 0.08)",
      "line-strong": "rgba(0, 0, 0, 0.14)",
      "panel": "#ffffff",
      "text": "#09090b",
      "faint": "#a1a1aa"
    },
    "dark": {
      "line": "rgb(255 255 255 / 0.08)",
      "line-strong": "rgb(255 255 255 / 0.16)",
      "panel": "#151517",
      "text": "#f2f2f3",
      "faint": "#56565c"
    }
  },
  "files": [
    {
      "path": "src/registry/ui/search-input.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport { VoiceBeam } from \"voice-glow\";\nimport { Orb } from \"./fx/Orb\";\n\nconst emptySubscribe = () => () => {};\n\nexport type SearchComposerProps = {\n  value?: string;\n  defaultValue?: string;\n  onChange?: (v: string) => void;\n  onSubmit?: (v: string) => void;\n  onClear?: () => void;\n  busy?: boolean;\n  placeholder?: string;\n  className?: string;\n  autoFocus?: boolean;\n};\n\nexport function SearchComposer({\n  value: controlledValue,\n  defaultValue = \"\",\n  onChange,\n  onSubmit,\n  onClear,\n  busy = false,\n  placeholder = \"What are we overthinking today?\",\n  className,\n  autoFocus = true,\n}: SearchComposerProps) {\n  const isControlled = controlledValue !== undefined;\n  const [internalValue, setInternalValue] = useState(defaultValue);\n  const currentValue = isControlled ? controlledValue : internalValue;\n\n  const input = useRef<HTMLInputElement>(null);\n  const energy = useRef(0);\n  const ready = useSyncExternalStore(\n    emptySubscribe,\n    () => true,\n    () => false,\n  );\n\n  useEffect(() => {\n    let raf = 0;\n    const tick = () => {\n      energy.current *= 0.91;\n      raf = requestAnimationFrame(tick);\n    };\n    raf = requestAnimationFrame(tick);\n    return () => cancelAnimationFrame(raf);\n  }, []);\n\n  useEffect(() => {\n    const typed = input.current?.value;\n    if (typed && typed !== currentValue) {\n      if (!isControlled) {\n        setInternalValue(typed);\n      }\n      onChange?.(typed);\n    }\n  }, [currentValue, isControlled, onChange]);\n\n  const submit = () => onSubmit?.(input.current?.value ?? currentValue);\n\n  useEffect(() => {\n    const onKey = (e: KeyboardEvent) => {\n      const typing = document.activeElement instanceof HTMLInputElement;\n      if (e.key === \"/\" && !typing) {\n        e.preventDefault();\n        input.current?.focus();\n      }\n    };\n    window.addEventListener(\"keydown\", onKey);\n    return () => window.removeEventListener(\"keydown\", onKey);\n  }, []);\n\n  const form = (\n    <form\n      role=\"search\"\n      onSubmit={(e) => {\n        e.preventDefault();\n        submit();\n      }}\n      className=\"flex w-full items-center gap-3 rounded-[5px] border border-line bg-panel/95 py-3 pl-6 pr-3 backdrop-blur-2xl transition-colors duration-300 focus-within:border-line-strong has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-inset has-[input:focus-visible]:ring-zinc-500 dark:has-[input:focus-visible]:ring-white/50\"\n    >\n      <label htmlFor=\"q\" className=\"sr-only\">\n        Describe an image\n      </label>\n      <input\n        id=\"q\"\n        ref={input}\n        autoFocus={autoFocus}\n        autoComplete=\"off\"\n        spellCheck={false}\n        maxLength={300}\n        value={currentValue}\n        onChange={(e) => {\n          energy.current = Math.min(1, energy.current + 0.5);\n          if (!isControlled) {\n            setInternalValue(e.target.value);\n          }\n          onChange?.(e.target.value);\n        }}\n        onKeyDown={(e) => {\n          if (e.key === \"Enter\" && !e.nativeEvent.isComposing) {\n            e.preventDefault();\n            submit();\n          }\n          if (e.key === \"Escape\") {\n            if (!isControlled) {\n              setInternalValue(\"\");\n            }\n            onClear?.();\n            input.current?.blur();\n          }\n        }}\n        placeholder={placeholder}\n        className=\"min-w-0 flex-1 bg-transparent py-2 text-[18px] leading-8 tracking-[-0.01em] text-text outline-none placeholder:text-faint\"\n      />\n      <button\n        type=\"submit\"\n        disabled={busy || currentValue.trim().length < 2}\n        aria-label={busy ? \"Searching\" : \"Search\"}\n        className=\"grid size-12 shrink-0 place-items-center rounded-[5px] border border-line transition-all duration-300 ease-out enabled:hover:border-line-strong enabled:hover:bg-white/6 enabled:active:scale-95 disabled:opacity-45 outline-none focus-visible:ring-2 focus-visible:ring-zinc-500 dark:focus-visible:ring-white/60\"\n      >\n        <Orb\n          state={busy ? \"searching\" : \"breathing\"}\n          size={64}\n          display={30}\n          interactive={false}\n        />\n      </button>\n    </form>\n  );\n\n  if (!ready) return form;\n\n  return (\n    <VoiceBeam\n      type=\"default\"\n      theme=\"dark\"\n      colorVariant=\"colorful\"\n      level={() => energy.current}\n      processing={busy}\n      strength={0.9}\n      className={className ? `w-full ${className}` : \"w-full\"}\n    >\n      {form}\n    </VoiceBeam>\n  );\n}\n\nexport const SearchInput = SearchComposer;\nexport type SearchInputProps = SearchComposerProps;\nexport { Orb, type OrbProps } from \"./fx/Orb\";\n",
      "type": "registry:ui",
      "target": "@ui/search-input.tsx"
    },
    {
      "path": "src/registry/ui/fx/Orb.tsx",
      "content": "\"use client\";\n\nexport { Orb, type OrbProps, ORB_STATES } from \"../orb\";\n",
      "type": "registry:ui",
      "target": "@ui/fx/Orb.tsx"
    }
  ]
}
