|
1 | | -import { memo } from "react"; |
| 1 | +import { memo, useRef } from "react"; |
2 | 2 | import { CopyIcon, CheckIcon } from "lucide-react"; |
3 | 3 | import { Button } from "../ui/button"; |
4 | 4 | import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; |
5 | 5 | import { cn } from "~/lib/utils"; |
| 6 | +import { anchoredToastManager } from "../ui/toast"; |
| 7 | +import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 8 | + |
| 9 | +const ANCHORED_TOAST_TIMEOUT_MS = 1000; |
| 10 | +const onCopy = (ref: React.RefObject<HTMLButtonElement | null>) => { |
| 11 | + if (ref.current) { |
| 12 | + anchoredToastManager.add({ |
| 13 | + data: { |
| 14 | + tooltipStyle: true, |
| 15 | + }, |
| 16 | + positionerProps: { |
| 17 | + anchor: ref.current, |
| 18 | + }, |
| 19 | + timeout: ANCHORED_TOAST_TIMEOUT_MS, |
| 20 | + title: "Copied!", |
| 21 | + }); |
| 22 | + } |
| 23 | +}; |
| 24 | + |
| 25 | +const onCopyError = (ref: React.RefObject<HTMLButtonElement | null>, error: Error) => { |
| 26 | + if (ref.current) { |
| 27 | + anchoredToastManager.add({ |
| 28 | + data: { |
| 29 | + tooltipStyle: true, |
| 30 | + }, |
| 31 | + positionerProps: { |
| 32 | + anchor: ref.current, |
| 33 | + }, |
| 34 | + timeout: ANCHORED_TOAST_TIMEOUT_MS, |
| 35 | + title: "Failed to copy", |
| 36 | + description: error.message, |
| 37 | + }); |
| 38 | + } |
| 39 | +}; |
6 | 40 |
|
7 | 41 | export const MessageCopyButton = memo(function MessageCopyButton({ |
8 | 42 | text, |
9 | | - title = "Copy message", |
10 | 43 | size = "xs", |
11 | 44 | variant = "outline", |
12 | 45 | className, |
13 | | - onCopy, |
14 | | - onError, |
15 | 46 | }: { |
16 | 47 | text: string; |
17 | 48 | title?: string; |
18 | 49 | size?: "xs" | "icon-xs"; |
19 | 50 | variant?: "outline" | "ghost"; |
20 | 51 | className?: string; |
21 | | - onCopy?: () => void; |
22 | | - onError?: (error: Error) => void; |
23 | 52 | }) { |
| 53 | + const ref = useRef<HTMLButtonElement>(null); |
24 | 54 | const { copyToClipboard, isCopied } = useCopyToClipboard<void>({ |
25 | | - ...(onCopy ? { onCopy: () => onCopy() } : {}), |
26 | | - ...(onError ? { onError: (error: Error) => onError(error) } : {}), |
| 55 | + onCopy: () => onCopy(ref), |
| 56 | + onError: (error: Error) => onCopyError(ref, error), |
| 57 | + timeout: ANCHORED_TOAST_TIMEOUT_MS, |
27 | 58 | }); |
28 | | - const buttonTitle = isCopied ? "Copied" : title; |
29 | 59 |
|
30 | 60 | return ( |
31 | | - <Button |
32 | | - type="button" |
33 | | - size={size} |
34 | | - variant={variant} |
35 | | - className={cn(className)} |
36 | | - onClick={() => copyToClipboard(text, undefined)} |
37 | | - title={buttonTitle} |
38 | | - aria-label={buttonTitle} |
39 | | - > |
40 | | - {isCopied ? <CheckIcon className="size-3 text-success" /> : <CopyIcon className="size-3" />} |
41 | | - </Button> |
| 61 | + <Tooltip> |
| 62 | + <TooltipTrigger |
| 63 | + render={ |
| 64 | + <Button |
| 65 | + aria-label="Copy link" |
| 66 | + disabled={isCopied} |
| 67 | + onClick={() => copyToClipboard(text)} |
| 68 | + ref={ref} |
| 69 | + type="button" |
| 70 | + size={size} |
| 71 | + variant={variant} |
| 72 | + className={cn(className)} |
| 73 | + /> |
| 74 | + } |
| 75 | + > |
| 76 | + {isCopied ? <CheckIcon className="size-3 text-success" /> : <CopyIcon className="size-3" />} |
| 77 | + </TooltipTrigger> |
| 78 | + <TooltipPopup> |
| 79 | + <p>Copy to clipboard</p> |
| 80 | + </TooltipPopup> |
| 81 | + </Tooltip> |
42 | 82 | ); |
43 | 83 | }); |
0 commit comments