{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block-command",
  "title": "Code Block Command",
  "author": "nghia.ly <physicalmeans@gmail.com>",
  "description": "Display install commands with package manager switcher and copy button.",
  "dependencies": [
    "@base-ui/react",
    "lucide-react",
    "motion",
    "jotai"
  ],
  "registryDependencies": [
    "https://lyminhnghia.github.io/r/copy-button.json"
  ],
  "files": [
    {
      "path": "src/registry/components/code-block-command/code-block-command.tsx",
      "content": "\"use client\"\n\nimport { TerminalSquareIcon } from \"lucide-react\"\nimport { useMemo } from \"react\"\n\nimport {\n  Tabs,\n  TabsContent,\n  TabsIndicator,\n  TabsList,\n  TabsTrigger,\n} from \"@/components/base/ui/tabs\"\nimport {\n  type PackageManager,\n  usePackageManager,\n} from \"@/hooks/use-package-manager\"\nimport { CopyButton } from \"@/registry/components/copy-button\"\n\n/**\n * Props for the CodeBlockCommand component.\n */\nexport type CodeBlockCommandProps = {\n  /**\n   * Command to execute with pnpm package manager.\n   */\n  pnpm?: string\n\n  /**\n   * Command to execute with yarn package manager.\n   */\n  yarn?: string\n\n  /**\n   * Command to execute with npm package manager.\n   */\n  npm?: string\n\n  /**\n   * Command to execute with bun package manager.\n   */\n  bun?: string\n\n  /**\n   * Callback invoked when a command is successfully copied to clipboard.\n   *\n   * Receives an object containing the selected package manager and the copied command text.\n   * Useful for tracking user interactions or showing custom success notifications.\n   *\n   * @param data - Object containing copy operation details\n   * @param data.packageManager - The package manager that was selected when copying\n   * @param data.command - The actual command text that was copied\n   *\n   * @example\n   * ```tsx\n   * <CodeBlockCommand\n   *   pnpm=\"pnpm add react\"\n   *   onCopySuccess={({ packageManager, command }) => {\n   *     console.log(`Copied ${command} for ${packageManager}`)\n   *   }}\n   * />\n   * ```\n   */\n  onCopySuccess?: (data: {\n    packageManager: PackageManager\n    command: string\n  }) => void\n\n  /**\n   * Callback invoked when copying to clipboard fails.\n   *\n   * Receives the error object for debugging or showing custom error messages.\n   *\n   * @param error - The error that occurred during the copy operation\n   *\n   * @example\n   * ```tsx\n   * <CodeBlockCommand\n   *   pnpm=\"pnpm add react\"\n   *   onCopyError={(error) => {\n   *     console.error('Copy failed:', error)\n   *   }}\n   * />\n   * ```\n   */\n  onCopyError?: (error: Error) => void\n}\n\nexport function CodeBlockCommand({\n  pnpm,\n  yarn,\n  npm,\n  bun,\n  onCopySuccess,\n  onCopyError,\n}: CodeBlockCommandProps) {\n  const [packageManager, setPackageManager] = usePackageManager()\n\n  const tabs = useMemo(() => {\n    return {\n      pnpm: pnpm,\n      yarn: yarn,\n      npm: npm,\n      bun: bun,\n    }\n  }, [pnpm, yarn, npm, bun])\n\n  return (\n    <div className=\"relative overflow-hidden rounded-xl bg-code\">\n      <Tabs\n        className=\"gap-0\"\n        value={packageManager}\n        onValueChange={(value) => {\n          setPackageManager(value as PackageManager)\n        }}\n      >\n        <div className=\"px-4 shadow-[inset_0_-1px_0_0] shadow-border\">\n          <TabsList className=\"h-10 rounded-none bg-transparent p-0 dark:bg-transparent [&_svg]:me-2 [&_svg]:size-4 [&_svg]:text-muted-foreground\">\n            {getIconForPackageManager(packageManager)}\n\n            {Object.entries(tabs).map(([key]) => {\n              return (\n                <TabsTrigger\n                  key={key}\n                  className=\"h-7 rounded-lg p-0 px-2 font-mono\"\n                  value={key}\n                >\n                  {key}\n                </TabsTrigger>\n              )\n            })}\n\n            <TabsIndicator className=\"h-0.5 translate-y-0 rounded-none bg-foreground shadow-none dark:bg-foreground\" />\n          </TabsList>\n        </div>\n\n        {Object.entries(tabs).map(([key, value]) => {\n          return (\n            <TabsContent key={key} value={key}>\n              <pre className=\"overflow-x-auto overscroll-x-contain p-4\">\n                <code\n                  data-slot=\"code-block\"\n                  data-language=\"bash\"\n                  className=\"font-mono text-sm leading-none text-muted-foreground\"\n                >\n                  <span className=\"select-none\">$ </span>\n                  {value}\n                </code>\n              </pre>\n            </TabsContent>\n          )\n        })}\n      </Tabs>\n\n      <CopyButton\n        className=\"absolute top-2 right-2 z-10 rounded-md border-none\"\n        variant=\"secondary\"\n        size=\"icon-xs\"\n        text={tabs[packageManager] || \"\"}\n        onCopySuccess={(copiedCommand) => {\n          onCopySuccess?.({\n            packageManager,\n            command: copiedCommand,\n          })\n        }}\n        onCopyError={onCopyError}\n      />\n    </div>\n  )\n}\n\nfunction getIconForPackageManager(manager: PackageManager) {\n  switch (manager) {\n    case \"pnpm\":\n      return (\n        <svg viewBox=\"0 0 24 24\">\n          <path\n            d=\"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            fill=\"currentColor\"\n          />\n        </svg>\n      )\n    case \"yarn\":\n      return (\n        <svg viewBox=\"0 0 24 24\">\n          <path\n            d=\"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            fill=\"currentColor\"\n          />\n        </svg>\n      )\n    case \"npm\":\n      return (\n        <svg viewBox=\"0 0 24 24\">\n          <path\n            d=\"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            fill=\"currentColor\"\n          />\n        </svg>\n      )\n    case \"bun\":\n      return (\n        <svg viewBox=\"0 0 24 24\">\n          <path\n            d=\"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            fill=\"currentColor\"\n          />\n        </svg>\n      )\n    default:\n      return <TerminalSquareIcon />\n  }\n}\n\n/**\n * Result of converting an npm command to all package managers.\n */\nexport type ConvertNpmCommandResult = {\n  /**\n   * Command for pnpm package manager.\n   */\n  pnpm: string\n\n  /**\n   * Command for yarn package manager.\n   */\n  yarn: string\n\n  /**\n   * Command for npm package manager.\n   */\n  npm: string\n\n  /**\n   * Command for bun package manager.\n   */\n  bun: string\n}\n\n/**\n * Converts a standard npm command into equivalent commands for pnpm, yarn, npm,\n * and bun. The result can be spread directly into `CodeBlockCommand` props.\n *\n * Supported command patterns:\n * - `npm install <pkg>` -> add commands for each manager\n * - `npx create-<name>` -> create commands for each manager\n * - `npm create <name>` -> create commands for each manager\n * - `npx <command>` -> execute commands for each manager\n * - `npm run <script>` -> run commands for each manager\n *\n * Unrecognized commands are returned as-is for all package managers.\n *\n * @param npmCommand - A standard npm/npx command string.\n * @returns An object with `pnpm`, `yarn`, `npm`, and `bun` command strings.\n *\n * @example\n * ```tsx\n * import { CodeBlockCommand, convertNpmCommand } from \"@/components/code-block-command\"\n *\n * <CodeBlockCommand {...convertNpmCommand(\"npx shadcn add button\")} />\n * ```\n */\nexport function convertNpmCommand(npmCommand: string): ConvertNpmCommandResult {\n  // npm install\n  if (npmCommand.startsWith(\"npm install\")) {\n    return {\n      pnpm: npmCommand.replaceAll(\"npm install\", \"pnpm add\"),\n      yarn: npmCommand.replaceAll(\"npm install\", \"yarn add\"),\n      npm: npmCommand,\n      bun: npmCommand.replaceAll(\"npm install\", \"bun add\"),\n    }\n  }\n\n  // npx create- (must be checked before generic npx)\n  if (npmCommand.startsWith(\"npx create-\")) {\n    return {\n      pnpm: npmCommand.replace(\"npx create-\", \"pnpm create \"),\n      yarn: npmCommand.replace(\"npx create-\", \"yarn create \"),\n      npm: npmCommand,\n      bun: npmCommand.replace(\"npx\", \"bunx --bun\"),\n    }\n  }\n\n  // npm create\n  if (npmCommand.startsWith(\"npm create\")) {\n    return {\n      pnpm: npmCommand.replace(\"npm create\", \"pnpm create\"),\n      yarn: npmCommand.replace(\"npm create\", \"yarn create\"),\n      npm: npmCommand,\n      bun: npmCommand.replace(\"npm create\", \"bun create\"),\n    }\n  }\n\n  // npx (general)\n  if (npmCommand.startsWith(\"npx\")) {\n    return {\n      pnpm: npmCommand.replace(\"npx\", \"pnpm dlx\"),\n      yarn: npmCommand.replace(\"npx\", \"yarn dlx\"),\n      npm: npmCommand,\n      bun: npmCommand.replace(\"npx\", \"bunx --bun\"),\n    }\n  }\n\n  // npm run\n  if (npmCommand.startsWith(\"npm run\")) {\n    return {\n      pnpm: npmCommand.replace(\"npm run\", \"pnpm\"),\n      yarn: npmCommand.replace(\"npm run\", \"yarn\"),\n      npm: npmCommand,\n      bun: npmCommand.replace(\"npm run\", \"bun\"),\n    }\n  }\n\n  return {\n    pnpm: npmCommand,\n    yarn: npmCommand,\n    npm: npmCommand,\n    bun: npmCommand,\n  }\n}\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/base/ui/tabs.tsx",
      "content": "\"use client\"\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Tabs({ className, ...props }: TabsPrimitive.Root.Props) {\n  return (\n    <TabsPrimitive.Root\n      data-slot=\"tabs\"\n      className={cn(\"flex flex-col gap-2\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction TabsList({ className, ...props }: TabsPrimitive.List.Props) {\n  return (\n    <TabsPrimitive.List\n      data-slot=\"tabs-list\"\n      className={cn(\n        \"relative z-0 flex h-8 w-fit items-center justify-center rounded-lg p-0.5\",\n        \"bg-zinc-100 text-muted-foreground dark:bg-zinc-900\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TabsIndicator({ className, ...props }: TabsPrimitive.Indicator.Props) {\n  return (\n    <TabsPrimitive.Indicator\n      data-slot=\"tabs-indicator\"\n      className={cn(\n        \"absolute bottom-0 left-0 -z-1 h-(--active-tab-height) w-(--active-tab-width) translate-x-(--active-tab-left) -translate-y-(--active-tab-bottom) rounded-md bg-white shadow-sm transition-[width,translate] duration-200 ease-in-out dark:bg-zinc-700\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {\n  return (\n    <TabsPrimitive.Tab\n      data-slot=\"tabs-trigger\"\n      className={cn(\n        \"flex flex-1 shrink-0 items-center justify-center gap-2 rounded-md px-4 py-1 font-sans text-sm font-medium whitespace-nowrap transition-[color,background-color] hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-active:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {\n  return (\n    <TabsPrimitive.Panel\n      data-slot=\"tabs-content\"\n      className={cn(\"flex-1 outline-none\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Tabs, TabsContent, TabsIndicator, TabsList, TabsTrigger }\n",
      "type": "registry:component"
    },
    {
      "path": "src/hooks/use-package-manager.ts",
      "content": "import { useAtom } from \"jotai\"\nimport { atomWithStorage } from \"jotai/utils\"\n\nexport type PackageManager = \"pnpm\" | \"yarn\" | \"npm\" | \"bun\"\n\nconst packageManagerAtom = atomWithStorage<PackageManager>(\n  \"packageManager\",\n  \"pnpm\"\n)\n\nexport function usePackageManager() {\n  return useAtom(packageManagerAtom)\n}\n",
      "type": "registry:hook"
    }
  ],
  "cssVars": {
    "theme": {
      "color-code": "var(--code)",
      "color-code-foreground": "var(--code-foreground)"
    },
    "light": {
      "code": "oklch(0.985 0 0)",
      "code-foreground": "oklch(0.141 0.005 285.823)"
    },
    "dark": {
      "code": "oklch(0.21 0.006 285.885)",
      "code-foreground": "oklch(0.985 0 0)"
    }
  },
  "type": "registry:component"
}