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 ════════════════════════════════════════════════════════════════ */ 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)" > - ↺ + ⊡