's'
This commit is contained in:
85
app/vip/components/Copyable.tsx
Normal file
85
app/vip/components/Copyable.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
const CopyIcon = () => (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0, opacity: 0.6 }}>
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CheckIcon = () => (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0, color: "var(--accent)" }}>
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
type CopyableProps = {
|
||||
text: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export function Copyable({ text, children, className, style }: CopyableProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
const fallbackCopy = () => {
|
||||
const ta = document.createElement("textarea");
|
||||
ta.value = text;
|
||||
ta.style.position = "fixed";
|
||||
ta.style.left = "-9999px";
|
||||
ta.style.top = "0";
|
||||
ta.setAttribute("readonly", "");
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
ta.setSelectionRange(0, text.length);
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
} finally {
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
};
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
fallbackCopy();
|
||||
}
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
} catch {
|
||||
try {
|
||||
fallbackCopy();
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}, [text]);
|
||||
|
||||
return (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={handleClick}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleClick()}
|
||||
className={className}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: "0.35rem",
|
||||
...style,
|
||||
}}
|
||||
title={copied ? "已复制" : "点击复制"}
|
||||
>
|
||||
{children}
|
||||
{copied ? <CheckIcon /> : <CopyIcon />}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user