File size: 2,815 Bytes
b7d4394
4a940a5
 
 
b7d4394
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50ae780
b7d4394
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { useState, useCallback } from "preact/hooks";
import { clipboardCopy } from "../../../shared/utils/clipboard";
import { useT } from "../../../shared/i18n/context";
import type { TranslationKey } from "../../../shared/i18n/translations";

interface CopyButtonProps {
  getText: () => string;
  class?: string;
  titleKey?: string;
  variant?: "icon" | "label";
}

const SVG_COPY = (
  <svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="9" y="9" width="13" height="13" rx="2" />
    <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
  </svg>
);

const SVG_CHECK = (
  <svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
  </svg>
);

const SVG_FAIL = (
  <svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
  </svg>
);

export function CopyButton({ getText, class: className, titleKey, variant = "icon" }: CopyButtonProps) {
  const t = useT();
  const [state, setState] = useState<"idle" | "ok" | "fail">("idle");

  const handleCopy = useCallback(async () => {
    const ok = await clipboardCopy(getText());
    setState(ok ? "ok" : "fail");
    setTimeout(() => setState("idle"), 2000);
  }, [getText]);

  if (variant === "label") {
    const bgClass =
      state === "ok"
        ? "bg-primary hover:bg-primary-hover"
        : state === "fail"
          ? "bg-red-600 hover:bg-red-700"
          : "bg-slate-700 hover:bg-slate-600";

    return (
      <button
        onClick={handleCopy}
        class={`flex items-center gap-1.5 px-3 py-1.5 ${bgClass} text-white rounded text-xs font-medium transition-colors ${className || ""}`}
      >
        <svg class="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
          <rect x="9" y="9" width="13" height="13" rx="2" />
          <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
        </svg>
        <span>
          {state === "ok" ? t("copied") : state === "fail" ? t("copyFailed") : t("copy")}
        </span>
      </button>
    );
  }

  return (
    <button
      onClick={handleCopy}
      class={`p-1.5 transition-colors rounded-md hover:bg-slate-100 dark:hover:bg-border-dark ${
        state === "ok"
          ? "text-primary"
          : state === "fail"
            ? "text-red-500"
            : "text-slate-400 dark:text-text-dim hover:text-primary"
      } ${className || ""}`}
      title={titleKey ? t(titleKey as TranslationKey) : undefined}
    >
      {state === "ok" ? SVG_CHECK : state === "fail" ? SVG_FAIL : SVG_COPY}
    </button>
  );
}