@conduction/docusaurus-preset 3.33.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 (25) hide show
  1. package/MISSING_COMPONENTS.md +5 -4
  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/LeafMock/LeafMock.jsx +23 -8
  21. package/src/components/LeafMock/LeafMock.module.css +123 -0
  22. package/src/components/LeafMock/__tests__/LeafMock.render.test.js +105 -0
  23. package/src/components/MockScene/MockScene.jsx +16 -2
  24. package/src/components/MockScene/MockScene.module.css +22 -2
  25. package/src/components/MockScene/__tests__/MockScene.render.test.js +101 -0
@@ -6,6 +6,11 @@
6
6
  * Tokens only. Accent discipline per leaf: contacts sync = mint,
7
7
  * calendar event bar = lavender, mail action chip = mint, files
8
8
  * record chip = forest. No orange anywhere in this component.
9
+ *
10
+ * All four leaves animate on the shared --lm-dur timeline (6s),
11
+ * KanbanMock conventions: base styles are the meaningful end state,
12
+ * loops reset by opacity swap, `.static` (running={false}) and
13
+ * prefers-reduced-motion freeze to the base styles.
9
14
  */
10
15
 
11
16
  .lm { display: contents; }
@@ -20,6 +25,7 @@
20
25
  }
21
26
 
22
27
  .lm .frame {
28
+ --lm-dur: 6s;
23
29
  width: 100%;
24
30
  max-width: 360px;
25
31
  aspect-ratio: 4 / 3;
@@ -235,3 +241,120 @@
235
241
  }
