# Code Block Command

Display install commands with package manager switcher and copy button.

```tsx
import { CodeBlockCommand } from "@/components/code-block-command"

export function CodeBlockCommandDemo() {
  return (
    <div className="w-full max-w-md">
      <CodeBlockCommand
        pnpm="pnpm dlx shadcn add @ncdai/code-block-command"
        yarn="yarn shadcn add @ncdai/code-block-command"
        npm="npx shadcn add @ncdai/code-block-command"
        bun="bunx --bun shadcn add @ncdai/code-block-command"
      />
    </div>
  )
}

```

## Features

* Switch between pnpm, yarn, npm, and bun with a single click.
* Automatically saves the user's preferred package manager for future visits.
* Copy commands instantly with smooth animations showing success or failure states.
* `convertNpmCommand` utility to auto-convert a standard npm command to all package managers.

## Installation

<CodeTabs>
  <TabsListInstallType />

  <TabsContent value="cli">
    ```bash
    npx shadcn add @ncdai/code-block-command
    ```
  </TabsContent>

  <TabsContent value="manual">
    <Steps>
      <Step>Install the following dependencies</Step>

      ```bash
      npm install clsx tailwind-merge class-variance-authority lucide-react motion jotai radix-ui @base-ui/react
      ```

      <Step>Add a cn helper</Step>

      ```ts title="lib/utils.ts" showLineNumbers
      import type { ClassValue } from "clsx"
      import { clsx } from "clsx"
      import { twMerge } from "tailwind-merge"

      export const cn = (...inputs: ClassValue[]) => {
        return twMerge(clsx(inputs))
      }

      ```

      <Step>Install the required components</Step>

      * [Copy Button](https://chanhdai.com/components/copy-button)

      <Step>Copy and paste the following code into your project</Step>

      ```tsx title="components/tabs.tsx" showLineNumbers
      "use client"

      import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"

      import { cn } from "@/lib/utils"

      function Tabs({ className, ...props }: TabsPrimitive.Root.Props) {
        return (
          <TabsPrimitive.Root
            data-slot="tabs"
            className={cn("flex flex-col gap-2", className)}
            {...props}
          />
        )
      }

      function TabsList({ className, ...props }: TabsPrimitive.List.Props) {
        return (
          <TabsPrimitive.List
            data-slot="tabs-list"
            className={cn(
              "relative z-0 flex h-8 w-fit items-center justify-center rounded-lg p-0.5",
              "bg-zinc-100 text-muted-foreground dark:bg-zinc-900",
              className
            )}
            {...props}
          />
        )
      }

      function TabsIndicator({ className, ...props }: TabsPrimitive.Indicator.Props) {
        return (
          <TabsPrimitive.Indicator
            data-slot="tabs-indicator"
            className={cn(
              "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",
              className
            )}
            {...props}
          />
        )
      }

      function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
        return (
          <TabsPrimitive.Tab
            data-slot="tabs-trigger"
            className={cn(
              "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",
              className
            )}
            {...props}
          />
        )
      }

      function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
        return (
          <TabsPrimitive.Panel
            data-slot="tabs-content"
            className={cn("flex-1 outline-none", className)}
            {...props}
          />
        )
      }

      export { Tabs, TabsContent, TabsIndicator, TabsList, TabsTrigger }

      ```

      ```ts title="hooks/use-package-manager.ts" showLineNumbers
      import { useAtom } from "jotai"
      import { atomWithStorage } from "jotai/utils"

      export type PackageManager = "pnpm" | "yarn" | "npm" | "bun"

      const packageManagerAtom = atomWithStorage<PackageManager>(
        "packageManager",
        "pnpm"
      )

      export function usePackageManager() {
        return useAtom(packageManagerAtom)
      }

      ```

      ````tsx title="components/code-block-command.tsx" showLineNumbers
      "use client"

      import { TerminalSquareIcon } from "lucide-react"
      import { useMemo } from "react"

      import {
        Tabs,
        TabsContent,
        TabsIndicator,
        TabsList,
        TabsTrigger,
      } from "@/components/tabs"
      import {
        type PackageManager,
        usePackageManager,
      } from "@/hooks/use-package-manager"
      import { CopyButton } from "@/components/copy-button"

      /**
       * Props for the CodeBlockCommand component.
       */
      export type CodeBlockCommandProps = {
        /**
         * Command to execute with pnpm package manager.
         */
        pnpm?: string

        /**
         * Command to execute with yarn package manager.
         */
        yarn?: string

        /**
         * Command to execute with npm package manager.
         */
        npm?: string

        /**
         * Command to execute with bun package manager.
         */
        bun?: string

        /**
         * Callback invoked when a command is successfully copied to clipboard.
         *
         * Receives an object containing the selected package manager and the copied command text.
         * Useful for tracking user interactions or showing custom success notifications.
         *
         * @param data - Object containing copy operation details
         * @param data.packageManager - The package manager that was selected when copying
         * @param data.command - The actual command text that was copied
         *
         * @example
         * ```tsx
         * <CodeBlockCommand
         *   pnpm="pnpm add react"
         *   onCopySuccess={({ packageManager, command }) => {
         *     console.log(`Copied ${command} for ${packageManager}`)
         *   }}
         * />
         * ```
         */
        onCopySuccess?: (data: {
          packageManager: PackageManager
          command: string
        }) => void

        /**
         * Callback invoked when copying to clipboard fails.
         *
         * Receives the error object for debugging or showing custom error messages.
         *
         * @param error - The error that occurred during the copy operation
         *
         * @example
         * ```tsx
         * <CodeBlockCommand
         *   pnpm="pnpm add react"
         *   onCopyError={(error) => {
         *     console.error('Copy failed:', error)
         *   }}
         * />
         * ```
         */
        onCopyError?: (error: Error) => void
      }

      export function CodeBlockCommand({
        pnpm,
        yarn,
        npm,
        bun,
        onCopySuccess,
        onCopyError,
      }: CodeBlockCommandProps) {
        const [packageManager, setPackageManager] = usePackageManager()

        const tabs = useMemo(() => {
          return {
            pnpm: pnpm,
            yarn: yarn,
            npm: npm,
            bun: bun,
          }
        }, [pnpm, yarn, npm, bun])

        return (
          <div className="relative overflow-hidden rounded-xl bg-code">
            <Tabs
              className="gap-0"
              value={packageManager}
              onValueChange={(value) => {
                setPackageManager(value as PackageManager)
              }}
            >
              <div className="px-4 shadow-[inset_0_-1px_0_0] shadow-border">
                <TabsList className="h-10 rounded-none bg-transparent p-0 dark:bg-transparent [&_svg]:me-2 [&_svg]:size-4 [&_svg]:text-muted-foreground">
                  {getIconForPackageManager(packageManager)}

                  {Object.entries(tabs).map(([key]) => {
                    return (
                      <TabsTrigger
                        key={key}
                        className="h-7 rounded-lg p-0 px-2 font-mono"
                        value={key}
                      >
                        {key}
                      </TabsTrigger>
                    )
                  })}

                  <TabsIndicator className="h-0.5 translate-y-0 rounded-none bg-foreground shadow-none dark:bg-foreground" />
                </TabsList>
              </div>

              {Object.entries(tabs).map(([key, value]) => {
                return (
                  <TabsContent key={key} value={key}>
                    <pre className="overflow-x-auto overscroll-x-contain p-4">
                      <code
                        data-slot="code-block"
                        data-language="bash"
                        className="font-mono text-sm leading-none text-muted-foreground"
                      >
                        <span className="select-none">$ </span>
                        {value}
                      </code>
                    </pre>
                  </TabsContent>
                )
              })}
            </Tabs>

            <CopyButton
              className="absolute top-2 right-2 z-10"
              variant="secondary"
              size="icon-xs"
              text={tabs[packageManager] || ""}
              onCopySuccess={(copiedCommand) => {
                onCopySuccess?.({
                  packageManager,
                  command: copiedCommand,
                })
              }}
              onCopyError={onCopyError}
            />
          </div>
        )
      }

      function getIconForPackageManager(manager: PackageManager) {
        switch (manager) {
          case "pnpm":
            return (
              <svg viewBox="0 0 24 24">
                <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"
                  fill="currentColor"
                />
              </svg>
            )
          case "yarn":
            return (
              <svg viewBox="0 0 24 24">
                <path
                  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"
                  fill="currentColor"
                />
              </svg>
            )
          case "npm":
            return (
              <svg viewBox="0 0 24 24">
                <path
                  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"
                  fill="currentColor"
                />
              </svg>
            )
          case "bun":
            return (
              <svg viewBox="0 0 24 24">
                <path
                  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"
                  fill="currentColor"
                />
              </svg>
            )
          default:
            return <TerminalSquareIcon />
        }
      }

      /**
       * Result of converting an npm command to all package managers.
       */
      export type ConvertNpmCommandResult = {
        /**
         * Command for pnpm package manager.
         */
        pnpm: string

        /**
         * Command for yarn package manager.
         */
        yarn: string

        /**
         * Command for npm package manager.
         */
        npm: string

        /**
         * Command for bun package manager.
         */
        bun: string
      }

      /**
       * Converts a standard npm command into equivalent commands for pnpm, yarn, npm,
       * and bun. The result can be spread directly into `CodeBlockCommand` props.
       *
       * Supported command patterns:
       * - `npm install <pkg>` -> add commands for each manager
       * - `npx create-<name>` -> create commands for each manager
       * - `npm create <name>` -> create commands for each manager
       * - `npx <command>` -> execute commands for each manager
       * - `npm run <script>` -> run commands for each manager
       *
       * Unrecognized commands are returned as-is for all package managers.
       *
       * @param npmCommand - A standard npm/npx command string.
       * @returns An object with `pnpm`, `yarn`, `npm`, and `bun` command strings.
       *
       * @example
       * ```tsx
       * import { CodeBlockCommand, convertNpmCommand } from "@/components/ncdai/code-block-command"
       *
       * <CodeBlockCommand {...convertNpmCommand("npx shadcn add button")} />
       * ```
       */
      export function convertNpmCommand(npmCommand: string): ConvertNpmCommandResult {
        // npm install
        if (npmCommand.startsWith("npm install")) {
          return {
            pnpm: npmCommand.replaceAll("npm install", "pnpm add"),
            yarn: npmCommand.replaceAll("npm install", "yarn add"),
            npm: npmCommand,
            bun: npmCommand.replaceAll("npm install", "bun add"),
          }
        }

        // npx create- (must be checked before generic npx)
        if (npmCommand.startsWith("npx create-")) {
          return {
            pnpm: npmCommand.replace("npx create-", "pnpm create "),
            yarn: npmCommand.replace("npx create-", "yarn create "),
            npm: npmCommand,
            bun: npmCommand.replace("npx", "bunx --bun"),
          }
        }

        // npm create
        if (npmCommand.startsWith("npm create")) {
          return {
            pnpm: npmCommand.replace("npm create", "pnpm create"),
            yarn: npmCommand.replace("npm create", "yarn create"),
            npm: npmCommand,
            bun: npmCommand.replace("npm create", "bun create"),
          }
        }

        // npx (general)
        if (npmCommand.startsWith("npx")) {
          return {
            pnpm: npmCommand.replace("npx", "pnpm dlx"),
            yarn: npmCommand.replace("npx", "yarn"),
            npm: npmCommand,
            bun: npmCommand.replace("npx", "bunx --bun"),
          }
        }

        // npm run
        if (npmCommand.startsWith("npm run")) {
          return {
            pnpm: npmCommand.replace("npm run", "pnpm"),
            yarn: npmCommand.replace("npm run", "yarn"),
            npm: npmCommand,
            bun: npmCommand.replace("npm run", "bun"),
          }
        }

        return {
          pnpm: npmCommand,
          yarn: npmCommand,
          npm: npmCommand,
          bun: npmCommand,
        }
      }

      ````

      <Step>Update the import paths to match your project setup</Step>
    </Steps>
  </TabsContent>
</CodeTabs>

## Usage

```tsx
import {
  CodeBlockCommand,
  convertNpmCommand,
} from "@/components/code-block-command"
```

```tsx
<CodeBlockCommand
  pnpm="pnpm dlx shadcn add @ncdai/code-block-command"
  yarn="yarn shadcn add @ncdai/code-block-command"
  npm="npx shadcn add @ncdai/code-block-command"
  bun="bunx --bun shadcn add @ncdai/code-block-command"
/>
```

Or use `convertNpmCommand` to auto-convert from a standard npm command:

```tsx
<CodeBlockCommand
  {...convertNpmCommand("npx shadcn add @ncdai/code-block-command")}
/>
```

## API Reference

### CodeBlockCommand

<TypeTable
  id="type-table-code-block-command.tsx-CodeBlockCommandProps"
  type={{
  "id": "code-block-command.tsx-CodeBlockCommandProps",
  "name": "CodeBlockCommandProps",
  "description": "Props for the CodeBlockCommand component.",
  "entries": [
    {
      "name": "pnpm",
      "description": "Command to execute with pnpm package manager.",
      "tags": [],
      "type": "string | undefined",
      "simplifiedType": "string",
      "required": false,
      "deprecated": false
    },
    {
      "name": "yarn",
      "description": "Command to execute with yarn package manager.",
      "tags": [],
      "type": "string | undefined",
      "simplifiedType": "string",
      "required": false,
      "deprecated": false
    },
    {
      "name": "npm",
      "description": "Command to execute with npm package manager.",
      "tags": [],
      "type": "string | undefined",
      "simplifiedType": "string",
      "required": false,
      "deprecated": false
    },
    {
      "name": "bun",
      "description": "Command to execute with bun package manager.",
      "tags": [],
      "type": "string | undefined",
      "simplifiedType": "string",
      "required": false,
      "deprecated": false
    },
    {
      "name": "onCopySuccess",
      "description": "Callback invoked when a command is successfully copied to clipboard.\n\nReceives an object containing the selected package manager and the copied command text.\nUseful for tracking user interactions or showing custom success notifications.",
      "tags": [
        {
          "name": "param",
          "text": "data - Object containing copy operation details"
        },
        {
          "name": "param",
          "text": "data.packageManager - The package manager that was selected when copying"
        },
        {
          "name": "param",
          "text": "data.command - The actual command text that was copied"
        },
        {
          "name": "example",
          "text": "```tsx\n<CodeBlockCommand\n  pnpm=\"pnpm add react\"\n  onCopySuccess={({ packageManager, command }) => {\n    console.log(`Copied ${command} for ${packageManager}`)\n  }}\n/>\n```"
        }
      ],
      "type": "((data: { packageManager: PackageManager; command: string; }) => void) | undefined",
      "simplifiedType": "function",
      "required": false,
      "deprecated": false
    },
    {
      "name": "onCopyError",
      "description": "Callback invoked when copying to clipboard fails.\n\nReceives the error object for debugging or showing custom error messages.",
      "tags": [
        {
          "name": "param",
          "text": "error - The error that occurred during the copy operation"
        },
        {
          "name": "example",
          "text": "```tsx\n<CodeBlockCommand\n  pnpm=\"pnpm add react\"\n  onCopyError={(error) => {\n    console.error('Copy failed:', error)\n  }}\n/>\n```"
        }
      ],
      "type": "((error: Error) => void) | undefined",
      "simplifiedType": "function",
      "required": false,
      "deprecated": false
    }
  ]
}}
/>

### convertNpmCommand

Converts a standard npm/npx command into equivalent commands for all package managers. The result can be spread directly into `CodeBlockCommand` props.

<TypeTable
  id="type-table-code-block-command.tsx-ConvertNpmCommandResult"
  type={{
  "id": "code-block-command.tsx-ConvertNpmCommandResult",
  "name": "ConvertNpmCommandResult",
  "description": "Result of converting an npm command to all package managers.",
  "entries": [
    {
      "name": "pnpm",
      "description": "Command for pnpm package manager.",
      "tags": [],
      "type": "string",
      "simplifiedType": "string",
      "required": true,
      "deprecated": false
    },
    {
      "name": "yarn",
      "description": "Command for yarn package manager.",
      "tags": [],
      "type": "string",
      "simplifiedType": "string",
      "required": true,
      "deprecated": false
    },
    {
      "name": "npm",
      "description": "Command for npm package manager.",
      "tags": [],
      "type": "string",
      "simplifiedType": "string",
      "required": true,
      "deprecated": false
    },
    {
      "name": "bun",
      "description": "Command for bun package manager.",
      "tags": [],
      "type": "string",
      "simplifiedType": "string",
      "required": true,
      "deprecated": false
    }
  ]
}}
/>

Supported command patterns:

<div data-table-nowrap="">
  | npm command         | pnpm                 | yarn                 | bun                        |
  | ------------------- | -------------------- | -------------------- | -------------------------- |
  | `npm install`       | `pnpm add`           | `yarn add`           | `bun add`                  |
  | `npx create-<name>` | `pnpm create <name>` | `yarn create <name>` | `bunx --bun create-<name>` |
  | `npm create`        | `pnpm create`        | `yarn create`        | `bun create`               |
  | `npx`               | `pnpm dlx`           | `yarn`               | `bunx --bun`               |
  | `npm run`           | `pnpm`               | `yarn`               | `bun`                      |
</div>

## Examples

### Convert from npm

Use `convertNpmCommand` to avoid manually writing commands for each package manager.

```tsx
import {
  CodeBlockCommand,
  convertNpmCommand,
} from "@/components/code-block-command"

export function CodeBlockCommandConvertDemo() {
  return (
    <div className="w-full max-w-md">
      <CodeBlockCommand
        {...convertNpmCommand("npx shadcn add @ncdai/code-block-command")}
      />
    </div>
  )
}

```

### Analytics Tracking

Track user interactions with analytics services like [PostHog](https://posthog.com), [OpenPanel](https://openpanel.dev), or [Google Analytics](https://analytics.google.com).

```tsx showLineNumbers
"use client"

import {
  CodeBlockCommand,
  convertNpmCommand,
} from "@/components/code-block-command"
import { trackEvent } from "@/lib/events"

export function InstallCommand() {
  return (
    <CodeBlockCommand
      {...convertNpmCommand("npx shadcn add @ncdai/code-block-command")}
      onCopySuccess={({ packageManager, command }) => {
        trackEvent({
          name: "command_copied",
          properties: {
            packageManager,
            command,
          },
        })
      }}
      onCopyError={(error) => {
        trackEvent({
          name: "command_copy_failed",
          properties: { error: error.message },
        })
      }}
    />
  )
}
```


Last updated on March 1, 2026