feat: add copy-to-clipboard button to code blocks

This commit is contained in:
2026-04-22 00:43:19 +02:00
parent fdc65cd870
commit 2342d297ec

View File

@@ -1,4 +1,5 @@
import { createFileRoute, Link } from '@tanstack/react-router' import { createFileRoute, Link } from '@tanstack/react-router'
import { useState } from 'react'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
import SyntaxHighlighter from 'react-syntax-highlighter' import SyntaxHighlighter from 'react-syntax-highlighter'
@@ -51,34 +52,67 @@ function CodeBlock({
readonly className?: string readonly className?: string
readonly children?: React.ReactNode readonly children?: React.ReactNode
}) { }) {
const [copied, setCopied] = useState(false)
const match = /language-(\w+)/.exec(className ?? '') const match = /language-(\w+)/.exec(className ?? '')
if (!match) { if (!match) {
return <code className={className}>{children}</code> return <code className={className}>{children}</code>
} }
const handleCopy = () => {
void navigator.clipboard.writeText(String(children).replace(/\n$/, ''))
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return ( return (
<SyntaxHighlighter <div style={{ position: 'relative', margin: '1.5rem 0' }}>
style={atomOneDark} <button
language={match[1]} type="button"
lineNumberStyle={{ color: 'var(--sea-ink-soft)' }} onClick={handleCopy}
wrapLongLines aria-label="Copy code"
showLineNumbers style={{
customStyle={{ position: 'absolute',
width: '100%', top: '0.6rem',
background: 'var(--surface)', right: '0.6rem',
border: '1px solid var(--line)', zIndex: 10,
borderRadius: '0.75rem', background: 'var(--surface-strong)',
fontSize: '0.875rem', border: '1px solid var(--line)',
lineHeight: '1.7', borderRadius: '0.4rem',
padding: '1.25rem 1.5rem', color: copied ? 'var(--lagoon)' : 'var(--sea-ink-soft)',
}} cursor: 'pointer',
codeTagProps={{ fontSize: '0.7rem',
style: { fontFamily: 'ui-monospace, monospace' }, fontFamily: 'Manrope, sans-serif',
}} letterSpacing: '0.05em',
wrapLines padding: '0.25rem 0.6rem',
> transition: 'color 0.2s',
{String(children).replace(/\n$/, '')} }}
</SyntaxHighlighter> >
{copied ? 'Copied!' : 'Copy'}
</button>
<SyntaxHighlighter
style={atomOneDark}
language={match[1]}
lineNumberStyle={{ color: 'var(--sea-ink-soft)' }}
wrapLongLines
showLineNumbers
customStyle={{
width: '100%',
background: 'var(--surface)',
border: '1px solid var(--line)',
borderRadius: '0.75rem',
fontSize: '0.875rem',
lineHeight: '1.7',
padding: '1.25rem 1.5rem',
}}
codeTagProps={{
style: { fontFamily: 'ui-monospace, monospace' },
}}
wrapLines
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
</div>
) )
} }