@conduction/docusaurus-preset 3.16.1 → 3.18.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 +1 -1
- package/src/components/ContentCard/ContentCard.jsx +27 -2
- package/src/components/ContentTypeFilter/ContentTypeFilter.jsx +8 -2
- package/src/components/FeaturedCard/FeaturedCard.jsx +41 -11
- package/src/components/FeaturedCard/FeaturedCard.module.css +14 -0
- package/src/components/GameModal/GameModal.jsx +37 -11
- package/src/components/ModuleCard/ModuleCard.jsx +49 -4
- package/src/components/NextSteps/NextSteps.jsx +15 -4
- package/src/components/PartnersFor/PartnersFor.jsx +55 -0
- package/src/components/index.js +1 -0
- package/src/theme/Footer/index.jsx +25 -9
- package/src/theme/Navbar/index.jsx +7 -3
package/package.json
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
39
|
import React from 'react';
|
|
40
|
+
import {translate} from '@docusaurus/Translate';
|
|
40
41
|
import HexThumbnail from '../primitives/HexThumbnail';
|
|
41
42
|
import Pill from '../primitives/Pill';
|
|
42
43
|
import {CONTENT_TYPE_LABELS} from '../ContentTypeFilter/contentTypes';
|
|
@@ -48,7 +49,14 @@ function readOrWatch(_contentType, minutes) {
|
|
|
48
49
|
/* No verb appended — the type line below the date (BLOG / TUTORIAL /
|
|
49
50
|
WEBINAR) already signals read vs. watch, and the extra word kept
|
|
50
51
|
wrapping to a second line on narrow card columns. */
|
|
51
|
-
return
|
|
52
|
+
return translate(
|
|
53
|
+
{
|
|
54
|
+
id: 'preset.contentCard.minLabel',
|
|
55
|
+
message: '{minutes} min',
|
|
56
|
+
description: 'Duration label under a content card (just minutes, no verb).',
|
|
57
|
+
},
|
|
58
|
+
{minutes},
|
|
59
|
+
);
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
function formatDate(date, locale = 'nl') {
|
|
@@ -168,7 +176,24 @@ export default function ContentCard({
|
|
|
168
176
|
|
|
169
177
|
{audience.length > 0 && (
|
|
170
178
|
<div className={styles.audienceLine}>
|
|
171
|
-
|
|
179
|
+
{translate(
|
|
180
|
+
{
|
|
181
|
+
id: 'preset.contentCard.audienceFor',
|
|
182
|
+
message: 'For: {list}',
|
|
183
|
+
description: 'Audience line under a content card. {list} is a comma-separated audience list.',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
list: audience
|
|
187
|
+
.map((a) => translate(
|
|
188
|
+
{
|
|
189
|
+
id: `preset.audience.short.${a}`,
|
|
190
|
+
message: AUDIENCE_SHORT_LABELS[a] || a,
|
|
191
|
+
description: `Short audience label used in the "For: ..." line. Slug: ${a}.`,
|
|
192
|
+
},
|
|
193
|
+
))
|
|
194
|
+
.join(', '),
|
|
195
|
+
},
|
|
196
|
+
)}
|
|
172
197
|
</div>
|
|
173
198
|
)}
|
|
174
199
|
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
*/
|
|
52
52
|
|
|
53
53
|
import React from 'react';
|
|
54
|
+
import {translate} from '@docusaurus/Translate';
|
|
54
55
|
import {
|
|
55
56
|
CONTENT_TYPES,
|
|
56
57
|
CONTENT_TYPE_PLURAL_LABELS,
|
|
@@ -64,12 +65,17 @@ export default function ContentTypeFilter({
|
|
|
64
65
|
onChange,
|
|
65
66
|
hrefForType,
|
|
66
67
|
counts,
|
|
67
|
-
allLabel
|
|
68
|
+
allLabel,
|
|
68
69
|
allCount,
|
|
69
70
|
types = CONTENT_TYPES,
|
|
70
71
|
labels,
|
|
71
72
|
className,
|
|
72
73
|
}) {
|
|
74
|
+
const resolvedAllLabel = allLabel ?? translate({
|
|
75
|
+
id: 'preset.contentTypeFilter.all',
|
|
76
|
+
message: 'Everything',
|
|
77
|
+
description: 'Default label on the "all" chip of the content type filter when no allLabel is passed',
|
|
78
|
+
});
|
|
73
79
|
const isLinkMode = typeof hrefForType === 'function';
|
|
74
80
|
const labelFor = (key) => {
|
|
75
81
|
if (labels && labels[key]) return labels[key];
|
|
@@ -116,7 +122,7 @@ export default function ContentTypeFilter({
|
|
|
116
122
|
|
|
117
123
|
return (
|
|
118
124
|
<div className={[styles.row, className].filter(Boolean).join(' ')}>
|
|
119
|
-
{renderChip(ALL,
|
|
125
|
+
{renderChip(ALL, resolvedAllLabel, allCount)}
|
|
120
126
|
{types.map((t) =>
|
|
121
127
|
renderChip(t, labelFor(t), counts && counts[t])
|
|
122
128
|
)}
|
|
@@ -27,16 +27,18 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import React from 'react';
|
|
30
|
+
import {translate} from '@docusaurus/Translate';
|
|
30
31
|
import HexThumbnail from '../primitives/HexThumbnail';
|
|
31
32
|
import HexBullet from '../primitives/HexBullet';
|
|
32
33
|
import AuthorByline from '../primitives/AuthorByline';
|
|
33
|
-
import {
|
|
34
|
+
import {AUDIENCE_SHORT_LABELS} from '../../data/audience';
|
|
34
35
|
import styles from './FeaturedCard.module.css';
|
|
35
36
|
|
|
36
37
|
function readOrWatch(contentType, minutes) {
|
|
37
38
|
if (!minutes) return null;
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
return contentType === 'webinar'
|
|
40
|
+
? translate({id: 'preset.featuredCard.minWatch', message: '{minutes} min watch', description: 'Duration label for video content'}, {minutes})
|
|
41
|
+
: translate({id: 'preset.featuredCard.minRead', message: '{minutes} min read', description: 'Duration label for text content'}, {minutes});
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
export default function FeaturedCard({
|
|
@@ -44,7 +46,7 @@ export default function FeaturedCard({
|
|
|
44
46
|
eyebrow,
|
|
45
47
|
title,
|
|
46
48
|
lede,
|
|
47
|
-
ctaLabel
|
|
49
|
+
ctaLabel,
|
|
48
50
|
author,
|
|
49
51
|
date,
|
|
50
52
|
dateLabel,
|
|
@@ -61,15 +63,39 @@ export default function FeaturedCard({
|
|
|
61
63
|
className,
|
|
62
64
|
}) {
|
|
63
65
|
const readWatch = readOrWatch(contentType, durationMinutes);
|
|
66
|
+
const resolvedCtaLabel = ctaLabel ?? translate({
|
|
67
|
+
id: 'preset.featuredCard.cta',
|
|
68
|
+
message: 'Read more',
|
|
69
|
+
description: 'Default CTA label on FeaturedCard when the consuming MDX does not pass ctaLabel',
|
|
70
|
+
});
|
|
64
71
|
const moduleLabel = moduleSlug
|
|
65
72
|
? (modulePosition && moduleTotalParts
|
|
66
|
-
?
|
|
67
|
-
: (modulePosition ?
|
|
73
|
+
? translate({id: 'preset.featuredCard.modulePartOf', message: 'Part {position} of {total}', description: 'Module-position label, e.g. "Part 2 of 4"'}, {position: modulePosition, total: moduleTotalParts})
|
|
74
|
+
: (modulePosition ? translate({id: 'preset.featuredCard.modulePart', message: 'Part {position}', description: 'Module-position label when total parts is unknown'}, {position: modulePosition}) : null))
|
|
68
75
|
: null;
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
/* Audience renders on its own "For: A, B, C" line below the meta bits,
|
|
77
|
+
matching ContentCard and ModuleCard. Each short label gets its own
|
|
78
|
+
translate() so consuming sites can localise the audience names
|
|
79
|
+
without forking the data file. */
|
|
80
|
+
const audienceLabels = audience
|
|
81
|
+
.map((a) => translate(
|
|
82
|
+
{
|
|
83
|
+
id: `preset.audience.short.${a}`,
|
|
84
|
+
message: AUDIENCE_SHORT_LABELS[a] || a,
|
|
85
|
+
description: `Short audience label used in the "For: ..." line. Slug: ${a}.`,
|
|
86
|
+
},
|
|
87
|
+
));
|
|
88
|
+
const audienceLine = audienceLabels.length > 0
|
|
89
|
+
? translate(
|
|
90
|
+
{
|
|
91
|
+
id: 'preset.featuredCard.audienceFor',
|
|
92
|
+
message: 'For: {list}',
|
|
93
|
+
description: 'Audience line under the FeaturedCard meta row. {list} is a comma-separated audience list.',
|
|
94
|
+
},
|
|
95
|
+
{list: audienceLabels.join(', ')},
|
|
96
|
+
)
|
|
71
97
|
: null;
|
|
72
|
-
const metaBits = [readWatch, moduleLabel, moduleSlug ? (moduleTitle || moduleSlug) : null
|
|
98
|
+
const metaBits = [readWatch, moduleLabel, moduleSlug ? (moduleTitle || moduleSlug) : null]
|
|
73
99
|
.filter(Boolean);
|
|
74
100
|
const Tag = href ? 'a' : 'div';
|
|
75
101
|
const composed = [styles.card, className].filter(Boolean).join(' ');
|
|
@@ -98,6 +124,10 @@ export default function FeaturedCard({
|
|
|
98
124
|
</div>
|
|
99
125
|
)}
|
|
100
126
|
|
|
127
|
+
{audienceLine && (
|
|
128
|
+
<div className={styles.audienceLine}>{audienceLine}</div>
|
|
129
|
+
)}
|
|
130
|
+
|
|
101
131
|
{(author || date) && (
|
|
102
132
|
<div className={styles.meta}>
|
|
103
133
|
<AuthorByline
|
|
@@ -112,9 +142,9 @@ export default function FeaturedCard({
|
|
|
112
142
|
</div>
|
|
113
143
|
)}
|
|
114
144
|
|
|
115
|
-
{
|
|
145
|
+
{resolvedCtaLabel && (
|
|
116
146
|
<span className={styles.cta}>
|
|
117
|
-
<span>{
|
|
147
|
+
<span>{resolvedCtaLabel}</span>
|
|
118
148
|
<span aria-hidden="true">→</span>
|
|
119
149
|
</span>
|
|
120
150
|
)}
|
|
@@ -70,6 +70,20 @@
|
|
|
70
70
|
.metaBits .metaBit { white-space: nowrap; }
|
|
71
71
|
.metaBits .metaSep { opacity: 0.6; }
|
|
72
72
|
|
|
73
|
+
/* "For: MKB, Overheid, Developers" — same line treatment as the meta
|
|
74
|
+
bits but its own row so the audience set never wraps into the
|
|
75
|
+
duration / module line. On-dark variant of the ContentCard
|
|
76
|
+
audienceLine. */
|
|
77
|
+
.audienceLine {
|
|
78
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
79
|
+
font-size: 12px;
|
|
80
|
+
letter-spacing: 0.04em;
|
|
81
|
+
color: var(--c-cobalt-200);
|
|
82
|
+
line-height: 1.4;
|
|
83
|
+
margin-top: calc(var(--space-3) * -1);
|
|
84
|
+
margin-bottom: var(--space-4);
|
|
85
|
+
}
|
|
86
|
+
|
|
73
87
|
.meta { margin-bottom: var(--space-5); }
|
|
74
88
|
|
|
75
89
|
.cta {
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
import React, {useEffect, useState, useCallback, useMemo} from 'react';
|
|
44
44
|
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
45
|
+
import Translate, {translate} from '@docusaurus/Translate';
|
|
45
46
|
import styles from './GameModal.module.css';
|
|
46
47
|
|
|
47
48
|
const STORAGE_KEY = 'conduction:minigames';
|
|
@@ -142,29 +143,43 @@ export default function GameModal({games = DEFAULT_GAMES, className}) {
|
|
|
142
143
|
|
|
143
144
|
if (!isBrowser || !open || !event) return null;
|
|
144
145
|
|
|
145
|
-
const eyebrow = event.won
|
|
146
|
-
|
|
146
|
+
const eyebrow = event.won
|
|
147
|
+
? translate({id: 'preset.gameModal.eyebrow.won', message: 'Mini-game complete', description: 'Eyebrow above the game-over heading when the player won'})
|
|
148
|
+
: translate({id: 'preset.gameModal.eyebrow.lost', message: 'Game over', description: 'Eyebrow above the game-over heading when the player lost'});
|
|
149
|
+
const title = event.title || (event.won
|
|
150
|
+
? translate({id: 'preset.gameModal.title.won', message: 'Nice run.', description: 'Default headline on the game-over modal when the player won'})
|
|
151
|
+
: translate({id: 'preset.gameModal.title.lost', message: "That's all of them.", description: 'Default headline on the game-over modal when the player lost'}));
|
|
147
152
|
const subtitle = event.subtitle ||
|
|
148
153
|
(event.won
|
|
149
|
-
? "You've cleared a hidden Conduction mini-game."
|
|
150
|
-
: "Try again any time, the rain doesn't stop.");
|
|
154
|
+
? translate({id: 'preset.gameModal.subtitle.won', message: "You've cleared a hidden Conduction mini-game.", description: 'Default subtitle on the game-over modal when the player won'})
|
|
155
|
+
: translate({id: 'preset.gameModal.subtitle.lost', message: "Try again any time, the rain doesn't stop.", description: 'Default subtitle on the game-over modal when the player lost'}));
|
|
151
156
|
|
|
152
157
|
return (
|
|
153
158
|
<div className={[styles.modal, className].filter(Boolean).join(' ')} role="dialog" aria-modal="true" aria-labelledby="gm-title">
|
|
154
159
|
<div className={styles.overlay} onClick={close} />
|
|
155
160
|
<div className={styles.panel}>
|
|
156
|
-
<button type="button" className={styles.close} onClick={close} aria-label=
|
|
161
|
+
<button type="button" className={styles.close} onClick={close} aria-label={translate({id: 'preset.gameModal.close', message: 'Close', description: 'Accessible label for the close (×) button on the game-over modal'})}>×</button>
|
|
157
162
|
<p className={styles.eyebrow}>{eyebrow}</p>
|
|
158
163
|
<h2 className={styles.title} id="gm-title">{title}</h2>
|
|
159
164
|
<p className={styles.subtitle}>{subtitle}</p>
|
|
160
165
|
|
|
161
166
|
{typeof event.score !== 'undefined' && (
|
|
162
|
-
<span className={styles.scorePill}>{event.summary ||
|
|
167
|
+
<span className={styles.scorePill}>{event.summary || translate({id: 'preset.gameModal.scorePill', message: 'score: {score}', description: 'Default score pill text. {score} is the numeric score.'}, {score: event.score})}</span>
|
|
163
168
|
)}
|
|
164
169
|
|
|
165
170
|
<div className={styles.progress}>
|
|
166
171
|
<div className={styles.progressLabel}>
|
|
167
|
-
<span
|
|
172
|
+
<span>
|
|
173
|
+
<Translate
|
|
174
|
+
id="preset.gameModal.progress.found"
|
|
175
|
+
description="Progress label below the game-over copy. {found} bolded count of games discovered; {total} is the total."
|
|
176
|
+
values={{
|
|
177
|
+
found: <strong>{foundCount}</strong>,
|
|
178
|
+
total: total,
|
|
179
|
+
}}>
|
|
180
|
+
{'{found} / {total} mini-games found'}
|
|
181
|
+
</Translate>
|
|
182
|
+
</span>
|
|
168
183
|
<span>{percent}%</span>
|
|
169
184
|
</div>
|
|
170
185
|
<div className={styles.progressBar}>
|
|
@@ -183,13 +198,24 @@ export default function GameModal({games = DEFAULT_GAMES, className}) {
|
|
|
183
198
|
|
|
184
199
|
<p className={styles.cta}>
|
|
185
200
|
{foundCount < total
|
|
186
|
-
?
|
|
187
|
-
|
|
201
|
+
? translate(
|
|
202
|
+
{
|
|
203
|
+
id: 'preset.gameModal.cta.remaining',
|
|
204
|
+
message: '{remaining, plural, one {# more game hidden somewhere. Keep clicking.} other {# more games hidden somewhere. Keep clicking.}}',
|
|
205
|
+
description: 'CTA text on the game-over modal when at least one game is still hidden. {remaining} is the count of games still hidden.',
|
|
206
|
+
},
|
|
207
|
+
{remaining: total - foundCount},
|
|
208
|
+
)
|
|
209
|
+
: translate({id: 'preset.gameModal.cta.allFound', message: 'All five found. You read the kit.', description: 'CTA text on the game-over modal when the player has discovered every mini-game.'})}
|
|
188
210
|
</p>
|
|
189
211
|
|
|
190
212
|
<div className={styles.actions}>
|
|
191
|
-
<button type="button" className={styles.btnSecondary} onClick={close}>
|
|
192
|
-
|
|
213
|
+
<button type="button" className={styles.btnSecondary} onClick={close}>
|
|
214
|
+
<Translate id="preset.gameModal.action.close" description="Close button label on the game-over modal">Close</Translate>
|
|
215
|
+
</button>
|
|
216
|
+
<button type="button" className={styles.btnPrimary} onClick={replay}>
|
|
217
|
+
<Translate id="preset.gameModal.action.replay" description="Replay button label on the game-over modal">Play again</Translate>
|
|
218
|
+
</button>
|
|
193
219
|
</div>
|
|
194
220
|
</div>
|
|
195
221
|
</div>
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import React from 'react';
|
|
27
|
+
import {translate} from '@docusaurus/Translate';
|
|
27
28
|
import HexThumbnail from '../primitives/HexThumbnail';
|
|
28
29
|
import {AUDIENCE_SHORT_LABELS} from '../../data/audience';
|
|
29
30
|
import {CONTENT_TYPE_PLURAL_LABELS} from '../ContentTypeFilter/contentTypes';
|
|
@@ -86,14 +87,41 @@ export default function ModuleCard({
|
|
|
86
87
|
the composite type summary below already signal whether this is
|
|
87
88
|
something the visitor reads or watches. Keeps the date line on a
|
|
88
89
|
single row in narrow columns. */
|
|
89
|
-
const totalLabel = typeof totalMinutes === 'number'
|
|
90
|
+
const totalLabel = typeof totalMinutes === 'number'
|
|
91
|
+
? translate(
|
|
92
|
+
{
|
|
93
|
+
id: 'preset.moduleCard.minLabel',
|
|
94
|
+
message: '{minutes} min',
|
|
95
|
+
description: 'Total duration label under a module card (just minutes, no verb).',
|
|
96
|
+
},
|
|
97
|
+
{minutes: totalMinutes},
|
|
98
|
+
)
|
|
99
|
+
: null;
|
|
90
100
|
const dateLabel = formatDate(latestDate, locale);
|
|
91
101
|
const dateReadLine = [dateLabel, totalLabel].filter(Boolean).join(' · ');
|
|
92
102
|
|
|
103
|
+
const moduleEyebrow = translate({
|
|
104
|
+
id: 'preset.moduleCard.moduleLabel',
|
|
105
|
+
message: 'MODULE',
|
|
106
|
+
description: 'All-caps eyebrow word in the module card type line.',
|
|
107
|
+
});
|
|
93
108
|
const partsLabel = typeof parts === 'number'
|
|
94
|
-
? (parts === 1
|
|
109
|
+
? (parts === 1
|
|
110
|
+
? translate({
|
|
111
|
+
id: 'preset.moduleCard.singlePart',
|
|
112
|
+
message: '1 PART',
|
|
113
|
+
description: 'Module-parts count when the module has exactly one part.',
|
|
114
|
+
})
|
|
115
|
+
: translate(
|
|
116
|
+
{
|
|
117
|
+
id: 'preset.moduleCard.multipleParts',
|
|
118
|
+
message: '{count} PARTS',
|
|
119
|
+
description: 'Module-parts count when the module has 2+ parts.',
|
|
120
|
+
},
|
|
121
|
+
{count: parts},
|
|
122
|
+
))
|
|
95
123
|
: null;
|
|
96
|
-
const typeLine = [
|
|
124
|
+
const typeLine = [moduleEyebrow, partsLabel].filter(Boolean).join(' · ');
|
|
97
125
|
|
|
98
126
|
/* Composite content-type summary on line 4. For a single-type
|
|
99
127
|
module ("Tutorials") this is one word; mixed modules read
|
|
@@ -141,7 +169,24 @@ export default function ModuleCard({
|
|
|
141
169
|
|
|
142
170
|
{audience.length > 0 && (
|
|
143
171
|
<div className={styles.audienceLine}>
|
|
144
|
-
|
|
172
|
+
{translate(
|
|
173
|
+
{
|
|
174
|
+
id: 'preset.moduleCard.audienceFor',
|
|
175
|
+
message: 'For: {list}',
|
|
176
|
+
description: 'Audience line under a module card. {list} is a comma-separated audience list.',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
list: audience
|
|
180
|
+
.map((a) => translate(
|
|
181
|
+
{
|
|
182
|
+
id: `preset.audience.short.${a}`,
|
|
183
|
+
message: AUDIENCE_SHORT_LABELS[a] || a,
|
|
184
|
+
description: `Short audience label used in the "For: ..." line. Slug: ${a}.`,
|
|
185
|
+
},
|
|
186
|
+
))
|
|
187
|
+
.join(', '),
|
|
188
|
+
},
|
|
189
|
+
)}
|
|
145
190
|
</div>
|
|
146
191
|
)}
|
|
147
192
|
</div>
|
|
@@ -34,19 +34,25 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import React from 'react';
|
|
37
|
+
import {translate} from '@docusaurus/Translate';
|
|
37
38
|
import styles from './NextSteps.module.css';
|
|
38
39
|
|
|
39
|
-
export function NextStep({href, title, cta
|
|
40
|
+
export function NextStep({href, title, cta, children, className}) {
|
|
40
41
|
const composed = [styles.card, className].filter(Boolean).join(' ');
|
|
41
42
|
const Tag = href ? 'a' : 'div';
|
|
42
43
|
const props = href ? {href} : {};
|
|
44
|
+
const resolvedCta = cta ?? translate({
|
|
45
|
+
id: 'preset.nextSteps.step.cta',
|
|
46
|
+
message: 'Read more',
|
|
47
|
+
description: 'Default CTA label on a NextStep card when the consuming MDX does not pass cta',
|
|
48
|
+
});
|
|
43
49
|
return (
|
|
44
50
|
<Tag className={composed} {...props}>
|
|
45
51
|
{title && <div className={styles.title}>{title}</div>}
|
|
46
52
|
{children && <div className={styles.body}>{children}</div>}
|
|
47
53
|
{href && (
|
|
48
54
|
<div className={styles.cta}>
|
|
49
|
-
{
|
|
55
|
+
{resolvedCta}
|
|
50
56
|
<svg viewBox="0 0 24 24" aria-hidden="true" width="14" height="14"
|
|
51
57
|
fill="none" stroke="currentColor" strokeWidth="2"
|
|
52
58
|
strokeLinecap="round" strokeLinejoin="round">
|
|
@@ -58,12 +64,17 @@ export function NextStep({href, title, cta = 'Read more', children, className})
|
|
|
58
64
|
);
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
export default function NextSteps({title
|
|
67
|
+
export default function NextSteps({title, lede, children, columns = 3, className}) {
|
|
68
|
+
const resolvedTitle = title ?? translate({
|
|
69
|
+
id: 'preset.nextSteps.title',
|
|
70
|
+
message: 'Next steps',
|
|
71
|
+
description: 'Default section heading on NextSteps when the consuming MDX does not pass title',
|
|
72
|
+
});
|
|
62
73
|
const composed = [styles.section, className].filter(Boolean).join(' ');
|
|
63
74
|
const gridClass = [styles.grid, styles['cols-' + columns]].join(' ');
|
|
64
75
|
return (
|
|
65
76
|
<section className={composed}>
|
|
66
|
-
{
|
|
77
|
+
{resolvedTitle && <h2 className={styles.title}>{resolvedTitle}</h2>}
|
|
67
78
|
{lede && <p className={styles.lede}>{lede}</p>}
|
|
68
79
|
<div className={gridClass}>{children}</div>
|
|
69
80
|
</section>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <PartnersFor />
|
|
3
|
+
*
|
|
4
|
+
* Section that lists the partners shipping a given app or solution.
|
|
5
|
+
* Reuses <PartnerCard/> (full variant) for each partner, with the
|
|
6
|
+
* <BecomePartner/> CTA as the trailing grid cell.
|
|
7
|
+
*
|
|
8
|
+
* Pure presentation: the consuming site resolves which partners ship
|
|
9
|
+
* the subject (via usePartnersByApp / usePartnersBySolution) and
|
|
10
|
+
* passes the locale-resolved copy in. When `partners` is empty, the
|
|
11
|
+
* grid contains only the BecomePartner cell so the section still
|
|
12
|
+
* recruits without looking broken.
|
|
13
|
+
*
|
|
14
|
+
* Usage in MDX:
|
|
15
|
+
*
|
|
16
|
+
* import {PartnersFor} from '@conduction/docusaurus-preset/components';
|
|
17
|
+
* import {usePartnersByApp, useBecomePartner} from '@site/src/data/partners-catalog';
|
|
18
|
+
*
|
|
19
|
+
* <PartnersFor
|
|
20
|
+
* eyebrow="Partners"
|
|
21
|
+
* title="Partners shipping OpenRegister"
|
|
22
|
+
* lede="Implementation, hosting, and integration partners that deliver OpenRegister to their customers."
|
|
23
|
+
* partners={usePartnersByApp('OpenRegister')}
|
|
24
|
+
* becomePartner={useBecomePartner()}
|
|
25
|
+
* />
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import React from 'react';
|
|
29
|
+
import Section from '../primitives/Section';
|
|
30
|
+
import SectionHead from '../primitives/SectionHead';
|
|
31
|
+
import PartnerCard, {PartnerGrid, BecomePartner} from '../PartnerCard/PartnerCard';
|
|
32
|
+
|
|
33
|
+
export default function PartnersFor({
|
|
34
|
+
eyebrow = 'Partners',
|
|
35
|
+
title,
|
|
36
|
+
lede,
|
|
37
|
+
partners = [],
|
|
38
|
+
becomePartner,
|
|
39
|
+
gridColumns = 3,
|
|
40
|
+
background = 'default',
|
|
41
|
+
spacing = 'default',
|
|
42
|
+
className,
|
|
43
|
+
}) {
|
|
44
|
+
return (
|
|
45
|
+
<Section background={background} spacing={spacing} className={className}>
|
|
46
|
+
<SectionHead eyebrow={eyebrow} title={title} lede={lede} />
|
|
47
|
+
<PartnerGrid columns={gridColumns}>
|
|
48
|
+
{partners.map((p, i) => (
|
|
49
|
+
<PartnerCard key={p.href || p.name || i} {...p} />
|
|
50
|
+
))}
|
|
51
|
+
{becomePartner && <BecomePartner {...becomePartner} />}
|
|
52
|
+
</PartnerGrid>
|
|
53
|
+
</Section>
|
|
54
|
+
);
|
|
55
|
+
}
|
package/src/components/index.js
CHANGED
|
@@ -41,6 +41,7 @@ export {default as SolutionCard, SolutionGrid} from './SolutionCard/SolutionCard
|
|
|
41
41
|
export {default as PartnerCard, PartnerGrid, BecomePartner} from './PartnerCard/PartnerCard.jsx';
|
|
42
42
|
export {default as PartnerDirectory} from './PartnerDirectory/PartnerDirectory.jsx';
|
|
43
43
|
export {default as PartnerSidecard} from './PartnerSidecard/PartnerSidecard.jsx';
|
|
44
|
+
export {default as PartnersFor} from './PartnersFor/PartnersFor.jsx';
|
|
44
45
|
export {default as ManagedCommonGround} from './ManagedCommonGround/ManagedCommonGround.jsx';
|
|
45
46
|
export {default as Clients, DEFAULT_CLIENTS, DEFAULT_PARTNERS} from './Clients/Clients.jsx';
|
|
46
47
|
export {default as ReferenceCard, ReferenceGrid} from './ReferenceCard/ReferenceCard.jsx';
|
|
@@ -23,6 +23,7 @@ import Link from '@docusaurus/Link';
|
|
|
23
23
|
import {useLocation} from '@docusaurus/router';
|
|
24
24
|
import {useThemeConfig} from '@docusaurus/theme-common';
|
|
25
25
|
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
26
|
+
import Translate, {translate} from '@docusaurus/Translate';
|
|
26
27
|
import {brandFor} from '../brand.jsx';
|
|
27
28
|
import {useLazyScript} from '../../utils/lazyScript';
|
|
28
29
|
import {useLazyStylesheet} from '../../utils/lazyStylesheet';
|
|
@@ -272,21 +273,32 @@ export default function Footer() {
|
|
|
272
273
|
they're absent on a product page. */}
|
|
273
274
|
{minigamesOn && (
|
|
274
275
|
<>
|
|
275
|
-
<div className="game-hud" aria-live="polite" aria-label=
|
|
276
|
+
<div className="game-hud" aria-live="polite" aria-label={translate({id: 'preset.footer.game.ariaLabel', message: 'Boat-sinking mini game', description: 'Accessible name for the footer minigame region'})}>
|
|
276
277
|
<div className="hud-block hud-counter">
|
|
277
278
|
<span className="hud-num" data-counter="">100</span>
|
|
278
|
-
<span className="hud-label">
|
|
279
|
+
<span className="hud-label">
|
|
280
|
+
<Translate id="preset.footer.game.boatsLeft" description="HUD label counting boats remaining">Boats left</Translate>
|
|
281
|
+
</span>
|
|
279
282
|
</div>
|
|
280
283
|
<div className="hud-block hud-timer">
|
|
281
284
|
<span className="hud-num" data-timer="">60</span>
|
|
282
|
-
<span className="hud-label">
|
|
285
|
+
<span className="hud-label">
|
|
286
|
+
<Translate id="preset.footer.game.seconds" description="HUD label for the seconds-remaining timer">Seconds</Translate>
|
|
287
|
+
</span>
|
|
283
288
|
</div>
|
|
284
289
|
</div>
|
|
285
290
|
|
|
286
|
-
<div className="game-over" role="dialog" aria-label=
|
|
287
|
-
<p className="go-title" data-go-title="">
|
|
288
|
-
|
|
289
|
-
|
|
291
|
+
<div className="game-over" role="dialog" aria-label={translate({id: 'preset.footer.game.overAriaLabel', message: 'Mini game over', description: 'Accessible name for the game-over dialog'})}>
|
|
292
|
+
<p className="go-title" data-go-title="">
|
|
293
|
+
<Translate id="preset.footer.game.timesUp" description="Default headline shown when the minigame timer hits zero">Time's up</Translate>
|
|
294
|
+
</p>
|
|
295
|
+
<p className="go-stat">
|
|
296
|
+
<span data-go-sunk="">0</span>{' '}
|
|
297
|
+
<Translate id="preset.footer.game.sunk" description="Suffix after the number of boats sunk in the game-over stat">sunk</Translate>
|
|
298
|
+
</p>
|
|
299
|
+
<button type="button" data-restart="">
|
|
300
|
+
<Translate id="preset.footer.game.playAgain" description="Button label to restart the minigame">Play again</Translate>
|
|
301
|
+
</button>
|
|
290
302
|
</div>
|
|
291
303
|
</>
|
|
292
304
|
)}
|
|
@@ -337,8 +349,12 @@ export default function Footer() {
|
|
|
337
349
|
<div className="wm">{wordmark}</div>
|
|
338
350
|
)}
|
|
339
351
|
<p>
|
|
340
|
-
|
|
341
|
-
|
|
352
|
+
<Translate
|
|
353
|
+
id="preset.footer.brandBlurb"
|
|
354
|
+
description="Footer brand-citation paragraph. {nextcloud} is the styled Nextcloud word."
|
|
355
|
+
values={{nextcloud: <span className="next-blue">Nextcloud</span>}}>
|
|
356
|
+
{'Open-source apps for {nextcloud}. Built and maintained by Conduction in Amsterdam, released under EUPL-1.2.'}
|
|
357
|
+
</Translate>
|
|
342
358
|
</p>
|
|
343
359
|
{/*
|
|
344
360
|
Brand citation. The producer chain stays dot-separated
|
|
@@ -48,6 +48,7 @@ import {useLocation} from '@docusaurus/router';
|
|
|
48
48
|
import useBaseUrl from '@docusaurus/useBaseUrl';
|
|
49
49
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
50
50
|
import {useThemeConfig} from '@docusaurus/theme-common';
|
|
51
|
+
import {translate} from '@docusaurus/Translate';
|
|
51
52
|
import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
|
|
52
53
|
import {brandFor, productWordmark, deriveStability} from '../brand.jsx';
|
|
53
54
|
import {ICONS} from '../../components/primitives/icons';
|
|
@@ -96,7 +97,7 @@ function NavItem({item, location, appVersion}) {
|
|
|
96
97
|
className={styles.iconLink}
|
|
97
98
|
target="_blank"
|
|
98
99
|
rel="noopener noreferrer"
|
|
99
|
-
aria-label={item['aria-label'] || 'GitHub repository'}
|
|
100
|
+
aria-label={item['aria-label'] || translate({id: 'preset.navbar.github.ariaLabel', message: 'GitHub repository', description: 'Default accessible label for the icon-only GitHub link in the navbar'})}
|
|
100
101
|
title="GitHub"
|
|
101
102
|
>
|
|
102
103
|
<span className={styles.iconGlyph} aria-hidden="true">{ICONS.github}</span>
|
|
@@ -108,7 +109,7 @@ function NavItem({item, location, appVersion}) {
|
|
|
108
109
|
(the Redocusaurus mount point used by every Conduction docs site).
|
|
109
110
|
Sites can override via `to` or `href`. */
|
|
110
111
|
if (typeIs(item, 'apiDocs')) {
|
|
111
|
-
const label = item.label || 'API Documentation';
|
|
112
|
+
const label = item.label || translate({id: 'preset.navbar.apiDocs.label', message: 'API Documentation', description: 'Default label for the API Documentation navbar link when the consuming site does not set one'});
|
|
112
113
|
const to = item.to || '/api';
|
|
113
114
|
const href = item.href;
|
|
114
115
|
const isActive = !href && (location?.pathname === to ||
|
|
@@ -225,7 +226,10 @@ export default function Navbar() {
|
|
|
225
226
|
sub-route (e.g. /docs/intro/img/app-logo.svg). */
|
|
226
227
|
const logoSrcRaw = navbar.logo?.src;
|
|
227
228
|
const logoSrc = useBaseUrl(logoSrcRaw || '');
|
|
228
|
-
const logoAlt = navbar.logo?.alt ||
|
|
229
|
+
const logoAlt = navbar.logo?.alt || translate(
|
|
230
|
+
{id: 'preset.navbar.logoAlt', message: '{title} avatar', description: 'Default alt text for the navbar logo. {title} is the site title.'},
|
|
231
|
+
{title: navbar.title},
|
|
232
|
+
);
|
|
229
233
|
|
|
230
234
|
/* Split into "left links" (regular nav) and "right CTAs" (locale,
|
|
231
235
|
external links, install button, GitHub icon, version pill).
|