Skip to content

Commit 894d7d3

Browse files
committed
fix mermaid charts in dark mode
1 parent 9eaee50 commit 894d7d3

File tree

1 file changed

+64
-16
lines changed

1 file changed

+64
-16
lines changed

src/components/mermaid.tsx

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
'use client';
2-
import {useEffect} from 'react';
2+
import {useEffect, useState} from 'react';
33
import {useTheme} from 'next-themes';
44

5+
/**
6+
* we target ```mermaid``` code blocks after they have been highlighted (not ideal),
7+
* then we strip the code from the html elements used for highlighting
8+
* then we render the mermaid chart both in light and dark modes
9+
* CSS takes care of showing the right one depending on the theme
10+
*/
511
export default function Mermaid() {
6-
const theme = useTheme();
12+
const [isDoneRendering, setDoneRendering] = useState(false);
13+
const {resolvedTheme: theme} = useTheme();
714
useEffect(() => {
815
const renderMermaid = async () => {
916
const escapeHTML = (str: string) => {
@@ -23,25 +30,66 @@ export default function Mermaid() {
2330
if (mermaidBlocks.length === 0) {
2431
return;
2532
}
33+
// @ts-ignore
2634
const {default: mermaid} = await import('mermaid');
27-
mermaid.initialize({
28-
startOnLoad: false,
29-
theme: theme.resolvedTheme === 'light' ? 'default' : 'dark',
30-
});
31-
mermaidBlocks.forEach(block => {
35+
mermaid.initialize({startOnLoad: false, theme: 'light'});
36+
mermaidBlocks.forEach(lightModeblock => {
3237
// get rid of code highlighting
33-
const code = block.textContent ?? '';
34-
block.innerHTML = escapeHTML(code);
38+
const code = lightModeblock.textContent ?? '';
39+
lightModeblock.innerHTML = escapeHTML(code);
3540
// force transparent background
36-
block.style.backgroundColor = 'transparent';
37-
const parentCodeTabs = block.closest('.code-tabs-wrapper');
38-
if (parentCodeTabs) {
39-
parentCodeTabs.innerHTML = block.outerHTML;
41+
lightModeblock.style.backgroundColor = 'transparent';
42+
lightModeblock.classList.add('light');
43+
const parentCodeTabs = lightModeblock.closest('.code-tabs-wrapper');
44+
if (!parentCodeTabs) {
45+
// eslint-disable-next-line no-console
46+
console.error('Mermaid code block was not wrapped in a code tab');
47+
return;
4048
}
49+
// empty the container
50+
parentCodeTabs.innerHTML = '';
51+
parentCodeTabs.appendChild(lightModeblock.cloneNode(true));
52+
53+
const darkModeBlock = lightModeblock.cloneNode(true) as HTMLPreElement;
54+
darkModeBlock.classList.add('dark');
55+
darkModeBlock.classList.remove('light');
56+
parentCodeTabs?.appendChild(darkModeBlock);
4157
});
42-
await mermaid.run({nodes: document.querySelectorAll('.language-mermaid')});
58+
await mermaid.run({nodes: document.querySelectorAll('.language-mermaid.light')});
59+
60+
mermaid.initialize({startOnLoad: false, theme: 'dark'});
61+
await mermaid
62+
.run({nodes: document.querySelectorAll('.language-mermaid.dark')})
63+
.then(() => setDoneRendering(true));
4364
};
4465
renderMermaid();
45-
}, [theme]);
46-
return null;
66+
}, []);
67+
// we have to wait for mermaid.js to finish rendering both light and dark charts
68+
// before we hide one of them depending on the theme
69+
// this is necessary because mermaid.js relies on the DOM for calculations
70+
return isDoneRendering ? (
71+
theme === 'dark' ? (
72+
<style>
73+
{`
74+
.dark .language-mermaid {
75+
display: none;
76+
}
77+
.dark .language-mermaid.dark {
78+
display: block;
79+
}
80+
`}
81+
</style>
82+
) : (
83+
<style>
84+
{`
85+
.language-mermaid.light {
86+
display: block;
87+
}
88+
.language-mermaid.dark {
89+
display: none;
90+
}
91+
`}
92+
</style>
93+
)
94+
) : null;
4795
}

0 commit comments

Comments
 (0)