@conduction/docusaurus-preset 2.7.0-beta.2 → 2.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/docusaurus-preset",
3
- "version": "2.7.0-beta.2",
3
+ "version": "2.7.0",
4
4
  "scripts": {
5
5
  "prepack": "node scripts/prepack-bundle-css.js"
6
6
  },
@@ -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
- * Brand variants used across hero, cta-banner, top-navbar, game-modal,
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: cobalt fill, white text
13
- * - secondary: white bg, cobalt-200 border, cobalt text
14
- * - ghost: plain text + arrow, for on-white surfaces
15
- * - on-dark-primary: primary inverted for use on cobalt CTA panels
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}>{body}</a>;
51
+ return <a href={href} className={composed} {...rest}>{children}</a>;
75
52
  }
76
- return <button type="button" className={composed} {...rest}>{body}</button>;
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/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,23 +39,8 @@ 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, repoUrl) => ({
43
+ const baseNavbar = (siteName) => ({
109
44
  title: siteName,
110
45
  logo: {
111
46
  alt: `${siteName} avatar`,
@@ -113,10 +48,8 @@ const baseNavbar = (siteName, repoUrl) => ({
113
48
  srcDark: 'img/logo-dark.svg',
114
49
  },
115
50
  items: [
116
- { type: 'versionPill', position: 'right' },
117
- { type: 'apiDocs', position: 'right' },
118
- { type: 'github', href: repoUrl || 'https://github.com/ConductionNL', position: 'right' },
119
51
  { type: 'localeDropdown', position: 'right' },
52
+ { href: 'https://github.com/ConductionNL', label: 'GitHub', position: 'right' },
120
53
  ],
121
54
  });
122
55
 
@@ -195,11 +128,6 @@ const baseFooter = () => ({
195
128
  * minigames (default true; set false to drop the brand canal-footer's
196
129
  * boat-sinking + kade-cyclist mini-games on product pages while
197
130
  * keeping the static skyline + canal decoration),
198
- * appVersion (explicit override for the navbar "Stable · v{x.y.z}"
199
- * pill; defaults to appinfo/info.xml then package.json — see
200
- * resolveAppVersion above),
201
- * repoUrl (target of the navbar GitHub icon; defaults to the
202
- * ConductionNL org root),
203
131
  * customCss[] (appended to brand.css), plugins[], presets,
204
132
  * i18n (overrides defaults)
205
133
  */
@@ -214,11 +142,6 @@ function createConfig(opts) {
214
142
  getClientModules(), so customCss carries site-specific CSS only. */
215
143
  const customCss = opts.customCss || [];
216
144
 
217
- /* App version drives the navbar "Stable · v{x.y.z}" pill (resolved
218
- once at config time). Pulled from appinfo/info.xml, then
219
- package.json — see resolveAppVersion above. */
220
- const appVersion = resolveAppVersion(opts);
221
-
222
145
  return {
223
146
  title: opts.title,
224
147
  tagline: opts.tagline || '',
@@ -230,17 +153,6 @@ function createConfig(opts) {
230
153
  organizationName: opts.organizationName || 'ConductionNL',
231
154
  projectName: opts.projectName || 'design-system',
232
155
 
233
- /* customFields is the canonical Docusaurus channel for build-time
234
- data the theme reads at runtime via useDocusaurusContext().
235
- appVersion drives the navbar "Stable · v{x.y.z}" pill; sites
236
- extend this by passing their own customFields in opts. */
237
- customFields: Object.assign(
238
- {
239
- ...(appVersion && {appVersion}),
240
- },
241
- opts.customFields || {}
242
- ),
243
-
244
156
  onBrokenLinks: 'warn',
245
157
  onBrokenMarkdownLinks: 'warn',
246
158
 
@@ -289,7 +201,7 @@ function createConfig(opts) {
289
201
  disableSwitch: false,
290
202
  respectPrefersColorScheme: true,
291
203
  },
292
- navbar: Object.assign(baseNavbar(opts.title, opts.repoUrl), opts.navbar || {}),
204
+ navbar: Object.assign(baseNavbar(opts.title), opts.navbar || {}),
293
205
  /* Per-property fallback so a site can override one slice of the
294
206
  footer (e.g. just `links`) and inherit the rest from the brand.
295
207
  Previously `opts.footer` replaced the whole footer wholesale,
@@ -323,6 +235,24 @@ function createConfig(opts) {
323
235
  built jointly with a partner (mydash + Sendent
324
236
  is the first case). */
325
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 || {},
326
256
  },
327
257
  opts.themeConfig || {}
328
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
- <span className="legal-links">
401
- <Link to="/privacy">Privacy</Link>
402
- <span className="sep">·</span>
403
- <Link to="/terms">Terms</Link>
404
- <span className="sep">·</span>
405
- <Link to="/iso">ISO</Link>
406
- </span>
407
- </div>
408
- <div className="right">
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 + locale + chrome.
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,41 +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: 'github', href } icon-only GitHub mark
28
- * { type: 'apiDocs', label?, to } icon + "API Documentation"
29
- * { type: 'versionPill', prefix? } "Stable · v{x.y.z}" pill
30
- * reads customFields.appVersion;
31
- * hidden when no version
32
- *
33
- * The pill prefix defaults to "Stable" but can be overridden per site
34
- * (e.g. prefix="Beta" while on a pre-1.0 release line).
35
- *
36
21
  * Mirrors preview/components/top-navbar.html in the design-system kit.
37
22
  */
38
23
 
39
24
  import React from 'react';
40
25
  import Link from '@docusaurus/Link';
41
26
  import {useLocation} from '@docusaurus/router';
42
- import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
43
27
  import {useThemeConfig} from '@docusaurus/theme-common';
44
28
  import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
45
- import {brandFor, productWordmark} from '../brand.jsx';
46
- import {ICONS} from '../../components/primitives/icons';
29
+ import {brandFor} from '../brand.jsx';
47
30
  import styles from './styles.module.css';
48
31
 
49
32
  /**
50
- * Render a single navbar item. The brand navbar supports a small
51
- * subset of Docusaurus item types plus the three brand-specific types
52
- * (github, apiDocs, versionPill); everything else falls back to a
53
- * 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.
54
37
  */
55
- function NavItem({item, location, appVersion}) {
38
+ function NavItem({item, location}) {
56
39
  if (item.type === 'localeDropdown') {
57
40
  return (
58
41
  <div className={styles.localeWrapper}>
@@ -61,61 +44,6 @@ function NavItem({item, location, appVersion}) {
61
44
  );
62
45
  }
63
46
 
64
- /* GitHub: icon-only link with an accessible label. The aria-label
65
- gives screen-readers + browser tooltips a name without rendering
66
- a visible text label in the navbar. */
67
- if (item.type === 'github') {
68
- return (
69
- <a
70
- href={item.href || 'https://github.com/ConductionNL'}
71
- className={styles.iconLink}
72
- target="_blank"
73
- rel="noopener noreferrer"
74
- aria-label={item['aria-label'] || 'GitHub repository'}
75
- title="GitHub"
76
- >
77
- <span className={styles.iconGlyph} aria-hidden="true">{ICONS.github}</span>
78
- </a>
79
- );
80
- }
81
-
82
- /* API Documentation: icon + label link. Target defaults to /api
83
- (the Redocusaurus mount point used by every Conduction docs site).
84
- Sites can override via `to` or `href`. */
85
- if (item.type === 'apiDocs') {
86
- const label = item.label || 'API Documentation';
87
- const to = item.to || '/api';
88
- const href = item.href;
89
- const isActive = !href && (location?.pathname === to ||
90
- location?.pathname?.startsWith(to + '/'));
91
- const className = `${styles.link} ${styles.iconLabelLink} ${isActive ? styles.linkActive : ''}`;
92
- const content = (
93
- <>
94
- <span className={styles.iconGlyph} aria-hidden="true">{ICONS.apiDocs}</span>
95
- {label}
96
- </>
97
- );
98
- if (href) {
99
- return <a href={href} className={className} target={href.startsWith('http') ? '_blank' : undefined} rel={href.startsWith('http') ? 'noopener noreferrer' : undefined}>{content}</a>;
100
- }
101
- return <Link to={to} className={className}>{content}</Link>;
102
- }
103
-
104
- /* Version pill: code-typeface "Stable · v{version}" chip. Source is
105
- customFields.appVersion (set by createConfig() from appinfo/info.xml
106
- or package.json). Hidden when no version is available so sites
107
- without an app version (Hydra, design-system itself) get a clean
108
- navbar instead of an empty pill. */
109
- if (item.type === 'versionPill') {
110
- if (!appVersion) return null;
111
- const prefix = item.prefix || 'Stable';
112
- return (
113
- <span className={styles.versionPill} title={`${prefix} · v${appVersion}`}>
114
- {prefix} · v{appVersion}
115
- </span>
116
- );
117
- }
118
-
119
47
  /* External link, no React-router prefetch */
120
48
  if (item.href && !item.to) {
121
49
  const isCta = item.cta === true;
@@ -155,52 +83,20 @@ function NavItem({item, location, appVersion}) {
155
83
 
156
84
  export default function Navbar() {
157
85
  const {navbar} = useThemeConfig();
158
- const {siteConfig} = useDocusaurusContext();
159
- const appVersion = siteConfig?.customFields?.appVersion;
160
86
  const location = useLocation();
161
87
  const items = navbar.items || [];
162
88
  const brand = brandFor(location.pathname, navbar.title);
163
-
164
- /* Wordmark resolution order:
165
- 1. ConNext / Common Ground sub-brand → custom JSX (Con<Next>, …)
166
- 2. Conduction product app (Open*, Docu*, My*, …) → prefix-light
167
- treatment: cobalt-400 prefix + blue-cobalt rest, matching the
168
- preview/apps.html convention.
169
- 3. Plain text title (single-word wordmark or unrecognised prefix). */
170
- let wordmark;
171
- if (brand) {
172
- wordmark = brand.wordmark;
173
- } else {
174
- const split = productWordmark(navbar.title, navbar.brandPrefix);
175
- wordmark = split ? (
176
- <>
177
- <span className={styles.wordmarkPrefix}>{split.prefix}</span>{split.rest}
178
- </>
179
- ) : navbar.title;
180
- }
181
-
89
+ const wordmark = brand ? brand.wordmark : navbar.title;
182
90
  /* Path-match: keep the visitor in the sub-brand section on logo click.
183
91
  Title-match (the site's primary brand IS a sub-brand): logo goes to
184
92
  site root since the section IS the site. */
185
93
  const homeHref = brand?.source === 'path' ? brand.home : '/';
186
94
 
187
- /* App icon. The brand rule is that every product navbar shows the
188
- app's hex-glyph next to the wordmark. The icon is sourced from
189
- navbar.logo (createConfig defaults it to img/logo.svg, which every
190
- Conduction docs site ships under static/img/). Sites can opt the
191
- icon out by passing `logo: null` in their navbar config. */
192
- const logoSrc = navbar.logo?.src;
193
- const logoAlt = navbar.logo?.alt || `${navbar.title} avatar`;
194
-
195
95
  /* Split into "left links" (regular nav) and "right CTAs" (locale,
196
- external links, install button, GitHub icon, version pill).
197
- Items default to the left unless they explicitly carry
198
- position="right" but the three brand-specific item types
199
- (github, apiDocs, versionPill) live on the right by convention,
200
- mirroring the docs-shell mock. */
201
- const RIGHT_TYPES = new Set(['localeDropdown', 'github', 'apiDocs', 'versionPill']);
202
- const leftItems = items.filter(i => i.position !== 'right' && !RIGHT_TYPES.has(i.type));
203
- 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');
204
100
 
205
101
  return (
206
102
  /* `navbar` (Docusaurus's framework class) is added alongside the
@@ -212,26 +108,17 @@ export default function Navbar() {
212
108
  <nav className={`navbar ${styles.nav}`} role="navigation" aria-label="Main">
213
109
  <div className={styles.left}>
214
110
  <Link to={homeHref} className={styles.wordmark}>
215
- {logoSrc && (
216
- <img
217
- src={logoSrc}
218
- alt={logoAlt}
219
- className={styles.wordmarkIcon}
220
- width="32"
221
- height="32"
222
- />
223
- )}
224
- <span className={styles.wordmarkText}>{wordmark}</span>
111
+ {wordmark}
225
112
  </Link>
226
113
  <div className={styles.links}>
227
114
  {leftItems.map((item, i) => (
228
- <NavItem key={i} item={item} location={location} appVersion={appVersion} />
115
+ <NavItem key={i} item={item} location={location} />
229
116
  ))}
230
117
  </div>
231
118
  </div>
232
119
  <div className={styles.ctas}>
233
120
  {rightItems.map((item, i) => (
234
- <NavItem key={i} item={item} location={location} appVersion={appVersion} />
121
+ <NavItem key={i} item={item} location={location} />
235
122
  ))}
236
123
  </div>
237
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) {
@@ -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;
@@ -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
- }