mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-04-09 09:03:40 +00:00
feat(docs): add click-to-zoom lightbox for Mermaid diagrams
Swizzled @docusaurus/theme-mermaid's Mermaid component to wrap every diagram with a portal-based lightbox overlay. An expand icon appears on diagram hover. Clicking opens the SVG in a full-screen overlay (90vw × 85vh, scrollable). Closes via backdrop click, Escape key, or close button. SSR-safe, no external dependencies. Matches Tibber electric color theme. Impact: Users can inspect complex flowcharts (e.g. the Options Flow wizard) without squinting at small embedded diagrams.
This commit is contained in:
parent
1c25ac1fb0
commit
552db6ef7d
2 changed files with 228 additions and 0 deletions
|
|
@ -226,3 +226,113 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
[data-theme='dark'] .bmc-logo-dark {
|
[data-theme='dark'] .bmc-logo-dark {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Mermaid diagram zoom / lightbox ──────────────────────────── */
|
||||||
|
|
||||||
|
/* Wrapper that holds the diagram + the expand button */
|
||||||
|
.mermaid-zoom-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expand button — hidden until the diagram is hovered */
|
||||||
|
.mermaid-zoom-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.75rem;
|
||||||
|
height: 1.75rem;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
border: 1px solid var(--ifm-color-emphasis-300);
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
color: var(--ifm-color-emphasis-600);
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease,
|
||||||
|
border-color 0.15s ease;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-zoom-wrapper:hover .mermaid-zoom-btn,
|
||||||
|
.mermaid-zoom-btn:focus-visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-zoom-btn:hover {
|
||||||
|
background: var(--ifm-color-primary);
|
||||||
|
border-color: var(--ifm-color-primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Full-screen backdrop */
|
||||||
|
.mermaid-lightbox {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
cursor: zoom-out;
|
||||||
|
animation: mermaid-fadein 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mermaid-fadein {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The white/dark card containing the diagram */
|
||||||
|
.mermaid-lightbox-inner {
|
||||||
|
position: relative;
|
||||||
|
max-width: min(90vw, 1200px);
|
||||||
|
max-height: 85vh;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: var(--ifm-background-color);
|
||||||
|
border-radius: var(--ifm-card-border-radius);
|
||||||
|
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.4);
|
||||||
|
cursor: default;
|
||||||
|
animation: mermaid-scalein 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mermaid-scalein {
|
||||||
|
from { transform: scale(0.95); opacity: 0; }
|
||||||
|
to { transform: scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-lightbox-inner svg {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close button (top-right corner of the card) */
|
||||||
|
.mermaid-lightbox-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.75rem;
|
||||||
|
height: 1.75rem;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
border: 1px solid var(--ifm-color-emphasis-300);
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
color: var(--ifm-color-emphasis-700);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-lightbox-close:hover {
|
||||||
|
background: var(--ifm-color-danger);
|
||||||
|
border-color: var(--ifm-color-danger);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
|
||||||
118
docs/user/src/theme/Mermaid/index.tsx
Normal file
118
docs/user/src/theme/Mermaid/index.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
import OriginalMermaid from '@theme-original/Mermaid';
|
||||||
|
import type MermaidType from '@theme/Mermaid';
|
||||||
|
import type { WrapperProps } from '@docusaurus/types';
|
||||||
|
|
||||||
|
type Props = WrapperProps<typeof MermaidType>;
|
||||||
|
|
||||||
|
export default function MermaidWrapper(props: Props): React.JSX.Element {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [overlayOpen, setOverlayOpen] = useState(false);
|
||||||
|
const [svgMarkup, setSvgMarkup] = useState('');
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
// Only run portals client-side (SSR safety)
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOpen = useCallback(() => {
|
||||||
|
const svg = containerRef.current?.querySelector('svg');
|
||||||
|
if (!svg) return;
|
||||||
|
// Clone the SVG and strip fixed dimensions so it scales freely
|
||||||
|
const clone = svg.cloneNode(true) as SVGElement;
|
||||||
|
clone.removeAttribute('width');
|
||||||
|
clone.removeAttribute('height');
|
||||||
|
clone.style.cssText = 'width:100%;height:auto;display:block;';
|
||||||
|
setSvgMarkup(clone.outerHTML);
|
||||||
|
setOverlayOpen(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
setOverlayOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Keyboard + body-scroll lock while overlay is open
|
||||||
|
useEffect(() => {
|
||||||
|
if (!overlayOpen) return;
|
||||||
|
const onKey = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') handleClose();
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', onKey);
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', onKey);
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
};
|
||||||
|
}, [overlayOpen, handleClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className="mermaid-zoom-wrapper">
|
||||||
|
<OriginalMermaid {...props} />
|
||||||
|
<button
|
||||||
|
className="mermaid-zoom-btn"
|
||||||
|
onClick={handleOpen}
|
||||||
|
aria-label="View diagram enlarged"
|
||||||
|
title="View enlarged"
|
||||||
|
>
|
||||||
|
{/* Expand / fullscreen icon */}
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="15"
|
||||||
|
height="15"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<polyline points="15 3 21 3 21 9" />
|
||||||
|
<polyline points="9 21 3 21 3 15" />
|
||||||
|
<line x1="21" y1="3" x2="14" y2="10" />
|
||||||
|
<line x1="3" y1="21" x2="10" y2="14" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{mounted &&
|
||||||
|
overlayOpen &&
|
||||||
|
createPortal(
|
||||||
|
<div
|
||||||
|
className="mermaid-lightbox"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="Enlarged diagram"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="mermaid-lightbox-inner"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
// Safe: SVG content is cloned from our own rendered DOM node
|
||||||
|
dangerouslySetInnerHTML={{ __html: svgMarkup }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="mermaid-lightbox-close"
|
||||||
|
onClick={handleClose}
|
||||||
|
aria-label="Close enlarged view"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18" />
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>,
|
||||||
|
document.body,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue