From 4bb4aba8b3b945432b85b197cd645fc1f0453a57 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Sun, 21 Jun 2026 13:14:00 +0800 Subject: [PATCH 1/2] feat(docs): add horizontal scroll + zoom interaction for mermaid flowcharts The Core-flow page has 11 wide horizontal mermaid flowcharts that were squished into the content column, making them unreadable. - Set .docusaurus-mermaid-container to overflow-x: auto - Let SVG keep natural width (min-width: 600px) - Add styled scrollbar for desktop - Subtle hover shadow for visual feedback Co-Authored-By: Claude --- website/src/css/custom.css | 54 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 7579d80..d8915d1 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -636,6 +636,60 @@ 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 42afbf84473314a6f0a7f95b510264ca8d861d35 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Sun, 21 Jun 2026 13:44:28 +0800 Subject: [PATCH 2/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/package-lock.json | 7 + website/package.json | 1 + website/src/css/custom.css | 54 ------ website/src/theme/Mermaid/index.js | 182 ++++++++++++++++++++ website/src/theme/Mermaid/styles.module.css | 116 +++++++++++++ 5 files changed, 306 insertions(+), 54 deletions(-) create mode 100644 website/src/theme/Mermaid/index.js create mode 100644 website/src/theme/Mermaid/styles.module.css diff --git a/website/package-lock.json b/website/package-lock.json index ac68185..7b711de 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -12,6 +12,7 @@ "@docusaurus/preset-classic": "3.1.1", "@docusaurus/theme-mermaid": "^3.1.1", "@mdx-js/react": "^3.0.0", + "@panzoom/panzoom": "^4.6.2", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", "react": "^18.0.0", @@ -2958,6 +2959,12 @@ "node": ">= 8" } }, + "node_modules/@panzoom/panzoom": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@panzoom/panzoom/-/panzoom-4.6.2.tgz", + "integrity": "sha512-Zn3B5/hwa6eYIPRSKX0xf2clv8nviTX8AnAU5kU/EugiTDhG41ya2wlBqYrZJYCWQROr/5XkWObZhIkepi89qw==", + "license": "MIT" + }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", diff --git a/website/package.json b/website/package.json index 2cdb0b1..b28824d 100644 --- a/website/package.json +++ b/website/package.json @@ -18,6 +18,7 @@ "@docusaurus/preset-classic": "3.1.1", "@docusaurus/theme-mermaid": "^3.1.1", "@mdx-js/react": "^3.0.0", + "@panzoom/panzoom": "^4.6.2", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", "react": "^18.0.0", 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 new file mode 100644 index 0000000..6402672 --- /dev/null +++ b/website/src/theme/Mermaid/index.js @@ -0,0 +1,182 @@ +import React, { useEffect, useRef, useState, useCallback } from 'react'; +import ErrorBoundary from '@docusaurus/ErrorBoundary'; +import { ErrorBoundaryErrorMessageFallback } from '@docusaurus/theme-common'; +import { + MermaidContainerClassName, + useMermaidRenderResult, +} from '@docusaurus/theme-mermaid/client'; +import panzoom from '@panzoom/panzoom'; + +import styles from './styles.module.css'; + +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(() => { + const div = ref.current; + renderResult.bindFunctions?.(div); + }, [renderResult]); + + // Handle click on the mermaid diagram to open modal + const handleClick = useCallback(() => { + setModalOpen(true); + }, []); + + // Initialize panzoom when modal opens + useEffect(() => { + if (!modalOpen || !modalRef.current) return; + + const modalEl = modalRef.current; + const svgEl = modalEl.querySelector('svg'); + if (!svgEl) return; + + // Clean up any previous instance + if (instanceRef.current) { + instanceRef.current.destroy(); + } + + const instance = panzoom(svgEl, { + maxScale: 10, + minScale: 0.3, + step: 0.3, + 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); + + // Keyboard handlers + const handleKeyDown = (e) => { + if (e.key === 'Escape') { + setModalOpen(false); + } + // Zoom with +/- + if (e.key === '+' || e.key === '=') { + e.preventDefault(); + instance.zoomIn(); + } + if (e.key === '-') { + e.preventDefault(); + instance.zoomOut(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + + return () => { + clearTimeout(resetTimer); + window.removeEventListener('keydown', handleKeyDown); + if (instanceRef.current) { + instanceRef.current.destroy(); + instanceRef.current = null; + } + }; + }, [modalOpen]); + + return ( + <> +
{ if (e.key === 'Enter') handleClick(); }} + title="Click to zoom" + /> + + {modalOpen && ( +
setModalOpen(false)} + role="presentation" + > +
+ + + + + + Scroll to zoom · Drag to pan + +
+
e.stopPropagation()} + role="presentation" + > +
+
+
+ )} + + ); +} + +function MermaidRenderer({ value }) { + const renderResult = useMermaidRenderResult({ text: value }); + if (renderResult === null) { + return null; + } + return ; +} + +export default function Mermaid(props) { + return ( + } + > + + + ); +} diff --git a/website/src/theme/Mermaid/styles.module.css b/website/src/theme/Mermaid/styles.module.css new file mode 100644 index 0000000..94f2b28 --- /dev/null +++ b/website/src/theme/Mermaid/styles.module.css @@ -0,0 +1,116 @@ +/* ── Inline diagram (clickable) ─────────────────────────── */ +.container { + max-width: 100%; + cursor: zoom-in; + transition: box-shadow 0.2s ease; +} + +.container > svg { + max-width: 100%; +} + +.container:hover { + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); +} + +[data-theme='dark'] .container:hover { + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35); +} + +/* ── Fullscreen modal overlay ─────────────────────────── */ +.overlay { + position: fixed; + inset: 0; + z-index: 9999; + background: rgba(0, 0, 0, 0.75); + backdrop-filter: blur(4px); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + animation: overlayIn 0.2s ease; +} + +@keyframes overlayIn { + from { opacity: 0; } + to { opacity: 1; } +} + +/* ── Toolbar at top of overlay ─────────────────────────── */ +.toolbar { + position: absolute; + top: 16px; + right: 16px; + display: flex; + align-items: center; + gap: 6px; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(6px); + border-radius: 8px; + padding: 6px 10px; + z-index: 10000; + user-select: none; +} + +.toolBtn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border: none; + border-radius: 6px; + background: transparent; + color: #eee; + font-size: 16px; + cursor: pointer; + transition: background 0.15s; +} + +.toolBtn:hover { + background: rgba(255, 255, 255, 0.15); +} + +.toolBtn:active { + background: rgba(255, 255, 255, 0.25); +} + +.toolHint { + color: rgba(255, 255, 255, 0.5); + font-size: 12px; + margin-left: 6px; + padding-left: 8px; + border-left: 1px solid rgba(255, 255, 255, 0.15); +} + +/* ── Scrollable body area (for large diagrams) ─────────── */ +.modalBody { + width: 90vw; + height: 85vh; + overflow: auto; + border-radius: 12px; + background: #fff; + box-shadow: 0 8px 40px rgba(0, 0, 0, 0.4); + cursor: grab; +} + +.modalBody:active { + cursor: grabbing; +} + +[data-theme='dark'] .modalBody { + background: #1a1a2e; +} + +/* ── Panzoom container inside modal body ───────────────── */ +.zoomContainer { + display: inline-block; + padding: 32px; + transform-origin: 0 0; + touch-action: none; +} + +.zoomContainer svg { + display: block; + max-width: none; +}