Skip to content

Commit df40a07

Browse files
[added a copy button]
1 parent ef953ab commit df40a07

File tree

2 files changed

+62
-3
lines changed

2 files changed

+62
-3
lines changed

src/components/code-example.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import linesToDiv from "./lines-to-div";
1414
import atApplyInjection from "./syntax-highlighter/at-apply.json";
1515
import atRulesInjection from "./syntax-highlighter/at-rules.json";
1616
import themeFnInjection from "./syntax-highlighter/theme-fn.json";
17+
import { CopyButton } from "./copy-button";
1718

1819
export function js(strings: TemplateStringsArray, ...args: any[]) {
1920
return { lang: "js", code: dedent(strings, ...args) };
@@ -50,7 +51,7 @@ export async function CodeExample({
5051
}) {
5152
return (
5253
<CodeExampleWrapper className={className}>
53-
{filename ? <CodeExampleFilename filename={filename} /> : null}
54+
{filename ? <CodeExampleFilename filename={filename} code={example.code} /> : null}
5455
<HighlightedCode example={example} />
5556
</CodeExampleWrapper>
5657
);
@@ -189,8 +190,13 @@ export function RawHighlightedCode({
189190
return <div className={className} dangerouslySetInnerHTML={{ __html: code }} />;
190191
}
191192

192-
function CodeExampleFilename({ filename }: { filename: string }) {
193-
return <div className="px-3 pt-0.5 pb-1.5 text-xs/5 text-gray-400 dark:text-white/50">{filename}</div>;
193+
function CodeExampleFilename({ filename, code }: { filename: string; code?: string }) {
194+
return (
195+
<div className="flex justify-between px-3 pt-0.5 pb-1.5 text-xs/5 text-gray-400 dark:text-white/50">
196+
{filename}
197+
{code && <CopyButton code={code} />}
198+
</div>
199+
);
194200
}
195201

196202
const highlighter = await createHighlighter({

src/components/copy-button.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
5+
export function CopyButton({ code }: { code: string }) {
6+
const [copied, setCopied] = useState(false)
7+
8+
const handleCopy = async () => {
9+
try {
10+
await navigator.clipboard.writeText(code)
11+
setCopied(true)
12+
setTimeout(() => setCopied(false), 2000)
13+
} catch (err) {
14+
console.error('Failed to copy:', err)
15+
}
16+
}
17+
18+
return (
19+
<button
20+
onClick={handleCopy}
21+
className="copy-button flex items-center rounded-lg transition-colors hover:text-white"
22+
title="Copy to clipboard"
23+
>
24+
{copied ? (
25+
<svg
26+
xmlns="http://www.w3.org/2000/svg"
27+
fill="none"
28+
viewBox="0 0 24 24"
29+
strokeWidth={1.5}
30+
stroke="currentColor"
31+
className="size-4 text-green-400"
32+
>
33+
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
34+
</svg>
35+
) : (
36+
<svg
37+
xmlns="http://www.w3.org/2000/svg"
38+
fill="none"
39+
viewBox="0 0 24 24"
40+
strokeWidth={1.5}
41+
stroke="currentColor"
42+
className="size-4"
43+
>
44+
<path
45+
strokeLinecap="round"
46+
strokeLinejoin="round"
47+
d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184"
48+
/>
49+
</svg>
50+
)}
51+
</button>
52+
)
53+
}

0 commit comments

Comments
 (0)