1import { memo, useRef } from "react";
2import { Check, Copy } from "lucide";
3import { Button } from "../ui/button";
4import { MorphIcon } from "~/components/MorphIcon";
5import { cn } from "~/lib/utils";
6import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
7import {
8 ANCHORED_COPY_TOAST_TIMEOUT_MS,
9 showAnchoredCopyErrorToast,
10 showAnchoredCopySuccessToast,
11} from "../ui/anchoredCopyToast";
12import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
13
14export const MessageCopyButton = memo(function MessageCopyButton({
15 text,
16 label = "Copy message",
17 icon = Copy,
18 extraFlavors,
19 size = "xs",
20 variant = "outline",
21 className,
22}: {
23 text: string;
24 label?: string;
25 /** The icon shown until the copy lands. */
26 icon?: typeof Copy;
27 /** Additional clipboard types written beside `text/plain` when the platform allows it. */
28 extraFlavors?: Readonly<Record<string, string>>;
29 size?: "xs" | "icon-xs";
30 variant?: "outline" | "ghost";
31 className?: string;
32}) {
33 const ref = useRef<HTMLButtonElement>(null);
34 const { copyToClipboard, isCopied } = useCopyToClipboard<void>({
35 onCopy: () => showAnchoredCopySuccessToast(ref),
36 onError: (error: Error) => showAnchoredCopyErrorToast(ref, error),
37 timeout: ANCHORED_COPY_TOAST_TIMEOUT_MS,
38 ...(extraFlavors ? { extraFlavors } : {}),
39 });
40
41 return (
42 <Tooltip>
43 <TooltipTrigger
44 render={
45 <Button
46 aria-label={label}
47 disabled={isCopied}
48 onClick={() => copyToClipboard(text)}
49 ref={ref}
50 type="button"
51 size={size}
52 variant={variant === "ghost" ? "ghost-muted" : variant}
53 className={className}
54 />
55 }
56 >
57 <MorphIcon
58 className={cn("size-3", isCopied && "text-primary")}
59 icon={isCopied ? Check : icon}
60 />
61 </TooltipTrigger>
62 <TooltipPopup>
63 <p>{label}</p>
64 </TooltipPopup>
65 </Tooltip>
66 );
67});