Add mermaid suport

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-03-09 18:48:24 +01:00
parent 85ed5296e7
commit c98ffaeda3
12 changed files with 4241 additions and 33 deletions

View File

@@ -24,6 +24,7 @@
"@radix-ui/react-tabs": "^1.1.13",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"mermaid": "^11.13.0",
"react-dropzone": "^14.3.8",
"react-intersection-observer": "^9.16.0",
"react-markdown": "^10.1.0",

View File

@@ -1,7 +1,10 @@
import { isValidElement } from "react";
import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw";
import remarkGfm from "remark-gfm";
import { MermaidDiagram } from "./MermaidDiagram";
type Props = {
content: string;
};
@@ -23,19 +26,31 @@ export function Markdown({ content }: Props) {
{children}
</a>
),
pre: ({ children, ...props }) => (
<pre
className="border border-border-solid rounded p-4 bg-transparent font-mono text-sm overflow-x-auto text-inherit"
{...props}
>
{children}
</pre>
),
code: ({ children, ...props }) => (
<code className="font-mono text-sm text-inherit" {...props}>
{children}
</code>
),
pre: ({ children, ...props }) => {
const child = isValidElement<{
className?: string;
children?: string;
}>(children)
? children
: null;
if (
child?.type === "code"
&& child.props.className === "language-mermaid"
&& typeof child.props.children === "string"
) {
return <MermaidDiagram chart={child.props.children} />;
}
return (
<pre
className="border border-border-solid rounded p-4 bg-transparent font-mono text-sm overflow-x-auto text-inherit"
{...props}
>
{children}
</pre>
);
},
}}
>
{content}

View File

@@ -0,0 +1,51 @@
import mermaid from "mermaid";
import { useEffect, useId, useState } from "react";
type Props = {
chart: string;
};
export function MermaidDiagram({ chart }: Props) {
const id = useId().replace(/:/g, "");
const [svg, setSvg] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
mermaid.initialize({ startOnLoad: false, theme: "neutral" });
mermaid
.render(`mermaid-${id}`, chart.trim())
.then((result) => {
if (!cancelled) {
setSvg(result.svg);
setError(null);
}
})
.catch((err: unknown) => {
if (!cancelled) {
setError(err instanceof Error ? err.message : String(err));
}
});
return () => {
cancelled = true;
};
}, [chart, id]);
if (error) {
return (
<pre className="border border-border-solid rounded p-4 bg-transparent font-mono text-sm overflow-x-auto text-inherit">
<code>{chart}</code>
</pre>
);
}
return (
<div
className="flex justify-center my-4"
dangerouslySetInnerHTML={svg ? { __html: svg } : undefined}
/>
);
}