@conduction/docusaurus-preset 2.8.1 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MISSING_COMPONENTS.md +2 -0
- package/package.json +4 -2
- package/src/components/DetailHero/DetailHero.jsx +36 -5
- package/src/components/Diagrams/Diagrams.jsx +45 -0
- package/src/components/PlatformDiagram/PlatformDiagram.jsx +1 -0
- package/src/components/index.js +6 -5
- package/src/data/apps-registry.js +4 -1
- package/src/diagrams/cn-arch-flow.js +168 -0
- package/src/diagrams/cn-pair.js +192 -0
- package/src/diagrams/index.js +4 -0
- package/src/theme/Navbar/index.jsx +11 -7
- package/src/theme/brand.jsx +33 -0
- package/static/lib/platform-diagram.css +15 -7
- package/static/lib/platform-diagram.js +5 -2
package/MISSING_COMPONENTS.md
CHANGED
|
@@ -90,6 +90,8 @@ slot-based children pass through. All exported from `Diagrams/Diagrams.jsx`.
|
|
|
90
90
|
- **DiagramPipeline** wraps `<cn-pipeline>` (renamed from Pipeline to avoid the components/Pipeline name collision)
|
|
91
91
|
- **SideBox** wraps `<cn-side-box>`
|
|
92
92
|
- **HoneycombBg** wraps `<cn-honeycomb-bg>`
|
|
93
|
+
- **Pair** wraps `<cn-pair>` (leftLabel, leftCaption, leftColor, rightLabel, rightCaption, rightColor, bridgeLabel, arrow). Two systems linked by an orange arrow; the integration-page headline diagram. Owns the one-orange-per-scene budget. Extracted 2026-05-13 from the `.pair-banner` pattern in `preview/product-pages/integrations.html`.
|
|
94
|
+
- **ArchFlow** wraps `<cn-arch-flow>` (arrow: right | down | none). One row of a request-flow / architecture diagram. Child elements style themselves by attribute (`accent` solid cobalt, `hex` orange pointy-top, `muted` half-opacity). Stack multiple rows for a multi-layer system view. Extracted 2026-05-13 from the `.arch-diagram` pattern in `preview/product-pages/technical-docs.html`.
|
|
93
95
|
|
|
94
96
|
## Theme swizzles
|
|
95
97
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/docusaurus-preset",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"prepack": "node scripts/prepack-bundle-css.js"
|
|
6
6
|
},
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"./diagrams/cn-domain-tree": "./src/diagrams/cn-domain-tree.js",
|
|
22
22
|
"./diagrams/cn-pipeline": "./src/diagrams/cn-pipeline.js",
|
|
23
23
|
"./diagrams/cn-side-box": "./src/diagrams/cn-side-box.js",
|
|
24
|
-
"./diagrams/cn-honeycomb-bg": "./src/diagrams/cn-honeycomb-bg.js"
|
|
24
|
+
"./diagrams/cn-honeycomb-bg": "./src/diagrams/cn-honeycomb-bg.js",
|
|
25
|
+
"./diagrams/cn-pair": "./src/diagrams/cn-pair.js",
|
|
26
|
+
"./diagrams/cn-arch-flow": "./src/diagrams/cn-arch-flow.js"
|
|
25
27
|
},
|
|
26
28
|
"files": [
|
|
27
29
|
"src/",
|
|
@@ -45,11 +45,25 @@
|
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
import React from 'react';
|
|
48
|
+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
48
49
|
import HexBullet from '../primitives/HexBullet';
|
|
49
50
|
import Button from '../primitives/Button';
|
|
51
|
+
import {deriveStability} from '../../theme/brand.jsx';
|
|
50
52
|
import {downloadsForApp, formatDownloads} from '../../data/app-downloads';
|
|
51
53
|
import styles from './DetailHero.module.css';
|
|
52
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Per-stability hex-bullet colour. Keeps the hero badge and the
|
|
57
|
+
* navbar pill on the same maturity story without each site having
|
|
58
|
+
* to pass a colour explicitly.
|
|
59
|
+
*/
|
|
60
|
+
const STABILITY_COLORS = {
|
|
61
|
+
Stable: 'var(--c-mint-500)',
|
|
62
|
+
Beta: 'var(--c-orange-knvb)',
|
|
63
|
+
RC: 'var(--c-blue-cobalt)',
|
|
64
|
+
Alpha: 'var(--c-red-vermillion)',
|
|
65
|
+
};
|
|
66
|
+
|
|
53
67
|
export default function DetailHero({
|
|
54
68
|
crumb,
|
|
55
69
|
status,
|
|
@@ -76,6 +90,23 @@ export default function DetailHero({
|
|
|
76
90
|
existing on-cream rendering used by connext apps detail pages. */
|
|
77
91
|
const bgClass = background === 'cobalt' ? styles.bgCobalt : null;
|
|
78
92
|
|
|
93
|
+
/* Reconcile the hero's badge row with the navbar version pill so
|
|
94
|
+
they can't drift apart. When the caller doesn't pass `version`
|
|
95
|
+
and/or `status` props, fall back to the same customFields.appVersion
|
|
96
|
+
the navbar reads, and auto-derive Stable/Beta/RC/Alpha from the
|
|
97
|
+
SemVer string via deriveStability(). Sites can still pass explicit
|
|
98
|
+
props to override (e.g. a static-site demo that wants to show
|
|
99
|
+
"Preview" instead of the auto-derived label). */
|
|
100
|
+
const {siteConfig} = useDocusaurusContext();
|
|
101
|
+
const appVersion = siteConfig?.customFields?.appVersion;
|
|
102
|
+
const resolvedVersion = version || (appVersion ? `v${appVersion}` : undefined);
|
|
103
|
+
const resolvedStatus = status || (appVersion
|
|
104
|
+
? {
|
|
105
|
+
label: deriveStability(appVersion),
|
|
106
|
+
color: STABILITY_COLORS[deriveStability(appVersion)],
|
|
107
|
+
}
|
|
108
|
+
: undefined);
|
|
109
|
+
|
|
79
110
|
return (
|
|
80
111
|
<section className={[styles.head, hasIllustration && styles.withIllustration, bgClass, className].filter(Boolean).join(' ')}>
|
|
81
112
|
{crumb && Array.isArray(crumb) && (
|
|
@@ -99,15 +130,15 @@ export default function DetailHero({
|
|
|
99
130
|
|
|
100
131
|
<div className={styles.headInner}>
|
|
101
132
|
<div className={styles.copy}>
|
|
102
|
-
{(
|
|
133
|
+
{(resolvedStatus || resolvedVersion || locales || dlCount > 0) && (
|
|
103
134
|
<div className={styles.badgeRow}>
|
|
104
|
-
{
|
|
135
|
+
{resolvedStatus && (
|
|
105
136
|
<span className={styles.badge}>
|
|
106
|
-
<HexBullet size="md" color={
|
|
107
|
-
{
|
|
137
|
+
<HexBullet size="md" color={resolvedStatus.color || STABILITY_COLORS[resolvedStatus.label] || 'var(--c-mint-500)'} />
|
|
138
|
+
{resolvedStatus.label}
|
|
108
139
|
</span>
|
|
109
140
|
)}
|
|
110
|
-
{
|
|
141
|
+
{resolvedVersion && <span className={[styles.badge, styles.versionBadge].join(' ')}>{resolvedVersion}</span>}
|
|
111
142
|
{locales && <span className={[styles.badge, styles.versionBadge].join(' ')}>{locales}</span>}
|
|
112
143
|
{dlCount > 0 && (
|
|
113
144
|
<span
|
|
@@ -146,3 +146,48 @@ export function HoneycombBg(props) {
|
|
|
146
146
|
useDiagramRuntime();
|
|
147
147
|
return <cn-honeycomb-bg {...props} />;
|
|
148
148
|
}
|
|
149
|
+
|
|
150
|
+
/* ============================================================
|
|
151
|
+
<Pair /> wraps <cn-pair>
|
|
152
|
+
Two systems linked by an orange arrow. Attribute names are
|
|
153
|
+
camelCase in React, converted to dashed on the element.
|
|
154
|
+
============================================================ */
|
|
155
|
+
export function Pair({
|
|
156
|
+
leftLabel, leftCaption, leftColor,
|
|
157
|
+
rightLabel, rightCaption, rightColor,
|
|
158
|
+
bridgeLabel, arrow,
|
|
159
|
+
children, ...rest
|
|
160
|
+
}) {
|
|
161
|
+
useDiagramRuntime();
|
|
162
|
+
return (
|
|
163
|
+
<cn-pair
|
|
164
|
+
{...attrs({
|
|
165
|
+
'left-label': leftLabel,
|
|
166
|
+
'left-caption': leftCaption,
|
|
167
|
+
'left-color': leftColor,
|
|
168
|
+
'right-label': rightLabel,
|
|
169
|
+
'right-caption': rightCaption,
|
|
170
|
+
'right-color': rightColor,
|
|
171
|
+
'bridge-label': bridgeLabel,
|
|
172
|
+
arrow,
|
|
173
|
+
})}
|
|
174
|
+
{...rest}
|
|
175
|
+
>
|
|
176
|
+
{children}
|
|
177
|
+
</cn-pair>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* ============================================================
|
|
182
|
+
<ArchFlow /> wraps <cn-arch-flow>
|
|
183
|
+
One row of an architecture diagram. Children with `accent`,
|
|
184
|
+
`hex`, or `muted` attributes are styled by the web component.
|
|
185
|
+
============================================================ */
|
|
186
|
+
export function ArchFlow({arrow, children, ...rest}) {
|
|
187
|
+
useDiagramRuntime();
|
|
188
|
+
return (
|
|
189
|
+
<cn-arch-flow {...attrs({arrow})} {...rest}>
|
|
190
|
+
{children}
|
|
191
|
+
</cn-arch-flow>
|
|
192
|
+
);
|
|
193
|
+
}
|
package/src/components/index.js
CHANGED
|
@@ -58,11 +58,12 @@ export {default as CookieCli} from './CookieCli/CookieCli.jsx';
|
|
|
58
58
|
export {default as GameModal} from './GameModal/GameModal.jsx';
|
|
59
59
|
|
|
60
60
|
/* Diagram-set web-component React wrappers (cn-hex, cn-hex-prism,
|
|
61
|
-
cn-platform, cn-domain-tree, cn-pipeline, cn-side-box, cn-honeycomb-bg
|
|
62
|
-
Type-checked, autocompletable React surface
|
|
63
|
-
diagram set in @conduction/diagrams. The
|
|
64
|
-
work in plain HTML; the wrappers are the
|
|
65
|
-
|
|
61
|
+
cn-platform, cn-domain-tree, cn-pipeline, cn-side-box, cn-honeycomb-bg,
|
|
62
|
+
cn-pair, cn-arch-flow). Type-checked, autocompletable React surface
|
|
63
|
+
for the framework-agnostic diagram set in @conduction/diagrams. The
|
|
64
|
+
bare web components still work in plain HTML; the wrappers are the
|
|
65
|
+
React-friendly version. */
|
|
66
|
+
export {Hex, HexPrism, Platform, DomainTree, DiagramPipeline, SideBox, HoneycombBg, Pair, ArchFlow} from './Diagrams/Diagrams.jsx';
|
|
66
67
|
export {default as ComposeBlock} from './ComposeBlock/ComposeBlock.jsx';
|
|
67
68
|
export {default as AppsGrid} from './AppsGrid/AppsGrid.jsx';
|
|
68
69
|
export {default as AppMock} from './AppMock/AppMock.jsx';
|
|
@@ -32,7 +32,6 @@ export const APPS_REGISTRY = {
|
|
|
32
32
|
openconnector: {slug: 'openconnector', name: 'OpenConnector', productHref: '/apps/openconnector', docsHref: 'https://docs.conduction.nl/openconnector', academyHref: '/academy?app=openconnector'},
|
|
33
33
|
docudesk: {slug: 'docudesk', name: 'DocuDesk', productHref: '/apps/docudesk', docsHref: 'https://docs.conduction.nl/docudesk', academyHref: '/academy?app=docudesk'},
|
|
34
34
|
mydash: {slug: 'mydash', name: 'MyDash', productHref: '/apps/mydash', docsHref: 'https://docs.conduction.nl/mydash', academyHref: '/academy?app=mydash'},
|
|
35
|
-
openwoo: {slug: 'openwoo', name: 'OpenWoo', productHref: '/apps/openwoo', docsHref: 'https://docs.conduction.nl/openwoo', academyHref: '/academy?app=openwoo'},
|
|
36
35
|
zaakafhandelapp: {slug: 'zaakafhandelapp', name: 'ZaakAfhandelApp', productHref: '/apps/zaakafhandelapp', docsHref: 'https://docs.conduction.nl/zaakafhandelapp', academyHref: '/academy?app=zaakafhandelapp'},
|
|
37
36
|
pipelinq: {slug: 'pipelinq', name: 'PipelinQ', productHref: '/apps/pipelinq', docsHref: 'https://docs.conduction.nl/pipelinq', academyHref: '/academy?app=pipelinq'},
|
|
38
37
|
procest: {slug: 'procest', name: 'Procest', productHref: '/apps/procest', docsHref: 'https://docs.conduction.nl/procest', academyHref: '/academy?app=procest'},
|
|
@@ -40,6 +39,10 @@ export const APPS_REGISTRY = {
|
|
|
40
39
|
softwarecatalog: {slug: 'softwarecatalog', name: 'SoftwareCatalog', productHref: '/apps/softwarecatalog', docsHref: 'https://docs.conduction.nl/softwarecatalog', academyHref: '/academy?app=softwarecatalog'},
|
|
41
40
|
larpingapp: {slug: 'larpingapp', name: 'LarpingApp', productHref: '/apps/larpingapp', docsHref: 'https://docs.conduction.nl/larpingapp', academyHref: '/academy?app=larpingapp'},
|
|
42
41
|
nldesign: {slug: 'nldesign', name: 'NLDesign', productHref: '/apps/nldesign', docsHref: 'https://docs.conduction.nl/nldesign', academyHref: '/academy?app=nldesign'},
|
|
42
|
+
shillinq: {slug: 'shillinq', name: 'Shillinq', productHref: '/apps/shillinq', docsHref: 'https://docs.conduction.nl/shillinq', academyHref: '/academy?app=shillinq'},
|
|
43
|
+
openbuilt: {slug: 'openbuilt', name: 'OpenBuilt', productHref: '/apps/openbuilt', docsHref: 'https://docs.conduction.nl/openbuilt', academyHref: '/academy?app=openbuilt'},
|
|
44
|
+
doriath: {slug: 'doriath', name: 'Doriath', productHref: '/apps/doriath', docsHref: 'https://docs.conduction.nl/doriath', academyHref: '/academy?app=doriath'},
|
|
45
|
+
'app-versions': {slug: 'app-versions', name: 'App Versions', productHref: '/apps/app-versions', docsHref: 'https://docs.conduction.nl/app-versions', academyHref: '/academy?app=app-versions'},
|
|
43
46
|
};
|
|
44
47
|
|
|
45
48
|
export const APP_SLUGS = Object.keys(APPS_REGISTRY);
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <cn-arch-flow> — a single row of an architecture diagram.
|
|
3
|
+
*
|
|
4
|
+
* The "Request flow" pattern from preview/product-pages/technical-docs.html.
|
|
5
|
+
* One row of boxes connected by arrows, where one box is solid cobalt
|
|
6
|
+
* (the "owned by us" node) and at most one is an orange pointy-top hex
|
|
7
|
+
* (the validator or attention node). Stack multiple <cn-arch-flow>s for
|
|
8
|
+
* a multi-layer system diagram.
|
|
9
|
+
*
|
|
10
|
+
* <cn-arch-flow arrow="right">
|
|
11
|
+
* <span>Client</span>
|
|
12
|
+
* <span accent>API layer</span>
|
|
13
|
+
* <span hex>Validator</span>
|
|
14
|
+
* </cn-arch-flow>
|
|
15
|
+
* <cn-arch-flow arrow="down">
|
|
16
|
+
* <span>Schema registry</span>
|
|
17
|
+
* <span accent>Storage adapter</span>
|
|
18
|
+
* <span>Audit log</span>
|
|
19
|
+
* </cn-arch-flow>
|
|
20
|
+
* <cn-arch-flow arrow="none">
|
|
21
|
+
* <span>Nextcloud DB</span>
|
|
22
|
+
* <span>MySQL</span>
|
|
23
|
+
* <span>PostgreSQL</span>
|
|
24
|
+
* <span>MongoDB</span>
|
|
25
|
+
* <span muted>…</span>
|
|
26
|
+
* </cn-arch-flow>
|
|
27
|
+
*
|
|
28
|
+
* Arrows are auto-inserted between consecutive children (skip with
|
|
29
|
+
* arrow="none"). Each child is styled by its attributes — no class
|
|
30
|
+
* names needed.
|
|
31
|
+
*
|
|
32
|
+
* Slots
|
|
33
|
+
* default — row members, in order. Auto-numbered to interleave
|
|
34
|
+
* arrows between them in the shadow DOM.
|
|
35
|
+
*
|
|
36
|
+
* Attributes
|
|
37
|
+
* arrow — right | down | none (default: right)
|
|
38
|
+
*
|
|
39
|
+
* Child attributes (set on the slotted elements, not on cn-arch-flow)
|
|
40
|
+
* accent — solid cobalt fill, white text. The "system" node.
|
|
41
|
+
* hex — orange pointy-top hex. Use once per scene; this is the
|
|
42
|
+
* one-orange-per-scene knob for the diagram.
|
|
43
|
+
* muted — opacity 0.5; for "and more" / out-of-scope placeholders.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
class CnArchFlow extends HTMLElement {
|
|
47
|
+
static get observedAttributes() { return ['arrow']; }
|
|
48
|
+
|
|
49
|
+
constructor() {
|
|
50
|
+
super();
|
|
51
|
+
this.attachShadow({ mode: 'open' });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
connectedCallback() {
|
|
55
|
+
this.render();
|
|
56
|
+
this._observer = new MutationObserver(() => this.render());
|
|
57
|
+
this._observer.observe(this, { childList: true, attributes: true, subtree: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
disconnectedCallback() {
|
|
61
|
+
if (this._observer) this._observer.disconnect();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
attributeChangedCallback() { if (this.shadowRoot) this.render(); }
|
|
65
|
+
|
|
66
|
+
render() {
|
|
67
|
+
const arrow = this.getAttribute('arrow') || 'right';
|
|
68
|
+
const nodes = [...this.children];
|
|
69
|
+
nodes.forEach((el, i) => { el.setAttribute('slot', `node-${i}`); });
|
|
70
|
+
|
|
71
|
+
const arrowGlyph = arrow === 'down' ? '↓' : '→';
|
|
72
|
+
const showArrows = arrow !== 'none';
|
|
73
|
+
|
|
74
|
+
let rowMarkup = '';
|
|
75
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
76
|
+
rowMarkup += `<slot name="node-${i}"></slot>`;
|
|
77
|
+
if (showArrows && i < nodes.length - 1) {
|
|
78
|
+
rowMarkup += `<span class="arrow" aria-hidden="true">${arrowGlyph}</span>`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.shadowRoot.innerHTML = `
|
|
83
|
+
<style>
|
|
84
|
+
:host {
|
|
85
|
+
display: block;
|
|
86
|
+
font-family: var(--conduction-typography-font-family-code, ui-monospace, monospace);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.panel {
|
|
90
|
+
background: var(--c-cobalt-50);
|
|
91
|
+
border: 1px solid var(--c-cobalt-100);
|
|
92
|
+
border-radius: var(--radius-md);
|
|
93
|
+
padding: var(--space-6);
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
justify-content: center;
|
|
97
|
+
gap: var(--space-3);
|
|
98
|
+
flex-wrap: wrap;
|
|
99
|
+
font-size: 11px;
|
|
100
|
+
text-align: center;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.arrow {
|
|
104
|
+
color: var(--c-cobalt-400);
|
|
105
|
+
font-size: 16px;
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Default node: white rounded box with cobalt-200 border. */
|
|
110
|
+
::slotted(*) {
|
|
111
|
+
flex: 1 1 0;
|
|
112
|
+
min-width: 80px;
|
|
113
|
+
padding: var(--space-3) var(--space-2);
|
|
114
|
+
background: white;
|
|
115
|
+
border: 1px solid var(--c-cobalt-200);
|
|
116
|
+
border-radius: var(--radius-sm);
|
|
117
|
+
color: var(--c-cobalt-800);
|
|
118
|
+
font-family: var(--conduction-typography-font-family-code, ui-monospace, monospace);
|
|
119
|
+
font-size: 11px;
|
|
120
|
+
text-align: center;
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
box-sizing: border-box;
|
|
125
|
+
min-height: 38px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* accent: the "system" node — solid cobalt brand fill. */
|
|
129
|
+
::slotted([accent]) {
|
|
130
|
+
background: var(--c-blue-cobalt);
|
|
131
|
+
color: white;
|
|
132
|
+
border-color: var(--c-blue-cobalt);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* hex: the orange pointy-top hex. The one-orange-per-scene knob.
|
|
136
|
+
* Replaces the rectangle entirely with a clip-pathed shape so the
|
|
137
|
+
* brand rule (hexes never rotate, always pointy-top) holds. */
|
|
138
|
+
::slotted([hex]) {
|
|
139
|
+
flex: 0 0 auto;
|
|
140
|
+
width: 64px;
|
|
141
|
+
min-width: 0;
|
|
142
|
+
height: 74px;
|
|
143
|
+
padding: 0;
|
|
144
|
+
background: var(--c-orange-knvb);
|
|
145
|
+
color: white;
|
|
146
|
+
border: 0;
|
|
147
|
+
border-radius: 0;
|
|
148
|
+
clip-path: var(--hex-pointy-top);
|
|
149
|
+
font-weight: 600;
|
|
150
|
+
font-size: 12px;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* muted: out-of-scope / "and more" placeholder. */
|
|
154
|
+
::slotted([muted]) {
|
|
155
|
+
opacity: 0.5;
|
|
156
|
+
}
|
|
157
|
+
</style>
|
|
158
|
+
|
|
159
|
+
<div class="panel">${rowMarkup}</div>
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!customElements.get('cn-arch-flow')) {
|
|
165
|
+
customElements.define('cn-arch-flow', CnArchFlow);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export { CnArchFlow };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <cn-pair> — two systems linked by a bridge, with one orange arrow.
|
|
3
|
+
*
|
|
4
|
+
* The "system A talks to system B" diagram. Used at the top of every
|
|
5
|
+
* integration / connector page where the relationship between two
|
|
6
|
+
* systems is the headline. Extracted from the .pair-banner pattern
|
|
7
|
+
* in preview/product-pages/integrations.html.
|
|
8
|
+
*
|
|
9
|
+
* <cn-pair
|
|
10
|
+
* left-label="Open Register"
|
|
11
|
+
* left-caption="nextcloud.example"
|
|
12
|
+
* right-label="xWiki"
|
|
13
|
+
* right-caption="xwiki.example"
|
|
14
|
+
* right-color="cobalt-700"
|
|
15
|
+
* bridge-label="OpenConnector integration">
|
|
16
|
+
* <svg slot="left-icon" viewBox="0 0 24 24">...</svg>
|
|
17
|
+
* <svg slot="right-icon" viewBox="0 0 24 24">...</svg>
|
|
18
|
+
* </cn-pair>
|
|
19
|
+
*
|
|
20
|
+
* The bridge arrow is always KNVB orange — this component owns the
|
|
21
|
+
* "one orange per scene" budget for the page. Don't place a second
|
|
22
|
+
* <cn-pair> or a <cn-hex color="orange"> on the same screen.
|
|
23
|
+
*
|
|
24
|
+
* Slots
|
|
25
|
+
* left-icon — SVG for the left hex (24×24 viewBox, currentColor stroke)
|
|
26
|
+
* right-icon — SVG for the right hex
|
|
27
|
+
*
|
|
28
|
+
* Attributes
|
|
29
|
+
* left-label, right-label — strong text under each hex
|
|
30
|
+
* left-caption, right-caption — optional Plex Mono code-style caption
|
|
31
|
+
* left-color, right-color — cobalt | cobalt-700 | nextcloud | commonground
|
|
32
|
+
* default: cobalt (left), cobalt-700 (right)
|
|
33
|
+
* bridge-label — text under the orange arrow
|
|
34
|
+
* arrow — glyph to render (default: ↔)
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const COLOR_MAP = {
|
|
38
|
+
'cobalt': 'var(--c-blue-cobalt)',
|
|
39
|
+
'cobalt-700': 'var(--c-cobalt-700)',
|
|
40
|
+
'cobalt-900': 'var(--c-cobalt-900)',
|
|
41
|
+
'nextcloud': 'var(--c-nextcloud-blue)',
|
|
42
|
+
'commonground': 'var(--c-commonground-yellow)',
|
|
43
|
+
'mint': 'var(--c-mint-500)',
|
|
44
|
+
'lavender': 'var(--c-lavender-500)',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
class CnPair extends HTMLElement {
|
|
48
|
+
static get observedAttributes() {
|
|
49
|
+
return [
|
|
50
|
+
'left-label', 'left-caption', 'left-color',
|
|
51
|
+
'right-label', 'right-caption', 'right-color',
|
|
52
|
+
'bridge-label', 'arrow',
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
constructor() {
|
|
57
|
+
super();
|
|
58
|
+
this.attachShadow({ mode: 'open' });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
connectedCallback() { this.render(); }
|
|
62
|
+
attributeChangedCallback() { if (this.shadowRoot) this.render(); }
|
|
63
|
+
|
|
64
|
+
render() {
|
|
65
|
+
const leftLabel = this.getAttribute('left-label') || '';
|
|
66
|
+
const leftCaption = this.getAttribute('left-caption') || '';
|
|
67
|
+
const leftColorKey = this.getAttribute('left-color') || 'cobalt';
|
|
68
|
+
const rightLabel = this.getAttribute('right-label') || '';
|
|
69
|
+
const rightCaption = this.getAttribute('right-caption') || '';
|
|
70
|
+
const rightColorKey= this.getAttribute('right-color') || 'cobalt-700';
|
|
71
|
+
const bridgeLabel = this.getAttribute('bridge-label') || '';
|
|
72
|
+
const arrow = this.getAttribute('arrow') || '↔';
|
|
73
|
+
|
|
74
|
+
const leftBg = COLOR_MAP[leftColorKey] || COLOR_MAP.cobalt;
|
|
75
|
+
const rightBg = COLOR_MAP[rightColorKey] || COLOR_MAP['cobalt-700'];
|
|
76
|
+
|
|
77
|
+
/* Common Ground yellow needs cobalt-900 ink for WCAG AA. Every
|
|
78
|
+
other supported fill takes white. */
|
|
79
|
+
const leftInk = leftColorKey === 'commonground' ? 'var(--c-cobalt-900)' : 'white';
|
|
80
|
+
const rightInk = rightColorKey === 'commonground' ? 'var(--c-cobalt-900)' : 'white';
|
|
81
|
+
|
|
82
|
+
this.shadowRoot.innerHTML = `
|
|
83
|
+
<style>
|
|
84
|
+
:host {
|
|
85
|
+
display: block;
|
|
86
|
+
font-family: var(--conduction-typography-font-family-body, system-ui, sans-serif);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.panel {
|
|
90
|
+
display: grid;
|
|
91
|
+
grid-template-columns: 1fr auto 1fr;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: var(--space-5);
|
|
94
|
+
padding: var(--space-6);
|
|
95
|
+
background: var(--c-cobalt-50);
|
|
96
|
+
border-radius: var(--radius-md);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.side {
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
align-items: center;
|
|
103
|
+
gap: var(--space-2);
|
|
104
|
+
text-align: center;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.hex {
|
|
108
|
+
width: 64px;
|
|
109
|
+
height: 74px;
|
|
110
|
+
clip-path: var(--hex-pointy-top);
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
}
|
|
115
|
+
.hex.left { background: ${leftBg}; color: ${leftInk}; }
|
|
116
|
+
.hex.right { background: ${rightBg}; color: ${rightInk}; }
|
|
117
|
+
|
|
118
|
+
::slotted(svg) {
|
|
119
|
+
width: 30px;
|
|
120
|
+
height: 30px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.label {
|
|
124
|
+
font-size: 16px;
|
|
125
|
+
font-weight: 600;
|
|
126
|
+
color: var(--c-cobalt-900);
|
|
127
|
+
}
|
|
128
|
+
.caption {
|
|
129
|
+
font-family: var(--conduction-typography-font-family-code, ui-monospace, monospace);
|
|
130
|
+
font-size: 11px;
|
|
131
|
+
color: var(--c-cobalt-400);
|
|
132
|
+
letter-spacing: 0.06em;
|
|
133
|
+
}
|
|
134
|
+
.caption:empty { display: none; }
|
|
135
|
+
|
|
136
|
+
.bridge {
|
|
137
|
+
font-family: var(--conduction-typography-font-family-code, ui-monospace, monospace);
|
|
138
|
+
font-size: 12px;
|
|
139
|
+
color: var(--c-cobalt-700);
|
|
140
|
+
letter-spacing: 0.05em;
|
|
141
|
+
text-align: center;
|
|
142
|
+
max-width: 160px;
|
|
143
|
+
}
|
|
144
|
+
.bridge .arrow {
|
|
145
|
+
display: block;
|
|
146
|
+
font-size: 28px;
|
|
147
|
+
color: var(--c-orange-knvb);
|
|
148
|
+
line-height: 1;
|
|
149
|
+
margin-bottom: 4px;
|
|
150
|
+
}
|
|
151
|
+
.bridge .arrow[aria-hidden="true"] + .bridge-label:empty { display: none; }
|
|
152
|
+
|
|
153
|
+
@media (max-width: 700px) {
|
|
154
|
+
.panel {
|
|
155
|
+
grid-template-columns: 1fr;
|
|
156
|
+
gap: var(--space-4);
|
|
157
|
+
}
|
|
158
|
+
.bridge .arrow { transform: rotate(90deg); }
|
|
159
|
+
}
|
|
160
|
+
</style>
|
|
161
|
+
|
|
162
|
+
<div class="panel" role="figure" aria-label="${this._escape(leftLabel)} ↔ ${this._escape(rightLabel)}">
|
|
163
|
+
<div class="side">
|
|
164
|
+
<span class="hex left"><slot name="left-icon"></slot></span>
|
|
165
|
+
<span class="label">${this._escape(leftLabel)}</span>
|
|
166
|
+
<span class="caption">${this._escape(leftCaption)}</span>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="bridge">
|
|
169
|
+
<span class="arrow" aria-hidden="true">${this._escape(arrow)}</span>
|
|
170
|
+
<span class="bridge-label">${this._escape(bridgeLabel)}</span>
|
|
171
|
+
</div>
|
|
172
|
+
<div class="side">
|
|
173
|
+
<span class="hex right"><slot name="right-icon"></slot></span>
|
|
174
|
+
<span class="label">${this._escape(rightLabel)}</span>
|
|
175
|
+
<span class="caption">${this._escape(rightCaption)}</span>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
_escape(s) {
|
|
182
|
+
return String(s).replace(/[<>&"']/g, c => ({
|
|
183
|
+
'<': '<', '>': '>', '&': '&', '"': '"', "'": '''
|
|
184
|
+
}[c]));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!customElements.get('cn-pair')) {
|
|
189
|
+
customElements.define('cn-pair', CnPair);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export { CnPair };
|
package/src/diagrams/index.js
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* <cn-pipeline> — horizontal flow of stages connected by arrows
|
|
18
18
|
* <cn-side-box> — rectangle-feed-prism pattern for non-app surfaces
|
|
19
19
|
* <cn-honeycomb-bg> — honeycomb backdrop wrapper for hero scenes
|
|
20
|
+
* <cn-pair> — two systems bridged by an orange arrow
|
|
21
|
+
* <cn-arch-flow> — single row of an architecture / request-flow diagram
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
export * from './cn-hex.js';
|
|
@@ -26,3 +28,5 @@ export * from './cn-platform.js';
|
|
|
26
28
|
export * from './cn-pipeline.js';
|
|
27
29
|
export * from './cn-side-box.js';
|
|
28
30
|
export * from './cn-honeycomb-bg.js';
|
|
31
|
+
export * from './cn-pair.js';
|
|
32
|
+
export * from './cn-arch-flow.js';
|
|
@@ -49,7 +49,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
|
|
|
49
49
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
50
50
|
import {useThemeConfig} from '@docusaurus/theme-common';
|
|
51
51
|
import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
|
|
52
|
-
import {brandFor, productWordmark} from '../brand.jsx';
|
|
52
|
+
import {brandFor, productWordmark, deriveStability} from '../brand.jsx';
|
|
53
53
|
import {ICONS} from '../../components/primitives/icons';
|
|
54
54
|
import styles from './styles.module.css';
|
|
55
55
|
|
|
@@ -126,14 +126,18 @@ function NavItem({item, location, appVersion}) {
|
|
|
126
126
|
return <Link to={to} className={className}>{content}</Link>;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
/* Version pill: code-typeface "
|
|
130
|
-
customFields.appVersion (set by createConfig() from
|
|
131
|
-
or package.json).
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
/* Version pill: code-typeface "{Stability} · v{version}" chip.
|
|
130
|
+
Source is customFields.appVersion (set by createConfig() from
|
|
131
|
+
appinfo/info.xml or package.json). The maturity prefix
|
|
132
|
+
(Stable/Beta/RC/Alpha) is auto-derived from the SemVer string —
|
|
133
|
+
`0.1.0` → Beta, `1.0.0-rc.2` → RC, `1.2.3` → Stable. Sites can
|
|
134
|
+
still pass an explicit `prefix` to override. Hidden when no
|
|
135
|
+
version is available so sites without an app version (Hydra,
|
|
136
|
+
design-system itself) get a clean navbar instead of an empty
|
|
137
|
+
pill. */
|
|
134
138
|
if (typeIs(item, 'versionPill')) {
|
|
135
139
|
if (!appVersion) return null;
|
|
136
|
-
const prefix = item.prefix ||
|
|
140
|
+
const prefix = item.prefix || deriveStability(appVersion);
|
|
137
141
|
return (
|
|
138
142
|
<span className={styles.versionPill} title={`${prefix} · v${appVersion}`}>
|
|
139
143
|
{prefix} · v{appVersion}
|
package/src/theme/brand.jsx
CHANGED
|
@@ -108,3 +108,36 @@ export function productWordmark(title, brandPrefix) {
|
|
|
108
108
|
}
|
|
109
109
|
return null;
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Derive the maturity label that prefixes the version pill and the
|
|
114
|
+
* DetailHero status badge. Driven by SemVer:
|
|
115
|
+
*
|
|
116
|
+
* 1.0.0+ → "Stable"
|
|
117
|
+
* 0.x.y → "Beta" (every 0.x is pre-1.0 by SemVer rule)
|
|
118
|
+
* *-alpha.N → "Alpha"
|
|
119
|
+
* *-beta.N → "Beta"
|
|
120
|
+
* *-rc.N → "RC"
|
|
121
|
+
* anything else → "Stable" (safe default for non-SemVer strings)
|
|
122
|
+
*
|
|
123
|
+
* Both the navbar pill (theme/Navbar) and DetailHero (components/
|
|
124
|
+
* DetailHero) call this so the chrome and the landing-page status
|
|
125
|
+
* badge can't drift apart. Sites can still pass an explicit string
|
|
126
|
+
* to override (`versionPill.prefix = 'Preview'` etc.).
|
|
127
|
+
*/
|
|
128
|
+
export function deriveStability(version) {
|
|
129
|
+
if (!version || typeof version !== 'string') return 'Stable';
|
|
130
|
+
/* Pre-release tags win first — '1.0.0-beta.2' is Beta, not Stable.
|
|
131
|
+
Match case-insensitively because info.xml authors aren't
|
|
132
|
+
consistent (`-Beta`, `-BETA`, …). */
|
|
133
|
+
const pre = version.match(/-(alpha|beta|rc)(?:\.|$|-)/i);
|
|
134
|
+
if (pre) {
|
|
135
|
+
const tag = pre[1].toLowerCase();
|
|
136
|
+
if (tag === 'rc') return 'RC';
|
|
137
|
+
return tag === 'alpha' ? 'Alpha' : 'Beta';
|
|
138
|
+
}
|
|
139
|
+
/* No pre-release tag — 0.x is by SemVer convention not yet stable. */
|
|
140
|
+
const major = version.match(/^(\d+)\./);
|
|
141
|
+
if (major && parseInt(major[1], 10) === 0) return 'Beta';
|
|
142
|
+
return 'Stable';
|
|
143
|
+
}
|
|
@@ -96,8 +96,12 @@ platform-diagram .workspace:not(.box-wrap):not(.workspace-corner-hex) .role {
|
|
|
96
96
|
margin-top: 14px;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
/* ---- Box-wrap (positioning shell for each list) ----
|
|
100
|
-
|
|
99
|
+
/* ---- Box-wrap (positioning shell for each list) ----
|
|
100
|
+
Hover raises the wrap's z-index so its absolute-positioned .desc
|
|
101
|
+
tooltip stacks above neighbouring lists (without this the tooltip
|
|
102
|
+
slides under the next .box-wrap and is unreadable). */
|
|
103
|
+
platform-diagram .box-wrap { position: relative; z-index: 1; }
|
|
104
|
+
platform-diagram .box-wrap:hover { z-index: 20; }
|
|
101
105
|
|
|
102
106
|
/* ---- List-box ---- */
|
|
103
107
|
platform-diagram .box {
|
|
@@ -138,6 +142,8 @@ platform-diagram .row .name {
|
|
|
138
142
|
min-width: 0;
|
|
139
143
|
}
|
|
140
144
|
platform-diagram .row + .row { border-top: 1px solid var(--c-cobalt-50); }
|
|
145
|
+
platform-diagram a.row { text-decoration: none; color: inherit; cursor: pointer; }
|
|
146
|
+
platform-diagram a.row:hover { text-decoration: none; }
|
|
141
147
|
|
|
142
148
|
/* Multi-column box: rows are wrapped in <.col> so the .row+.row border only
|
|
143
149
|
draws within a single column. The columns sit side by side with a thin
|
|
@@ -396,15 +402,17 @@ platform-diagram .box-wrap.app-builder {
|
|
|
396
402
|
opacity: var(--pd-list-progress);
|
|
397
403
|
}
|
|
398
404
|
|
|
399
|
-
/* App Builder list:
|
|
400
|
-
|
|
405
|
+
/* App Builder list: when a COMING SOON-style badge is attached we treat the
|
|
406
|
+
cluster as a placeholder (dashed border, faded text). When no badge is
|
|
407
|
+
set the cluster styles like every other list — OpenBuilt is a real app. */
|
|
408
|
+
platform-diagram .box-wrap.app-builder.has-badge .box {
|
|
401
409
|
border-style: dashed;
|
|
402
410
|
background: var(--c-cobalt-50);
|
|
403
411
|
opacity: 0.92;
|
|
404
412
|
}
|
|
405
|
-
platform-diagram .box-wrap.app-builder .row { color: var(--c-cobalt-400); font-style: italic; }
|
|
406
|
-
platform-diagram .box-wrap.app-builder .row .desc { font-style: normal; }
|
|
407
|
-
platform-diagram .box-wrap.app-builder .row:hover .name { color: var(--c-cobalt-700); }
|
|
413
|
+
platform-diagram .box-wrap.app-builder.has-badge .row { color: var(--c-cobalt-400); font-style: italic; }
|
|
414
|
+
platform-diagram .box-wrap.app-builder.has-badge .row .desc { font-style: normal; }
|
|
415
|
+
platform-diagram .box-wrap.app-builder.has-badge .row:hover .name { color: var(--c-cobalt-700); }
|
|
408
416
|
|
|
409
417
|
/* Badge (e.g. COMING SOON) — generic, attaches to any .box-wrap */
|
|
410
418
|
platform-diagram .box-wrap .badge {
|
|
@@ -139,6 +139,7 @@
|
|
|
139
139
|
name: itemEl.getAttribute('name') || '',
|
|
140
140
|
meta: itemEl.getAttribute('meta') || '',
|
|
141
141
|
desc: itemEl.getAttribute('desc') || '',
|
|
142
|
+
href: itemEl.getAttribute('href') || '',
|
|
142
143
|
brand: itemEl.hasAttribute('brand') || itemEl.hasAttribute('brand-color'),
|
|
143
144
|
brandColor: itemEl.getAttribute('brand-color') || '',
|
|
144
145
|
glyph: svg ? svg.cloneNode(true) : null,
|
|
@@ -160,7 +161,8 @@
|
|
|
160
161
|
const positionClass = POSITION_CLASS[list.position] || list.position;
|
|
161
162
|
const familyClass = list.family ? `fam-${list.family}` : '';
|
|
162
163
|
const colsClass = list.columns > 1 ? `cols-${list.columns}` : '';
|
|
163
|
-
|
|
164
|
+
const badgeClass = list.badge ? 'has-badge' : '';
|
|
165
|
+
wrap.className = `box-wrap ${positionClass} ${familyClass} ${colsClass} ${badgeClass}`.trim();
|
|
164
166
|
|
|
165
167
|
if (list.badge) {
|
|
166
168
|
const badge = document.createElement('span');
|
|
@@ -173,8 +175,9 @@
|
|
|
173
175
|
box.className = 'box' + (list.columns > 1 ? ` cols-${list.columns}` : '');
|
|
174
176
|
|
|
175
177
|
function makeRow(item) {
|
|
176
|
-
const row = document.createElement('div');
|
|
178
|
+
const row = document.createElement(item.href ? 'a' : 'div');
|
|
177
179
|
row.className = 'row';
|
|
180
|
+
if (item.href) row.setAttribute('href', item.href);
|
|
178
181
|
|
|
179
182
|
const glyph = document.createElement('span');
|
|
180
183
|
glyph.className = 'glyph' + (item.brand ? ' brand' : '');
|