Skip to content

[added a copy button] #2222

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! Sorry I haven't had time to properly review this yet but this line does not look like it's generic enough to handle all the code highlighter annotations. Some of them appear inline (so at the end of a line containing code).

This also doesn't handle diff views at all: https://tailwindcss-com-git-fork-sayanchaterjee-fea-b7f6d8-tailwindlabs.vercel.app/docs/upgrade-guide#using-postcss

Maybe a better way is to either use the output from shiki somehow or rely on the DOM state to get the visible code?

I'm not sure what I'd expect to happen there for the diff example though. Maybe it's fine because in v3 this also didn't do anything special, see app.js here.

.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
53 changes: 53 additions & 0 deletions src/components/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'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>
)
}