236
242
  .lm .fileLines { flex: 1; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
237
243
  .lm .recordChip { background: var(--c-forest-500); }
244
+
245
+ /* ======================================================================
246
+ Animations — one shared 6s timeline (--lm-dur) per leaf
247
+ ====================================================================== */
248
+
249
+ /* ---- Contacts: the sync arrow double-pulses, then the contact data
250
+ lands on the phone as three staggered rows. ---- */
251
+
252
+ .lm .syncArrow { animation: lmSyncPulse var(--lm-dur) ease-in-out infinite; }
253
+ @keyframes lmSyncPulse {
254
+ 0%, 6% { transform: scale(1); }
255
+ 12% { transform: scale(1.18); }
256
+ 18% { transform: scale(1); }
257
+ 26% { transform: scale(1.18); }
258
+ 32%, 100% { transform: scale(1); }
259
+ }
260
+
261
+ .lm .phoneRow { animation: lmPhoneRow var(--lm-dur) ease-out infinite both; }
262
+ .lm .phoneRow:nth-child(2) { animation-delay: 0.35s; }
263
+ .lm .phoneRow:nth-child(3) { animation-delay: 0.7s; }
264
+ @keyframes lmPhoneRow {
265
+ 0%, 18% { opacity: 0; transform: translateX(-4px); }
266
+ 26%, 92% { opacity: 1; transform: none; }
267
+ 97%, 100% { opacity: 0; transform: none; }
268
+ }
269
+
270
+ /* ---- Calendar: the linked day pops highlighted, then the event chip
271
+ slides in beneath the month grid. ---- */
272
+
273
+ .lm .dayActive { animation: lmDayPop var(--lm-dur) ease-in-out infinite; }
274
+ @keyframes lmDayPop {
275
+ 0%, 8% { background-color: var(--c-cobalt-50); border-color: var(--c-cobalt-100); transform: scale(1); }
276
+ 16% { background-color: var(--c-lavender-300); border-color: var(--c-lavender-500); transform: scale(1.35); }
277
+ 22%, 90% { background-color: var(--c-lavender-300); border-color: var(--c-lavender-500); transform: scale(1); }
278
+ 98%, 100% { background-color: var(--c-cobalt-50); border-color: var(--c-cobalt-100); transform: scale(1); }
279
+ }
280
+
281
+ .lm .eventChip { animation: lmChipSlide var(--lm-dur) ease-out infinite both; }
282
+ @keyframes lmChipSlide {
283
+ 0%, 22% { opacity: 0; transform: translateY(8px); }
284
+ 32%, 92% { opacity: 1; transform: none; }
285
+ 98%, 100% { opacity: 0; transform: none; }
286
+ }
287
+
288
+ /* ---- Mail: the middle row highlights, then the create-from-email
289
+ chip pops with an overshoot. ---- */
290
+
291
+ .lm .mailRowActive { animation: lmRowHl var(--lm-dur) ease-in-out infinite; }
292
+ @keyframes lmRowHl {
293
+ 0%, 10% { background-color: transparent; border-color: transparent; }
294
+ 20%, 90% { background-color: var(--c-cobalt-50); border-color: var(--c-cobalt-100); }
295
+ 98%, 100% { background-color: transparent; border-color: transparent; }
296
+ }
297
+
298
+ .lm .mail .actionChip { transform-origin: center; animation: lmChipPop var(--lm-dur) ease-in-out infinite both; }
299
+ @keyframes lmChipPop {
300
+ 0%, 26% { opacity: 0; transform: scale(0); }
301
+ 34% { opacity: 1; transform: scale(1.18); }
302
+ 39%, 90% { opacity: 1; transform: scale(1); }
303
+ 97%, 100% { opacity: 0; transform: scale(1); }
304
+ }
305
+
306
+ /* ---- Files: the linked row slides in, the record chip pops, and its
307
+ link icon draws itself (the path carries pathLength="1"). ---- */
308
+
309
+ .lm .files .fileRow:nth-child(2) { animation: lmFileRow var(--lm-dur) ease-out infinite both; }
310
+ @keyframes lmFileRow {
311
+ 0%, 8% { opacity: 0; transform: translateY(-4px); }
312
+ 16%, 92% { opacity: 1; transform: none; }
313
+ 98%, 100% { opacity: 0; transform: none; }
314
+ }
315
+
316
+ .lm .files .recordChip { transform-origin: center; animation: lmChipPop var(--lm-dur) ease-in-out infinite both; }
317
+ .lm .files .recordChip .linkIcon path {
318
+ stroke-dasharray: 1;
319
+ stroke-dashoffset: 1;
320
+ animation: lmLinkDraw var(--lm-dur) ease-in-out infinite both;
321
+ }
322
+ @keyframes lmLinkDraw {
323
+ 0%, 34% { stroke-dashoffset: 1; }
324
+ 56%, 100% { stroke-dashoffset: 0; }
325
+ }
326
+
327
+ /* ======================================================================
328
+ Static frames — running={false} adds `.static` to `.lm .frame`; the
329
+ media query mirrors it for prefers-reduced-motion. Base styles are
330
+ the end state, so freezing is animation: none plus the icon-draw
331
+ dash reset.
332
+ ====================================================================== */
333
+
334
+ .lm .static .syncArrow,
335
+ .lm .static .phoneRow,
336
+ .lm .static .dayActive,
337
+ .lm .static .eventChip,
338
+ .lm .static .mailRowActive,
339
+ .lm .static .mail .actionChip,
340
+ .lm .static .files .fileRow:nth-child(2),
341
+ .lm .static .files .recordChip,
342
+ .lm .static .files .recordChip .linkIcon path {
343
+ animation: none;
344
+ }
345
+ .lm .static .files .recordChip .linkIcon path { stroke-dasharray: none; stroke-dashoffset: 0; }
346
+
347
+ @media (prefers-reduced-motion: reduce) {
348
+ .lm .syncArrow,
349
+ .lm .phoneRow,
350
+ .lm .dayActive,
351
+ .lm .eventChip,
352
+ .lm .mailRowActive,
353
+ .lm .mail .actionChip,
354
+ .lm .files .fileRow:nth-child(2),
355
+ .lm .files .recordChip,
356
+ .lm .files .recordChip .linkIcon path {
357
+ animation: none;
358
+ }
359
+ .lm .files .recordChip .linkIcon path { stroke-dasharray: none; stroke-dashoffset: 0; }
360
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * LeafMock.render.test.js — SSR-renders the real <LeafMock> for each
3
+ * of its four leaves and asserts the animation hooks and the P0
4
+ * `running` prop plumbing (static class on the frame). Same
5
+ * esbuild-bundle-then-renderToStaticMarkup harness as
6
+ * KanbanMock.render.test.js; CSS modules stubbed to identity.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const test = require('node:test');
12
+ const {before, after} = test;
13
+ const assert = require('node:assert/strict');
14
+ const path = require('node:path');
15
+ const fs = require('node:fs/promises');
16
+ const {build} = require('esbuild');
17
+ const React = require('react');
18
+ const {renderToStaticMarkup} = require('react-dom/server');
19
+
20
+ const COMPONENT = path.resolve(__dirname, '..', 'LeafMock.jsx');
21
+ const PRESET_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
22
+ const SCRATCH = path.join(PRESET_ROOT, '.tmp-leaf-mock-test');
23
+
24
+ const LEAVES = ['contacts', 'calendar', 'mail', 'files'];
25
+
26
+ const cssModuleStub = {
27
+ name: 'css-module-stub',
28
+ setup(b) {
29
+ b.onResolve({filter: /\.module\.css$/}, (args) => ({path: args.path, namespace: 'css-stub'}));
30
+ b.onLoad({filter: /.*/, namespace: 'css-stub'}, () => ({
31
+ contents: 'export default new Proxy({}, {get: (_, p) => p});',
32
+ loader: 'js',
33
+ }));
34
+ },
35
+ };
36
+
37
+ let LeafMock;
38
+
39
+ before(async () => {
40
+ await fs.mkdir(SCRATCH, {recursive: true});
41
+ const tmpDir = await fs.mkdtemp(path.join(SCRATCH, 'run-'));
42
+ const outFile = path.join(tmpDir, 'bundle.cjs');
43
+ await build({
44
+ entryPoints: [COMPONENT],
45
+ outfile: outFile,
46
+ bundle: true,
47
+ format: 'cjs',
48
+ jsx: 'automatic',
49
+ jsxImportSource: 'react',
50
+ tsconfigRaw: {compilerOptions: {jsx: 'react-jsx', jsxImportSource: 'react'}},
51
+ platform: 'node',
52
+ external: ['react'],
53
+ plugins: [cssModuleStub],
54
+ logLevel: 'warning',
55
+ });
56
+ delete require.cache[require.resolve(outFile)];
57
+ LeafMock = require(outFile).default;
58
+ });
59
+
60
+ after(async () => {
61
+ await fs.rm(SCRATCH, {recursive: true, force: true});
62
+ });
63
+
64
+ function render(props) {
65
+ return renderToStaticMarkup(React.createElement(LeafMock, props));
66
+ }
67
+
68
+ test('SSR smoke: every leaf renders a frame, none falls through to Unknown leaf', () => {
69
+ for (const leaf of LEAVES) {
70
+ const html = render({leaf});
71
+ assert.match(html, /class="frame size-md"/, `${leaf}: frame missing`);
72
+ assert.doesNotMatch(html, /Unknown leaf/, `${leaf}: fell through`);
73
+ }
74
+ });
75
+
76
+ test('P0 plumbing: running={false} adds the static class on every leaf; default omits it', () => {
77
+ for (const leaf of LEAVES) {
78
+ assert.match(render({leaf, running: false}), /class="frame size-md static"/, `${leaf}: no static frame`);
79
+ assert.doesNotMatch(render({leaf}), /\bstatic\b/, `${leaf}: static leaked into the running frame`);
80
+ }
81
+ });
82
+
83
+ test('contacts: sync arrow and three phone rows (the staggered landing targets)', () => {
84
+ const html = render({leaf: 'contacts'});
85
+ assert.match(html, /class="syncArrow"/);
86
+ assert.equal((html.match(/class="phoneRow"/g) || []).length, 3);
87
+ });
88
+
89
+ test('calendar: active day and the sliding event chip', () => {
90
+ const html = render({leaf: 'calendar'});
91
+ assert.match(html, /day dayActive/);
92
+ assert.match(html, /class="eventChip"/);
93
+ });
94
+
95
+ test('mail: highlighted row carries the popping action chip', () => {
96
+ const html = render({leaf: 'mail'});
97
+ assert.match(html, /mailRow mailRowActive/);
98
+ assert.match(html, /class="actionChip"/);
99
+ });
100
+
101
+ test('files: the record chip link icon carries pathLength="1" for the draw animation', () => {
102
+ const html = render({leaf: 'files'});
103
+ assert.match(html, /actionChip recordChip/);
104
+ assert.match(html, /pathLength="1"/);
105
+ });
@@ -44,6 +44,12 @@
44
44
  * - items: Array of item descriptors (see above)
45
45
  * - width: scene width in px (default 960)
46
46
  * - height: scene height in px (default 420)
47
+ * - running: boolean (default true) — the scene assembles: items
48
+ * land one by one on a 220ms stagger (opacity + translateY only,
49
+ * so the inline left/top positioning is never fought), hold, and
50
+ * fade for the loop reset. The loop period derives from the item
51
+ * count (--ms-dur, set inline). `running={false}` and
52
+ * prefers-reduced-motion show the assembled scene.
47
53
  * - className: string
48
54
  */
49
55
 
@@ -64,16 +70,24 @@ function renderItem(item) {
64
70
  return item.node || null;
65
71
  }
66
72
 
67
- export default function MockScene({ items = [], width = 960, height = 420, className }) {
73
+ export default function MockScene({ items = [], width = 960, height = 420, running = true, className }) {
74
+ /* Landing timing: item i arrives at i × 220ms; ~5s hold after the
75
+ last landing, then a fade window before the loop restarts. */
76
+ const STEP = 220;
77
+ const duration = items.length * STEP + 5600;
68
78
  return (
69
79
  <div className={[amStyles.am, styles.scene, className].filter(Boolean).join(' ')}>
70
- <div className={styles.sceneFrame} style={{ width, height }}>
80
+ <div
81
+ className={[styles.sceneFrame, !running && styles.static].filter(Boolean).join(' ')}
82
+ style={{ width, height, '--ms-dur': `${duration}ms` }}
83
+ >
71
84
  {items.map((item, i) => {
72
85
  const wrapperStyle = {
73
86
  ...(item.style || {}),
74
87
  left: item.x || 0,
75
88
  top: item.y || 0,
76
89
  zIndex: item.z != null ? item.z : i,
90
+ animationDelay: `${i * STEP}ms`,
77
91
  };
78
92
  return (
79
93
  <div key={i} className={styles.sceneItem} style={wrapperStyle}>
@@ -21,8 +21,28 @@
21
21
 
22
22
  .am .sceneItem {
23
23
  position: absolute;
24
- /* `left`, `top`, and optionally `z-index` are set inline by the
25
- component from the item's x/y/z values. */
24
+ /* `left`, `top`, optionally `z-index`, and the landing
25
+ animation-delay (i × 220ms) are set inline by the component from
26
+ the item's x/y/z values and index. The landing is transform +
27
+ opacity only — the inline left/top positioning is never fought.
28
+ 0% and 100% are both hidden, so the staggered delays stay
29
+ seamless in steady state; `both` keeps a delayed item on its
30
+ hidden first frame before the initial landing. */
31
+ animation: msLand var(--ms-dur, 8s) cubic-bezier(0.2, 0.7, 0.3, 1) infinite both;
32
+ }
33
+
34
+ @keyframes msLand {
35
+ 0% { opacity: 0; transform: translateY(16px); }
36
+ 4% { opacity: 1; transform: none; }
37
+ 94% { opacity: 1; transform: none; }
38
+ 99%, 100% { opacity: 0; transform: none; }
39
+ }
40
+
41
+ /* Static scene (running={false} and reduced motion): assembled. */
42
+ .am .static .sceneItem { animation: none; }
43
+
44
+ @media (prefers-reduced-motion: reduce) {
45
+ .am .sceneItem { animation: none; }
26
46
  }
27
47
 
28
48
  /* Widgets inside a scene drop their LaunchPad-style cobalt-900 dashboard
@@ -0,0 +1,101 @@
1
+ /**
2
+ * MockScene.render.test.js — SSR-renders the real <MockScene> and
3
+ * asserts the landing-stagger wiring: inline left/top positioning is
4
+ * preserved untouched (the animation is transform-only), each item
5
+ * carries its 220ms-stagger animation-delay, the loop period --ms-dur
6
+ * derives from the item count, and running={false} puts the static
7
+ * class on the scene frame. Same esbuild harness as
8
+ * KanbanMock.render.test.js; CSS modules stubbed to identity.
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const test = require('node:test');
14
+ const {before, after} = test;
15
+ const assert = require('node:assert/strict');
16
+ const path = require('node:path');
17
+ const fs = require('node:fs/promises');
18
+ const {build} = require('esbuild');
19
+ const React = require('react');
20
+ const {renderToStaticMarkup} = require('react-dom/server');
21
+
22
+ const COMPONENT = path.resolve(__dirname, '..', 'MockScene.jsx');
23
+ const PRESET_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
24
+ const SCRATCH = path.join(PRESET_ROOT, '.tmp-mock-scene-test');
25
+
26
+ const ITEMS = [
27
+ {type: 'widget', kind: 'nextcloud-mail', x: 0, y: 0, size: 'sm'},
28
+ {type: 'sidebar', kind: 'openregister-metadata', x: 220, y: 30, size: 'md'},
29
+ {type: 'widget', kind: 'openregister-activity', x: 540, y: 80, size: 'md'},
30
+ ];
31
+
32
+ const cssModuleStub = {
33
+ name: 'css-module-stub',
34
+ setup(b) {
35
+ b.onResolve({filter: /\.module\.css$/}, (args) => ({path: args.path, namespace: 'css-stub'}));
36
+ b.onLoad({filter: /.*/, namespace: 'css-stub'}, () => ({
37
+ contents: 'export default new Proxy({}, {get: (_, p) => p});',
38
+ loader: 'js',
39
+ }));
40
+ },
41
+ };
42
+
43
+ let MockScene;
44
+
45
+ before(async () => {
46
+ await fs.mkdir(SCRATCH, {recursive: true});
47
+ const tmpDir = await fs.mkdtemp(path.join(SCRATCH, 'run-'));
48
+ const outFile = path.join(tmpDir, 'bundle.cjs');
49
+ await build({
50
+ entryPoints: [COMPONENT],
51
+ outfile: outFile,
52
+ bundle: true,
53
+ format: 'cjs',
54
+ jsx: 'automatic',
55
+ jsxImportSource: 'react',
56
+ tsconfigRaw: {compilerOptions: {jsx: 'react-jsx', jsxImportSource: 'react'}},
57
+ platform: 'node',
58
+ external: ['react'],
59
+ plugins: [cssModuleStub],
60
+ logLevel: 'warning',
61
+ });
62
+ delete require.cache[require.resolve(outFile)];
63
+ MockScene = require(outFile).default;
64
+ });
65
+
66
+ after(async () => {
67
+ await fs.rm(SCRATCH, {recursive: true, force: true});
68
+ });
69
+
70
+ function render(props) {
71
+ return renderToStaticMarkup(React.createElement(MockScene, props));
72
+ }
73
+
74
+ test('SSR smoke: renders the scene frame with one wrapper per item', () => {
75
+ const html = render({items: ITEMS});
76
+ assert.match(html, /class="sceneFrame"/);
77
+ assert.equal((html.match(/class="sceneItem"/g) || []).length, ITEMS.length);
78
+ });
79
+
80
+ test('positioning: inline left/top from item x/y are preserved (animation is transform-only)', () => {
81
+ const html = render({items: ITEMS});
82
+ assert.match(html, /left:220px;top:30px/);
83
+ assert.match(html, /left:540px;top:80px/);
84
+ });
85
+
86
+ test('landing stagger: each item carries its 220ms-step animation-delay', () => {
87
+ const html = render({items: ITEMS});
88
+ for (let i = 0; i < ITEMS.length; i++) {
89
+ assert.match(html, new RegExp(`animation-delay:${i * 220}ms`), `item ${i}: missing delay`);
90
+ }
91
+ });
92
+
93
+ test('loop period: --ms-dur on the frame derives from the item count (+ hold)', () => {
94
+ const html = render({items: ITEMS});
95
+ assert.match(html, new RegExp(`--ms-dur:${ITEMS.length * 220 + 5600}ms`));
96
+ });
97
+
98
+ test('P0 plumbing: running={false} adds the static class to the scene frame; default omits it', () => {
99
+ assert.match(render({items: ITEMS, running: false}), /class="sceneFrame static"/);
100
+ assert.doesNotMatch(render({items: ITEMS}), /sceneFrame static/);
101
+ });