Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

103 lines
2.9 KiB
TypeScript

"use client";
import { analytics, type AnalyticsProperties } from "@/lib/analytics";
import { cn } from "@/lib/utils";
import { CheckIcon, CopyIcon, SparklesIcon, TerminalIcon } from "lucide-react";
import { useState } from "react";
import { Menu } from "@base-ui/react/menu";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
} from "@/components/ui/dropdown-menu";
import { SETUP_PROMPT } from "./setup-prompt";
export function CopyCommandButton({
command = "npx assistant-ui init",
analyticsContext,
withPromptOption = false,
}: {
command?: string;
analyticsContext?: AnalyticsProperties;
withPromptOption?: boolean;
}) {
const [copied, setCopied] = useState(false);
const flash = () => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const copyCommand = () => {
navigator.clipboard.writeText(command);
analytics.cta.npmCommandCopied(command, analyticsContext);
flash();
};
const copyPrompt = () => {
navigator.clipboard.writeText(SETUP_PROMPT);
analytics.cta.promptCopied(analyticsContext);
flash();
};
const wrapperClassName =
"group border-border/60 bg-muted/30 hover:border-border hover:bg-muted/50 inline-flex w-fit items-center gap-1.5 rounded-md border px-3 py-1.5 font-mono text-sm transition-all";
const copyIcon = (
<div className="text-muted-foreground relative flex size-4 items-center justify-center">
<CheckIcon
className={cn(
"absolute size-3.5 text-green-500 transition-all duration-100",
copied ? "scale-100 opacity-100" : "scale-50 opacity-0",
)}
/>
<CopyIcon
className={cn(
"absolute size-3.5 transition-all duration-100",
copied
? "scale-50 opacity-0"
: "scale-100 opacity-50 group-hover:opacity-100",
)}
/>
</div>
);
if (!withPromptOption) {
return (
<button type="button" onClick={copyCommand} className={wrapperClassName}>
<span className="text-muted-foreground/70">$</span>
<span>{command}</span>
<div className="ml-1">{copyIcon}</div>
</button>
);
}
return (
<DropdownMenu>
<Menu.Trigger
aria-label="Copy options"
render={
<button
type="button"
className={cn(wrapperClassName, "cursor-pointer")}
/>
}
>
<span className="text-muted-foreground/70">$</span>
<span>{command}</span>
<div className="ml-1">{copyIcon}</div>
</Menu.Trigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={copyCommand}>
<TerminalIcon className="size-3.5" />
Copy CLI command
</DropdownMenuItem>
<DropdownMenuItem onClick={copyPrompt}>
<SparklesIcon className="size-3.5" />
Copy coding agent prompt
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}