Skip to content

Commit 7460797

Browse files
committed
feat: codeblock add copy button when hover.
1 parent eb835d2 commit 7460797

1 file changed

Lines changed: 67 additions & 5 deletions

File tree

src/components/ChatInterface.jsx

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,74 @@ const markdownComponents = {
213213
</code>
214214
);
215215
}
216+
const [copied, setCopied] = React.useState(false);
217+
const textToCopy = raw;
218+
219+
const handleCopy = () => {
220+
const doSet = () => {
221+
setCopied(true);
222+
setTimeout(() => setCopied(false), 1500);
223+
};
224+
try {
225+
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
226+
navigator.clipboard.writeText(textToCopy).then(doSet).catch(() => {
227+
// Fallback
228+
const ta = document.createElement('textarea');
229+
ta.value = textToCopy;
230+
ta.style.position = 'fixed';
231+
ta.style.opacity = '0';
232+
document.body.appendChild(ta);
233+
ta.select();
234+
try { document.execCommand('copy'); } catch {}
235+
document.body.removeChild(ta);
236+
doSet();
237+
});
238+
} else {
239+
const ta = document.createElement('textarea');
240+
ta.value = textToCopy;
241+
ta.style.position = 'fixed';
242+
ta.style.opacity = '0';
243+
document.body.appendChild(ta);
244+
ta.select();
245+
try { document.execCommand('copy'); } catch {}
246+
document.body.removeChild(ta);
247+
doSet();
248+
}
249+
} catch {}
250+
};
251+
216252
return (
217-
<pre className="bg-gray-900 dark:bg-gray-900 border border-gray-700/40 rounded-lg p-3 overflow-x-auto my-2">
218-
<code className={`text-gray-100 dark:text-gray-100 text-sm font-mono ${className || ''}`} {...props}>
219-
{children}
220-
</code>
221-
</pre>
253+
<div className="relative group my-2">
254+
<button
255+
type="button"
256+
onClick={handleCopy}
257+
className="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 focus:opacity-100 active:opacity-100 transition-opacity text-xs px-2 py-1 rounded-md bg-gray-700/80 hover:bg-gray-700 text-white border border-gray-600"
258+
title={copied ? 'Copied' : 'Copy code'}
259+
aria-label={copied ? 'Copied' : 'Copy code'}
260+
>
261+
{copied ? (
262+
<span className="flex items-center gap-1">
263+
<svg className="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor">
264+
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
265+
</svg>
266+
Copied
267+
</span>
268+
) : (
269+
<span className="flex items-center gap-1">
270+
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
271+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
272+
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
273+
</svg>
274+
Copy
275+
</span>
276+
)}
277+
</button>
278+
<pre className="bg-gray-900 dark:bg-gray-900 border border-gray-700/40 rounded-lg p-3 overflow-x-auto">
279+
<code className={`text-gray-100 dark:text-gray-100 text-sm font-mono ${className || ''}`} {...props}>
280+
{children}
281+
</code>
282+
</pre>
283+
</div>
222284
);
223285
},
224286
blockquote: ({ children }) => (

0 commit comments

Comments
 (0)