{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "version-selector",
  "type": "registry:ui",
  "title": "VersionSelector",
  "description": "Compact documentation version switcher and runtime environment selector.",
  "registryDependencies": [
    "https://ui.arpitbhayani.me/r/aui.json"
  ],
  "files": [
    {
      "path": "ui/version-selector.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface VersionOption {\n  label: string;\n  value: string;\n  badge?: string;\n}\n\nexport interface VersionSelectorProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n  label?: string;\n  versions: VersionOption[];\n  value?: string;\n  defaultValue?: string;\n  onChange?: (val: string) => void;\n  name?: string;\n  disabled?: boolean;\n}\n\nexport function VersionSelector({\n  label = \"Version:\",\n  versions,\n  value: controlledValue,\n  defaultValue,\n  onChange,\n  name,\n  disabled = false,\n  className,\n  ...props\n}: VersionSelectorProps) {\n  const isControlled = controlledValue !== undefined;\n  const [uncontrolledValue, setUncontrolledValue] = React.useState<string>(\n    defaultValue ?? versions[0]?.value ?? \"\"\n  );\n  const selectedValue = isControlled ? controlledValue : uncontrolledValue;\n  const [isOpen, setIsOpen] = React.useState(false);\n  const [highlightedIndex, setHighlightedIndex] = React.useState(0);\n\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const listRef = React.useRef<HTMLUListElement>(null);\n\n  const safeVersions = React.useMemo(() => versions || [], [versions]);\n  const selectedOption = React.useMemo(\n    () => safeVersions.find((v) => v.value === selectedValue) || safeVersions[0],\n    [safeVersions, selectedValue]\n  );\n\n  React.useEffect(() => {\n    if (!isOpen) return;\n    const handleClickOutside = (e: MouseEvent) => {\n      if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n        setIsOpen(false);\n      }\n    };\n    document.addEventListener(\"mousedown\", handleClickOutside);\n    return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n  }, [isOpen]);\n\n  React.useEffect(() => {\n    if (!isOpen || !listRef.current) return;\n    const items = listRef.current.querySelectorAll<HTMLLIElement>(\"li\");\n    const activeItem = items[highlightedIndex];\n    if (activeItem) {\n      activeItem.scrollIntoView({ block: \"nearest\" });\n    }\n  }, [highlightedIndex, isOpen]);\n\n  React.useEffect(() => {\n    if (isOpen) {\n      const idx = safeVersions.findIndex((v) => v.value === selectedValue);\n      setHighlightedIndex(idx >= 0 ? idx : 0);\n    }\n  }, [isOpen, safeVersions, selectedValue]);\n\n  const handleSelect = (option: VersionOption) => {\n    if (!isControlled) {\n      setUncontrolledValue(option.value);\n    }\n    onChange?.(option.value);\n    setIsOpen(false);\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    if (disabled) return;\n\n    if (!isOpen) {\n      if (e.key === \"Enter\" || e.key === \" \" || e.key === \"ArrowDown\") {\n        e.preventDefault();\n        setIsOpen(true);\n      }\n      return;\n    }\n\n    if (e.key === \"Escape\") {\n      e.preventDefault();\n      setIsOpen(false);\n    } else if (e.key === \"ArrowDown\") {\n      e.preventDefault();\n      setHighlightedIndex((prev) => (prev < safeVersions.length - 1 ? prev + 1 : prev));\n    } else if (e.key === \"ArrowUp\") {\n      e.preventDefault();\n      setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : 0));\n    } else if (e.key === \"Enter\" || e.key === \" \") {\n      e.preventDefault();\n      if (safeVersions[highlightedIndex]) {\n        handleSelect(safeVersions[highlightedIndex]);\n      }\n    }\n  };\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn(\"inline-flex items-center gap-2 relative\", className)}\n      onKeyDown={handleKeyDown}\n      {...props}\n    >\n      {label && (\n        <span className=\"font-mono text-xs uppercase tracking-wider text-muted-foreground select-none\">\n          {label}\n        </span>\n      )}\n      <div className=\"relative inline-block\">\n        {name && <input type=\"hidden\" name={name} value={selectedValue} />}\n        <button\n          type=\"button\"\n          className={cn(\n            \"inline-flex items-center gap-1.5 font-mono text-xs font-medium px-2.5 py-1.5 rounded border border-border bg-card text-foreground cursor-pointer transition-colors hover:bg-muted/60 focus:outline-none focus:ring-2 focus:ring-primary/40 select-none\",\n            isOpen && \"border-primary ring-2 ring-primary/40\",\n            disabled && \"opacity-50 cursor-not-allowed\"\n          )}\n          onClick={() => !disabled && setIsOpen((prev) => !prev)}\n          disabled={disabled}\n          aria-haspopup=\"listbox\"\n          aria-expanded={isOpen}\n        >\n          <span>{selectedOption ? selectedOption.label : \"Select...\"}</span>\n          {selectedOption?.badge && (\n            <span className=\"inline-flex items-center text-[10px] font-bold uppercase tracking-wider px-1 py-0.5 rounded border border-border bg-muted text-muted-foreground leading-none\">\n              {selectedOption.badge}\n            </span>\n          )}\n          <svg\n            width=\"12\"\n            height=\"12\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            className={cn(\"shrink-0 text-muted-foreground transition-transform duration-150\", isOpen && \"rotate-180 text-foreground\")}\n            aria-hidden=\"true\"\n          >\n            <path d=\"m6 9 6 6 6-6\" />\n          </svg>\n        </button>\n\n        {isOpen && (\n          <div\n            className=\"absolute top-[calc(100%+4px)] left-0 min-w-full w-max z-50 bg-card border border-border rounded-md shadow-lg py-1 animate-in fade-in-0 zoom-in-95\"\n            role=\"dialog\"\n          >\n            <ul\n              ref={listRef}\n              className=\"list-none m-0 p-0 max-h-60 overflow-y-auto\"\n              role=\"listbox\"\n              tabIndex={-1}\n            >\n              {safeVersions.map((ver, idx) => {\n                const isSelected = ver.value === selectedValue;\n                const isHighlighted = idx === highlightedIndex;\n\n                return (\n                  <li\n                    key={ver.value}\n                    className={cn(\n                      \"flex items-center justify-between gap-3.5 px-2.5 py-1.5 font-mono text-xs cursor-pointer transition-colors text-foreground\",\n                      (isHighlighted || isSelected) && \"bg-muted/70 text-foreground\",\n                      isSelected && \"font-semibold\"\n                    )}\n                    role=\"option\"\n                    aria-selected={isSelected}\n                    onClick={() => handleSelect(ver)}\n                    onMouseEnter={() => setHighlightedIndex(idx)}\n                  >\n                    <span>{ver.label}</span>\n                    <div className=\"inline-flex items-center gap-1.5\">\n                      {ver.badge && (\n                        <span className=\"inline-flex items-center text-[10px] font-bold uppercase tracking-wider px-1 py-0.5 rounded border border-border bg-muted text-muted-foreground leading-none\">\n                          {ver.badge}\n                        </span>\n                      )}\n                      {isSelected && (\n                        <svg\n                          width=\"12\"\n                          height=\"12\"\n                          viewBox=\"0 0 24 24\"\n                          fill=\"none\"\n                          stroke=\"currentColor\"\n                          strokeWidth=\"2.5\"\n                          strokeLinecap=\"round\"\n                          strokeLinejoin=\"round\"\n                          className=\"text-primary shrink-0\"\n                          aria-hidden=\"true\"\n                        >\n                          <polyline points=\"20 6 9 17 4 12\" />\n                        </svg>\n                      )}\n                    </div>\n                  </li>\n                );\n              })}\n            </ul>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ]
}