File tree Expand file tree Collapse file tree 2 files changed +77
-4
lines changed Expand file tree Collapse file tree 2 files changed +77
-4
lines changed Original file line number Diff line number Diff line change
1
+ import { useRef , type ComponentProps } from 'react'
2
+ import { Copy } from 'lucide-react'
3
+ import { Button } from '@/components/ui/button'
4
+ import { copyToClipboard } from '@/lib/utils/clipboard'
5
+
6
+ type CodeBlockProps = {
7
+ onCopySuccess ?: ( text : string ) => void
8
+ onCopyError ?: ( text : string , error ?: unknown ) => void
9
+ copyButtonLabel ?: string
10
+ copiedButtonLabel ?: string
11
+ } & Omit < ComponentProps < 'pre' > , 'onCopy' >
12
+
13
+ export function CodeBlock ( {
14
+ children,
15
+ className,
16
+ onCopySuccess,
17
+ onCopyError,
18
+ ...props
19
+ } : CodeBlockProps ) {
20
+ const preRef = useRef < HTMLPreElement > ( null )
21
+
22
+ const handleCopy = async ( ) => {
23
+ // Extract text content from the actual DOM element
24
+ const textContent = preRef . current ?. textContent ?? ''
25
+
26
+ try {
27
+ const success = await copyToClipboard ( textContent )
28
+
29
+ if ( success ) {
30
+ onCopySuccess ?.( textContent )
31
+ } else {
32
+ onCopyError ?.( textContent )
33
+ }
34
+ } catch ( error ) {
35
+ onCopyError ?.( textContent , error )
36
+ }
37
+ }
38
+
39
+ return (
40
+ < div className = "relative group" >
41
+ < pre
42
+ ref = { preRef }
43
+ className = { `overflow-x-auto whitespace-pre-wrap break-words ${ className || '' } ` }
44
+ { ...props }
45
+ >
46
+ { children }
47
+ </ pre >
48
+ < Button
49
+ onClick = { handleCopy }
50
+ variant = "secondary"
51
+ size = "sm"
52
+ className = "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-xs"
53
+ >
54
+ < Copy className = "h-4 w-4" />
55
+ < span className = "sr-only" > Copy code snippet</ span >
56
+ </ Button >
57
+ </ div >
58
+ )
59
+ }
Original file line number Diff line number Diff line change 1
1
import ReactMarkdown from 'react-markdown'
2
2
import remarkGfm from 'remark-gfm'
3
+ import { toast } from 'sonner'
4
+ import { CodeBlock } from '@/components/CodeBlock'
3
5
4
6
type MarkdownContentProps = {
5
7
content : string
6
8
}
7
9
8
10
export function MarkdownContent ( { content } : MarkdownContentProps ) {
11
+ const handleCopySuccess = ( ) => {
12
+ toast . success ( 'Copied code snippet to clipboard' )
13
+ }
14
+
15
+ const handleCopyError = ( ) => {
16
+ toast . error ( 'Failed to copy code snippet' )
17
+ }
18
+
9
19
return (
10
20
< div className = "prose prose-sm dark:prose-invert max-w-none break-words [&>*+*]:mt-4 [&>h1+*]:mt-3 [&>h2+*]:mt-3 [&>h3+*]:mt-3 [&>p+ul]:mt-3 [&>p+ol]:mt-3" >
11
21
< ReactMarkdown
12
22
remarkPlugins = { [ remarkGfm ] }
13
23
components = { {
14
- pre : ( { node, ...props } ) => (
15
- < pre
16
- className = "overflow-x-auto whitespace-pre-wrap break-words"
24
+ pre : ( { node, className, children, ...props } ) => (
25
+ < CodeBlock
26
+ className = { className }
27
+ onCopySuccess = { handleCopySuccess }
28
+ onCopyError = { handleCopyError }
17
29
{ ...props }
18
- />
30
+ >
31
+ { children }
32
+ </ CodeBlock >
19
33
) ,
20
34
code : ( { node, ...props } ) => (
21
35
< code className = "break-words whitespace-pre-wrap" { ...props } />
You can’t perform that action at this time.
0 commit comments