Particular.Tech
Developerui

Install command

One command shown for npm, pnpm, bun, and yarn; the choice persists.

npx shadcn@latest add @particular/log-viewer
npm install @particular-tech/sdk
npm install --save-dev @particular-tech/cli

Installation

npx shadcn@latest add @particular/install-command
npm dependencies
  • cn@^0.4.0
Installs these registry items

Source

ui/install-command.tsx
"use client"

import * as React from "react"
import { cn } from "cn"

import { CopyButton } from "@/components/ui/copy-button"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"

const packageManagers = ["npm", "pnpm", "bun", "yarn"] as const
type PackageManager = (typeof packageManagers)[number]

type CommandSpec =
  | { exec: string; install?: never; dev?: never }
  | { install: string; dev?: boolean; exec?: never }

function formatCommand(pm: PackageManager, spec: CommandSpec): string {
  if (spec.exec !== undefined) {
    const runner = { npm: "npx", pnpm: "pnpm dlx", bun: "bunx --bun", yarn: "yarn dlx" }[pm]
    return `${runner} ${spec.exec}`
  }
  const add = { npm: "npm install", pnpm: "pnpm add", bun: "bun add", yarn: "yarn add" }[pm]
  const devFlag = spec.dev ? (pm === "npm" ? " --save-dev" : " -D") : ""
  return `${add}${devFlag} ${spec.install}`
}

const STORAGE_KEY = "particular:package-manager"
const CHANGE_EVENT = "particular:package-manager-change"

function isPackageManager(value: unknown): value is PackageManager {
  return packageManagers.some((pm) => pm === value)
}

function subscribe(onChange: () => void) {
  window.addEventListener("storage", onChange)
  window.addEventListener(CHANGE_EVENT, onChange)
  return () => {
    window.removeEventListener("storage", onChange)
    window.removeEventListener(CHANGE_EVENT, onChange)
  }
}

function readPackageManager(): PackageManager {
  const stored = window.localStorage.getItem(STORAGE_KEY)
  return isPackageManager(stored) ? stored : "npm"
}

/** The visitor's package manager, shared by every InstallCommand on the page and persisted. */
function usePackageManager() {
  const pm = React.useSyncExternalStore(subscribe, readPackageManager, () => "npm" as const)
  const setPm = React.useCallback((next: PackageManager) => {
    window.localStorage.setItem(STORAGE_KEY, next)
    window.dispatchEvent(new Event(CHANGE_EVENT))
  }, [])
  return [pm, setPm] as const
}

function InstallCommand({
  className,
  ...props
}: CommandSpec & Omit<React.ComponentProps<"div">, keyof CommandSpec>) {
  const { exec, install, dev, ...rest } = props
  const spec: CommandSpec = exec !== undefined ? { exec } : { install: install ?? "", dev }
  const [pm, setPm] = usePackageManager()
  const command = formatCommand(pm, spec)

  return (
    <div
      data-slot="install-command"
      className={cn("overflow-hidden rounded-lg border bg-card", className)}
      {...rest}
    >
      <Tabs
        value={pm}
        onValueChange={(value) => {
          if (isPackageManager(value)) setPm(value)
        }}
      >
        <div className="flex items-center justify-between gap-2 border-b bg-muted/40 pr-1.5 pl-1">
          <TabsList variant="line" className="h-9">
            {packageManagers.map((name) => (
              <TabsTrigger key={name} value={name} className="font-mono text-xs">
                {name}
              </TabsTrigger>
            ))}
          </TabsList>
          <CopyButton value={command} size="icon-xs" label="Copy command" />
        </div>
      </Tabs>
      <pre className="overflow-x-auto px-4 py-3 font-mono text-[0.8125rem]">
        <code>
          <span aria-hidden className="mr-2 text-muted-foreground select-none">
            $
          </span>
          {command}
        </code>
      </pre>
    </div>
  )
}

export { InstallCommand, formatCommand, usePackageManager, packageManagers, type PackageManager, type CommandSpec }