/* Copyright 2026 Marimo. All rights reserved. */ import type { MermaidConfig } from "mermaid"; import mermaid from "mermaid"; import React, { useState } from "react"; import { useAsyncData } from "@/hooks/useAsyncData"; import { ErrorBanner } from "@/plugins/impl/common/error-banner"; import { useTheme } from "@/theme/useTheme"; import { Logger } from "@/utils/Logger"; interface Props { diagram: string; config?: MermaidConfig; } const DEFAULT_CONFIG: MermaidConfig = { startOnLoad: true, theme: "forest", logLevel: "fatal", securityLevel: "strict", fontFamily: "var(--text-font)", arrowMarkerAbsolute: false, flowchart: { htmlLabels: true, curve: "linear", }, sequence: { diagramMarginX: 50, diagramMarginY: 10, actorMargin: 50, width: 150, height: 65, boxMargin: 10, boxTextMargin: 5, noteMargin: 10, messageMargin: 35, mirrorActors: true, bottomMarginAdj: 1, useMaxWidth: true, rightAngles: false, showSequenceNumbers: false, }, gantt: { titleTopMargin: 25, barHeight: 20, barGap: 4, topPadding: 50, leftPadding: 75, gridLineStartPadding: 35, fontSize: 11, numberSectionStyles: 4, axisFormat: "%Y-%m-%d", }, }; function randomAlpha() { const alphabet = "abcdefghijklmnopqrstuvwxyz"; const length = 6; return Array.from( { length }, () => alphabet[Math.floor(Math.random() * alphabet.length)], ).join(""); } const Mermaid: React.FC = ({ diagram }) => { // oxlint-disable-next-line react/hook-use-state const [id] = useState(() => randomAlpha()); const darkMode = useTheme().theme === "dark"; mermaid.initialize({ ...DEFAULT_CONFIG, theme: darkMode ? "dark" : "forest", darkMode: darkMode, }); // Frontmatter does not work with newlines, so we trim the diagram to avoid these errors const trimmedDiagram = diagram.trim(); const { data: svg, error } = useAsyncData(async () => { const result = await mermaid .render(id, trimmedDiagram, undefined) .catch((error) => { document.getElementById(id)?.remove(); Logger.warn("Failed to render mermaid diagram", error); throw error; }); return result.svg; }, [trimmedDiagram, id, darkMode]); if (error) { return ; } if (!svg) { return null; } return
; }; export default Mermaid;