/ 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  }