/ components / Markdown.tsx
Markdown.tsx
1 import ReactMarkdown from 'react-markdown'; 2 import remarkGfm from 'remark-gfm'; 3 import type { Components } from 'react-markdown'; 4 import { cn } from '@/lib/utils'; 5 6 interface Props { 7 children: string; 8 className?: string; 9 } 10 11 const components: Components = { 12 h1: ({ children }) => <h1 className="text-lg font-semibold mt-4 mb-2">{children}</h1>, 13 h2: ({ children }) => <h2 className="text-base font-semibold mt-3 mb-1.5">{children}</h2>, 14 h3: ({ children }) => <h3 className="text-sm font-semibold mt-2 mb-1">{children}</h3>, 15 p: ({ children }) => <p className="mb-2 last:mb-0">{children}</p>, 16 ul: ({ children }) => <ul className="list-disc pl-4 mb-2 last:mb-0 space-y-1">{children}</ul>, 17 ol: ({ children }) => <ol className="list-decimal pl-4 mb-2 last:mb-0 space-y-1">{children}</ol>, 18 li: ({ children }) => <li>{children}</li>, 19 blockquote: ({ children }) => ( 20 <blockquote className="border-l-2 border-muted-foreground/30 pl-3 italic text-muted-foreground mb-2 last:mb-0"> 21 {children} 22 </blockquote> 23 ), 24 a: ({ href, children }) => ( 25 <a 26 href={href} 27 target="_blank" 28 rel="noopener noreferrer" 29 className="underline underline-offset-2 hover:text-foreground" 30 > 31 {children} 32 </a> 33 ), 34 code: ({ className, children }) => { 35 const isBlock = className?.startsWith('language-'); 36 if (isBlock) { 37 return ( 38 <code 39 className={`block bg-muted/50 rounded-md p-3 text-xs font-mono overflow-x-auto whitespace-pre mb-2 last:mb-0 ${className ?? ''}`} 40 > 41 {children} 42 </code> 43 ); 44 } 45 return <code className="font-mono text-[0.85em] bg-muted/70 rounded px-1 py-0.5 text-foreground">{children}</code>; 46 }, 47 pre: ({ children }) => <>{children}</>, 48 table: ({ children }) => ( 49 <div className="overflow-x-auto mb-2 last:mb-0"> 50 <table className="min-w-full text-sm border-collapse">{children}</table> 51 </div> 52 ), 53 th: ({ children }) => ( 54 <th className="border border-border px-2 py-1 text-left font-medium bg-muted/30">{children}</th> 55 ), 56 td: ({ children }) => <td className="border border-border px-2 py-1">{children}</td>, 57 hr: () => <hr className="border-border my-3" />, 58 }; 59 60 export function Markdown({ children, className }: Props) { 61 return ( 62 <div className={cn('select-text', className)}> 63 <ReactMarkdown remarkPlugins={[remarkGfm]} components={components}> 64 {children} 65 </ReactMarkdown> 66 </div> 67 ); 68 }