diff --git a/src/components/mermaid.tsx b/src/components/mermaid.tsx
index dfaabe8599129..174d44fef8b1e 100644
--- a/src/components/mermaid.tsx
+++ b/src/components/mermaid.tsx
@@ -1,9 +1,16 @@
'use client';
-import {useEffect} from 'react';
+import {useEffect, useState} from 'react';
import {useTheme} from 'next-themes';
+/**
+ * we target ```mermaid``` code blocks after they have been highlighted (not ideal),
+ * then we strip the code from the html elements used for highlighting
+ * then we render the mermaid chart both in light and dark modes
+ * CSS takes care of showing the right one depending on the theme
+ */
export default function Mermaid() {
- const theme = useTheme();
+ const [isDoneRendering, setDoneRendering] = useState(false);
+ const {resolvedTheme: theme} = useTheme();
useEffect(() => {
const renderMermaid = async () => {
const escapeHTML = (str: string) => {
@@ -24,24 +31,64 @@ export default function Mermaid() {
return;
}
const {default: mermaid} = await import('mermaid');
- mermaid.initialize({
- startOnLoad: false,
- theme: theme.resolvedTheme === 'light' ? 'default' : 'dark',
- });
- mermaidBlocks.forEach(block => {
+ mermaid.initialize({startOnLoad: false});
+ mermaidBlocks.forEach(lightModeblock => {
// get rid of code highlighting
- const code = block.textContent ?? '';
- block.innerHTML = escapeHTML(code);
+ const code = lightModeblock.textContent ?? '';
+ lightModeblock.innerHTML = escapeHTML(code);
// force transparent background
- block.style.backgroundColor = 'transparent';
- const parentCodeTabs = block.closest('.code-tabs-wrapper');
- if (parentCodeTabs) {
- parentCodeTabs.innerHTML = block.outerHTML;
+ lightModeblock.style.backgroundColor = 'transparent';
+ lightModeblock.classList.add('light');
+ const parentCodeTabs = lightModeblock.closest('.code-tabs-wrapper');
+ if (!parentCodeTabs) {
+ // eslint-disable-next-line no-console
+ console.error('Mermaid code block was not wrapped in a code tab');
+ return;
}
+ // empty the container
+ parentCodeTabs.innerHTML = '';
+ parentCodeTabs.appendChild(lightModeblock.cloneNode(true));
+
+ const darkModeBlock = lightModeblock.cloneNode(true) as HTMLPreElement;
+ darkModeBlock.classList.add('dark');
+ darkModeBlock.classList.remove('light');
+ parentCodeTabs?.appendChild(darkModeBlock);
});
- await mermaid.run({nodes: document.querySelectorAll('.language-mermaid')});
+ await mermaid.run({nodes: document.querySelectorAll('.language-mermaid.light')});
+
+ mermaid.initialize({startOnLoad: false, theme: 'dark'});
+ await mermaid
+ .run({nodes: document.querySelectorAll('.language-mermaid.dark')})
+ .then(() => setDoneRendering(true));
};
renderMermaid();
- }, [theme]);
- return null;
+ }, []);
+ // we have to wait for mermaid.js to finish rendering both light and dark charts
+ // before we hide one of them depending on the theme
+ // this is necessary because mermaid.js relies on the DOM for calculations
+ return isDoneRendering ? (
+ theme === 'dark' ? (
+
+ ) : (
+
+ )
+ ) : null;
}