{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block-command",
  "title": "Code Block Command",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "@glasscn/utils",
    "@glasscn/button",
    "@glasscn/badge"
  ],
  "files": [
    {
      "path": "components/ui/code-block-command.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { WandSparklesIcon } from \"lucide-react\";\n\nimport { CopyButton } from \"@/components/custom/copy-button\";\nimport { type Badge } from \"@/components/ui/badge\";\nimport { PackageManagerIcon } from \"@/components/ui/icons/package-manager-icons\";\nimport { cn } from \"@/lib/utils\";\nimport type { PackageManager } from \"@/lib/package-manager\";\n\nexport type { PackageManager };\nexport { convertNpmCommand } from \"@/lib/package-manager\";\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\ntype PackageManagerContextValue = {\n  packageManager: PackageManager;\n  setPackageManager: (pm: PackageManager) => void;\n};\n\nconst PackageManagerContext = React.createContext<PackageManagerContextValue>({\n  packageManager: \"npm\",\n  setPackageManager: () => { },\n});\n\nexport function PackageManagerProvider({\n  children,\n  defaultPackageManager = \"npm\",\n}: {\n  children: React.ReactNode;\n  defaultPackageManager?: PackageManager;\n}) {\n  const [packageManager, setPackageManagerState] =\n    React.useState<PackageManager>(() => {\n      if (typeof window !== \"undefined\") {\n        return (\n          (localStorage.getItem(\"packageManager\") as PackageManager) ??\n          defaultPackageManager\n        );\n      }\n      return defaultPackageManager;\n    });\n\n  const setPackageManager = React.useCallback((pm: PackageManager) => {\n    setPackageManagerState(pm);\n    localStorage.setItem(\"packageManager\", pm);\n  }, []);\n\n  return (\n    <PackageManagerContext.Provider value={{ packageManager, setPackageManager }}>\n      {children}\n    </PackageManagerContext.Provider>\n  );\n}\n\nexport function usePackageManager() {\n  return React.useContext(PackageManagerContext);\n}\n\ntype ActiveTab = PackageManager | \"prompt\";\n\nexport type CodeBlockCommandProps = {\n  prompt?: string;\n  npm?: string;\n  pnpm?: string;\n  yarn?: string;\n  bun?: string;\n  onCopySuccess?: (text: string) => void;\n  onCopyError?: (error: Error) => void;\n  className?: string;\n  badgeComponent?: React.ComponentType<React.ComponentProps<typeof Badge>>;\n};\n\nconst PACKAGE_MANAGERS: PackageManager[] = [\"pnpm\", \"yarn\", \"npm\", \"bun\"];\n\nexport function CodeBlockCommand({\n  prompt,\n  npm,\n  pnpm,\n  yarn,\n  bun,\n  onCopySuccess,\n  onCopyError,\n  className,\n  badgeComponent: BadgeComponent,\n}: CodeBlockCommandProps) {\n  const { packageManager, setPackageManager } = usePackageManager();\n\n  const commands: Record<PackageManager, string | undefined> = { npm, pnpm, yarn, bun };\n\n  const availablePMs = PACKAGE_MANAGERS.filter((pm) => commands[pm] !== undefined);\n\n  // Local tab state — \"prompt\" tab doesn't affect the shared PM context\n  const [activeTab, setActiveTab] = React.useState<ActiveTab>(() => {\n    if (availablePMs.includes(packageManager)) return packageManager;\n    return availablePMs[0] ?? \"npm\";\n  });\n\n  // Keep active tab in sync when the shared PM context changes\n  React.useEffect(() => {\n    if (availablePMs.includes(packageManager)) {\n      setActiveTab(packageManager);\n    }\n  }, [packageManager]); // eslint-disable-line react-hooks/exhaustive-deps\n\n  function handleTabClick(tab: ActiveTab) {\n    setActiveTab(tab);\n    if (tab !== \"prompt\") {\n      setPackageManager(tab);\n    }\n  }\n\n  const activeContent =\n    activeTab === \"prompt\" ? (prompt ?? \"\") : (commands[activeTab] ?? \"\");\n\n  return (\n    <div\n      data-slot=\"code-block-command\"\n      className={cn(\"relative overflow-hidden rounded-xl border bg-muted\", className)}\n    >\n      <div data-slot=\"code-block-command-header\" className=\"flex items-center border-b px-4\">\n        {BadgeComponent ? (\n          <BadgeComponent\n            data-slot=\"code-block-command-active-icon\"\n            variant=\"outline\"\n            className=\"mr-3 size-6 shrink-0 rounded-full p-0\"\n          >\n            {activeTab === \"prompt\" ? (\n              <WandSparklesIcon aria-hidden className=\"size-3.5\" />\n            ) : (\n              <PackageManagerIcon packageManager={activeTab} className=\"size-3.5\" />\n            )}\n          </BadgeComponent>\n        ) : activeTab === \"prompt\" ? (\n          <WandSparklesIcon\n            aria-hidden\n            data-slot=\"code-block-command-active-icon\"\n            className=\"mr-3 size-5 shrink-0 text-muted-foreground\"\n          />\n        ) : (\n          <PackageManagerIcon\n            packageManager={activeTab}\n            data-slot=\"code-block-command-active-icon\"\n            className=\"mr-3 size-5 shrink-0 text-muted-foreground\"\n          />\n        )}\n\n        {prompt && (\n          <TabButton active={activeTab === \"prompt\"} onClick={() => handleTabClick(\"prompt\")}>\n            prompt\n          </TabButton>\n        )}\n\n        {availablePMs.map((pm) => (\n          <TabButton key={pm} active={activeTab === pm} onClick={() => handleTabClick(pm)}>\n            {pm}\n          </TabButton>\n        ))}\n      </div>\n\n      <div data-slot=\"code-block-command-content\" className=\"px-4 py-3 pr-12\">\n        <pre className=\"overflow-x-auto overscroll-x-contain\">\n          <code\n            data-slot=\"code-block-command-code\"\n            className=\"font-mono text-sm leading-none text-foreground/80\"\n          >\n            {activeTab !== \"prompt\" && <span className=\"select-none\">$ </span>}\n            {activeContent}\n          </code>\n        </pre>\n      </div>\n\n      <CopyButton\n        data-slot=\"code-block-command-copy\"\n        size=\"icon-sm\"\n        className=\"absolute top-2 right-2\"\n        text={activeContent}\n        onCopySuccess={onCopySuccess}\n        onCopyError={onCopyError}\n      />\n    </div>\n  );\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction TabButton({\n  active,\n  onClick,\n  children,\n}: {\n  active: boolean;\n  onClick: () => void;\n  children: React.ReactNode;\n}) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      data-slot=\"code-block-command-tab\"\n      data-active={active}\n      className={cn(\n        \"h-10 border-b-2 border-transparent px-2 font-mono text-sm transition-colors\",\n        active\n          ? \"border-foreground text-foreground\"\n          : \"text-muted-foreground hover:text-foreground\",\n      )}\n    >\n      {children}\n    </button>\n  );\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/icons/package-manager-icons.tsx",
      "content": "import type { SVGProps } from \"react\";\n\nimport type { PackageManager } from \"@/lib/package-manager\";\nimport { cn } from \"@/lib/utils\";\n\n// Brand colors from Simple Icons (simpleicons.org)\n// npm   → #CB3837  pnpm → #F69220\n// yarn  → #2C8EBB  bun  → #FBF0DF (cream — dark bg brand; inverted for light mode)\n\nfunction NpmIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg role=\"img\" viewBox=\"0 0 128 128\" xmlns=\"http://www.w3.org/2000/svg\" {...props}>\n      <path\n        fill=\"#cb3837\"\n        d=\"M2 38.5h124v43.71H64v7.29H36.44v-7.29H2zm6.89 36.43h13.78V53.07h6.89v21.86h6.89V45.79H8.89zm34.44-29.14v36.42h13.78v-7.28h13.78V45.79zm13.78 7.29H64v14.56h-6.89zm20.67-7.29v29.14h13.78V53.07h6.89v21.86h6.89V53.07h6.89v21.86h6.89V45.79z\"\n      />\n    </svg>\n  );\n}\n\nfunction PnpmIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg role=\"img\" viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n      <path 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    </svg>\n  );\n}\n\nfunction YarnIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg role=\"img\" viewBox=\"0 0 128 128\" xmlns=\"http://www.w3.org/2000/svg\" {...props}>\n      <g fill=\"#2c8ebb\">\n        <path d=\"M99.24 80.71C94.9 80.76 91.1 83 87.89 85c-6 3.71-9 3.47-9 3.47l-.1-.17c-.41-.67 1.92-6.68-.69-13.84-2.82-7.83-7.3-9.72-6.94-10.32 1.53-2.59 5.36-6.7 6.89-14.36.91-4.64.67-12.28-1.39-16.28-.38-.74-3.78 1.24-3.78 1.24s-3.18-7.09-4.07-7.66c-2.87-1.84-6 7.61-6 7.61a14 14 0 00-11.71 4.5 9.64 9.64 0 01-3.85 2.27c-.41.14-.91.12-2.15 3.47-1.9 5.07 3.24 10.81 3.24 10.81s-6.13 4.33-8.4 9.72a24.78 24.78 0 00-1.75 11.68s-4.36 3.78-4.64 7.68a12.87 12.87 0 001.77 7.83 1.94 1.94 0 002.63.91s-2.9 3.38-.19 4.81c2.47 1.29 6.63 2 8.83-.19 1.6-1.6 1.92-5.17 2.51-6.63.14-.34.62.57 1.08 1a10 10 0 001.36 1s-3.9 1.68-2.3 5.51c.53 1.27 2.42 2.08 5.51 2.06 1.15 0 13.76-.72 17.12-1.53a4.33 4.33 0 002.61-1.46 63 63 0 0015.49-7c4.74-3.09 6.68-3.93 10.51-4.84 3.16-.75 2.95-5.65-1.24-5.58z\" />\n        <path d=\"M64 2a62 62 0 1062 62A62 62 0 0064 2zm37.3 87.83c-3.35.81-4.91 1.44-9.41 4.36a67 67 0 01-15.56 7.18 8.71 8.71 0 01-3.64 1.77c-3.81.93-16.88 1.63-17.91 1.63h-.24c-4 0-6.27-1.24-7.49-2.54-3.4 1.7-7.8 1-11-.69a5.55 5.55 0 01-3-3.9 6 6 0 010-2.06 6.66 6.66 0 01-.79-1A16.38 16.38 0 0130 84.52c.29-3.73 2.87-7.06 4.55-8.83A28.56 28.56 0 0136.61 64a26.82 26.82 0 016.82-9c-1.65-2.78-3.33-7.06-1.7-11.42 1.17-3.11 2.13-4.84 4.24-5.58a6.84 6.84 0 002.51-1.34A17.65 17.65 0 0160.34 31c.19-.48.41-1 .65-1.46 1.6-3.4 3.3-5.31 5.29-6a4.88 4.88 0 014.4.5c.65.43 1.48 1 3.9 6a4.69 4.69 0 012.85-.1 3.81 3.81 0 012.39 1.94c2.47 4.74 2.8 13.19 1.72 18.62a33.8 33.8 0 01-5.84 13.31 25.73 25.73 0 015.77 9.43 25.42 25.42 0 011.41 10.41A28.7 28.7 0 0186 81.91c3.06-1.89 7.68-4.74 13.19-4.81a6.62 6.62 0 017 5.7 6.35 6.35 0 01-4.89 7.03z\" />\n      </g>\n    </svg>\n  );\n}\n\nfunction BunIcon(props: SVGProps<SVGSVGElement>) {\n  return (\n    <svg role=\"img\" viewBox=\"0 0 128 128\" xmlns=\"http://www.w3.org/2000/svg\" {...props}>\n      <path d=\"M113.744 41.999a18.558 18.558 0 0 0-.8-.772c-.272-.246-.528-.524-.8-.771s-.528-.525-.8-.771c-.272-.247-.528-.525-.8-.772s-.528-.524-.8-.771-.528-.525-.8-.772-.528-.524-.8-.771c7.936 7.52 12.483 17.752 12.656 28.481 0 25.565-26.912 46.363-60 46.363-18.528 0-35.104-6.526-46.128-16.756l.8.772.8.771.8.772.8.771.8.772.8.771.8.771c11.008 10.662 27.952 17.527 46.928 17.527 33.088 0 60-20.797 60-46.285 0-10.893-4.864-21.215-13.456-29.33z\" />\n      <path\n        fill=\"#fbf0df\"\n        d=\"M116.8 65.08c0 23.467-25.072 42.49-56 42.49s-56-19.023-56-42.49c0-14.55 9.6-27.401 24.352-35.023C43.904 22.435 53.088 14.628 60.8 14.628S75.104 21 92.448 30.058C107.2 37.677 116.8 50.53 116.8 65.08Z\"\n      />\n      <path\n        fill=\"#f6dece\"\n        d=\"M116.8 65.08a32.314 32.314 0 0 0-1.28-8.918c-4.368 51.377-69.36 53.846-94.912 38.48 11.486 8.584 25.66 13.144 40.192 12.928 30.88 0 56-19.054 56-42.49z\"\n      />\n      <path\n        fill=\"#fffefc\"\n        d=\"M39.248 27.234c7.152-4.135 16.656-11.896 26-11.911a15.372 15.372 0 0 0-4.448-.695c-3.872 0-8 1.93-13.2 4.83-1.808 1.018-3.68 2.144-5.664 3.317-3.728 2.222-8 4.736-12.8 7.251C13.904 37.972 4.8 51.071 4.8 65.08v1.836c9.696-33.033 27.312-35.547 34.448-39.682z\"\n      />\n      <path\n        fill=\"#ccbea7\"\n        d=\"M56.192 18.532A24.553 24.553 0 0 1 53.867 29.1a25.407 25.407 0 0 1-6.683 8.671c-.448.386-.096 1.127.48.91 5.392-2.02 12.672-8.068 9.6-20.272-.128-.695-1.072-.51-1.072.123zm3.632 0a24.474 24.474 0 0 1 3.646 10.12c.445 3.587.08 7.224-1.07 10.662-.192.54.496 1.003.88.556 3.504-4.32 6.56-12.899-2.592-22.156-.464-.4-1.184.216-.864.756zm4.416-.262a25.702 25.702 0 0 1 7.521 7.925A24.71 24.71 0 0 1 75.2 36.414c-.016.13.02.26.101.365a.543.543 0 0 0 .718.117.509.509 0 0 0 .221-.313c1.472-5.384.64-14.564-11.472-19.332-.64-.246-1.056.587-.528.957zM34.704 34.315a27.418 27.418 0 0 0 9.91-5.222 26.262 26.262 0 0 0 6.842-8.663c.288-.556 1.2-.34 1.056.277-2.768 12.343-12.032 14.92-17.792 14.58-.608.016-.592-.802-.016-.972z\"\n      />\n      <path d=\"M60.8 111.443c-33.088 0-60-20.798-60-46.363 0-15.429 9.888-29.823 26.448-38.448 4.8-2.469 8.912-4.953 12.576-7.128 2.016-1.203 3.92-2.33 5.76-3.379C51.2 12.916 56 10.771 60.8 10.771c4.8 0 8.992 1.852 14.24 4.845 1.6.88 3.2 1.836 4.912 2.885 3.984 2.376 8.48 5.06 14.4 8.131 16.56 8.625 26.448 23.004 26.448 38.448 0 25.565-26.912 46.363-60 46.363zm0-96.814c-3.872 0-8 1.928-13.2 4.829-1.808 1.018-3.68 2.144-5.664 3.317-3.728 2.222-8 4.736-12.8 7.251C13.904 37.972 4.8 51.071 4.8 65.08c0 23.436 25.12 42.506 56 42.506s56-19.07 56-42.506c0-14.01-9.104-27.108-24.352-35.023-6.048-3.086-10.768-5.986-14.592-8.27-1.744-1.033-3.344-1.99-4.8-2.838-4.848-2.778-8.384-4.32-12.256-4.32z\" />\n      <path\n        fill=\"#b71422\"\n        d=\"M72.08 76.343c-.719 2.839-2.355 5.383-4.672 7.267a11.07 11.07 0 0 1-6.4 2.9 11.13 11.13 0 0 1-6.608-2.9c-2.293-1.892-3.906-4.436-4.608-7.267a1.073 1.073 0 0 1 .05-.5 1.11 1.11 0 0 1 .272-.428 1.19 1.19 0 0 1 .958-.322h19.744a1.185 1.185 0 0 1 .947.33 1.073 1.073 0 0 1 .317.92z\"\n      />\n      <path\n        fill=\"#ff6164\"\n        d=\"M54.4 83.733a11.24 11.24 0 0 0 6.592 2.932 11.239 11.239 0 0 0 6.576-2.932 16.652 16.652 0 0 0 1.6-1.65 10.904 10.904 0 0 0-3.538-2.564 11.26 11.26 0 0 0-4.302-1 10.121 10.121 0 0 0-4.549 1.192 9.71 9.71 0 0 0-3.451 3.097c.368.323.688.632 1.072.925z\"\n      />\n      <path d=\"M54.656 82.514a8.518 8.518 0 0 1 2.97-2.347 8.836 8.836 0 0 1 3.734-.862 9.78 9.78 0 0 1 6.4 2.608c.368-.386.72-.787 1.056-1.188-2.035-1.87-4.726-2.933-7.536-2.978a10.487 10.487 0 0 0-4.335.975 10.125 10.125 0 0 0-3.489 2.666c.378.396.779.772 1.2 1.126z\" />\n      <path d=\"M60.944 87.436a12.078 12.078 0 0 1-7.12-3.086c-2.477-2.02-4.22-4.75-4.976-7.791-.054-.27-.045-.55.027-.817a1.83 1.83 0 0 1 .389-.726 2.25 2.25 0 0 1 .81-.595 2.32 2.32 0 0 1 .998-.192h19.744c.343-.007.683.06.996.196a2.3 2.3 0 0 1 .812.591c.182.212.313.46.382.728.07.267.076.545.018.815-.756 3.042-2.5 5.771-4.976 7.791a12.078 12.078 0 0 1-7.104 3.086zm-9.872-11.417c-.256 0-.32.108-.336.139.676 2.638 2.206 4.999 4.368 6.742a10.122 10.122 0 0 0 5.84 2.7 10.207 10.207 0 0 0 5.84-2.67c2.155-1.745 3.679-4.106 4.352-6.741a.333.333 0 0 0-.14-.113.348.348 0 0 0-.18-.026z\" />\n      <path\n        fill=\"#febbd0\"\n        d=\"M85.152 77.3c5.17 0 9.36-2.377 9.36-5.308s-4.19-5.307-9.36-5.307c-5.17 0-9.36 2.376-9.36 5.307 0 2.931 4.19 5.307 9.36 5.307zm-48.432 0c5.17 0 9.36-2.377 9.36-5.308s-4.19-5.307-9.36-5.307c-5.17 0-9.36 2.376-9.36 5.307 0 2.931 4.19 5.307 9.36 5.307z\"\n      />\n      <path d=\"M41.12 69.863a9.052 9.052 0 0 0 4.902-1.425 8.578 8.578 0 0 0 3.254-3.812 8.22 8.22 0 0 0 .508-4.913 8.41 8.41 0 0 0-2.408-4.357 8.92 8.92 0 0 0-4.514-2.33 9.12 9.12 0 0 0-5.096.48 8.755 8.755 0 0 0-3.96 3.131 8.287 8.287 0 0 0-1.486 4.725c0 2.252.927 4.412 2.577 6.005 1.65 1.594 3.888 2.492 6.223 2.496zm39.632 0a9.054 9.054 0 0 0 4.915-1.403 8.582 8.582 0 0 0 3.275-3.802 8.22 8.22 0 0 0 .528-4.917 8.408 8.408 0 0 0-2.398-4.368 8.92 8.92 0 0 0-4.512-2.344 9.12 9.12 0 0 0-5.103.473 8.756 8.756 0 0 0-3.967 3.13 8.287 8.287 0 0 0-1.49 4.73c-.004 2.245.914 4.4 2.555 5.994 1.64 1.593 3.869 2.495 6.197 2.507z\" />\n      <path\n        fill=\"#fff\"\n        d=\"M38.4 61.902a3.4 3.4 0 0 0 1.844-.531c.547-.35.974-.847 1.227-1.43a3.088 3.088 0 0 0 .195-1.847 3.16 3.16 0 0 0-.902-1.639 3.351 3.351 0 0 0-1.696-.878 3.426 3.426 0 0 0-1.916.179 3.29 3.29 0 0 0-1.489 1.176 3.113 3.113 0 0 0-.559 1.776c0 .844.347 1.654.964 2.253a3.374 3.374 0 0 0 2.332.94zm39.632 0a3.4 3.4 0 0 0 1.844-.531c.547-.35.974-.847 1.227-1.43a3.088 3.088 0 0 0 .195-1.847 3.16 3.16 0 0 0-.902-1.639 3.351 3.351 0 0 0-1.696-.878 3.426 3.426 0 0 0-1.916.179 3.29 3.29 0 0 0-1.489 1.176 3.113 3.113 0 0 0-.559 1.776c0 .84.342 1.644.953 2.242.61.598 1.44.94 2.311.952z\"\n      />\n    </svg>\n  );\n}\n\nconst PM_STYLES: Record<PackageManager, { style?: React.CSSProperties; className?: string }> = {\n  npm: {},\n  pnpm: { style: { color: \"#F69220\" } },\n  yarn: {},\n  bun: {},\n};\n\nexport function PackageManagerIcon({\n  packageManager,\n  className,\n}: {\n  packageManager: PackageManager;\n  className?: string;\n}) {\n  const { style, className: colorClass } = PM_STYLES[packageManager];\n  const sharedProps = { \"aria-hidden\": true as const, style, className: cn(colorClass, className) };\n\n  switch (packageManager) {\n    case \"npm\":\n      return <NpmIcon {...sharedProps} />;\n    case \"pnpm\":\n      return <PnpmIcon {...sharedProps} />;\n    case \"yarn\":\n      return <YarnIcon {...sharedProps} />;\n    case \"bun\":\n      return <BunIcon {...sharedProps} />;\n  }\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "components/custom/copy-button.tsx",
      "content": "\"use client\";\n\nimport { CheckIcon, CircleXIcon, CopyIcon } from \"lucide-react\";\nimport type { HTMLMotionProps, Variants } from \"motion/react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport type { ComponentProps } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport type { CopyState } from \"@/hooks/use-copy-to-clipboard\";\nimport { useCopyToClipboard } from \"@/hooks/use-copy-to-clipboard\";\n\nexport const motionIconVariants: Variants = {\n  initial: { opacity: 0, scale: 0.8, filter: \"blur(2px)\" },\n  animate: { opacity: 1, scale: 1, filter: \"blur(0px)\" },\n  exit: { opacity: 0, scale: 0.8 },\n};\n\nexport const motionIconProps: HTMLMotionProps<\"span\"> = {\n  variants: motionIconVariants,\n  initial: \"initial\",\n  animate: \"animate\",\n  exit: \"exit\",\n  transition: { duration: 0.25, ease: \"easeOut\" },\n};\n\nexport type CopyStateIconProps = {\n  state: CopyState;\n  idleIcon?: React.ReactNode;\n  doneIcon?: React.ReactNode;\n  errorIcon?: React.ReactNode;\n};\n\nexport function CopyStateIcon({ state, idleIcon, doneIcon, errorIcon }: CopyStateIconProps) {\n  return (\n    <AnimatePresence mode=\"wait\" initial={false}>\n      {state === \"idle\" ? (\n        <motion.span key=\"idle\" {...motionIconProps}>\n          {idleIcon ?? <CopyIcon />}\n        </motion.span>\n      ) : state === \"done\" ? (\n        <motion.span key=\"done\" {...motionIconProps}>\n          {doneIcon ?? <CheckIcon strokeWidth={3} color=\"green\" />}\n        </motion.span>\n      ) : state === \"error\" ? (\n        <motion.span key=\"error\" {...motionIconProps}>\n          {errorIcon ?? <CircleXIcon />}\n        </motion.span>\n      ) : null}\n    </AnimatePresence>\n  );\n}\n\nexport type CopyButtonProps = ComponentProps<typeof Button> & {\n  text: string | (() => string);\n  onCopySuccess?: (text: string) => void;\n  onCopyError?: (error: Error) => void;\n} & Pick<CopyStateIconProps, \"idleIcon\" | \"doneIcon\" | \"errorIcon\">;\n\nexport function CopyButton({\n  size = \"icon\",\n  variant = \"ghost\",\n  children,\n  text,\n  idleIcon,\n  doneIcon,\n  errorIcon,\n  onClick,\n  onCopySuccess,\n  onCopyError,\n  ...props\n}: CopyButtonProps) {\n  const { state, copy } = useCopyToClipboard({ onCopySuccess, onCopyError });\n\n  return (\n    <Button\n      size={size}\n      variant={variant}\n      onClick={(e) => {\n        void copy(text);\n        onClick?.(e);\n      }}\n      aria-label=\"Copy\"\n      {...props}\n    >\n      <CopyStateIcon state={state} idleIcon={idleIcon} doneIcon={doneIcon} errorIcon={errorIcon} />\n      {children}\n    </Button>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "hooks/use-copy-to-clipboard.ts",
      "content": "import { useState } from \"react\";\n\nexport type CopyState = \"idle\" | \"done\" | \"error\";\n\ntype UseCopyToClipboardOptions = {\n  onCopySuccess?: (text: string) => void;\n  onCopyError?: (error: Error) => void;\n  timeout?: number;\n};\n\nexport function useCopyToClipboard({ onCopySuccess, onCopyError, timeout = 2000 }: UseCopyToClipboardOptions = {}) {\n  const [state, setState] = useState<CopyState>(\"idle\");\n\n  async function copy(text: string | (() => string)) {\n    const value = typeof text === \"function\" ? text() : text;\n    try {\n      await navigator.clipboard.writeText(value);\n      setState(\"done\");\n      onCopySuccess?.(value);\n    } catch (err) {\n      setState(\"error\");\n      onCopyError?.(err instanceof Error ? err : new Error(String(err)));\n    } finally {\n      setTimeout(() => setState(\"idle\"), timeout);\n    }\n  }\n\n  return { state, copy };\n}\n",
      "type": "registry:hook"
    },
    {
      "path": "lib/package-manager.ts",
      "content": "export type PackageManager = \"pnpm\" | \"yarn\" | \"npm\" | \"bun\";\n\nexport function convertNpmCommand(command: string): Record<PackageManager, string> {\n  const pnpm = command\n    .replace(/^npm install/, \"pnpm add\")\n    .replace(/^npx create-(\\S+)/, \"pnpm create $1\")\n    .replace(/^npx/, \"pnpm dlx\")\n    .replace(/^npm run/, \"pnpm\");\n\n  const yarn = command\n    .replace(/^npm install/, \"yarn add\")\n    .replace(/^npx create-(\\S+)/, \"yarn create $1\")\n    .replace(/^npx/, \"yarn dlx\")\n    .replace(/^npm run/, \"yarn\");\n\n  const bun = command\n    .replace(/^npm install/, \"bun add\")\n    .replace(/^npx create-(\\S+)/, \"bunx --bun create-$1\")\n    .replace(/^npx/, \"bunx --bun\")\n    .replace(/^npm run/, \"bun\");\n\n  return { npm: command, pnpm, yarn, bun };\n}\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:ui"
}