{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "install-command",
  "title": "Install Command",
  "description": "A switchable install line for a library, with the runner and installer kept apart.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/install-command/install-command.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Clipboard, Terminal } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\"\n\n/** A labelled thing to copy, when the built-in verbs do not fit. */\nexport type InstallTab = {\n  id: string\n  label: string\n  value: string\n  /** Wrap rather than scroll, for anything that is not one line. */\n  wrap?: boolean\n}\n\nexport type InstallCommandProps = Omit<\n  React.HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  /**\n   * Your own tabs, which replace the package managers entirely. For a block\n   * that offers a skill, a server config, and a curl call rather than an\n   * install.\n   */\n  tabs?: readonly InstallTab[]\n  defaultTab?: string\n  /** Arguments for a one-off runner, such as \"shadcn@latest add tabs\". */\n  run?: string\n  /** Packages to add as a dependency, such as \"my-lib\". */\n  add?: string\n  /** An instruction to paste into a coding agent, offered beside the commands. */\n  prompt?: string\n  managers?: readonly PackageManager[]\n  defaultManager?: PackageManager\n  packageLabel?: string\n  promptLabel?: string\n  note?: React.ReactNode\n}\n\nconst runners: Record<PackageManager, string> = {\n  npm: \"npx\",\n  pnpm: \"pnpm dlx\",\n  yarn: \"yarn dlx\",\n  bun: \"bunx --bun\",\n}\n\nconst installers: Record<PackageManager, string> = {\n  npm: \"npm install\",\n  pnpm: \"pnpm add\",\n  yarn: \"yarn add\",\n  bun: \"bun add\",\n}\n\nconst DEFAULT_MANAGERS = [\"npm\", \"pnpm\", \"yarn\", \"bun\"] as const\n\nexport function InstallCommand({\n  tabs,\n  defaultTab,\n  run,\n  add,\n  prompt,\n  managers = DEFAULT_MANAGERS,\n  defaultManager = \"npm\",\n  packageLabel = \"package\",\n  promptLabel = \"agent\",\n  note,\n  className,\n  ...rootProps\n}: InstallCommandProps) {\n  const custom = tabs && tabs.length > 0 ? tabs : undefined\n\n  const [manager, setManager] = React.useState<PackageManager>(defaultManager)\n  const [mode, setMode] = React.useState<\"run\" | \"add\" | \"prompt\">(\n    run ? \"run\" : add ? \"add\" : \"prompt\"\n  )\n  const [tabId, setTabId] = React.useState(\n    () => defaultTab ?? custom?.[0]?.id ?? \"\"\n  )\n  const activeTab = custom?.find((tab) => tab.id === tabId) ?? custom?.[0]\n  const [copyState, setCopyState] = React.useState<\"idle\" | \"copied\" | \"error\">(\n    \"idle\"\n  )\n\n  // The chosen manager applies to both commands, so picking pnpm and then the\n  // package option gives pnpm add rather than snapping back to the default.\n  const value = activeTab\n    ? activeTab.value\n    : mode === \"prompt\" && prompt\n      ? prompt\n      : mode === \"add\" && add\n        ? `${installers[manager]} ${add}`\n        : `${runners[manager]} ${run ?? \"\"}`.trim()\n\n  const copy = async () => {\n    try {\n      await navigator.clipboard.writeText(value)\n      setCopyState(\"copied\")\n    } catch {\n      // Denied permission, an insecure context, or a sandboxed frame.\n      setCopyState(\"error\")\n    }\n    window.setTimeout(() => setCopyState(\"idle\"), 1400)\n  }\n\n  return (\n    <div\n      data-slot=\"install-command\"\n      className={cn(\"min-w-0\", className)}\n      {...rootProps}\n    >\n      <div className=\"border-border bg-muted/40 overflow-hidden rounded-[calc(var(--radius)+0.15rem)] border\">\n        <div\n          data-slot=\"install-command-bar\"\n          className=\"border-border flex items-center gap-2 border-b px-2.5 py-1.5\"\n        >\n          <span\n            aria-hidden=\"true\"\n            className=\"border-border bg-card text-muted-foreground flex size-6 shrink-0 items-center justify-center rounded-md border\"\n          >\n            <Terminal size={13} />\n          </span>\n\n          <div\n            role=\"group\"\n            aria-label=\"Install command\"\n            className=\"flex min-w-0 flex-wrap items-center gap-0.5\"\n          >\n            {custom\n              ? custom.map((tab) => (\n                  <button\n                    key={tab.id}\n                    type=\"button\"\n                    aria-pressed={activeTab?.id === tab.id}\n                    className=\"text-muted-foreground hover:text-foreground aria-pressed:border-border aria-pressed:bg-card aria-pressed:text-foreground min-h-8 rounded-md border border-transparent px-2 font-[family-name:var(--font-mono),monospace] text-xs transition-colors duration-150 motion-reduce:transition-none\"\n                    onClick={() => setTabId(tab.id)}\n                  >\n                    {tab.label}\n                  </button>\n                ))\n              : null}\n\n            {!custom && run\n              ? managers.map((entry) => (\n                  <button\n                    key={entry}\n                    type=\"button\"\n                    aria-pressed={mode === \"run\" && manager === entry}\n                    className=\"text-muted-foreground hover:text-foreground aria-pressed:border-border aria-pressed:bg-card aria-pressed:text-foreground min-h-8 rounded-md border border-transparent px-2 font-[family-name:var(--font-mono),monospace] text-xs transition-colors duration-150 motion-reduce:transition-none\"\n                    onClick={() => {\n                      setManager(entry)\n                      setMode(\"run\")\n                    }}\n                  >\n                    {entry}\n                  </button>\n                ))\n              : null}\n\n            {!custom && add ? (\n              <>\n                {run ? (\n                  <span\n                    aria-hidden=\"true\"\n                    className=\"bg-border mx-1 h-4 w-px self-center\"\n                  />\n                ) : null}\n                <button\n                  type=\"button\"\n                  aria-pressed={mode === \"add\"}\n                  className=\"text-muted-foreground hover:text-foreground aria-pressed:border-border aria-pressed:bg-card aria-pressed:text-foreground min-h-8 rounded-md border border-transparent px-2 font-[family-name:var(--font-mono),monospace] text-xs transition-colors duration-150 motion-reduce:transition-none\"\n                  onClick={() => setMode(\"add\")}\n                >\n                  {packageLabel}\n                </button>\n              </>\n            ) : null}\n\n            {!custom && prompt ? (\n              <button\n                type=\"button\"\n                aria-pressed={mode === \"prompt\"}\n                className=\"text-muted-foreground hover:text-foreground aria-pressed:border-border aria-pressed:bg-card aria-pressed:text-foreground min-h-8 rounded-md border border-transparent px-2 font-[family-name:var(--font-mono),monospace] text-xs transition-colors duration-150 motion-reduce:transition-none\"\n                onClick={() => setMode(\"prompt\")}\n              >\n                {promptLabel}\n              </button>\n            ) : null}\n          </div>\n\n          <button\n            type=\"button\"\n            data-slot=\"install-command-copy\"\n            aria-label={\n              copyState === \"copied\"\n                ? \"Copied\"\n                : copyState === \"error\"\n                  ? \"Copy failed\"\n                  : \"Copy\"\n            }\n            className=\"text-muted-foreground hover:bg-card hover:text-foreground ml-auto inline-flex size-8 shrink-0 items-center justify-center rounded-md transition-colors duration-150 motion-reduce:transition-none\"\n            onClick={copy}\n          >\n            {copyState === \"copied\" ? (\n              <Check size={14} />\n            ) : (\n              <Clipboard size={14} />\n            )}\n          </button>\n        </div>\n\n        <pre\n          data-slot=\"install-command-value\"\n          data-mode={activeTab ? activeTab.id : mode}\n          className={cn(\n            \"text-foreground m-0 overflow-x-auto rounded-none bg-transparent px-3 py-2.5 font-[family-name:var(--font-mono),monospace] text-xs leading-relaxed\",\n            (activeTab ? activeTab.wrap : mode === \"prompt\")\n              ? \"whitespace-pre-wrap\"\n              : \"whitespace-pre\"\n          )}\n        >\n          <code>{value}</code>\n        </pre>\n      </div>\n\n      {note ? (\n        <p className=\"text-muted-foreground mt-2 text-xs\">{note}</p>\n      ) : null}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "docs",
    "install",
    "command",
    "copy"
  ],
  "type": "registry:ui"
}