{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "package-command",
  "title": "Package Command",
  "description": "A copyable install command with bun, pnpm, yarn and npm tabs. Pass the npm form and it rewrites it for each package manager; the choice is remembered and shared across every command on the page.",
  "dependencies": [
    "@base-ui/react",
    "@hugeicons/core-free-icons",
    "@hugeicons/react",
    "cn"
  ],
  "files": [
    {
      "path": "registry/notra/package-command/components/package-command.tsx",
      "content": "\"use client\";\n\nimport { Tabs } from \"@base-ui/react/tabs\";\nimport {\n  Alert02Icon,\n  Copy01Icon,\n  Tick02Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useMemo, useRef, useState } from \"react\";\n\nimport {\n  PACKAGE_COMMAND_COPIED_MS,\n  PACKAGE_COMMAND_COPY_LABELS,\n  PACKAGE_MANAGERS,\n} from \"../constants/package-command\";\nimport { usePackageManager } from \"../hooks/use-package-manager\";\nimport {\n  convertNpmCommand,\n  toPackageManager,\n  writePackageManager,\n} from \"../lib/package-command\";\nimport type { CopyStatus, PackageCommandProps } from \"../types/package-command\";\nimport { PackageManagerIcon } from \"./package-manager-icon\";\n\nfunction CopyCommandButton({ text }: { text: string }) {\n  const [status, setStatus] = useState<CopyStatus>(\"idle\");\n  const resetRef = useRef(0);\n\n  const copy = async () => {\n    window.clearTimeout(resetRef.current);\n    try {\n      await navigator.clipboard.writeText(text);\n      setStatus(\"copied\");\n    } catch {\n      setStatus(\"failed\");\n    }\n    resetRef.current = window.setTimeout(\n      () => setStatus(\"idle\"),\n      PACKAGE_COMMAND_COPIED_MS\n    );\n  };\n\n  return (\n    <button\n      aria-label={PACKAGE_COMMAND_COPY_LABELS[status]}\n      className=\"text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-ring relative inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-[10px] outline-2 outline-transparent transition-colors\"\n      onClick={copy}\n      type=\"button\"\n    >\n      <HugeiconsIcon\n        className={cn(\n          \"transition-[opacity,scale] duration-150\",\n          status !== \"idle\" && \"scale-50 opacity-0\"\n        )}\n        icon={Copy01Icon}\n        size={15}\n      />\n      <HugeiconsIcon\n        className={cn(\n          \"absolute transition-[opacity,scale] duration-150\",\n          status !== \"copied\" && \"scale-50 opacity-0\"\n        )}\n        icon={Tick02Icon}\n        size={15}\n      />\n      <HugeiconsIcon\n        className={cn(\n          \"text-destructive absolute transition-[opacity,scale] duration-150\",\n          status !== \"failed\" && \"scale-50 opacity-0\"\n        )}\n        icon={Alert02Icon}\n        size={15}\n      />\n    </button>\n  );\n}\n\nexport function PackageCommand({ command, className }: PackageCommandProps) {\n  const manager = usePackageManager();\n  const commands = useMemo(() => convertNpmCommand(command), [command]);\n\n  return (\n    <Tabs.Root\n      className={cn(\n        \"border-shell-border bg-shell w-full min-w-0 rounded-2xl border p-0.5 pt-0\",\n        className\n      )}\n      data-slot=\"package-command\"\n      onValueChange={(value) => {\n        const next = toPackageManager(value);\n        if (next) {\n          writePackageManager(next);\n        }\n      }}\n      value={manager}\n    >\n      <Tabs.List\n        aria-label=\"Package manager\"\n        className=\"relative flex h-9 items-stretch gap-3.5 overflow-x-auto px-3 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n      >\n        {PACKAGE_MANAGERS.map((item) => (\n          <Tabs.Tab\n            className=\"text-muted-foreground hover:text-foreground data-active:text-foreground focus-visible:outline-ring flex shrink-0 cursor-pointer items-center gap-1.5 rounded-sm font-mono text-xs outline-2 -outline-offset-2 outline-transparent transition-colors\"\n            key={item}\n            value={item}\n          >\n            <PackageManagerIcon manager={item} />\n            {item}\n          </Tabs.Tab>\n        ))}\n        <Tabs.Indicator className=\"bg-foreground absolute bottom-1 left-(--active-tab-left) h-0.5 w-(--active-tab-width) rounded-full transition-[left,width] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none\" />\n      </Tabs.List>\n      {PACKAGE_MANAGERS.map((item) => (\n        <Tabs.Panel\n          className=\"border-table-card-border bg-table-card shadow-table-card flex h-11 min-w-0 items-center gap-2 rounded-[14px] border ps-3.5 pe-1.5 outline-none\"\n          key={item}\n          value={item}\n        >\n          <code className=\"min-w-0 flex-1 overflow-x-auto mask-[linear-gradient(to_right,black_calc(100%-1rem),transparent)] font-mono text-[0.8125rem] whitespace-nowrap [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\">\n            <span className=\"text-muted-foreground select-none\">$ </span>\n            {commands[item]}\n            <span className=\"inline-block w-4\" />\n          </code>\n          <CopyCommandButton text={commands[item]} />\n        </Tabs.Panel>\n      ))}\n    </Tabs.Root>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/package-command/components/package-command.tsx"
    },
    {
      "path": "registry/notra/package-command/components/package-manager-icon.tsx",
      "content": "import type { PackageManagerIconProps } from \"../types/package-command\";\n\nconst PATHS = {\n  bun: \"M12 22.596c6.628 0 12-4.338 12-9.688 0-3.318-2.057-6.248-5.219-7.986-1.286-.715-2.297-1.357-3.139-1.89C14.058 2.025 13.08 1.404 12 1.404c-1.097 0-2.334.785-3.966 1.821a49.92 49.92 0 0 1-2.816 1.697C2.057 6.66 0 9.59 0 12.908c0 5.35 5.372 9.687 12 9.687v.001ZM10.599 4.715c.334-.759.503-1.58.498-2.409 0-.145.202-.187.23-.029.658 2.783-.902 4.162-2.057 4.624-.124.048-.199-.121-.103-.209a5.763 5.763 0 0 0 1.432-1.977Zm2.058-.102a5.82 5.82 0 0 0-.782-2.306v-.016c-.069-.123.086-.263.185-.172 1.962 2.111 1.307 4.067.556 5.051-.082.103-.23-.003-.189-.126a5.85 5.85 0 0 0 .23-2.431Zm1.776-.561a5.727 5.727 0 0 0-1.612-1.806v-.014c-.112-.085-.024-.274.114-.218 2.595 1.087 2.774 3.18 2.459 4.407a.116.116 0 0 1-.049.071.11.11 0 0 1-.153-.026.122.122 0 0 1-.022-.083 5.891 5.891 0 0 0-.737-2.331Zm-5.087.561c-.617.546-1.282.76-2.063 1-.117 0-.195-.078-.156-.181 1.752-.909 2.376-1.649 2.999-2.778 0 0 .155-.118.188.085 0 .304-.349 1.329-.968 1.874Zm4.945 11.237a2.957 2.957 0 0 1-.937 1.553c-.346.346-.8.565-1.286.62a2.178 2.178 0 0 1-1.327-.62 2.955 2.955 0 0 1-.925-1.553.244.244 0 0 1 .064-.198.234.234 0 0 1 .193-.069h3.965a.226.226 0 0 1 .19.07c.05.053.073.125.063.197Zm-5.458-2.176a1.862 1.862 0 0 1-2.384-.245 1.98 1.98 0 0 1-.233-2.447c.207-.319.503-.566.848-.713a1.84 1.84 0 0 1 1.092-.11c.366.075.703.261.967.531a1.98 1.98 0 0 1 .408 2.114 1.931 1.931 0 0 1-.698.869v.001Zm8.495.005a1.86 1.86 0 0 1-2.381-.253 1.964 1.964 0 0 1-.547-1.366c0-.384.11-.76.32-1.079.207-.319.503-.567.849-.713a1.844 1.844 0 0 1 1.093-.108c.367.076.704.262.968.534a1.98 1.98 0 0 1 .4 2.117 1.932 1.932 0 0 1-.702.868Z\",\n  pnpm: \"M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z\",\n  yarn: \"M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm.768 4.105c.183 0 .363.053.525.157.125.083.287.185.755 1.154.31-.088.468-.042.551-.019.204.056.366.19.463.375.477.917.542 2.553.334 3.605-.241 1.232-.755 2.029-1.131 2.576.324.329.778.899 1.117 1.825.278.774.31 1.478.273 2.015a5.51 5.51 0 0 0 .602-.329c.593-.366 1.487-.917 2.553-.931.714-.009 1.269.445 1.353 1.103a1.23 1.23 0 0 1-.945 1.362c-.649.158-.95.278-1.821.843-1.232.797-2.539 1.242-3.012 1.39a1.686 1.686 0 0 1-.704.343c-.737.181-3.266.315-3.466.315h-.046c-.783 0-1.214-.241-1.45-.491-.658.329-1.51.19-2.122-.134a1.078 1.078 0 0 1-.58-1.153 1.243 1.243 0 0 1-.153-.195c-.162-.25-.528-.936-.454-1.946.056-.723.556-1.367.88-1.71a5.522 5.522 0 0 1 .408-2.256c.306-.727.885-1.348 1.32-1.737-.32-.537-.644-1.367-.329-2.21.227-.602.412-.936.82-1.08h-.005c.199-.074.389-.153.486-.259a3.418 3.418 0 0 1 2.298-1.103c.037-.093.079-.185.125-.283.31-.658.639-1.029 1.024-1.168a.94.94 0 0 1 .328-.06zm.006.7c-.507.016-1.001 1.519-1.001 1.519s-1.27-.204-2.266.871c-.199.218-.468.334-.746.44-.079.028-.176.023-.417.672-.371.991.625 2.094.625 2.094s-1.186.839-1.626 1.881c-.486 1.144-.338 2.261-.338 2.261s-.843.732-.899 1.487c-.051.663.139 1.2.343 1.515.227.343.51.176.51.176s-.561.653-.037.931c.477.25 1.283.394 1.71-.037.31-.31.371-1.001.486-1.283.028-.065.12.111.209.199.097.093.264.195.264.195s-.755.324-.445 1.066c.102.246.468.403 1.066.398.222-.005 2.664-.139 3.313-.296.375-.088.505-.283.505-.283s1.566-.431 2.998-1.357c.917-.598 1.293-.76 2.034-.936.612-.148.57-1.098-.241-1.084-.839.009-1.575.44-2.196.825-1.163.718-1.742.672-1.742.672l-.018-.032c-.079-.13.371-1.293-.134-2.678-.547-1.515-1.413-1.881-1.344-1.997.297-.5 1.038-1.297 1.334-2.78.176-.899.13-2.377-.269-3.151-.074-.144-.732.241-.732.241s-.616-1.371-.788-1.483a.271.271 0 0 0-.157-.046z\",\n  npm: \"M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z\",\n} as const;\n\nexport function PackageManagerIcon({ manager }: PackageManagerIconProps) {\n  return (\n    <svg aria-hidden=\"true\" className=\"size-3.5 shrink-0\" viewBox=\"0 0 24 24\">\n      <path d={PATHS[manager]} fill=\"currentColor\" />\n    </svg>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/package-command/components/package-manager-icon.tsx"
    },
    {
      "path": "registry/notra/package-command/constants/package-command.ts",
      "content": "export const PACKAGE_MANAGERS = [\"bun\", \"pnpm\", \"yarn\", \"npm\"] as const;\n\nexport const DEFAULT_PACKAGE_MANAGER = \"bun\";\n\nexport const PACKAGE_MANAGER_STORAGE_KEY = \"notra-package-manager\";\n\nexport const PACKAGE_COMMAND_COPIED_MS = 1500;\n\nexport const PACKAGE_COMMAND_COPY_LABELS = {\n  idle: \"Copy command\",\n  copied: \"Copied\",\n  failed: \"Copy failed\",\n} as const;\n",
      "type": "registry:lib",
      "target": "components/package-command/constants/package-command.ts"
    },
    {
      "path": "registry/notra/package-command/hooks/use-package-manager.ts",
      "content": "\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\n\nimport { DEFAULT_PACKAGE_MANAGER } from \"../constants/package-command\";\nimport {\n  readPackageManager,\n  subscribePackageManager,\n} from \"../lib/package-command\";\n\nexport function usePackageManager() {\n  return useSyncExternalStore(\n    subscribePackageManager,\n    readPackageManager,\n    () => DEFAULT_PACKAGE_MANAGER\n  );\n}\n",
      "type": "registry:hook",
      "target": "components/package-command/hooks/use-package-manager.ts"
    },
    {
      "path": "registry/notra/package-command/lib/package-command.ts",
      "content": "import {\n  DEFAULT_PACKAGE_MANAGER,\n  PACKAGE_MANAGER_STORAGE_KEY,\n  PACKAGE_MANAGERS,\n} from \"../constants/package-command\";\nimport type { PackageCommands, PackageManager } from \"../types/package-command\";\n\nconst listeners = new Set<() => void>();\nlet selected: PackageManager | undefined;\n\nfunction storage() {\n  try {\n    return window.localStorage;\n  } catch {\n    return undefined;\n  }\n}\n\nfunction notify() {\n  for (const listener of listeners) {\n    listener();\n  }\n}\n\nfunction onStorage() {\n  selected = undefined;\n  notify();\n}\n\nexport function toPackageManager(value: unknown): PackageManager | undefined {\n  return PACKAGE_MANAGERS.find((manager) => manager === value);\n}\n\nexport function readPackageManager(): PackageManager {\n  return (\n    selected ??\n    toPackageManager(storage()?.getItem(PACKAGE_MANAGER_STORAGE_KEY)) ??\n    DEFAULT_PACKAGE_MANAGER\n  );\n}\n\nfunction persist(manager: PackageManager) {\n  try {\n    storage()?.setItem(PACKAGE_MANAGER_STORAGE_KEY, manager);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nexport function writePackageManager(manager: PackageManager) {\n  selected = manager;\n  persist(manager);\n  notify();\n}\n\nexport function subscribePackageManager(listener: () => void) {\n  if (listeners.size === 0) {\n    window.addEventListener(\"storage\", onStorage);\n  }\n  listeners.add(listener);\n  return () => {\n    listeners.delete(listener);\n    if (listeners.size === 0) {\n      window.removeEventListener(\"storage\", onStorage);\n    }\n  };\n}\n\nexport function convertNpmCommand(command: string): PackageCommands {\n  const swap = (from: string, to: Omit<PackageCommands, \"npm\">) => ({\n    bun: command.replace(from, to.bun),\n    pnpm: command.replace(from, to.pnpm),\n    yarn: command.replace(from, to.yarn),\n    npm: command,\n  });\n\n  if (command.startsWith(\"npm install\")) {\n    return swap(\"npm install\", {\n      bun: \"bun add\",\n      pnpm: \"pnpm add\",\n      yarn: \"yarn add\",\n    });\n  }\n  if (command.startsWith(\"npx create-\")) {\n    return swap(\"npx create-\", {\n      bun: \"bun create \",\n      pnpm: \"pnpm create \",\n      yarn: \"yarn create \",\n    });\n  }\n  if (command.startsWith(\"npm create\")) {\n    return swap(\"npm create\", {\n      bun: \"bun create\",\n      pnpm: \"pnpm create\",\n      yarn: \"yarn create\",\n    });\n  }\n  if (command.startsWith(\"npx\")) {\n    return swap(\"npx\", {\n      bun: \"bunx --bun\",\n      pnpm: \"pnpm dlx\",\n      yarn: \"yarn dlx\",\n    });\n  }\n  if (command.startsWith(\"npm run\")) {\n    return swap(\"npm run\", { bun: \"bun run\", pnpm: \"pnpm\", yarn: \"yarn\" });\n  }\n  return { bun: command, pnpm: command, yarn: command, npm: command };\n}\n",
      "type": "registry:lib",
      "target": "components/package-command/lib/package-command.ts"
    },
    {
      "path": "registry/notra/package-command/types/package-command.ts",
      "content": "import type { PACKAGE_MANAGERS } from \"../constants/package-command\";\n\nexport type PackageManager = (typeof PACKAGE_MANAGERS)[number];\n\nexport type PackageCommands = Record<PackageManager, string>;\n\nexport interface PackageCommandProps {\n  command: string;\n  className?: string;\n}\n\nexport interface PackageManagerIconProps {\n  manager: PackageManager;\n}\n\nexport type CopyStatus = \"idle\" | \"copied\" | \"failed\";\n",
      "type": "registry:lib",
      "target": "components/package-command/types/package-command.ts"
    }
  ],
  "cssVars": {
    "theme": {
      "color-shell": "var(--shell)",
      "color-shell-border": "var(--shell-border)",
      "color-table-card": "var(--table-card)",
      "color-table-card-border": "var(--table-card-border)",
      "shadow-table-card": "var(--table-card-shadow)"
    },
    "light": {
      "shell": "var(--table-shell)",
      "shell-border": "var(--table-shell-border)",
      "table-shell": "#f3f3f2",
      "table-shell-border": "#e7e7e5",
      "table-card": "#ffffff",
      "table-card-border": "#e5e5e5",
      "table-card-shadow": "0 1px 2px hsl(240 10% 10% / 0.06), 0 2px 8px -2px hsl(240 10% 10% / 0.06)"
    },
    "dark": {
      "shell": "var(--table-shell)",
      "shell-border": "var(--table-shell-border)",
      "table-shell": "#161616",
      "table-shell-border": "#232323",
      "table-card": "#1f1f1f",
      "table-card-border": "#333333",
      "table-card-shadow": "inset 0 1px 0 hsl(0 0% 100% / 0.05), inset 0 0 0 1px hsl(0 0% 100% / 0.015), inset 0 12px 18px -16px hsl(0 0% 100% / 0.035)"
    }
  },
  "type": "registry:component"
}