@conduction/docusaurus-preset 3.32.0 → 3.34.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.
Files changed (31) hide show
  1. package/MISSING_COMPONENTS.md +7 -6
  2. package/package.json +1 -1
  3. package/src/components/AgentTrace/AgentTrace.jsx +26 -5
  4. package/src/components/AgentTrace/AgentTrace.module.css +41 -2
  5. package/src/components/AgentTrace/__tests__/AgentTrace.render.test.js +108 -0
  6. package/src/components/AppMock/AppMock.jsx +14 -5
  7. package/src/components/AppMock/AppMock.module.css +439 -0
  8. package/src/components/AppMock/__tests__/AppMock.render.test.js +181 -0
  9. package/src/components/AppMock/variants/LarpingAppMock.jsx +23 -4
  10. package/src/components/AppMock/variants/LaunchPadBiMock.jsx +12 -5
  11. package/src/components/AppMock/variants/LaunchPadTilesMock.jsx +6 -1
  12. package/src/components/AppMock/variants/LaunchPadWidgetsMock.jsx +17 -8
  13. package/src/components/AppMock/variants/NLDesignMock.jsx +15 -4
  14. package/src/components/AppMock/variants/PipelinQMock.jsx +12 -2
  15. package/src/components/AppMock/variants/PortaliqMock.jsx +21 -9
  16. package/src/components/AppMock/variants/ScholiqMock.jsx +12 -6
  17. package/src/components/FlowMock/FlowMock.jsx +5 -3
  18. package/src/components/FlowMock/FlowMock.module.css +36 -9
  19. package/src/components/FlowMock/__tests__/FlowMock.render.test.js +83 -0
  20. package/src/components/KanbanMock/KanbanMock.jsx +35 -5
  21. package/src/components/KanbanMock/KanbanMock.module.css +120 -28
  22. package/src/components/KanbanMock/__tests__/KanbanMock.render.test.js +114 -0
  23. package/src/components/LeafMock/LeafMock.jsx +23 -8
  24. package/src/components/LeafMock/LeafMock.module.css +123 -0
  25. package/src/components/LeafMock/__tests__/LeafMock.render.test.js +105 -0
  26. package/src/components/MockScene/MockScene.jsx +16 -2
  27. package/src/components/MockScene/MockScene.module.css +22 -2
  28. package/src/components/MockScene/__tests__/MockScene.render.test.js +101 -0
  29. package/src/components/WidgetShelf/WidgetShelf.jsx +321 -28
  30. package/src/components/WidgetShelf/WidgetShelf.module.css +193 -1
  31. package/src/components/WidgetShelf/__tests__/WidgetShelf.render.test.js +149 -0
@@ -7,6 +7,17 @@
7
7
  * duplicated for a seamless loop, paused on hover/focus) and the
8
8
  * static responsive grid (carousel={false}, and what
9
9
  * prefers-reduced-motion collapses the carousel to).
10
+ *
11
+ * The CSS keyframe marquee is the SSR/no-JS fallback; once the
12
+ * component mounts, the JSX adds .jsDriven to the track and a rAF
13
+ * loop drives the same transform so the prev/next/pause controls can
14
+ * steer it. Controls are cobalt ghost buttons (never orange fill)
15
+ * and are hidden under reduced motion together with the marquee.
16
+ *
17
+ * Auto panels (.auto*): token-built mini mocks for widgets without an
18
+ * explicit panel. Four archetypes (KPI tile, bar chart, list rows,
19
+ * donut gauge); the accent comes from --wsa / --wsa-soft, set inline
20
+ * by the JSX from a hash of the widget title.
10
21
  */
11
22
 
12
23
  .shelf {
@@ -89,6 +100,56 @@
89
100
  to { transform: translateX(-50%); }
90
101
  }
91
102
 
