feat: add copy-to-clipboard button to code blocks
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user