Skip to content

feat/fix copy button #2224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/components/code-example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import linesToDiv from "./lines-to-div";
import atApplyInjection from "./syntax-highlighter/at-apply.json";
import atRulesInjection from "./syntax-highlighter/at-rules.json";
import themeFnInjection from "./syntax-highlighter/theme-fn.json";
import { CopyButton } from "./copy-button";

export function js(strings: TemplateStringsArray, ...args: any[]) {
return { lang: "js", code: dedent(strings, ...args) };
Expand Down Expand Up @@ -50,7 +51,7 @@ export async function CodeExample({
}) {
return (
<CodeExampleWrapper className={className}>
{filename ? <CodeExampleFilename filename={filename} /> : null}
{filename ? <CodeExampleFilename filename={filename} code={example.code} /> : null}
<HighlightedCode example={example} />
</CodeExampleWrapper>
);
Expand Down Expand Up @@ -189,8 +190,20 @@ export function RawHighlightedCode({
return <div className={className} dangerouslySetInnerHTML={{ __html: code }} />;
}

function CodeExampleFilename({ filename }: { filename: string }) {
return <div className="px-3 pt-0.5 pb-1.5 text-xs/5 text-gray-400 dark:text-white/50">{filename}</div>;
function cleanCodeForCopy(code:string) {
return code
.split('\n')
.filter(line => !line.includes('[!code highlight'))
.join('\n');
}

function CodeExampleFilename({ filename, code }: { filename: string; code?: string }) {
return (
<div className="flex justify-between px-3 pt-0.5 pb-1.5 text-xs/5 text-gray-400 dark:text-white/50">
{filename}
{code && <CopyButton code={cleanCodeForCopy(code)} />}
</div>
);
}

const highlighter = await createHighlighter({
Expand Down
54 changes: 54 additions & 0 deletions src/components/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Added a copy button to the code block to copy the code to clipboard
'use client'

import { useState } from 'react'

export function CopyButton({ code }: { code: string }) {
const [copied, setCopied] = useState(false)

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(code)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch (err) {
console.error('Failed to copy:', err)
}
}

return (
<button
onClick={handleCopy}
className="copy-button flex items-center rounded-lg transition-colors hover:text-white"
title="Copy to clipboard"
>
{copied ? (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="size-4 text-green-400"
>
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="size-4"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
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"
/>
</svg>
)}
</button>
)
}