@conduction/docusaurus-preset 2.7.0-beta.3 → 2.7.1
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/package.json +1 -1
- package/src/components/DetailHero/DetailHero.jsx +1 -17
- package/src/components/primitives/Button.jsx +9 -32
- package/src/components/primitives/Button.module.css +0 -34
- package/src/css/brand.css +0 -154
- package/src/css/tokens.css +1 -2
- package/src/index.js +21 -97
- package/src/theme/Footer/index.jsx +48 -37
- package/src/theme/Navbar/index.jsx +15 -157
- package/src/theme/Navbar/styles.module.css +0 -93
- package/src/theme/brand.jsx +0 -47
- package/static/lib/canal-footer.css +0 -19
- package/static/lib/platform-diagram.css +1 -1
- package/src/components/primitives/icons.jsx +0 -64
package/package.json
CHANGED
|
@@ -147,7 +147,6 @@ export default function DetailHero({
|
|
|
147
147
|
variant="primary"
|
|
148
148
|
tone={primaryCta.tone}
|
|
149
149
|
href={primaryCta.href}
|
|
150
|
-
icon={primaryCta.icon}
|
|
151
150
|
>
|
|
152
151
|
{primaryCta.label}
|
|
153
152
|
</Button>
|
|
@@ -157,26 +156,11 @@ export default function DetailHero({
|
|
|
157
156
|
variant="secondary"
|
|
158
157
|
tone={secondaryCta.tone}
|
|
159
158
|
href={secondaryCta.href}
|
|
160
|
-
icon={secondaryCta.icon}
|
|
161
159
|
>
|
|
162
160
|
{secondaryCta.label}
|
|
163
161
|
</Button>
|
|
164
162
|
)}
|
|
165
|
-
{tertiaryCta &&
|
|
166
|
-
/* On a cobalt-bg hero the default ghost variant
|
|
167
|
-
(cobalt-700 text) disappears against the dark panel;
|
|
168
|
-
auto-switch to on-dark-tertiary (white text + white
|
|
169
|
-
border) so the CTA reads at parity with the primary
|
|
170
|
-
and secondary buttons. Sites can still pass an
|
|
171
|
-
explicit `variant` to opt out. */
|
|
172
|
-
<Button
|
|
173
|
-
variant={tertiaryCta.variant || (background === 'cobalt' ? 'on-dark-tertiary' : 'ghost')}
|
|
174
|
-
href={tertiaryCta.href}
|
|
175
|
-
icon={tertiaryCta.icon}
|
|
176
|
-
>
|
|
177
|
-
{tertiaryCta.label} →
|
|
178
|
-
</Button>
|
|
179
|
-
)}
|
|
163
|
+
{tertiaryCta && <Button variant="ghost" href={tertiaryCta.href}>{tertiaryCta.label} →</Button>}
|
|
180
164
|
</div>
|
|
181
165
|
)}
|
|
182
166
|
</div>
|
|
@@ -1,45 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* <Button />
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* and per-page CTA rows.
|
|
4
|
+
* Three brand variants (primary / secondary / ghost) used across hero,
|
|
5
|
+
* cta-banner, top-navbar, game-modal, and per-page CTA rows.
|
|
6
6
|
*
|
|
7
7
|
* Renders as <a> when href is provided, <button> otherwise. Inherits
|
|
8
8
|
* the brand transition (140ms ease) and KNVB-orange focus ring from
|
|
9
9
|
* brand.css.
|
|
10
10
|
*
|
|
11
11
|
* Variants:
|
|
12
|
-
* - primary:
|
|
13
|
-
* - secondary:
|
|
14
|
-
* - ghost:
|
|
15
|
-
* - on-dark
|
|
16
|
-
* - on-dark-secondary: ghost-style with white-translucent border, on cobalt
|
|
17
|
-
* - on-dark-tertiary: transparent fill, solid white border + white text,
|
|
18
|
-
* for the "View on GitHub" CTA on cobalt-bg heroes
|
|
12
|
+
* - primary: cobalt fill, white text
|
|
13
|
+
* - secondary: white bg, cobalt-200 border, cobalt text
|
|
14
|
+
* - ghost: plain text + arrow
|
|
15
|
+
* - on-dark: primary variant inverted for use inside cobalt CTA panels
|
|
19
16
|
*
|
|
20
17
|
* Tone (optional): override the primary/secondary fill colour. The
|
|
21
18
|
* brand default is cobalt; product pages with an orange-accent identity
|
|
22
19
|
* (e.g. mydash) pass `tone="orange"` to flip the primary CTA to
|
|
23
20
|
* KNVB-orange while keeping the rest of the brand chrome intact.
|
|
24
21
|
*
|
|
25
|
-
* Icon (optional): pass a key from icons.jsx (e.g. `"github"`) or a
|
|
26
|
-
* React node directly. The icon sits inline before the label and
|
|
27
|
-
* inherits the button's font-size + colour.
|
|
28
|
-
*
|
|
29
22
|
* Usage:
|
|
30
23
|
*
|
|
31
24
|
* <Button href="/apps">Install</Button>
|
|
32
25
|
* <Button variant="secondary" href="/partners">Get a demo</Button>
|
|
33
26
|
* <Button variant="ghost" href="https://github.com/...">View on GitHub →</Button>
|
|
34
|
-
* <Button
|
|
35
|
-
* variant="on-dark-tertiary"
|
|
36
|
-
* icon="github"
|
|
37
|
-
* href="https://github.com/ConductionNL/shillinq"
|
|
38
|
-
* >View on GitHub</Button>
|
|
27
|
+
* <Button tone="orange" href="/install">Install from app store</Button>
|
|
39
28
|
*/
|
|
40
29
|
|
|
41
30
|
import React from 'react';
|
|
42
|
-
import {ICONS} from './icons';
|
|
43
31
|
import styles from './Button.module.css';
|
|
44
32
|
|
|
45
33
|
export default function Button({
|
|
@@ -47,7 +35,6 @@ export default function Button({
|
|
|
47
35
|
tone,
|
|
48
36
|
href,
|
|
49
37
|
size = 'md',
|
|
50
|
-
icon,
|
|
51
38
|
className,
|
|
52
39
|
children,
|
|
53
40
|
...rest
|
|
@@ -60,18 +47,8 @@ export default function Button({
|
|
|
60
47
|
className,
|
|
61
48
|
].filter(Boolean).join(' ');
|
|
62
49
|
|
|
63
|
-
/* Icon: accept a string key (looked up in ICONS) or a React node
|
|
64
|
-
directly. The wrapping span lets us apply consistent inline metrics
|
|
65
|
-
without forcing every caller to size their SVG. */
|
|
66
|
-
const iconNode = typeof icon === 'string' ? ICONS[icon] : icon;
|
|
67
|
-
const iconEl = iconNode ? (
|
|
68
|
-
<span className={styles.icon} aria-hidden="true">{iconNode}</span>
|
|
69
|
-
) : null;
|
|
70
|
-
|
|
71
|
-
const body = <>{iconEl}{children}</>;
|
|
72
|
-
|
|
73
50
|
if (href) {
|
|
74
|
-
return <a href={href} className={composed} {...rest}>{
|
|
51
|
+
return <a href={href} className={composed} {...rest}>{children}</a>;
|
|
75
52
|
}
|
|
76
|
-
return <button type="button" className={composed} {...rest}>{
|
|
53
|
+
return <button type="button" className={composed} {...rest}>{children}</button>;
|
|
77
54
|
}
|
|
@@ -17,22 +17,6 @@
|
|
|
17
17
|
font-family: inherit;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/* Inline icon slot. SVG renders at 1em × 1em via icons.jsx, so it
|
|
21
|
-
scales with the button's font-size. The wrapping span keeps the
|
|
22
|
-
icon vertically centred even when the label wraps. */
|
|
23
|
-
.icon {
|
|
24
|
-
display: inline-flex;
|
|
25
|
-
align-items: center;
|
|
26
|
-
justify-content: center;
|
|
27
|
-
line-height: 1;
|
|
28
|
-
font-size: 1.05em;
|
|
29
|
-
}
|
|
30
|
-
.icon :global(svg) {
|
|
31
|
-
width: 1em;
|
|
32
|
-
height: 1em;
|
|
33
|
-
display: block;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
20
|
.s-sm { padding: 9px 14px; font-size: 13px; }
|
|
37
21
|
.s-md { padding: 13px 22px; font-size: 15px; }
|
|
38
22
|
.s-lg { padding: 16px 28px; font-size: 16px; }
|
|
@@ -100,24 +84,6 @@
|
|
|
100
84
|
color: white;
|
|
101
85
|
}
|
|
102
86
|
|
|
103
|
-
/* On-dark tertiary: the "View on GitHub" CTA that sits next to the
|
|
104
|
-
primary/secondary buttons on a cobalt-bg DetailHero. Transparent
|
|
105
|
-
fill, solid white border, white text — readable on cobalt-900 and
|
|
106
|
-
visually weighted below the primary/secondary CTAs without resorting
|
|
107
|
-
to the ghost variant (which is cobalt-700 text and disappears on
|
|
108
|
-
the dark hero). */
|
|
109
|
-
.v-on-dark-tertiary {
|
|
110
|
-
background: transparent;
|
|
111
|
-
color: white;
|
|
112
|
-
border-color: white;
|
|
113
|
-
}
|
|
114
|
-
.v-on-dark-tertiary:hover {
|
|
115
|
-
background: rgba(255, 255, 255, 0.12);
|
|
116
|
-
color: white;
|
|
117
|
-
border-color: white;
|
|
118
|
-
text-decoration: none;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
87
|
.v-on-dark-primary :global(.next-blue),
|
|
122
88
|
.v-primary :global(.next-blue) { color: var(--c-nextcloud-cyan); }
|
|
123
89
|
|
package/src/css/brand.css
CHANGED
|
@@ -156,157 +156,3 @@ a:not(.navbar__link):not(.footer__link-item):not(.button) {
|
|
|
156
156
|
a:not(.navbar__link):not(.footer__link-item):not(.button):hover {
|
|
157
157
|
text-decoration-color: var(--c-orange-knvb);
|
|
158
158
|
}
|
|
159
|
-
|
|
160
|
-
/* =========================================================================
|
|
161
|
-
Docs-page styling — mirror preview/product-pages/_docs-shell.css
|
|
162
|
-
|
|
163
|
-
The Docusaurus defaults (Infima) ship a cramped sidebar, Title-Case
|
|
164
|
-
menu items, and an aggressive vertical rhythm. The design-system
|
|
165
|
-
product-page mocks define a calmer treatment: a 280px sidebar, code-
|
|
166
|
-
typeface group labels, a left-border active state on cobalt-50, and a
|
|
167
|
-
900px content column with a 40/26/22 px heading scale at 1.65 body
|
|
168
|
-
line-height. These overrides pull the live docs deploys (shillinq,
|
|
169
|
-
openregister, …) into line with the mocks.
|
|
170
|
-
========================================================================= */
|
|
171
|
-
|
|
172
|
-
/* Sidebar: 280px column with a 1px cobalt-100 right edge. */
|
|
173
|
-
.theme-doc-sidebar-container {
|
|
174
|
-
width: 280px !important;
|
|
175
|
-
border-right: 1px solid var(--c-cobalt-100) !important;
|
|
176
|
-
background: white;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/* Menu list typography + spacing */
|
|
180
|
-
.menu {
|
|
181
|
-
padding: var(--space-6) var(--space-5) !important;
|
|
182
|
-
font-size: 13px;
|
|
183
|
-
}
|
|
184
|
-
.menu__list .menu__list {
|
|
185
|
-
padding-left: var(--space-3);
|
|
186
|
-
margin-left: 0;
|
|
187
|
-
}
|
|
188
|
-
.menu__list-item {
|
|
189
|
-
margin: 0;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/* Default link state: idle sentence-case cobalt-700 on white,
|
|
193
|
-
4-px-radius hover tint cobalt-50. */
|
|
194
|
-
.menu__link {
|
|
195
|
-
color: var(--c-cobalt-700);
|
|
196
|
-
padding: 4px 10px;
|
|
197
|
-
border-radius: var(--radius-sm);
|
|
198
|
-
margin-bottom: 2px;
|
|
199
|
-
font-weight: 400;
|
|
200
|
-
line-height: 1.4;
|
|
201
|
-
}
|
|
202
|
-
.menu__link:hover {
|
|
203
|
-
background: var(--c-cobalt-50);
|
|
204
|
-
color: var(--c-cobalt-900);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/* Active item: 2-px cobalt left border + cobalt-50 fill + cobalt-blue text.
|
|
208
|
-
The padding tweak compensates for the border so the label stays
|
|
209
|
-
aligned with the idle siblings above and below. */
|
|
210
|
-
.menu__link--active,
|
|
211
|
-
.menu__link--active:hover {
|
|
212
|
-
background: var(--c-cobalt-50);
|
|
213
|
-
color: var(--c-blue-cobalt);
|
|
214
|
-
font-weight: 500;
|
|
215
|
-
border-left: 2px solid var(--c-blue-cobalt);
|
|
216
|
-
padding-left: 8px;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/* Category headers (Features / Integrations / Technical groups) get
|
|
220
|
-
the code-typeface uppercase eyebrow treatment, matching the
|
|
221
|
-
`.docs-sidebar .group` rule in the mock. */
|
|
222
|
-
.menu__list-item-collapsible > .menu__link,
|
|
223
|
-
.menu__link--sublist {
|
|
224
|
-
font-family: var(--conduction-typography-font-family-code);
|
|
225
|
-
font-size: 10px;
|
|
226
|
-
text-transform: uppercase;
|
|
227
|
-
letter-spacing: 0.1em;
|
|
228
|
-
color: var(--c-cobalt-400);
|
|
229
|
-
font-weight: 500;
|
|
230
|
-
margin: var(--space-4) 0 var(--space-2);
|
|
231
|
-
}
|
|
232
|
-
.menu__list-item-collapsible > .menu__link:hover {
|
|
233
|
-
background: transparent;
|
|
234
|
-
color: var(--c-cobalt-700);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/* Caret on collapsible categories — desaturate to cobalt-300. */
|
|
238
|
-
.menu__caret::before,
|
|
239
|
-
.menu__link--sublist-caret::after {
|
|
240
|
-
filter: opacity(0.5);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/* Content column: 900px max width, generous padding, brand type scale.
|
|
244
|
-
We avoid touching the global .markdown selector (it'd hit MDX pages
|
|
245
|
-
too); scope to the doc-item container instead. */
|
|
246
|
-
.theme-doc-markdown {
|
|
247
|
-
max-width: 900px;
|
|
248
|
-
padding: var(--space-8) var(--space-10);
|
|
249
|
-
}
|
|
250
|
-
.theme-doc-markdown h1 {
|
|
251
|
-
font-size: 40px;
|
|
252
|
-
font-weight: 700;
|
|
253
|
-
letter-spacing: -0.02em;
|
|
254
|
-
line-height: 1.1;
|
|
255
|
-
margin: 0 0 var(--space-4);
|
|
256
|
-
color: var(--c-cobalt-900);
|
|
257
|
-
}
|
|
258
|
-
.theme-doc-markdown h2 {
|
|
259
|
-
font-size: 26px;
|
|
260
|
-
font-weight: 600;
|
|
261
|
-
letter-spacing: -0.01em;
|
|
262
|
-
margin: var(--space-8) 0 var(--space-3);
|
|
263
|
-
color: var(--c-cobalt-900);
|
|
264
|
-
}
|
|
265
|
-
.theme-doc-markdown h3 {
|
|
266
|
-
font-size: 20px;
|
|
267
|
-
font-weight: 600;
|
|
268
|
-
margin: var(--space-6) 0 var(--space-3);
|
|
269
|
-
color: var(--c-cobalt-900);
|
|
270
|
-
}
|
|
271
|
-
.theme-doc-markdown p,
|
|
272
|
-
.theme-doc-markdown ul,
|
|
273
|
-
.theme-doc-markdown ol {
|
|
274
|
-
font-size: 15px;
|
|
275
|
-
line-height: 1.65;
|
|
276
|
-
color: var(--c-cobalt-800);
|
|
277
|
-
margin: 0 0 var(--space-3);
|
|
278
|
-
}
|
|
279
|
-
.theme-doc-markdown ul,
|
|
280
|
-
.theme-doc-markdown ol {
|
|
281
|
-
padding-left: var(--space-5);
|
|
282
|
-
}
|
|
283
|
-
.theme-doc-markdown li {
|
|
284
|
-
margin-bottom: var(--space-2);
|
|
285
|
-
}
|
|
286
|
-
.theme-doc-markdown strong {
|
|
287
|
-
color: var(--c-cobalt-900);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/* Code-typeface breadcrumb above the H1. Docusaurus renders this
|
|
291
|
-
via the DocBreadcrumbs component; we restyle in-place. */
|
|
292
|
-
.theme-doc-breadcrumbs {
|
|
293
|
-
font-family: var(--conduction-typography-font-family-code);
|
|
294
|
-
font-size: 11px;
|
|
295
|
-
letter-spacing: 0.08em;
|
|
296
|
-
text-transform: uppercase;
|
|
297
|
-
color: var(--c-cobalt-400);
|
|
298
|
-
margin-bottom: var(--space-4);
|
|
299
|
-
}
|
|
300
|
-
.theme-doc-breadcrumbs .breadcrumbs__link {
|
|
301
|
-
color: var(--c-cobalt-400);
|
|
302
|
-
background: transparent;
|
|
303
|
-
padding: 0;
|
|
304
|
-
}
|
|
305
|
-
.theme-doc-breadcrumbs .breadcrumbs__link:hover {
|
|
306
|
-
color: var(--c-orange-knvb);
|
|
307
|
-
background: transparent;
|
|
308
|
-
}
|
|
309
|
-
.theme-doc-breadcrumbs .breadcrumbs__item--active .breadcrumbs__link {
|
|
310
|
-
color: var(--c-cobalt-700);
|
|
311
|
-
background: transparent;
|
|
312
|
-
}
|
package/src/css/tokens.css
CHANGED
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
--c-red-vermillion: #AE1C28; /* Dutch flag red — sparingly */
|
|
24
24
|
--c-white: #FFFFFF;
|
|
25
25
|
--c-nextcloud-blue: #0082C9; /* Nextcloud official */
|
|
26
|
-
--c-nextcloud-cyan: #1CAFFF; /* Nextcloud
|
|
27
|
-
--gradient-nextcloud: linear-gradient(45deg, #0082C9, #1CAFFF);
|
|
26
|
+
--c-nextcloud-cyan: #1CAFFF; /* Nextcloud lighter cyan */
|
|
28
27
|
--c-commonground-yellow: #F6AD00; /* Common Ground official, commonground.nl */
|
|
29
28
|
|
|
30
29
|
/* Cobalt tints — derived for surfaces, hex-prism faces, soft fills */
|
package/src/index.js
CHANGED
|
@@ -21,56 +21,6 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
const path = require('path');
|
|
24
|
-
const fs = require('fs');
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Resolve the app version that drives the navbar "Stable · v{x.y.z}"
|
|
28
|
-
* pill. Order of precedence:
|
|
29
|
-
*
|
|
30
|
-
* 1. opts.appVersion (explicit override)
|
|
31
|
-
* 2. appinfo/info.xml <version> tag (Nextcloud app convention; every
|
|
32
|
-
* app in the Conduction fleet has one)
|
|
33
|
-
* 3. package.json version (sites without an info.xml — Hydra,
|
|
34
|
-
* design-system itself, conduction-website)
|
|
35
|
-
* 4. undefined (pill is hidden by the Navbar swizzle)
|
|
36
|
-
*
|
|
37
|
-
* The resolver runs at config build-time inside `process.cwd()`, which
|
|
38
|
-
* is the consuming site's repo root. Failures are swallowed silently so
|
|
39
|
-
* a missing info.xml never breaks the site build — the pill just hides.
|
|
40
|
-
*/
|
|
41
|
-
function resolveAppVersion(opts) {
|
|
42
|
-
if (opts.appVersion) return String(opts.appVersion);
|
|
43
|
-
|
|
44
|
-
/* Nextcloud apps: appinfo/info.xml carries the canonical version.
|
|
45
|
-
We avoid pulling in an XML parser for one tag — a non-greedy regex
|
|
46
|
-
against the file content is robust enough for the standard
|
|
47
|
-
`<version>x.y.z</version>` shape the app store mandates. */
|
|
48
|
-
try {
|
|
49
|
-
const infoPath = path.join(process.cwd(), 'appinfo', 'info.xml');
|
|
50
|
-
if (fs.existsSync(infoPath)) {
|
|
51
|
-
const xml = fs.readFileSync(infoPath, 'utf8');
|
|
52
|
-
const m = xml.match(/<version>\s*([^<\s]+)\s*<\/version>/);
|
|
53
|
-
if (m && m[1]) return m[1];
|
|
54
|
-
}
|
|
55
|
-
} catch (e) {
|
|
56
|
-
/* fall through */
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/* Non-Nextcloud sites: package.json version. Walks up from cwd in
|
|
60
|
-
case the site builds from a sub-directory; one level deep is
|
|
61
|
-
enough for the conduction-website layout. */
|
|
62
|
-
try {
|
|
63
|
-
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
64
|
-
if (fs.existsSync(pkgPath)) {
|
|
65
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
66
|
-
if (pkg.version) return pkg.version;
|
|
67
|
-
}
|
|
68
|
-
} catch (e) {
|
|
69
|
-
/* fall through */
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
24
|
|
|
75
25
|
/**
|
|
76
26
|
* Brand-default i18n block. Nederlands at the URL root, others at /en/, /de/, /fr/.
|
|
@@ -89,40 +39,17 @@ const I18N = {
|
|
|
89
39
|
/**
|
|
90
40
|
* Brand-default navbar. Sites pass their own items[] and logo; the chrome
|
|
91
41
|
* styling (cobalt-on-white, Plex-Mono caption) is locked.
|
|
92
|
-
*
|
|
93
|
-
* The right-side default carries the four chrome items that the brand
|
|
94
|
-
* navbar swizzle (theme/Navbar/index.jsx) renders as icons + pill:
|
|
95
|
-
*
|
|
96
|
-
* versionPill "Stable · v{x.y.z}" reading customFields.appVersion;
|
|
97
|
-
* hidden when no version is available, so non-Nextcloud
|
|
98
|
-
* sites get a clean navbar.
|
|
99
|
-
* apiDocs Book icon + "API Documentation", pointing at /api
|
|
100
|
-
* (Redocusaurus convention). Sites without an OpenAPI
|
|
101
|
-
* spec remove this item from their own config.
|
|
102
|
-
* github GitHub mark, opens the org GitHub by default.
|
|
103
|
-
* localeDropdown Existing locale switcher (nl/en/de/fr).
|
|
104
|
-
*
|
|
105
|
-
* The `opts.repoUrl` plumbing below overrides the GitHub item's href
|
|
106
|
-
* to point at the specific app repo when the site provides it.
|
|
107
42
|
*/
|
|
108
|
-
const baseNavbar = (siteName
|
|
43
|
+
const baseNavbar = (siteName) => ({
|
|
109
44
|
title: siteName,
|
|
110
45
|
logo: {
|
|
111
46
|
alt: `${siteName} avatar`,
|
|
112
47
|
src: 'img/logo.svg',
|
|
113
48
|
srcDark: 'img/logo-dark.svg',
|
|
114
49
|
},
|
|
115
|
-
/* `custom-*` prefix is the Docusaurus convention for theme-defined
|
|
116
|
-
navbar item types — items prefixed this way bypass the strict Joi
|
|
117
|
-
schema validator in @docusaurus/theme-classic, so the brand Navbar
|
|
118
|
-
swizzle can dispatch on them without registering each shape with
|
|
119
|
-
core. The swizzle accepts both `custom-github` and the bare
|
|
120
|
-
`github` (etc.) for forward-compat. */
|
|
121
50
|
items: [
|
|
122
|
-
{ type: 'custom-versionPill', position: 'right' },
|
|
123
|
-
{ type: 'custom-apiDocs', position: 'right' },
|
|
124
|
-
{ type: 'custom-github', href: repoUrl || 'https://github.com/ConductionNL', position: 'right' },
|
|
125
51
|
{ type: 'localeDropdown', position: 'right' },
|
|
52
|
+
{ href: 'https://github.com/ConductionNL', label: 'GitHub', position: 'right' },
|
|
126
53
|
],
|
|
127
54
|
});
|
|
128
55
|
|
|
@@ -201,11 +128,6 @@ const baseFooter = () => ({
|
|
|
201
128
|
* minigames (default true; set false to drop the brand canal-footer's
|
|
202
129
|
* boat-sinking + kade-cyclist mini-games on product pages while
|
|
203
130
|
* keeping the static skyline + canal decoration),
|
|
204
|
-
* appVersion (explicit override for the navbar "Stable · v{x.y.z}"
|
|
205
|
-
* pill; defaults to appinfo/info.xml then package.json — see
|
|
206
|
-
* resolveAppVersion above),
|
|
207
|
-
* repoUrl (target of the navbar GitHub icon; defaults to the
|
|
208
|
-
* ConductionNL org root),
|
|
209
131
|
* customCss[] (appended to brand.css), plugins[], presets,
|
|
210
132
|
* i18n (overrides defaults)
|
|
211
133
|
*/
|
|
@@ -220,11 +142,6 @@ function createConfig(opts) {
|
|
|
220
142
|
getClientModules(), so customCss carries site-specific CSS only. */
|
|
221
143
|
const customCss = opts.customCss || [];
|
|
222
144
|
|
|
223
|
-
/* App version drives the navbar "Stable · v{x.y.z}" pill (resolved
|
|
224
|
-
once at config time). Pulled from appinfo/info.xml, then
|
|
225
|
-
package.json — see resolveAppVersion above. */
|
|
226
|
-
const appVersion = resolveAppVersion(opts);
|
|
227
|
-
|
|
228
145
|
return {
|
|
229
146
|
title: opts.title,
|
|
230
147
|
tagline: opts.tagline || '',
|
|
@@ -236,17 +153,6 @@ function createConfig(opts) {
|
|
|
236
153
|
organizationName: opts.organizationName || 'ConductionNL',
|
|
237
154
|
projectName: opts.projectName || 'design-system',
|
|
238
155
|
|
|
239
|
-
/* customFields is the canonical Docusaurus channel for build-time
|
|
240
|
-
data the theme reads at runtime via useDocusaurusContext().
|
|
241
|
-
appVersion drives the navbar "Stable · v{x.y.z}" pill; sites
|
|
242
|
-
extend this by passing their own customFields in opts. */
|
|
243
|
-
customFields: Object.assign(
|
|
244
|
-
{
|
|
245
|
-
...(appVersion && {appVersion}),
|
|
246
|
-
},
|
|
247
|
-
opts.customFields || {}
|
|
248
|
-
),
|
|
249
|
-
|
|
250
156
|
onBrokenLinks: 'warn',
|
|
251
157
|
onBrokenMarkdownLinks: 'warn',
|
|
252
158
|
|
|
@@ -295,7 +201,7 @@ function createConfig(opts) {
|
|
|
295
201
|
disableSwitch: false,
|
|
296
202
|
respectPrefersColorScheme: true,
|
|
297
203
|
},
|
|
298
|
-
navbar: Object.assign(baseNavbar(opts.title
|
|
204
|
+
navbar: Object.assign(baseNavbar(opts.title), opts.navbar || {}),
|
|
299
205
|
/* Per-property fallback so a site can override one slice of the
|
|
300
206
|
footer (e.g. just `links`) and inherit the rest from the brand.
|
|
301
207
|
Previously `opts.footer` replaced the whole footer wholesale,
|
|
@@ -329,6 +235,24 @@ function createConfig(opts) {
|
|
|
329
235
|
built jointly with a partner (mydash + Sendent
|
|
330
236
|
is the first case). */
|
|
331
237
|
footerBrand: opts.footerBrand || null,
|
|
238
|
+
/* Legal-bar links (Privacy / Terms / ISO) plus the two ISO
|
|
239
|
+
9001 + 27001 certification badges on the right side of the
|
|
240
|
+
canal-footer. Default keeps prior behaviour (pages live at
|
|
241
|
+
/privacy, /terms, /iso on docs.conduction.nl + www.conduction.nl).
|
|
242
|
+
Consumer sites that don't ship those pages can opt out per
|
|
243
|
+
slot to silence broken-link warnings:
|
|
244
|
+
|
|
245
|
+
legalLinks: {
|
|
246
|
+
privacy: false, // hide the Privacy link
|
|
247
|
+
terms: false, // hide the Terms link
|
|
248
|
+
iso: false, // hide the ISO link AND the cert badges
|
|
249
|
+
// (badges follow iso link by default)
|
|
250
|
+
// any slot can also take a string for an external URL:
|
|
251
|
+
privacy: 'https://docs.conduction.nl/privacy',
|
|
252
|
+
// certs default-follow iso, override here:
|
|
253
|
+
isoCertifications: true | false,
|
|
254
|
+
} */
|
|
255
|
+
legalLinks: opts.legalLinks || {},
|
|
332
256
|
},
|
|
333
257
|
opts.themeConfig || {}
|
|
334
258
|
),
|
|
@@ -52,6 +52,32 @@ export default function Footer() {
|
|
|
52
52
|
../../index.js for the option semantics. */
|
|
53
53
|
const minigamesOn = themeConfig.minigames !== false;
|
|
54
54
|
const footerBrand = themeConfig.footerBrand || null;
|
|
55
|
+
/* legalLinks: opt-in/out of the Privacy / Terms / ISO links inside
|
|
56
|
+
the legal-bar, and the two ISO 9001/27001 certification badges on
|
|
57
|
+
the right. Defaults preserve current behaviour for sites that ship
|
|
58
|
+
those pages (docs.conduction.nl, www.conduction.nl). Consumer
|
|
59
|
+
sites that don't ship them can pass { privacy: false, terms: false,
|
|
60
|
+
iso: false, isoCertifications: false } via createConfig opts to
|
|
61
|
+
silence broken-link warnings. Each link slot also accepts a string
|
|
62
|
+
to override the destination (e.g. an external URL pointing at the
|
|
63
|
+
canonical Conduction legal page). */
|
|
64
|
+
const legalLinks = themeConfig.legalLinks || {};
|
|
65
|
+
const legalLink = (key, fallback) => {
|
|
66
|
+
if (legalLinks[key] === false) return null;
|
|
67
|
+
if (typeof legalLinks[key] === 'string' && legalLinks[key]) return legalLinks[key];
|
|
68
|
+
return fallback;
|
|
69
|
+
};
|
|
70
|
+
const privacyTo = legalLink('privacy', '/privacy');
|
|
71
|
+
const termsTo = legalLink('terms', '/terms');
|
|
72
|
+
const isoTo = legalLink('iso', '/iso');
|
|
73
|
+
/* ISO certification badges (the two 9001 + 27001 pills on the right
|
|
74
|
+
of the legal-bar) default to following the iso link's visibility —
|
|
75
|
+
no point claiming certifications when the linked detail page
|
|
76
|
+
doesn't ship. Sites can force a state with
|
|
77
|
+
legalLinks.isoCertifications: true | false. */
|
|
78
|
+
const showIsoCerts = legalLinks.isoCertifications !== undefined
|
|
79
|
+
? Boolean(legalLinks.isoCertifications)
|
|
80
|
+
: Boolean(isoTo);
|
|
55
81
|
|
|
56
82
|
const location = useLocation();
|
|
57
83
|
/* Brand switch follows the pathname: /connext or /commonground sections
|
|
@@ -316,15 +342,6 @@ export default function Footer() {
|
|
|
316
342
|
Open-source apps for <span className="next-blue">Nextcloud</span>. Built and
|
|
317
343
|
maintained by Conduction in Amsterdam, released under EUPL-1.2.
|
|
318
344
|
</p>
|
|
319
|
-
{/*
|
|
320
|
-
Brand citation. The producer chain stays dot-separated
|
|
321
|
-
(Conduction · sub-brand · partner) and connects to
|
|
322
|
-
Nextcloud through a vermillion-red heart — the "loves"
|
|
323
|
-
relationship is between the producer stack and the
|
|
324
|
-
platform it ships on. Nextcloud is a link to
|
|
325
|
-
nextcloud.com so visitors can verify the platform
|
|
326
|
-
upstream in one click.
|
|
327
|
-
*/}
|
|
328
345
|
<div className="triad">
|
|
329
346
|
<span>
|
|
330
347
|
<span className="h"></span>
|
|
@@ -335,17 +352,7 @@ export default function Footer() {
|
|
|
335
352
|
.map((b, i) => (
|
|
336
353
|
<React.Fragment key={i}> · {b.wordmark}</React.Fragment>
|
|
337
354
|
))}
|
|
338
|
-
{' '}
|
|
339
|
-
<svg className="heart" viewBox="0 0 24 24" fill="currentColor" aria-label="loves" role="img">
|
|
340
|
-
<path d="M12 21s-6.7-4.35-9.2-8.4C.8 9.2 2 5.5 5.2 4.7c2-.5 3.8.4 4.8 1.9 1-1.5 2.8-2.4 4.8-1.9 3.2.8 4.4 4.5 2.4 7.9C18.7 16.65 12 21 12 21z"/>
|
|
341
|
-
</svg>
|
|
342
|
-
{' '}
|
|
343
|
-
<a
|
|
344
|
-
href="https://nextcloud.com"
|
|
345
|
-
target="_blank"
|
|
346
|
-
rel="noopener noreferrer"
|
|
347
|
-
className="next-blue"
|
|
348
|
-
>Nextcloud</a>
|
|
355
|
+
{' '}· <span className="next-blue">Nextcloud</span>
|
|
349
356
|
</span>
|
|
350
357
|
</div>
|
|
351
358
|
<div className="socials">
|
|
@@ -397,24 +404,28 @@ export default function Footer() {
|
|
|
397
404
|
<div className="legal-bar">
|
|
398
405
|
<div className="left">
|
|
399
406
|
<span>{copyright}</span>
|
|
400
|
-
|
|
401
|
-
<
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
<Link to="/iso" className="iso-badge" aria-label="ISO 9001:2015 certified, see details">
|
|
410
|
-
<span className="iso-mark">ISO</span>
|
|
411
|
-
<span className="iso-num">9001:2015</span>
|
|
412
|
-
</Link>
|
|
413
|
-
<Link to="/iso" className="iso-badge" aria-label="ISO 27001:2022 certified, see details">
|
|
414
|
-
<span className="iso-mark">ISO</span>
|
|
415
|
-
<span className="iso-num">27001:2022</span>
|
|
416
|
-
</Link>
|
|
407
|
+
{(privacyTo || termsTo || isoTo) && (
|
|
408
|
+
<span className="legal-links">
|
|
409
|
+
{privacyTo && <Link to={privacyTo}>Privacy</Link>}
|
|
410
|
+
{privacyTo && termsTo && <span className="sep">·</span>}
|
|
411
|
+
{termsTo && <Link to={termsTo}>Terms</Link>}
|
|
412
|
+
{(privacyTo || termsTo) && isoTo && <span className="sep">·</span>}
|
|
413
|
+
{isoTo && <Link to={isoTo}>ISO</Link>}
|
|
414
|
+
</span>
|
|
415
|
+
)}
|
|
417
416
|
</div>
|
|
417
|
+
{showIsoCerts && (
|
|
418
|
+
<div className="right">
|
|
419
|
+
<Link to={isoTo || '/iso'} className="iso-badge" aria-label="ISO 9001:2015 certified, see details">
|
|
420
|
+
<span className="iso-mark">ISO</span>
|
|
421
|
+
<span className="iso-num">9001:2015</span>
|
|
422
|
+
</Link>
|
|
423
|
+
<Link to={isoTo || '/iso'} className="iso-badge" aria-label="ISO 27001:2022 certified, see details">
|
|
424
|
+
<span className="iso-mark">ISO</span>
|
|
425
|
+
<span className="iso-num">27001:2022</span>
|
|
426
|
+
</Link>
|
|
427
|
+
</div>
|
|
428
|
+
)}
|
|
418
429
|
</div>
|
|
419
430
|
)}
|
|
420
431
|
</div>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Brand Navbar swizzle.
|
|
3
3
|
*
|
|
4
4
|
* Replaces Docusaurus's default Infima navbar with the Conduction
|
|
5
|
-
* top-navbar pattern: brand wordmark + nav links +
|
|
5
|
+
* top-navbar pattern: brand wordmark + nav links + Partners + Install.
|
|
6
6
|
* Navigation items come from themeConfig.navbar.items (configured by
|
|
7
7
|
* the consuming site); the chrome (typography, spacing, brand citation)
|
|
8
8
|
* stays locked in this component.
|
|
@@ -18,65 +18,24 @@
|
|
|
18
18
|
* sub-brand section keeps you in that section. Outside a sub-brand
|
|
19
19
|
* section it goes to the site root.
|
|
20
20
|
*
|
|
21
|
-
* Item types the brand navbar recognises (sites declare them in
|
|
22
|
-
* docusaurus.config.js → themeConfig.navbar.items):
|
|
23
|
-
*
|
|
24
|
-
* { type: 'doc', label, to } internal doc link
|
|
25
|
-
* { type: 'link', label, to | href } internal/external link
|
|
26
|
-
* { type: 'localeDropdown' } Docusaurus locale switcher
|
|
27
|
-
* { type: 'custom-github', href } icon-only GitHub mark
|
|
28
|
-
* { type: 'custom-apiDocs', label?, to } icon + "API Documentation"
|
|
29
|
-
* { type: 'custom-versionPill', prefix? } "Stable · v{x.y.z}" pill
|
|
30
|
-
* reads customFields.appVersion;
|
|
31
|
-
* hidden when no version
|
|
32
|
-
*
|
|
33
|
-
* The `custom-` prefix is required so Docusaurus's themeConfig schema
|
|
34
|
-
* validator passes (`@docusaurus/theme-classic` rejects unknown bare
|
|
35
|
-
* type names). The swizzle below accepts both the prefixed and the
|
|
36
|
-
* bare names so 2.7.0-beta.1 sites that wired the bare names keep
|
|
37
|
-
* working after the upgrade.
|
|
38
|
-
*
|
|
39
|
-
* The pill prefix defaults to "Stable" but can be overridden per site
|
|
40
|
-
* (e.g. prefix="Beta" while on a pre-1.0 release line).
|
|
41
|
-
*
|
|
42
21
|
* Mirrors preview/components/top-navbar.html in the design-system kit.
|
|
43
22
|
*/
|
|
44
23
|
|
|
45
24
|
import React from 'react';
|
|
46
25
|
import Link from '@docusaurus/Link';
|
|
47
26
|
import {useLocation} from '@docusaurus/router';
|
|
48
|
-
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
49
27
|
import {useThemeConfig} from '@docusaurus/theme-common';
|
|
50
28
|
import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
|
|
51
|
-
import {brandFor
|
|
52
|
-
import {ICONS} from '../../components/primitives/icons';
|
|
29
|
+
import {brandFor} from '../brand.jsx';
|
|
53
30
|
import styles from './styles.module.css';
|
|
54
31
|
|
|
55
32
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* with `custom-` bypass schema validation and are passed through to
|
|
61
|
-
* the theme as-is. The brand Navbar then dispatches on them below.
|
|
62
|
-
*
|
|
63
|
-
* Sites may also use the bare names (`github`, `apiDocs`,
|
|
64
|
-
* `versionPill`) — they render identically here but Docusaurus will
|
|
65
|
-
* reject the config at load time. Accept both forms so the migration
|
|
66
|
-
* from 2.7.0-beta.1 to .beta.2 doesn't break sites that already
|
|
67
|
-
* configured the bare names.
|
|
68
|
-
*/
|
|
69
|
-
function typeIs(item, kind) {
|
|
70
|
-
return item.type === kind || item.type === 'custom-' + kind;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Render a single navbar item. The brand navbar supports a small
|
|
75
|
-
* subset of Docusaurus item types plus the three brand-specific types
|
|
76
|
-
* (github, apiDocs, versionPill); everything else falls back to a
|
|
77
|
-
* plain link for forward-compatibility.
|
|
33
|
+
* Render a single navbar item. The brand top-navbar supports a small
|
|
34
|
+
* subset of Docusaurus item types (link, locale dropdown, optional
|
|
35
|
+
* primary CTA via items[].cta = true). Everything else falls back to
|
|
36
|
+
* a plain link for forward-compatibility.
|
|
78
37
|
*/
|
|
79
|
-
function NavItem({item, location
|
|
38
|
+
function NavItem({item, location}) {
|
|
80
39
|
if (item.type === 'localeDropdown') {
|
|
81
40
|
return (
|
|
82
41
|
<div className={styles.localeWrapper}>
|
|
@@ -85,61 +44,6 @@ function NavItem({item, location, appVersion}) {
|
|
|
85
44
|
);
|
|
86
45
|
}
|
|
87
46
|
|
|
88
|
-
/* GitHub: icon-only link with an accessible label. The aria-label
|
|
89
|
-
gives screen-readers + browser tooltips a name without rendering
|
|
90
|
-
a visible text label in the navbar. */
|
|
91
|
-
if (typeIs(item, 'github')) {
|
|
92
|
-
return (
|
|
93
|
-
<a
|
|
94
|
-
href={item.href || 'https://github.com/ConductionNL'}
|
|
95
|
-
className={styles.iconLink}
|
|
96
|
-
target="_blank"
|
|
97
|
-
rel="noopener noreferrer"
|
|
98
|
-
aria-label={item['aria-label'] || 'GitHub repository'}
|
|
99
|
-
title="GitHub"
|
|
100
|
-
>
|
|
101
|
-
<span className={styles.iconGlyph} aria-hidden="true">{ICONS.github}</span>
|
|
102
|
-
</a>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/* API Documentation: icon + label link. Target defaults to /api
|
|
107
|
-
(the Redocusaurus mount point used by every Conduction docs site).
|
|
108
|
-
Sites can override via `to` or `href`. */
|
|
109
|
-
if (typeIs(item, 'apiDocs')) {
|
|
110
|
-
const label = item.label || 'API Documentation';
|
|
111
|
-
const to = item.to || '/api';
|
|
112
|
-
const href = item.href;
|
|
113
|
-
const isActive = !href && (location?.pathname === to ||
|
|
114
|
-
location?.pathname?.startsWith(to + '/'));
|
|
115
|
-
const className = `${styles.link} ${styles.iconLabelLink} ${isActive ? styles.linkActive : ''}`;
|
|
116
|
-
const content = (
|
|
117
|
-
<>
|
|
118
|
-
<span className={styles.iconGlyph} aria-hidden="true">{ICONS.apiDocs}</span>
|
|
119
|
-
{label}
|
|
120
|
-
</>
|
|
121
|
-
);
|
|
122
|
-
if (href) {
|
|
123
|
-
return <a href={href} className={className} target={href.startsWith('http') ? '_blank' : undefined} rel={href.startsWith('http') ? 'noopener noreferrer' : undefined}>{content}</a>;
|
|
124
|
-
}
|
|
125
|
-
return <Link to={to} className={className}>{content}</Link>;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/* Version pill: code-typeface "Stable · v{version}" chip. Source is
|
|
129
|
-
customFields.appVersion (set by createConfig() from appinfo/info.xml
|
|
130
|
-
or package.json). Hidden when no version is available so sites
|
|
131
|
-
without an app version (Hydra, design-system itself) get a clean
|
|
132
|
-
navbar instead of an empty pill. */
|
|
133
|
-
if (typeIs(item, 'versionPill')) {
|
|
134
|
-
if (!appVersion) return null;
|
|
135
|
-
const prefix = item.prefix || 'Stable';
|
|
136
|
-
return (
|
|
137
|
-
<span className={styles.versionPill} title={`${prefix} · v${appVersion}`}>
|
|
138
|
-
{prefix} · v{appVersion}
|
|
139
|
-
</span>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
47
|
/* External link, no React-router prefetch */
|
|
144
48
|
if (item.href && !item.to) {
|
|
145
49
|
const isCta = item.cta === true;
|
|
@@ -179,57 +83,20 @@ function NavItem({item, location, appVersion}) {
|
|
|
179
83
|
|
|
180
84
|
export default function Navbar() {
|
|
181
85
|
const {navbar} = useThemeConfig();
|
|
182
|
-
const {siteConfig} = useDocusaurusContext();
|
|
183
|
-
const appVersion = siteConfig?.customFields?.appVersion;
|
|
184
86
|
const location = useLocation();
|
|
185
87
|
const items = navbar.items || [];
|
|
186
88
|
const brand = brandFor(location.pathname, navbar.title);
|
|
187
|
-
|
|
188
|
-
/* Wordmark resolution order:
|
|
189
|
-
1. ConNext / Common Ground sub-brand → custom JSX (Con<Next>, …)
|
|
190
|
-
2. Conduction product app (Open*, Docu*, My*, …) → prefix-light
|
|
191
|
-
treatment: cobalt-400 prefix + blue-cobalt rest, matching the
|
|
192
|
-
preview/apps.html convention.
|
|
193
|
-
3. Plain text title (single-word wordmark or unrecognised prefix). */
|
|
194
|
-
let wordmark;
|
|
195
|
-
if (brand) {
|
|
196
|
-
wordmark = brand.wordmark;
|
|
197
|
-
} else {
|
|
198
|
-
const split = productWordmark(navbar.title, navbar.brandPrefix);
|
|
199
|
-
wordmark = split ? (
|
|
200
|
-
<>
|
|
201
|
-
<span className={styles.wordmarkPrefix}>{split.prefix}</span>{split.rest}
|
|
202
|
-
</>
|
|
203
|
-
) : navbar.title;
|
|
204
|
-
}
|
|
205
|
-
|
|
89
|
+
const wordmark = brand ? brand.wordmark : navbar.title;
|
|
206
90
|
/* Path-match: keep the visitor in the sub-brand section on logo click.
|
|
207
91
|
Title-match (the site's primary brand IS a sub-brand): logo goes to
|
|
208
92
|
site root since the section IS the site. */
|
|
209
93
|
const homeHref = brand?.source === 'path' ? brand.home : '/';
|
|
210
94
|
|
|
211
|
-
/* App icon. The brand rule is that every product navbar shows the
|
|
212
|
-
app's hex-glyph next to the wordmark. The icon is sourced from
|
|
213
|
-
navbar.logo (createConfig defaults it to img/logo.svg, which every
|
|
214
|
-
Conduction docs site ships under static/img/). Sites can opt the
|
|
215
|
-
icon out by passing `logo: null` in their navbar config. */
|
|
216
|
-
const logoSrc = navbar.logo?.src;
|
|
217
|
-
const logoAlt = navbar.logo?.alt || `${navbar.title} avatar`;
|
|
218
|
-
|
|
219
95
|
/* Split into "left links" (regular nav) and "right CTAs" (locale,
|
|
220
|
-
external links, install button
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
mirroring the docs-shell mock. */
|
|
225
|
-
const RIGHT_TYPES = new Set([
|
|
226
|
-
'localeDropdown',
|
|
227
|
-
'github', 'custom-github',
|
|
228
|
-
'apiDocs', 'custom-apiDocs',
|
|
229
|
-
'versionPill', 'custom-versionPill',
|
|
230
|
-
]);
|
|
231
|
-
const leftItems = items.filter(i => i.position !== 'right' && !RIGHT_TYPES.has(i.type));
|
|
232
|
-
const rightItems = items.filter(i => i.position === 'right' || RIGHT_TYPES.has(i.type));
|
|
96
|
+
external links, install button). The brand pattern groups them
|
|
97
|
+
this way; consumers control order via item.position. */
|
|
98
|
+
const leftItems = items.filter(i => i.position !== 'right' && i.type !== 'localeDropdown');
|
|
99
|
+
const rightItems = items.filter(i => i.position === 'right' || i.type === 'localeDropdown');
|
|
233
100
|
|
|
234
101
|
return (
|
|
235
102
|
/* `navbar` (Docusaurus's framework class) is added alongside the
|
|
@@ -241,26 +108,17 @@ export default function Navbar() {
|
|
|
241
108
|
<nav className={`navbar ${styles.nav}`} role="navigation" aria-label="Main">
|
|
242
109
|
<div className={styles.left}>
|
|
243
110
|
<Link to={homeHref} className={styles.wordmark}>
|
|
244
|
-
{
|
|
245
|
-
<img
|
|
246
|
-
src={logoSrc}
|
|
247
|
-
alt={logoAlt}
|
|
248
|
-
className={styles.wordmarkIcon}
|
|
249
|
-
width="32"
|
|
250
|
-
height="32"
|
|
251
|
-
/>
|
|
252
|
-
)}
|
|
253
|
-
<span className={styles.wordmarkText}>{wordmark}</span>
|
|
111
|
+
{wordmark}
|
|
254
112
|
</Link>
|
|
255
113
|
<div className={styles.links}>
|
|
256
114
|
{leftItems.map((item, i) => (
|
|
257
|
-
<NavItem key={i} item={item} location={location}
|
|
115
|
+
<NavItem key={i} item={item} location={location} />
|
|
258
116
|
))}
|
|
259
117
|
</div>
|
|
260
118
|
</div>
|
|
261
119
|
<div className={styles.ctas}>
|
|
262
120
|
{rightItems.map((item, i) => (
|
|
263
|
-
<NavItem key={i} item={item} location={location}
|
|
121
|
+
<NavItem key={i} item={item} location={location} />
|
|
264
122
|
))}
|
|
265
123
|
</div>
|
|
266
124
|
</nav>
|
|
@@ -24,9 +24,6 @@
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
.wordmark {
|
|
27
|
-
display: inline-flex;
|
|
28
|
-
align-items: center;
|
|
29
|
-
gap: 10px;
|
|
30
27
|
font-size: 22px;
|
|
31
28
|
font-weight: 700;
|
|
32
29
|
letter-spacing: -0.02em;
|
|
@@ -35,33 +32,6 @@
|
|
|
35
32
|
}
|
|
36
33
|
.wordmark:hover { color: var(--c-blue-cobalt); text-decoration: none; }
|
|
37
34
|
|
|
38
|
-
/* App-glyph hex next to the wordmark. Every product navbar shows the
|
|
39
|
-
app's icon (sourced from navbar.logo); sized to match the wordmark's
|
|
40
|
-
cap height so the two land on the same baseline. */
|
|
41
|
-
.wordmarkIcon {
|
|
42
|
-
width: 32px;
|
|
43
|
-
height: 32px;
|
|
44
|
-
display: block;
|
|
45
|
-
flex-shrink: 0;
|
|
46
|
-
object-fit: contain;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/* Wordmark text wrapper; gives us a single hook for tweaks like
|
|
50
|
-
line-height alignment without touching the parent flex container. */
|
|
51
|
-
.wordmarkText {
|
|
52
|
-
display: inline-block;
|
|
53
|
-
line-height: 1;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/* "Light" prefix syllable for product wordmarks. Mirrors
|
|
57
|
-
preview/apps.html .lockup .word .light: cobalt-400 + regular weight,
|
|
58
|
-
so "OpenRegister" reads as muted-"Open" + bold-"Register" and the
|
|
59
|
-
eye lands on the noun, not the brand prefix. */
|
|
60
|
-
.wordmarkPrefix {
|
|
61
|
-
color: var(--c-cobalt-400);
|
|
62
|
-
font-weight: 400;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
35
|
.links {
|
|
66
36
|
display: flex;
|
|
67
37
|
gap: 28px;
|
|
@@ -130,69 +100,6 @@
|
|
|
130
100
|
color: var(--c-orange-knvb);
|
|
131
101
|
}
|
|
132
102
|
|
|
133
|
-
/* Inline SVG slot used by `github` and `apiDocs` items. The icon
|
|
134
|
-
inherits the link's font-size + colour so a single `currentColor`
|
|
135
|
-
path stays on brand whether the link is active, hovered, or idle. */
|
|
136
|
-
.iconGlyph {
|
|
137
|
-
display: inline-flex;
|
|
138
|
-
align-items: center;
|
|
139
|
-
justify-content: center;
|
|
140
|
-
line-height: 1;
|
|
141
|
-
}
|
|
142
|
-
.iconGlyph :global(svg) {
|
|
143
|
-
width: 1em;
|
|
144
|
-
height: 1em;
|
|
145
|
-
display: block;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/* Icon-only navbar link. Used by the github item; no visible label,
|
|
149
|
-
the aria-label gives it a name. Slightly larger icon (18px) than
|
|
150
|
-
text links so it reads as a fixed glyph rather than letterform. */
|
|
151
|
-
.iconLink {
|
|
152
|
-
display: inline-flex;
|
|
153
|
-
align-items: center;
|
|
154
|
-
justify-content: center;
|
|
155
|
-
width: 34px;
|
|
156
|
-
height: 34px;
|
|
157
|
-
border-radius: var(--radius-sm);
|
|
158
|
-
color: var(--c-cobalt-700);
|
|
159
|
-
font-size: 18px;
|
|
160
|
-
text-decoration: none;
|
|
161
|
-
transition: color 160ms ease, background 160ms ease;
|
|
162
|
-
}
|
|
163
|
-
.iconLink:hover {
|
|
164
|
-
color: var(--c-blue-cobalt);
|
|
165
|
-
background: var(--c-cobalt-50);
|
|
166
|
-
text-decoration: none;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/* Icon + label link. Used by the apiDocs item. Same typography as
|
|
170
|
-
plain .link but with an inline icon before the label, gap matches
|
|
171
|
-
the brand button-icon spacing. */
|
|
172
|
-
.iconLabelLink {
|
|
173
|
-
display: inline-flex;
|
|
174
|
-
align-items: center;
|
|
175
|
-
gap: 8px;
|
|
176
|
-
font-size: 14px;
|
|
177
|
-
}
|
|
178
|
-
.iconLabelLink .iconGlyph {
|
|
179
|
-
font-size: 16px;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/* Stable-version pill. Code typeface, cobalt-50 fill, cobalt-400 text,
|
|
183
|
-
pill-radius. Source is customFields.appVersion via createConfig();
|
|
184
|
-
when undefined the pill isn't rendered at all (see NavItem). */
|
|
185
|
-
.versionPill {
|
|
186
|
-
font-family: var(--conduction-typography-font-family-code);
|
|
187
|
-
font-size: 11px;
|
|
188
|
-
letter-spacing: 0.04em;
|
|
189
|
-
color: var(--c-cobalt-400);
|
|
190
|
-
background: var(--c-cobalt-50);
|
|
191
|
-
border-radius: var(--radius-pill);
|
|
192
|
-
padding: 4px 10px;
|
|
193
|
-
white-space: nowrap;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
103
|
/* Responsive collapse, simplified for v1.
|
|
197
104
|
TODO: hamburger menu like the design-system mock. */
|
|
198
105
|
@media (max-width: 900px) {
|
package/src/theme/brand.jsx
CHANGED
|
@@ -61,50 +61,3 @@ export function brandFor(pathname, title) {
|
|
|
61
61
|
}
|
|
62
62
|
return null;
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Conduction product-app wordmark patterns. The brand convention,
|
|
67
|
-
* codified in preview/apps.html, is to render the prefix syllable in
|
|
68
|
-
* cobalt-400 / regular weight and the rest in blue-cobalt / bold:
|
|
69
|
-
*
|
|
70
|
-
* <span class="light">Open</span>Register
|
|
71
|
-
* <span class="light">Docu</span>Desk
|
|
72
|
-
* <span class="light">My</span>Dash
|
|
73
|
-
*
|
|
74
|
-
* This list covers the prefixes used across the Conduction fleet.
|
|
75
|
-
* Sites whose wordmark doesn't start with one of these (e.g. Shillinq,
|
|
76
|
-
* Decidesk) keep the whole wordmark in blue-cobalt unless they pass
|
|
77
|
-
* an explicit `navbar.brandPrefix` to override.
|
|
78
|
-
*/
|
|
79
|
-
const PRODUCT_PREFIXES = [
|
|
80
|
-
'OpenAI', /* must precede 'Open' so 'OpenAI Bridge' splits as OpenAI/Bridge */
|
|
81
|
-
'Open',
|
|
82
|
-
'Docu',
|
|
83
|
-
'My',
|
|
84
|
-
'Pipe',
|
|
85
|
-
'Pro',
|
|
86
|
-
'Decid',
|
|
87
|
-
'Schol',
|
|
88
|
-
'Larping',
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Split a wordmark into (prefix, rest) so the prefix can render in the
|
|
93
|
-
* cobalt-400 "light" treatment. `brandPrefix` (optional) overrides
|
|
94
|
-
* auto-detection — sites with a non-standard wordmark pass it
|
|
95
|
-
* explicitly. Returns `null` when no split applies (single-word
|
|
96
|
-
* wordmarks or unrecognised prefixes); callers should render the
|
|
97
|
-
* wordmark as-is in that case.
|
|
98
|
-
*/
|
|
99
|
-
export function productWordmark(title, brandPrefix) {
|
|
100
|
-
if (!title) return null;
|
|
101
|
-
if (brandPrefix && title.startsWith(brandPrefix) && title.length > brandPrefix.length) {
|
|
102
|
-
return {prefix: brandPrefix, rest: title.slice(brandPrefix.length)};
|
|
103
|
-
}
|
|
104
|
-
for (const p of PRODUCT_PREFIXES) {
|
|
105
|
-
if (title.startsWith(p) && title.length > p.length) {
|
|
106
|
-
return {prefix: p, rest: title.slice(p.length)};
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
@@ -190,25 +190,6 @@
|
|
|
190
190
|
display: inline-block; margin-right: 6px;
|
|
191
191
|
vertical-align: middle;
|
|
192
192
|
}
|
|
193
|
-
/* "Conduction ♥ Nextcloud" citation: a vermillion-red heart between
|
|
194
|
-
the producer chain and the Nextcloud platform link. Sized to the
|
|
195
|
-
surrounding 11px caption with a small inline lift so the heart
|
|
196
|
-
centres on the cap-height. */
|
|
197
|
-
.canal-footer .brand .triad .heart {
|
|
198
|
-
width: 11px; height: 11px;
|
|
199
|
-
color: var(--c-red-vermillion);
|
|
200
|
-
display: inline-block;
|
|
201
|
-
vertical-align: -1px;
|
|
202
|
-
margin: 0 2px;
|
|
203
|
-
}
|
|
204
|
-
.canal-footer .brand .triad a.next-blue {
|
|
205
|
-
color: var(--c-nextcloud-blue);
|
|
206
|
-
text-decoration: none;
|
|
207
|
-
transition: color 140ms ease;
|
|
208
|
-
}
|
|
209
|
-
.canal-footer .brand .triad a.next-blue:hover {
|
|
210
|
-
color: var(--c-nextcloud-cyan);
|
|
211
|
-
}
|
|
212
193
|
.canal-footer .brand .socials {
|
|
213
194
|
display: flex; gap: 10px;
|
|
214
195
|
margin-top: 18px;
|
|
@@ -75,7 +75,7 @@ platform-diagram .workspace:not(.box-wrap):not(.workspace-corner-hex) {
|
|
|
75
75
|
width: 360px;
|
|
76
76
|
aspect-ratio: 1.7320508 / 2;
|
|
77
77
|
clip-path: var(--hex-pointy-top);
|
|
78
|
-
background: var(--
|
|
78
|
+
background: var(--c-nextcloud-blue);
|
|
79
79
|
display: flex; flex-direction: column;
|
|
80
80
|
align-items: center; justify-content: center;
|
|
81
81
|
color: white;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Brand icon set.
|
|
3
|
-
*
|
|
4
|
-
* Single source of truth for the SVG glyphs used in the brand chrome
|
|
5
|
-
* (Button, Navbar, hero CTAs). Each icon renders at 1em × 1em with
|
|
6
|
-
* `currentColor`, so it inherits the surrounding font size and colour
|
|
7
|
-
* automatically — pass it inside a Button or link and it lines up
|
|
8
|
-
* without extra styling.
|
|
9
|
-
*
|
|
10
|
-
* <Icon name="github" />
|
|
11
|
-
* <Button icon="github" variant="on-dark-tertiary" href={...}>View on GitHub</Button>
|
|
12
|
-
*
|
|
13
|
-
* Adding a new icon: define the React node in ICONS below, keep the
|
|
14
|
-
* viewBox at 0 0 24 24, and use stroke or fill — never both with
|
|
15
|
-
* different colours, so currentColor stays the single ink.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import React from 'react';
|
|
19
|
-
|
|
20
|
-
export const ICONS = {
|
|
21
|
-
/* Official GitHub mark, simplified to a single filled path so it
|
|
22
|
-
reads cleanly at 14–20px. Source: github.com/logos. */
|
|
23
|
-
github: (
|
|
24
|
-
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
25
|
-
<path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.57.1.78-.25.78-.55v-2.16c-3.2.69-3.87-1.36-3.87-1.36-.52-1.33-1.27-1.69-1.27-1.69-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.68 1.25 3.34.95.1-.74.4-1.25.73-1.54-2.55-.29-5.23-1.28-5.23-5.69 0-1.26.45-2.28 1.18-3.09-.12-.29-.51-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11 11 0 015.8 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.11 3.05.74.81 1.18 1.83 1.18 3.09 0 4.42-2.69 5.4-5.25 5.68.41.36.78 1.06.78 2.13v3.16c0 .3.21.66.79.55C20.21 21.39 23.5 17.08 23.5 12 23.5 5.65 18.35.5 12 .5z"/>
|
|
26
|
-
</svg>
|
|
27
|
-
),
|
|
28
|
-
|
|
29
|
-
/* API / OpenAPI reference. A stylised open book with a small
|
|
30
|
-
keyhole, matches the Redocusaurus reference mock at
|
|
31
|
-
preview/product-pages/api-reference.html. */
|
|
32
|
-
apiDocs: (
|
|
33
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
34
|
-
<path d="M4 4h6a2 2 0 012 2v14a2 2 0 00-2-2H4z"/>
|
|
35
|
-
<path d="M20 4h-6a2 2 0 00-2 2v14a2 2 0 012-2h6z"/>
|
|
36
|
-
<path d="M8 9h2M8 13h2M16 9h-2M16 13h-2"/>
|
|
37
|
-
</svg>
|
|
38
|
-
),
|
|
39
|
-
|
|
40
|
-
/* Generic right arrow used by ghost CTAs. Wrapped in this set so the
|
|
41
|
-
hero CTA can compose `View on GitHub` + `→` with consistent inline
|
|
42
|
-
metrics on any font-size. */
|
|
43
|
-
arrowRight: (
|
|
44
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
45
|
-
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
46
|
-
<polyline points="13 6 19 12 13 18"/>
|
|
47
|
-
</svg>
|
|
48
|
-
),
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Render a brand icon by name (string key from ICONS). Sized 1em × 1em
|
|
53
|
-
* via inline style; pass extra CSS via `className`. If the caller wants
|
|
54
|
-
* a custom icon, they can render any React node directly — the helper
|
|
55
|
-
* is just a convenience.
|
|
56
|
-
*/
|
|
57
|
-
export default function Icon({name, className, style}) {
|
|
58
|
-
const node = ICONS[name];
|
|
59
|
-
if (!node) return null;
|
|
60
|
-
return React.cloneElement(node, {
|
|
61
|
-
className,
|
|
62
|
-
style: {width: '1em', height: '1em', display: 'inline-block', flexShrink: 0, ...style},
|
|
63
|
-
});
|
|
64
|
-
}
|