103
+ /* Once the rAF loop owns the transform, the keyframe animation must
104
+ let go — otherwise it would overwrite the inline transform. */
105
+ .track.jsDriven { animation: none; }
106
+
107
+ /* ---- Controls (pause/play + prev/next) ----
108
+ *
109
+ * Cobalt ghost buttons per the kit: transparent ground, cobalt ink,
110
+ * cobalt-200 hairline; hover tints, focus ring is a visible cobalt
111
+ * outline. Never orange. Hidden under reduced motion because the
112
+ * static grid has nothing to steer. */
113
+
114
+ .controls {
115
+ display: flex;
116
+ justify-content: flex-end;
117
+ gap: var(--space-2);
118
+ margin-top: var(--space-4);
119
+ }
120
+
121
+ .ctrl {
122
+ appearance: none;
123
+ display: inline-flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ width: 38px;
127
+ height: 38px;
128
+ padding: 0;
129
+ border: 1px solid var(--c-cobalt-200);
130
+ border-radius: var(--radius-pill);
131
+ background: transparent;
132
+ color: var(--c-blue-cobalt);
133
+ cursor: pointer;
134
+ }
135
+ .ctrl:hover {
136
+ background: var(--c-cobalt-50);
137
+ border-color: var(--c-blue-cobalt);
138
+ }
139
+ .ctrl:focus-visible {
140
+ outline: 2px solid var(--c-blue-cobalt);
141
+ outline-offset: 2px;
142
+ }
143
+ .ctrl svg { width: 16px; height: 16px; display: block; }
144
+ .chev {
145
+ fill: none;
146
+ stroke: currentColor;
147
+ stroke-width: 2.4;
148
+ stroke-linecap: round;
149
+ stroke-linejoin: round;
150
+ }
151
+ .glyph { fill: currentColor; }
152
+
92
153
  /* Reduced motion: drop the duplicate copy and render the first group
93
154
  as the static wrapping grid — identical to carousel={false}. The
94
155
  .cols-2 / .cols-3 class on the group supplies the column template. */
@@ -98,7 +159,8 @@
98
159
  -webkit-mask-image: none;
99
160
  mask-image: none;
100
161
  }
101
- .track { animation: none; display: block; width: 100%; }
162
+ .controls { display: none; }
163
+ .track { animation: none; display: block; width: 100%; transform: none !important; }
102
164
  .group {
103
165
  display: grid;
104
166
  gap: var(--space-5);
@@ -150,3 +212,133 @@
150
212
  margin: 0;
151
213
  padding: var(--space-2) var(--space-5) var(--space-5);
152
214
  }
