Skip to content

Commit e0654b1

Browse files
authored
fix mermaid charts in dark mode (#11754)
* fix mermaid charts in dark mode * fix linting error
1 parent 74099cc commit e0654b1

File tree

1 file changed

+63
-16
lines changed

1 file changed

+63
-16
lines changed

src/components/mermaid.tsx

+63-16
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) => {
@@ -24,24 +31,64 @@ export default function Mermaid() {
2431
return;
2532
}
2633
const {default: mermaid} = await import('mermaid');
27-
mermaid.initialize({
28-
startOnLoad: false,
29-
theme: theme.resolvedTheme === 'light' ? 'default' : 'dark',
30-
});
31-
mermaidBlocks.forEach(block => {
34+
mermaid.initialize({startOnLoad: false});
35+
mermaidBlocks.forEach(lightModeblock => {
3236
// get rid of code highlighting
33-
const code = block.textContent ?? '';
34-
block.innerHTML = escapeHTML(code);
37+
const code = lightModeblock.textContent ?? '';
38+
lightModeblock.innerHTML = escapeHTML(code);
3539
// force transparent background
36-
block.style.backgroundColor = 'transparent';
37-
const parentCodeTabs = block.closest('.code-tabs-wrapper');
38-
if (parentCodeTabs) {
39-
parentCodeTabs.innerHTML = block.outerHTML;
40+
lightModeblock.style.backgroundColor = 'transparent';
41+
lightModeblock.classList.add('light');
42+
const parentCodeTabs = lightModeblock.closest('.code-tabs-wrapper');
43+
if (!parentCodeTabs) {
44+
// eslint-disable-next-line no-console
45+
console.error('Mermaid code block was not wrapped in a code tab');
46+
return;
4047
}
48+
// empty the container
49+
parentCodeTabs.innerHTML = '';
50+
parentCodeTabs.appendChild(lightModeblock.cloneNode(true));
51+
52+
const darkModeBlock = lightModeblock.cloneNode(true) as HTMLPreElement;
53+
darkModeBlock.classList.add('dark');
54+
darkModeBlock.classList.remove('light');
55+
parentCodeTabs?.appendChild(darkModeBlock);
4156
});
42-
await mermaid.run({nodes: document.querySelectorAll('.language-mermaid')});
57+
await mermaid.run({nodes: document.querySelectorAll('.language-mermaid.light')});
58+
59+
mermaid.initialize({startOnLoad: false, theme: 'dark'});
60+
await mermaid
61+
.run({nodes: document.querySelectorAll('.language-mermaid.dark')})
62+
.then(() => setDoneRendering(true));
4363
};
4464
renderMermaid();
45-
}, [theme]);
46-
return null;
65+
}, []);
66+
// we have to wait for mermaid.js to finish rendering both light and dark charts
67+
// before we hide one of them depending on the theme
68+
// this is necessary because mermaid.js relies on the DOM for calculations
69+
return isDoneRendering ? (
70+
theme === 'dark' ? (
71+
<style>
72+
{`
73+
.dark .language-mermaid {
74+
display: none;
75+
}
76+
.dark .language-mermaid.dark {
77+
display: block;
78+
}
79+
`}
80+
</style>
81+
) : (
82+
<style>
83+
{`
84+
.language-mermaid.light {
85+
display: block;
86+
}
87+
.language-mermaid.dark {
88+
display: none;
89+
}
90+
`}
91+
</style>
92+
)
93+
) : null;
4794
}

0 commit comments

Comments
 (0)