// Generic Markdown renderer for arbitrary content (AI chat answers, notes, …). // Mirrors the house pattern established by common/components/Changelog.tsx — // markdown-to-jsx with per-element Tailwind overrides on semantic design tokens // (no `prose` plugin) — but drops the changelog-specific h1/h2 anchor behaviour // so it's a drop-in for any Markdown string. Tuned for chat density: smaller // headings, tight vertical rhythm, token-based code/quote/table styling. // // Pure/presentational and safe in a client tree. markdown-to-jsx escapes raw // HTML by default, so streaming model output can't inject markup. import type { AnchorHTMLAttributes, ComponentType } from "react"; import MarkdownToJsx from "markdown-to-jsx"; const OVERRIDES = { h1: { props: { className: "mt-4 mb-2 text-base font-semibold text-foreground" } }, h2: { props: { className: "mt-4 mb-2 text-sm font-semibold text-foreground" } }, h3: { props: { className: "mt-3 mb-1 text-sm font-semibold text-foreground" } }, h4: { props: { className: "mt-3 mb-1 text-sm font-semibold text-foreground" } }, p: { props: { className: "my-2 leading-relaxed" } }, ul: { props: { className: "list-disc pl-5 my-2 space-y-1 marker:text-muted-foreground", }, }, ol: { props: { className: "list-decimal pl-5 my-2 space-y-1 marker:text-muted-foreground", }, }, li: { props: { className: "leading-relaxed" } }, strong: { props: { className: "font-semibold text-foreground" } }, em: { props: { className: "italic" } }, code: { props: { className: "px-1 py-0.5 rounded bg-muted font-mono text-[0.85em]", }, }, pre: { props: { className: "my-2 p-3 rounded-md bg-muted overflow-x-auto text-xs font-mono", }, }, a: { props: { className: "text-brand underline decoration-brand/40 hover:decoration-brand", target: "_blank", rel: "noopener noreferrer", }, }, hr: { props: { className: "my-4 border-t border-border" } }, blockquote: { props: { className: "my-2 pl-3 border-l-2 border-border text-muted-foreground", }, }, table: { props: { className: "my-2 block w-full overflow-x-auto text-left border-collapse", }, }, th: { props: { className: "border-b border-border px-2 py-1 font-semibold text-foreground", }, }, td: { props: { className: "border-b border-border/50 px-2 py-1 align-top" } }, }; // Render a Markdown string. `forceBlock` keeps single-line input block-level so // spacing is consistent regardless of content shape. `linkComponent` optionally // overrides how `` renders (e.g. to make in-page citation anchors scroll // instead of opening a new tab); when omitted, links use the default external // styling above. export function Markdown({ children, className, linkComponent, }: { children: string; className?: string; linkComponent?: ComponentType>; }) { const overrides = linkComponent ? { ...OVERRIDES, a: { component: linkComponent } } : OVERRIDES; return (
{children}
); }