MarkdownText.tsx raw
1 import { ExternalLink } from 'lucide-react'
2 import { memo } from 'react'
3 import Markdown from 'react-markdown'
4 import remarkGfm from 'remark-gfm'
5 import remarkBreaks from 'remark-breaks'
6
7 const MarkdownText = memo(function MarkdownText({ text }: { text: string }) {
8 return (
9 <Markdown
10 remarkPlugins={[remarkGfm, remarkBreaks]}
11 components={{
12 a: ({ href, children, ...props }) => (
13 <a
14 {...props}
15 href={href}
16 target="_blank"
17 rel="noreferrer noopener"
18 className="break-words inline-flex items-baseline gap-1 underline text-foreground"
19 >
20 {children} <ExternalLink className="size-3" />
21 </a>
22 ),
23 p: ({ children, ...props }) => <span {...props} className="break-words">{children}</span>,
24 code: ({ className, children, ...props }) => {
25 const isBlock = className?.startsWith('language-')
26 if (isBlock) {
27 return (
28 <code {...props} className={`${className ?? ''} break-words whitespace-pre-wrap`}>
29 {children}
30 </code>
31 )
32 }
33 return (
34 <code {...props} className="bg-muted px-1 py-0.5 rounded text-sm break-words">
35 {children}
36 </code>
37 )
38 },
39 pre: (props) => (
40 <pre
41 {...props}
42 className="bg-muted rounded-md p-3 overflow-x-auto my-2 text-sm whitespace-pre-wrap"
43 />
44 ),
45 img: (props) => (
46 <img
47 {...props}
48 className="max-w-full max-h-[50vh] object-contain rounded-md my-2"
49 loading="lazy"
50 />
51 )
52 }}
53 >
54 {text}
55 </Markdown>
56 )
57 })
58
59 export default MarkdownText
60