| 55 | // ─── Component ──────────────────────────────────────────────────────────────── |
| 56 | |
| 57 | export function Markdown({ children, dimColor = false, className }: MarkdownProps) { |
| 58 | // Memoised to avoid re-parsing on every parent render. |
| 59 | const content = useMemo(() => children, [children]); |
| 60 | |
| 61 | return ( |
| 62 | <div |
| 63 | className={cn( |
| 64 | "markdown-body text-sm leading-relaxed font-mono", |
| 65 | dimColor ? "text-surface-500" : "text-surface-100", |
| 66 | |
| 67 | // Headings |
| 68 | "[&_h1]:text-base [&_h1]:font-bold [&_h1]:mb-2 [&_h1]:mt-3 [&_h1]:text-surface-50", |
| 69 | "[&_h2]:text-sm [&_h2]:font-semibold [&_h2]:mb-1.5 [&_h2]:mt-2.5 [&_h2]:text-surface-100", |
| 70 | "[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:mb-1 [&_h3]:mt-2 [&_h3]:text-surface-200", |
| 71 | |
| 72 | // Paragraphs |
| 73 | "[&_p]:my-1 [&_p]:leading-relaxed", |
| 74 | |
| 75 | // Lists |
| 76 | "[&_ul]:my-1 [&_ul]:pl-4 [&_ul]:list-disc", |
| 77 | "[&_ol]:my-1 [&_ol]:pl-4 [&_ol]:list-decimal", |
| 78 | "[&_li]:my-0.5", |
| 79 | |
| 80 | // Blockquote |
| 81 | "[&_blockquote]:border-l-2 [&_blockquote]:border-brand-500 [&_blockquote]:pl-3", |
| 82 | "[&_blockquote]:my-2 [&_blockquote]:text-surface-400 [&_blockquote]:italic", |
| 83 | |
| 84 | // Horizontal rule |
| 85 | "[&_hr]:border-surface-700 [&_hr]:my-3", |
| 86 | |
| 87 | // Tables (GFM) |
| 88 | "[&_table]:w-full [&_table]:text-xs [&_table]:border-collapse [&_table]:my-2", |
| 89 | "[&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:border [&_th]:border-surface-700 [&_th]:bg-surface-800 [&_th]:font-semibold", |
| 90 | "[&_td]:px-2 [&_td]:py-1 [&_td]:border [&_td]:border-surface-700", |
| 91 | "[&_tr:nth-child(even)_td]:bg-surface-900/40", |
| 92 | |
| 93 | // Links |
| 94 | "[&_a]:text-brand-400 [&_a]:no-underline [&_a:hover]:underline", |
| 95 | |
| 96 | // Strong / em |
| 97 | "[&_strong]:font-bold [&_strong]:text-surface-50", |
| 98 | "[&_em]:italic", |
| 99 | |
| 100 | className |
| 101 | )} |
| 102 | > |
| 103 | <ReactMarkdown |
| 104 | remarkPlugins={[remarkGfm]} |
| 105 | components={{ |
| 106 | code({ className: cls, children: codeChildren, ...rest }) { |
| 107 | const isBlock = /language-/.test(cls ?? ""); |
| 108 | if (isBlock) { |
| 109 | return ( |
| 110 | <code className={cn("block text-surface-200", cls)} {...rest}> |
| 111 | {codeChildren} |
| 112 | </code> |
| 113 | ); |
| 114 | } |