Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70ffea0e16 | ||
|
|
b3e3594571 |
No files matched your search
@@ -37,7 +37,7 @@ import ArchifyDiagram from '@site/src/components/ArchifyDiagram';
|
|||||||
<ArchifyDiagram name="organization-structure" title="Illustrative organization structure" />
|
<ArchifyDiagram name="organization-structure" title="Illustrative organization structure" />
|
||||||
```
|
```
|
||||||
|
|
||||||
Supply a text alternative below each diagram. The wrapper uses a local, base-path-aware iframe with a descriptive title, scripts and downloads enabled, and no same-origin or parent-navigation permissions. A full-viewer link provides a wider canvas in a new tab; that direct page is not iframe-sandboxed. Only publish reviewed, trusted diagrams, and do not describe iframe isolation as protection for the standalone route. Generated output is ignored by Git and rebuilt in CI.
|
Supply a text alternative below each diagram. The wrapper uses a local, base-path-aware iframe with a descriptive title, scripts and downloads enabled, and no same-origin or parent-navigation permissions. The Open full viewer button expands the diagram in a full-screen modal on the current page, with the same iframe sandbox. Close it with the Close button or Escape; focus returns to the opener. It does not open a new tab. The separate standalone HTML asset remains directly addressable and is not itself iframe-sandboxed, so publish only reviewed, trusted diagrams. Generated output is ignored by Git and rebuilt in CI.
|
||||||
|
|
||||||
## Integration boundaries
|
## Integration boundaries
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ test('Archify is pinned, licensed and rebuilt, not a remote embed', () => {
|
|||||||
assert(wrapper.includes('sandbox="allow-scripts allow-downloads"'));
|
assert(wrapper.includes('sandbox="allow-scripts allow-downloads"'));
|
||||||
assert(!wrapper.includes('allow-same-origin'));
|
assert(!wrapper.includes('allow-same-origin'));
|
||||||
assert(wrapper.includes('useBaseUrl'));
|
assert(wrapper.includes('useBaseUrl'));
|
||||||
|
assert(wrapper.includes('showModal()') && wrapper.includes('onClose='));
|
||||||
|
assert(!wrapper.includes('target="_blank"'));
|
||||||
|
assert(wrapper.includes('Close full-screen diagram'));
|
||||||
});
|
});
|
||||||
test('example is explicitly illustrative and contains valid connected nodes', () => {
|
test('example is explicitly illustrative and contains valid connected nodes', () => {
|
||||||
const spec = JSON.parse(read('diagrams/organization-structure.architecture.json'));
|
const spec = JSON.parse(read('diagrams/organization-structure.architecture.json'));
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
import React from 'react';
|
import React, {useEffect, useId, useRef, useState} from 'react';
|
||||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||||
|
|
||||||
export default function ArchifyDiagram({name, title}: {name: string; title: string}): React.ReactElement {
|
export default function ArchifyDiagram({name, title}: {name: string; title: string}): React.ReactElement {
|
||||||
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) throw new Error('Invalid diagram name');
|
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) throw new Error('Invalid diagram name');
|
||||||
const source = useBaseUrl(`/diagrams/${name}/`);
|
const source = useBaseUrl(`/diagrams/${name}/`);
|
||||||
const json = useBaseUrl(`/diagrams/${name}.json`);
|
const json = useBaseUrl(`/diagrams/${name}.json`);
|
||||||
|
const dialog = useRef<HTMLDialogElement>(null);
|
||||||
|
const opener = useRef<HTMLButtonElement>(null);
|
||||||
|
const headingId = useId();
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!expanded) return;
|
||||||
|
dialog.current?.showModal();
|
||||||
|
const previousOverflow = document.body.style.overflow;
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
return () => {document.body.style.overflow = previousOverflow;};
|
||||||
|
}, [expanded]);
|
||||||
|
const close = () => {dialog.current?.close();};
|
||||||
return <section className="archify-panel" aria-label={title}>
|
return <section className="archify-panel" aria-label={title}>
|
||||||
<div className="archify-toolbar"><strong>Interactive diagram</strong><div><a href={source} target="_blank" rel="noopener noreferrer">Open full viewer ↗</a><a href={json} download>JSON source ↓</a></div></div>
|
<div className="archify-toolbar"><strong>Interactive diagram</strong><div><button ref={opener} type="button" className="archify-expand" onClick={() => setExpanded(true)}>Open full viewer ⛶</button><a href={json} download>JSON source ↓</a></div></div>
|
||||||
<iframe src={source} title={title} sandbox="allow-scripts allow-downloads" loading="lazy" referrerPolicy="no-referrer" />
|
<iframe src={source} title={title} sandbox="allow-scripts allow-downloads" loading="lazy" referrerPolicy="no-referrer" />
|
||||||
|
<dialog ref={dialog} className="archify-modal" aria-labelledby={headingId} onClose={() => {setExpanded(false); opener.current?.focus();}}>
|
||||||
|
<div className="archify-modal-toolbar"><strong id={headingId}>{title}</strong><button type="button" onClick={close} autoFocus aria-label="Close full-screen diagram">Close ×</button></div>
|
||||||
|
{expanded && <iframe src={source} title={`${title} — expanded`} sandbox="allow-scripts allow-downloads" referrerPolicy="no-referrer" />}
|
||||||
|
</dialog>
|
||||||
</section>;
|
</section>;
|
||||||
}
|
}
|
||||||
@@ -101,4 +101,12 @@ a:focus-visible, button:focus-visible {outline: 3px solid #b88719; outline-offse
|
|||||||
.archify-toolbar div {display: flex; gap: 20px; flex-wrap: wrap;}
|
.archify-toolbar div {display: flex; gap: 20px; flex-wrap: wrap;}
|
||||||
.archify-panel iframe {width: 100%; height: 720px; border: 0; display: block; background: #f6f6f6;}
|
.archify-panel iframe {width: 100%; height: 720px; border: 0; display: block; background: #f6f6f6;}
|
||||||
@media (max-width: 700px) {.archify-panel iframe {height: 650px;}}
|
@media (max-width: 700px) {.archify-panel iframe {height: 650px;}}
|
||||||
|
.archify-expand {border: 0; padding: 0; background: transparent; color: var(--ifm-link-color); font: inherit; cursor: pointer;}
|
||||||
|
.archify-expand:hover {text-decoration: underline;}
|
||||||
|
.archify-modal {position: fixed; inset: 0; margin: 0; padding: 0; width: 100vw; height: 100dvh; max-width: none; max-height: none; border: 0; background: var(--surface); color: var(--ifm-color-content);}
|
||||||
|
.archify-modal[open] {display: flex; flex-direction: column;}
|
||||||
|
.archify-modal::backdrop {background: #000b;}
|
||||||
|
.archify-modal-toolbar {display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 20px; border-bottom: 1px solid var(--line);}
|
||||||
|
.archify-modal-toolbar button {flex-shrink: 0; padding: 8px 16px; background: var(--gold); color: #14131a; font: inherit; font-weight: 700; border: 0; cursor: pointer;}
|
||||||
|
.archify-panel .archify-modal iframe {flex: 1; min-height: 0; height: 100%; width: 100%;}
|
||||||
@media (prefers-reduced-motion: reduce) {.start-card {transition: none;} .start-card:hover {transform: none;}}
|
@media (prefers-reduced-motion: reduce) {.start-card {transition: none;} .start-card:hover {transform: none;}}
|
||||||
Reference in new issue
Block a user