From 4dd9fab6492b5d80f613b646226bbf82c91e5fed Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Sun, 21 Jun 2026 13:44:28 +0800 Subject: [PATCH 1/2] feat(docs): add click-to-zoom modal with panzoom for mermaid flowcharts Replace the basic scrollbar approach with a full interactive viewer: - Click any mermaid diagram to open a fullscreen modal overlay - Pan (drag) and zoom (scroll/+/- buttons) to freely explore - Toolbar with zoom in/out/reset/close buttons - Escape key to close, +/=/- keyboard shortcuts - Dark/light theme support - Smooth overlay animation Co-Authored-By: Claude --- website/src/css/custom.css | 54 -------------------------------------- 1 file changed, 54 deletions(-) diff --git a/website/src/css/custom.css b/website/src/css/custom.css index d8915d1..7579d80 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -636,60 +636,6 @@ color: var(--ifm-color-primary); } -/* ════════════════════════════════════════════════════════════════ - MERMAID DIAGRAMS — horizontal scroll + zoom on hover - ════════════════════════════════════════════════════════════════ */ - -/* Wrapper around each mermaid SVG: enable horizontal scroll */ -.docusaurus-mermaid-container { - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - margin: 1.5rem 0; - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: var(--gu-radius); - background: #fff; - padding: 0.5rem; -} - -[data-theme='dark'] .docusaurus-mermaid-container { - background: #1a1a2e; - border-color: rgba(93, 173, 226, 0.15); -} - -/* Allow the SVG to keep its natural width (don't shrink to container) */ -.docusaurus-mermaid-container svg { - width: auto !important; - max-width: none !important; - height: auto !important; - min-width: 600px; /* Ensures diagrams don't get squashed too small */ - display: block; -} - -/* Zoom-on-hover: scale up slightly for readability */ -.docusaurus-mermaid-container:hover { - box-shadow: 0 2px 12px rgba(0,0,0,0.12); -} -[data-theme='dark'] .docusaurus-mermaid-container:hover { - box-shadow: 0 2px 12px rgba(0,0,0,0.35); -} - -/* Make the scrollbar visible and styled */ -.docusaurus-mermaid-container::-webkit-scrollbar { - height: 8px; -} -.docusaurus-mermaid-container::-webkit-scrollbar-track { - background: var(--ifm-color-emphasis-100); - border-radius: 4px; -} -.docusaurus-mermaid-container::-webkit-scrollbar-thumb { - background: var(--ifm-color-emphasis-400); - border-radius: 4px; -} -.docusaurus-mermaid-container::-webkit-scrollbar-thumb:hover { - background: var(--ifm-color-emphasis-500); -} - /* ════════════════════════════════════════════════════════════════ PRINT-FRIENDLY ════════════════════════════════════════════════════════════════ */ From 8927f132671b61e404e071423b2273bbecea1a57 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Sun, 21 Jun 2026 14:17:43 +0800 Subject: [PATCH 2/2] fix(docs): center and auto-fit mermaid SVG to fullscreen on zoom Previously the modal body was only 90vw x 85vh with nested scrollable divs, and the SVG rendered at its tiny native size in the top-left corner. Fixes: - Modal now covers the entire viewport (100vw x 100vh) - On open, auto-scale SVG to fit available space using viewBox dimensions - SVG is centered both horizontally and vertically - Window resize also re-fits the SVG - Replaced the old 'reset' button with a 'fit to screen' button (0) - Keyboard shortcut 0 to re-fit Co-Authored-By: Claude --- website/src/theme/Mermaid/index.js | 99 +++++++++++++++------ website/src/theme/Mermaid/styles.module.css | 86 +++++++++--------- 2 files changed, 119 insertions(+), 66 deletions(-) diff --git a/website/src/theme/Mermaid/index.js b/website/src/theme/Mermaid/index.js index 6402672..380dae5 100644 --- a/website/src/theme/Mermaid/index.js +++ b/website/src/theme/Mermaid/index.js @@ -9,11 +9,12 @@ import panzoom from '@panzoom/panzoom'; import styles from './styles.module.css'; +const PADDING = 80; // pixels of padding inside modal + function MermaidRenderResult({ renderResult }) { const ref = useRef(null); const [modalOpen, setModalOpen] = useState(false); const modalRef = useRef(null); - const zoomRef = useRef(null); const instanceRef = useRef(null); useEffect(() => { @@ -21,12 +22,11 @@ function MermaidRenderResult({ renderResult }) { renderResult.bindFunctions?.(div); }, [renderResult]); - // Handle click on the mermaid diagram to open modal const handleClick = useCallback(() => { setModalOpen(true); }, []); - // Initialize panzoom when modal opens + // Initialize panzoom and auto-fit SVG when modal opens useEffect(() => { if (!modalOpen || !modalRef.current) return; @@ -34,32 +34,59 @@ function MermaidRenderResult({ renderResult }) { const svgEl = modalEl.querySelector('svg'); if (!svgEl) return; - // Clean up any previous instance + // Clean up previous if (instanceRef.current) { instanceRef.current.destroy(); } const instance = panzoom(svgEl, { maxScale: 10, - minScale: 0.3, - step: 0.3, + minScale: 0.1, + step: 0.15, contain: 'outside', pinchAndPan: true, }); instanceRef.current = instance; - // Reset zoom on open - // Use a small delay to let the modal render finish - const resetTimer = setTimeout(() => { - instance.reset({ animate: false }); - }, 50); + // Auto-fit: scale the SVG to fill the modal while keeping aspect ratio + const fitToScreen = () => { + const parent = modalRef.current; + if (!parent) return; + const svg = parent.querySelector('svg'); + if (!svg) return; + + const containerW = parent.clientWidth; + const containerH = parent.clientHeight; + const svgW = svg.viewBox?.baseVal?.width || svg.width?.baseVal?.value || svg.getBoundingClientRect().width; + const svgH = svg.viewBox?.baseVal?.height || svg.height?.baseVal?.value || svg.getBoundingClientRect().height; + + if (!svgW || !svgH) return; + + const availableW = containerW - PADDING; + const availableH = containerH - PADDING; + const scale = Math.min(availableW / svgW, availableH / svgH, 3); // cap at 3x + + if (scale > 0) { + instance.zoom(scale, { animate: false }); + // Center the SVG + const scaledW = svgW * scale; + const scaledH = svgH * scale; + const offsetX = (containerW - scaledW) / 2; + const offsetY = (containerH - scaledH) / 2; + instance.pan(offsetX, offsetY, { animate: false }); + } + }; + + // Wait for layout, then fit + const fitTimer = setTimeout(fitToScreen, 80); + + // Re-fit on resize + window.addEventListener('resize', fitToScreen); - // Keyboard handlers const handleKeyDown = (e) => { if (e.key === 'Escape') { setModalOpen(false); } - // Zoom with +/- if (e.key === '+' || e.key === '=') { e.preventDefault(); instance.zoomIn(); @@ -68,12 +95,17 @@ function MermaidRenderResult({ renderResult }) { e.preventDefault(); instance.zoomOut(); } + if (e.key === '0') { + e.preventDefault(); + fitToScreen(); // reset to fit + } }; window.addEventListener('keydown', handleKeyDown); return () => { - clearTimeout(resetTimer); + clearTimeout(fitTimer); + window.removeEventListener('resize', fitToScreen); window.removeEventListener('keydown', handleKeyDown); if (instanceRef.current) { instanceRef.current.destroy(); @@ -126,11 +158,32 @@ function MermaidRenderResult({ renderResult }) { className={styles.toolBtn} onClick={(e) => { e.stopPropagation(); - instanceRef.current?.reset({ animate: true }); + // Re-fit to screen + const parent = modalRef.current; + const svg = parent?.querySelector('svg'); + if (!svg || !instanceRef.current) return; + const containerW = parent.clientWidth; + const containerH = parent.clientHeight; + const svgW = svg.viewBox?.baseVal?.width || svg.width?.baseVal?.value || svg.getBoundingClientRect().width; + const svgH = svg.viewBox?.baseVal?.height || svg.height?.baseVal?.value || svg.getBoundingClientRect().height; + if (!svgW || !svgH) return; + const availableW = containerW - PADDING; + const availableH = containerH - PADDING; + const scale = Math.min(availableW / svgW, availableH / svgH, 3); + if (scale > 0) { + instanceRef.current.zoom(scale, { animate: true }); + const scaledW = svgW * scale; + const scaledH = svgH * scale; + instanceRef.current.pan( + (containerW - scaledW) / 2, + (containerH - scaledH) / 2, + { animate: true } + ); + } }} - title="Reset" + title="Fit to screen (0)" > - ↺ + ⊡