{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "install-tabs",
  "title": "Install Tabs",
  "description": "Package-manager tabs for a CLI command (pnpm, npm, yarn, bun) with a copy button. Remembers the reader's choice and keeps every instance on the page in sync.",
  "dependencies": [
    "lucide-react",
    "@radix-ui/react-tabs"
  ],
  "files": [
    {
      "path": "registry/tealmac/install-tabs.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\nimport { Check, Copy } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype PackageManager = \"pnpm\" | \"npm\" | \"yarn\" | \"bun\"\n\nconst PACKAGE_MANAGERS: PackageManager[] = [\"pnpm\", \"npm\", \"yarn\", \"bun\"]\n\n/** How each manager runs a package without installing it. */\nconst RUNNERS: Record<PackageManager, string> = {\n  pnpm: \"pnpm dlx\",\n  npm: \"npx\",\n  yarn: \"yarn dlx\",\n  bun: \"bunx --bun\",\n}\n\nconst isManager = (value: unknown): value is PackageManager =>\n  typeof value === \"string\" && (PACKAGE_MANAGERS as string[]).includes(value)\n\n/* -------------------------------------------------------------------------------------------------\n * Shared, SSR-safe preference store: localStorage when available, memory otherwise.\n * Instances with the same storageKey stay in sync, including across browser tabs.\n * -----------------------------------------------------------------------------------------------*/\n\nconst memory = new Map<string, string>()\nconst listeners = new Map<string, Set<() => void>>()\n\nfunction readStored(key: string): string | null {\n  if (memory.has(key)) return memory.get(key) ?? null\n  try {\n    return window.localStorage.getItem(key)\n  } catch {\n    return null\n  }\n}\n\nfunction writeStored(key: string, value: string) {\n  memory.set(key, value)\n  try {\n    window.localStorage.setItem(key, value)\n  } catch {\n    // Storage blocked (private mode, sandboxed iframe): the in-memory value still syncs this page.\n  }\n  listeners.get(key)?.forEach((notify) => notify())\n}\n\nfunction subscribeStored(key: string, notify: () => void) {\n  let set = listeners.get(key)\n  if (!set) listeners.set(key, (set = new Set()))\n  set.add(notify)\n  const onStorage = (event: StorageEvent) => {\n    if (event.key !== key) return\n    if (event.newValue === null) memory.delete(key)\n    else memory.set(key, event.newValue)\n    notify()\n  }\n  window.addEventListener(\"storage\", onStorage)\n  return () => {\n    set.delete(notify)\n    window.removeEventListener(\"storage\", onStorage)\n  }\n}\n\nconst noopSubscribe = () => () => {}\n\nfunction useStoredManager(key: string | undefined) {\n  const subscribe = React.useCallback(\n    (notify: () => void) => (key ? subscribeStored(key, notify) : () => {}),\n    [key]\n  )\n  const stored = React.useSyncExternalStore(\n    key ? subscribe : noopSubscribe,\n    () => (key ? readStored(key) : null),\n    // The server (and hydration) never sees storage, so markup always matches.\n    () => null\n  )\n  return isManager(stored) ? stored : null\n}\n\n/* -------------------------------------------------------------------------------------------------\n * InstallTabs\n * -----------------------------------------------------------------------------------------------*/\n\ntype InstallTabsProps = Omit<\n  React.ComponentProps<\"div\">,\n  \"defaultValue\" | \"dir\"\n> & {\n  /** Command to run through each manager's runner, e.g. \"shadcn@latest add @tealmac/button\". */\n  command?: string\n  /** Explicit command per manager; overrides `command`. Only the managers listed get a tab. */\n  commands?: Partial<Record<PackageManager, string>>\n  /** Tab selected before any stored preference is read. */\n  defaultValue?: PackageManager\n  /** Controlled selected manager. */\n  value?: PackageManager\n  /** Called when the reader picks another manager. */\n  onValueChange?: (manager: PackageManager) => void\n  /** Remember the choice in localStorage under this key and sync every instance using it. */\n  storageKey?: string\n}\n\nfunction InstallTabs({\n  command,\n  commands,\n  defaultValue = \"pnpm\",\n  value: valueProp,\n  onValueChange,\n  storageKey,\n  className,\n  ...props\n}: InstallTabsProps) {\n  const entries = React.useMemo(() => {\n    if (commands) {\n      return PACKAGE_MANAGERS.flatMap((m) =>\n        commands[m] ? [[m, commands[m]] as [PackageManager, string]] : []\n      )\n    }\n    return PACKAGE_MANAGERS.map(\n      (m) =>\n        [m, command ? `${RUNNERS[m]} ${command}` : \"\"] as [\n          PackageManager,\n          string,\n        ]\n    )\n  }, [command, commands])\n\n  const stored = useStoredManager(storageKey)\n  const [internal, setInternal] = React.useState<PackageManager>(defaultValue)\n  const wanted = valueProp ?? stored ?? internal\n  const available = entries.map(([m]) => m)\n  const value = available.includes(wanted)\n    ? wanted\n    : (available[0] ?? defaultValue)\n  const current = entries.find(([m]) => m === value)?.[1] ?? \"\"\n\n  return (\n    <TabsPrimitive.Root\n      data-slot=\"install-tabs\"\n      value={value}\n      onValueChange={(next) => {\n        if (!isManager(next)) return\n        setInternal(next)\n        if (storageKey) writeStored(storageKey, next)\n        onValueChange?.(next)\n      }}\n      className={cn(\n        \"bg-card text-card-foreground w-full min-w-0 overflow-hidden rounded-xl border\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"bg-muted/40 flex h-10 items-center gap-2 border-b ps-1.5 pe-1.5\">\n        <TabsPrimitive.List\n          aria-label=\"Package manager\"\n          className=\"flex min-w-0 [scrollbar-width:none] items-center overflow-x-auto\"\n        >\n          {entries.map(([manager]) => (\n            <TabsPrimitive.Trigger\n              key={manager}\n              value={manager}\n              className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 data-[state=active]:text-foreground data-[state=active]:after:bg-foreground relative h-10 shrink-0 px-2.5 font-mono text-xs transition-colors duration-150 outline-none after:absolute after:inset-x-2.5 after:bottom-0 after:h-px after:bg-transparent focus-visible:ring-[3px] focus-visible:ring-inset\"\n            >\n              {manager}\n            </TabsPrimitive.Trigger>\n          ))}\n        </TabsPrimitive.List>\n        <CopyCommandButton value={current} />\n      </div>\n      {entries.map(([manager, cmd]) => (\n        <TabsPrimitive.Content\n          key={manager}\n          value={manager}\n          className=\"focus-visible:ring-ring/50 overflow-x-auto px-4 py-3 outline-none focus-visible:ring-[3px] focus-visible:ring-inset\"\n        >\n          <code\n            dir=\"ltr\"\n            data-slot=\"install-tabs-command\"\n            className=\"font-mono text-[13px] leading-6 whitespace-pre\"\n          >\n            <span\n              aria-hidden=\"true\"\n              className=\"text-muted-foreground me-2 select-none\"\n            >\n              $\n            </span>\n            {cmd}\n          </code>\n        </TabsPrimitive.Content>\n      ))}\n    </TabsPrimitive.Root>\n  )\n}\n\nfunction CopyCommandButton({ value }: { value: string }) {\n  const [copied, setCopied] = React.useState(false)\n  const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n  React.useEffect(() => () => clearTimeout(timer.current), [])\n  return (\n    <>\n      <button\n        type=\"button\"\n        data-slot=\"install-tabs-copy\"\n        aria-label={copied ? \"Copied\" : \"Copy command\"}\n        title=\"Copy command\"\n        onClick={async () => {\n          try {\n            await navigator.clipboard.writeText(value)\n          } catch {\n            return\n          }\n          setCopied(true)\n          clearTimeout(timer.current)\n          timer.current = setTimeout(() => setCopied(false), 1800)\n        }}\n        className=\"text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring/50 ms-auto flex size-7 shrink-0 items-center justify-center rounded-md transition-colors duration-150 outline-none focus-visible:ring-[3px]\"\n      >\n        {copied ? (\n          <Check aria-hidden=\"true\" className=\"size-3.5\" />\n        ) : (\n          <Copy aria-hidden=\"true\" className=\"size-3.5\" />\n        )}\n      </button>\n      <span className=\"sr-only\" aria-live=\"polite\">\n        {copied ? \"Copied to clipboard\" : \"\"}\n      </span>\n    </>\n  )\n}\n\nexport {\n  InstallTabs,\n  RUNNERS as packageManagerRunners,\n  type InstallTabsProps,\n  type PackageManager,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}