215
+
216
+ /* ---- Auto-generated mini panels ----
217
+ *
218
+ * White widget tile on the tinted panel ground, same chrome family as
219
+ * the hand-built .w mocks. All greeked (bars, dots, one number); the
220
+ * accent tokens --wsa / --wsa-soft arrive inline from the JSX. */
221
+
222
+ .auto {
223
+ box-sizing: border-box;
224
+ width: 100%;
225
+ max-width: 280px;
226
+ min-height: 124px;
227
+ background: white;
228
+ border: 1px solid var(--c-cobalt-100);
229
+ border-radius: var(--radius-md);
230
+ box-shadow: var(--shadow-1);
231
+ padding: 16px 18px;
232
+ }
233
+
234
+ /* KPI number tile */
235
+ .autoKpi {
236
+ display: flex;
237
+ flex-direction: column;
238
+ justify-content: center;
239
+ gap: 10px;
240
+ }
241
+ .autoHex {
242
+ display: block;
243
+ width: 13px;
244
+ height: 15px;
245
+ clip-path: var(--hex-pointy-top);
246
+ background: var(--wsa);
247
+ }
248
+ .autoKpiValue {
249
+ font-family: var(--conduction-typography-font-family-code);
250
+ font-size: 34px;
251
+ font-weight: 700;
252
+ line-height: 1;
253
+ letter-spacing: -0.01em;
254
+ color: var(--c-cobalt-900);
255
+ }
256
+ .autoKpiBar {
257
+ display: block;
258
+ height: 5px;
259
+ border-radius: 1.5px;
260
+ background: var(--wsa-soft);
261
+ }
262
+
263
+ /* Mini bar chart */
264
+ .autoBars {
265
+ display: flex;
266
+ align-items: flex-end;
267
+ gap: 8px;
268
+ height: 124px;
269
+ }
270
+ .autoBar {
271
+ flex: 1;
272
+ border-radius: 2px 2px 0 0;
273
+ background: var(--c-cobalt-200);
274
+ }
275
+ .autoBarHi { background: var(--wsa); }
276
+
277
+ /* List rows */
278
+ .autoList {
279
+ display: flex;
280
+ flex-direction: column;
281
+ justify-content: center;
282
+ gap: 12px;
283
+ }
284
+ .autoRow {
285
+ display: flex;
286
+ align-items: center;
287
+ gap: 10px;
288
+ }
289
+ .autoDot {
290
+ flex-shrink: 0;
291
+ width: 12px;
292
+ height: 14px;
293
+ clip-path: var(--hex-pointy-top);
294
+ background: var(--c-cobalt-200);
295
+ }
296
+ .autoDotHi { background: var(--wsa); }
297
+ .autoRowBars {
298
+ flex: 1;
299
+ display: flex;
300
+ flex-direction: column;
301
+ gap: 5px;
302
+ }
303
+ .autoRowBar {
304
+ display: block;
305
+ height: 5px;
306
+ border-radius: 1.5px;
307
+ background: var(--c-cobalt-700);
308
+ }
309
+ .autoRowLine {
310
+ display: block;
311
+ width: 86%;
312
+ height: 4px;
313
+ border-radius: 1px;
314
+ background: var(--c-cobalt-100);
315
+ }
316
+
317
+ /* Donut gauge */
318
+ .autoDonut {
319
+ display: flex;
320
+ align-items: center;
321
+ gap: 18px;
322
+ }
323
+ .autoRing {
324
+ flex-shrink: 0;
325
+ width: 84px;
326
+ height: 84px;
327
+ }
328
+ .autoRingTrack {
329
+ fill: none;
330
+ stroke: var(--c-cobalt-100);
331
+ stroke-width: 6;
332
+ }
333
+ .autoRingFill {
334
+ fill: none;
335
+ stroke: var(--wsa);
336
+ stroke-width: 6;
337
+ stroke-linecap: round;
338
+ }
339
+ .autoDonutBars {
340
+ flex: 1;
341
+ display: flex;
342
+ flex-direction: column;
343
+ gap: 6px;
344
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * WidgetShelf.render.test.js — renders the real <WidgetShelf> JSX to
3
+ * static markup (the SSR path Docusaurus takes at build time) and
4
+ * asserts on the output: derived carousel speed, auto-generated
5
+ * panels for widgets without an explicit `panel`, and the carousel
6
+ * controls with their accessible names.
7
+ *
8
+ * Uses the same esbuild-bundle-then-renderToStaticMarkup technique as
9
+ * AiDisclosure.render.test.js / scripts/build-kit.mjs. CSS modules
10
+ * are stubbed with an identity proxy (styles.foo → 'foo'), so class
11
+ * assertions read as the source class names.
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const test = require('node:test');
17
+ const {before, after} = test;
18
+ const assert = require('node:assert/strict');
19
+ const path = require('node:path');
20
+ const fs = require('node:fs/promises');
21
+ const {build} = require('esbuild');
22
+ const React = require('react');
23
+ const {renderToStaticMarkup} = require('react-dom/server');
24
+
25
+ const COMPONENT = path.resolve(__dirname, '..', 'WidgetShelf.jsx');
26
+ const PRESET_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
27
+ const SCRATCH = path.join(PRESET_ROOT, '.tmp-widget-shelf-test');
28
+
29
+ const cssModuleStub = {
30
+ name: 'css-module-stub',
31
+ setup(b) {
32
+ b.onResolve({filter: /\.module\.css$/}, (args) => ({path: args.path, namespace: 'css-stub'}));
33
+ b.onLoad({filter: /.*/, namespace: 'css-stub'}, () => ({
34
+ contents: 'export default new Proxy({}, {get: (_, p) => p});',
35
+ loader: 'js',
36
+ }));
37
+ },
38
+ };
39
+
40
+ let WidgetShelf;
41
+
42
+ before(async () => {
43
+ await fs.mkdir(SCRATCH, {recursive: true});
44
+ const tmpDir = await fs.mkdtemp(path.join(SCRATCH, 'run-'));
45
+ const outFile = path.join(tmpDir, 'bundle.cjs');
46
+ await build({
47
+ entryPoints: [COMPONENT],
48
+ outfile: outFile,
49
+ bundle: true,
50
+ format: 'cjs',
51
+ jsx: 'automatic',
52
+ jsxImportSource: 'react',
53
+ tsconfigRaw: {compilerOptions: {jsx: 'react-jsx', jsxImportSource: 'react'}},
54
+ platform: 'node',
55
+ external: ['react'],
56
+ plugins: [cssModuleStub],
57
+ logLevel: 'warning',
58
+ });
59
+ delete require.cache[require.resolve(outFile)];
60
+ WidgetShelf = require(outFile).default;
61
+ });
62
+
63
+ after(async () => {
64
+ await fs.rm(SCRATCH, {recursive: true, force: true});
65
+ });
66
+
67
+ function render(props) {
68
+ return renderToStaticMarkup(React.createElement(WidgetShelf, props));
69
+ }
70
+
71
+ function makeWidgets(n) {
72
+ return Array.from({length: n}, (_, i) => ({
73
+ title: `Widget number ${i}`,
74
+ desc: `Description ${i}`,
75
+ }));
76
+ }
77
+
78
+ test('SSR smoke: carousel renders every widget twice (live group + aria-hidden dup)', () => {
79
+ const html = render({title: 'Shelf', widgets: makeWidgets(5)});
80
+ assert.equal((html.match(/Widget number 3/g) || []).length, 2);
81
+ assert.match(html, /aria-hidden="true"/);
82
+ });
83
+
84
+ test('default loop duration derives from widget count at 6s per card', () => {
85
+ const html = render({widgets: makeWidgets(15)});
86
+ assert.match(html, /--ws-speed:90s/);
87
+ });
88
+
89
+ test('default loop duration never drops below the 30s floor', () => {
90
+ const html = render({widgets: makeWidgets(3)});
91
+ assert.match(html, /--ws-speed:30s/);
92
+ });
93
+
94
+ test('an explicit speed prop overrides the derived duration', () => {
95
+ const html = render({widgets: makeWidgets(15), speed: 45});
96
+ assert.match(html, /--ws-speed:45s/);
97
+ });
98
+
99
+ test('a widget without a panel gets an auto-generated mini panel', () => {
100
+ const html = render({widgets: [{title: 'Werkvoorraad', desc: 'x'}]});
101
+ assert.match(html, /class="auto auto(Kpi|Bars|List|Donut)"/);
102
+ });
103
+
104
+ test('an explicit panel always wins over the auto panel', () => {
105
+ const html = render({
106
+ widgets: [{title: 'Custom', desc: 'x', panel: React.createElement('div', {className: 'w w-custom'})}],
107
+ });
108
+ assert.match(html, /w-custom/);
109
+ assert.doesNotMatch(html, /class="auto /);
110
+ });
111
+
112
+ test('auto panels are deterministic: same input, same markup', () => {
113
+ const props = {widgets: makeWidgets(8)};
114
+ assert.equal(render(props), render(props));
115
+ });
116
+
117
+ test('auto panels vary: 8 distinct titles hit at least two archetypes and two accent families', () => {
118
+ const html = render({widgets: makeWidgets(8)});
119
+ const kinds = new Set(
120
+ (html.match(/class="auto auto(Kpi|Bars|List|Donut)"/g) || []).map((m) => m),
121
+ );
122
+ assert.ok(kinds.size >= 2, `expected >=2 archetypes, saw ${[...kinds].join(', ')}`);
123
+ const families = new Set(
124
+ (html.match(/--wsa:var\(--c-([a-z]+)-500\)/g) || []).map((m) => m),
125
+ );
126
+ assert.ok(families.size >= 2, `expected >=2 accent families, saw ${[...families].join(', ')}`);
127
+ });
128
+
129
+ test('auto panels never use reserved accent families (coral/orange, gold)', () => {
130
+ // 64 distinct titles sweep the family picker; the hex-family policy
131
+ // reserves coral (KNVB orange) and gold, so neither may ever appear.
132
+ const html = render({widgets: makeWidgets(64)});
133
+ assert.doesNotMatch(html, /--c-coral|--c-gold|--c-orange/);
134
+ });
135
+
136
+ test('carousel renders pause/play and prev/next controls with accessible names', () => {
137
+ const html = render({widgets: makeWidgets(4)});
138
+ assert.match(html, /aria-label="Previous widgets"/);
139
+ assert.match(html, /aria-label="Next widgets"/);
140
+ assert.match(html, /aria-label="Pause"/);
141
+ assert.match(html, /<button type="button"/);
142
+ });
143
+
144
+ test('the static grid (carousel={false}) has no controls and no duplicate group', () => {
145
+ const html = render({widgets: makeWidgets(4), carousel: false});
146
+ assert.doesNotMatch(html, /aria-label="Previous widgets"/);
147
+ assert.doesNotMatch(html, /aria-label="Pause"/);
148
+ assert.equal((html.match(/Widget number 3/g) || []).length, 1);
149
+ });