@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.
- package/MISSING_COMPONENTS.md +7 -6
- package/package.json +1 -1
- package/src/components/AgentTrace/AgentTrace.jsx +26 -5
- package/src/components/AgentTrace/AgentTrace.module.css +41 -2
- package/src/components/AgentTrace/__tests__/AgentTrace.render.test.js +108 -0
- package/src/components/AppMock/AppMock.jsx +14 -5
- package/src/components/AppMock/AppMock.module.css +439 -0
- package/src/components/AppMock/__tests__/AppMock.render.test.js +181 -0
- package/src/components/AppMock/variants/LarpingAppMock.jsx +23 -4
- package/src/components/AppMock/variants/LaunchPadBiMock.jsx +12 -5
- package/src/components/AppMock/variants/LaunchPadTilesMock.jsx +6 -1
- package/src/components/AppMock/variants/LaunchPadWidgetsMock.jsx +17 -8
- package/src/components/AppMock/variants/NLDesignMock.jsx +15 -4
- package/src/components/AppMock/variants/PipelinQMock.jsx +12 -2
- package/src/components/AppMock/variants/PortaliqMock.jsx +21 -9
- package/src/components/AppMock/variants/ScholiqMock.jsx +12 -6
- package/src/components/FlowMock/FlowMock.jsx +5 -3
- package/src/components/FlowMock/FlowMock.module.css +36 -9
- package/src/components/FlowMock/__tests__/FlowMock.render.test.js +83 -0
- package/src/components/KanbanMock/KanbanMock.jsx +35 -5
- package/src/components/KanbanMock/KanbanMock.module.css +120 -28
- package/src/components/KanbanMock/__tests__/KanbanMock.render.test.js +114 -0
- package/src/components/LeafMock/LeafMock.jsx +23 -8
- package/src/components/LeafMock/LeafMock.module.css +123 -0
- package/src/components/LeafMock/__tests__/LeafMock.render.test.js +105 -0
- package/src/components/MockScene/MockScene.jsx +16 -2
- package/src/components/MockScene/MockScene.module.css +22 -2
- package/src/components/MockScene/__tests__/MockScene.render.test.js +101 -0
- package/src/components/WidgetShelf/WidgetShelf.jsx +321 -28
- package/src/components/WidgetShelf/WidgetShelf.module.css +193 -1
- package/src/components/WidgetShelf/__tests__/WidgetShelf.render.test.js +149 -0
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
* on a register without ETL. The mock shows four graph cards in a
|
|
6
6
|
* 2×2 grid (bar chart, line chart, donut KPI, second bar with a
|
|
7
7
|
* different accent), plus a ranked-list table widget on the right.
|
|
8
|
+
*
|
|
9
|
+
* Animated (9s loop, --am-dur): the charts draw themselves — bars
|
|
10
|
+
* grow from the axis in a stagger, the line draws along its path
|
|
11
|
+
* (pathLength="1" dash technique) and its end dot pops, the donut
|
|
12
|
+
* sweeps in, and the KPI delta bars fade up (inline delays).
|
|
13
|
+
* `running={false}` / prefers-reduced-motion show the drawn charts.
|
|
14
|
+
* Keyframes live in AppMock.module.css (`.lpBi` section).
|
|
8
15
|
*/
|
|
9
16
|
|
|
10
17
|
import React from 'react';
|
|
@@ -20,7 +27,7 @@ export default function LaunchPadBiMock() {
|
|
|
20
27
|
<div className={styles.bell}></div>
|
|
21
28
|
<div className={styles.avatar}></div>
|
|
22
29
|
</div>
|
|
23
|
-
<div className={[styles.body, styles.launchpad].filter(Boolean).join(' ')}>
|
|
30
|
+
<div className={[styles.body, styles.launchpad, styles.lpBi].filter(Boolean).join(' ')}>
|
|
24
31
|
<div className={styles.grid}>
|
|
25
32
|
{/* Column 1: Bar chart on top, line chart below */}
|
|
26
33
|
<div className={styles.col}>
|
|
@@ -45,7 +52,7 @@ export default function LaunchPadBiMock() {
|
|
|
45
52
|
<div className={styles.chart}>
|
|
46
53
|
<svg viewBox="0 0 100 40" preserveAspectRatio="none">
|
|
47
54
|
<path className={styles.fill} d="M0,32 L14,24 L28,28 L42,18 L56,22 L70,12 L84,16 L100,8 L100,40 L0,40 Z"/>
|
|
48
|
-
<path className={styles.stroke} d="M0,32 L14,24 L28,28 L42,18 L56,22 L70,12 L84,16 L100,8"/>
|
|
55
|
+
<path className={styles.stroke} pathLength="1" d="M0,32 L14,24 L28,28 L42,18 L56,22 L70,12 L84,16 L100,8"/>
|
|
49
56
|
<circle className={styles.dot} cx="100" cy="8" r="2.5"/>
|
|
50
57
|
</svg>
|
|
51
58
|
</div>
|
|
@@ -89,7 +96,7 @@ export default function LaunchPadBiMock() {
|
|
|
89
96
|
</div>
|
|
90
97
|
<div style={{display: 'flex', alignItems: 'baseline', gap: 4}}>
|
|
91
98
|
<div style={{height: 12, width: 28, background: 'var(--c-cobalt-900)', borderRadius: 1}}></div>
|
|
92
|
-
<div style={{height: 4, width: 16, background: 'var(--c-mint-500)', borderRadius: 1}}></div>
|
|
99
|
+
<div className={styles.biDelta} style={{height: 4, width: 16, background: 'var(--c-mint-500)', borderRadius: 1}}></div>
|
|
93
100
|
</div>
|
|
94
101
|
<div style={{height: 3, width: '70%', background: 'var(--c-cobalt-200)', borderRadius: 1}}></div>
|
|
95
102
|
</div>
|
|
@@ -99,7 +106,7 @@ export default function LaunchPadBiMock() {
|
|
|
99
106
|
</div>
|
|
100
107
|
<div style={{display: 'flex', alignItems: 'baseline', gap: 4}}>
|
|
101
108
|
<div style={{height: 12, width: 22, background: 'var(--c-cobalt-900)', borderRadius: 1}}></div>
|
|
102
|
-
<div style={{height: 4, width: 14, background: 'var(--c-orange-knvb)', borderRadius: 1}}></div>
|
|
109
|
+
<div className={styles.biDelta} style={{height: 4, width: 14, background: 'var(--c-orange-knvb)', borderRadius: 1, animationDelay: '0.2s'}}></div>
|
|
103
110
|
</div>
|
|
104
111
|
<div style={{height: 3, width: '60%', background: 'var(--c-cobalt-200)', borderRadius: 1}}></div>
|
|
105
112
|
</div>
|
|
@@ -109,7 +116,7 @@ export default function LaunchPadBiMock() {
|
|
|
109
116
|
</div>
|
|
110
117
|
<div style={{display: 'flex', alignItems: 'baseline', gap: 4}}>
|
|
111
118
|
<div style={{height: 12, width: 30, background: 'var(--c-cobalt-900)', borderRadius: 1}}></div>
|
|
112
|
-
<div style={{height: 4, width: 12, background: 'var(--c-mint-500)', borderRadius: 1}}></div>
|
|
119
|
+
<div className={styles.biDelta} style={{height: 4, width: 12, background: 'var(--c-mint-500)', borderRadius: 1, animationDelay: '0.4s'}}></div>
|
|
113
120
|
</div>
|
|
114
121
|
<div style={{height: 3, width: '80%', background: 'var(--c-cobalt-200)', borderRadius: 1}}></div>
|
|
115
122
|
</div>
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* a sub-grid of further tiles. The mock pictures exactly that mix:
|
|
8
8
|
* four launcher tiles top-left, a 2×2 sub-grid bottom-left ("custom
|
|
9
9
|
* tile group"), and two info widgets right.
|
|
10
|
+
*
|
|
11
|
+
* Animated (7s loop, --am-dur): the custom tile group is pressed
|
|
12
|
+
* (a short scale dip) and unfolds its four sub-tiles in a stagger.
|
|
13
|
+
* `running={false}` / prefers-reduced-motion show the unfolded
|
|
14
|
+
* group. Keyframes live in AppMock.module.css (`.lpTiles` section).
|
|
10
15
|
*/
|
|
11
16
|
|
|
12
17
|
import React from 'react';
|
|
@@ -22,7 +27,7 @@ export default function LaunchPadTilesMock() {
|
|
|
22
27
|
<div className={styles.bell}></div>
|
|
23
28
|
<div className={styles.avatar}></div>
|
|
24
29
|
</div>
|
|
25
|
-
<div className={[styles.body, styles.launchpad].filter(Boolean).join(' ')}>
|
|
30
|
+
<div className={[styles.body, styles.launchpad, styles.lpTiles].filter(Boolean).join(' ')}>
|
|
26
31
|
<div className={styles.grid}>
|
|
27
32
|
{/* Column 1: Launcher tiles in cobalt + KNVB */}
|
|
28
33
|
<div className={styles.col}>
|
|
@@ -11,11 +11,20 @@
|
|
|
11
11
|
* - Jira → external-app status board
|
|
12
12
|
* - RSS → headlines feed
|
|
13
13
|
* - Video → video-call shortcut
|
|
14
|
+
*
|
|
15
|
+
* Animated (10s loop, --am-dur): the dashboard assembles — the seven
|
|
16
|
+
* widgets arrive one by one on a 900ms stagger (inline
|
|
17
|
+
* animation-delay, reading order), hold, then fade for the reset.
|
|
18
|
+
* `running={false}` / prefers-reduced-motion show the assembled
|
|
19
|
+
* board. Keyframes live in AppMock.module.css (`.lpWidgets` section).
|
|
14
20
|
*/
|
|
15
21
|
|
|
16
22
|
import React from 'react';
|
|
17
23
|
import styles from '../AppMock.module.css';
|
|
18
24
|
|
|
25
|
+
/* Per-widget arrival delay on the shared stagger. */
|
|
26
|
+
const ARRIVE = (i) => ({animationDelay: `${i * 900}ms`});
|
|
27
|
+
|
|
19
28
|
export default function LaunchPadWidgetsMock() {
|
|
20
29
|
return (
|
|
21
30
|
<>
|
|
@@ -26,11 +35,11 @@ export default function LaunchPadWidgetsMock() {
|
|
|
26
35
|
<div className={styles.bell}></div>
|
|
27
36
|
<div className={styles.avatar}></div>
|
|
28
37
|
</div>
|
|
29
|
-
<div className={[styles.body, styles.launchpad].filter(Boolean).join(' ')}>
|
|
38
|
+
<div className={[styles.body, styles.launchpad, styles.lpWidgets].filter(Boolean).join(' ')}>
|
|
30
39
|
<div className={styles.grid}>
|
|
31
40
|
{/* Column 1: Procest werkvoorraad + DocuDesk upload */}
|
|
32
41
|
<div className={styles.col}>
|
|
33
|
-
<div className={[styles.w, styles['w-werkvoorraad']].join(' ')}>
|
|
42
|
+
<div className={[styles.w, styles['w-werkvoorraad']].join(' ')} style={ARRIVE(0)}>
|
|
34
43
|
<div className={styles.wHead}>
|
|
35
44
|
<div className={styles.h}></div><div className={styles.t}></div>
|
|
36
45
|
</div>
|
|
@@ -42,7 +51,7 @@ export default function LaunchPadWidgetsMock() {
|
|
|
42
51
|
<div className={styles.item}><div className={styles.stage}></div><div className={styles.l1}></div><div className={[styles.av, styles.b].join(' ')}></div></div>
|
|
43
52
|
</div>
|
|
44
53
|
</div>
|
|
45
|
-
<div className={[styles.w, styles['w-upload']].join(' ')}>
|
|
54
|
+
<div className={[styles.w, styles['w-upload']].join(' ')} style={ARRIVE(1)}>
|
|
46
55
|
<div className={styles.wHead}>
|
|
47
56
|
<div className={styles.h}></div><div className={styles.t}></div>
|
|
48
57
|
</div>
|
|
@@ -53,7 +62,7 @@ export default function LaunchPadWidgetsMock() {
|
|
|
53
62
|
</div>
|
|
54
63
|
</div>
|
|
55
64
|
{/* Column 2: Mail (Important mail) */}
|
|
56
|
-
<div className={[styles.w, styles['w-mail']].join(' ')}>
|
|
65
|
+
<div className={[styles.w, styles['w-mail']].join(' ')} style={ARRIVE(2)}>
|
|
57
66
|
<div className={styles.wHead}>
|
|
58
67
|
<div className={styles.h}></div><div className={styles.t}></div>
|
|
59
68
|
</div>
|
|
@@ -71,7 +80,7 @@ export default function LaunchPadWidgetsMock() {
|
|
|
71
80
|
</div>
|
|
72
81
|
{/* Column 3: Jira status board + RSS feed */}
|
|
73
82
|
<div className={styles.col}>
|
|
74
|
-
<div className={[styles.w, styles['w-jira']].join(' ')}>
|
|
83
|
+
<div className={[styles.w, styles['w-jira']].join(' ')} style={ARRIVE(3)}>
|
|
75
84
|
<div className={styles.wHead}>
|
|
76
85
|
<div className={styles.h}></div><div className={styles.t}></div>
|
|
77
86
|
</div>
|
|
@@ -83,7 +92,7 @@ export default function LaunchPadWidgetsMock() {
|
|
|
83
92
|
<div className={styles.item}><div className={styles.id}></div><div className={styles.l}></div><div className={styles.pip}></div></div>
|
|
84
93
|
</div>
|
|
85
94
|
</div>
|
|
86
|
-
<div className={[styles.w, styles['w-rss']].join(' ')}>
|
|
95
|
+
<div className={[styles.w, styles['w-rss']].join(' ')} style={ARRIVE(4)}>
|
|
87
96
|
<div className={styles.wHead}>
|
|
88
97
|
<div className={styles.h}></div><div className={styles.t}></div>
|
|
89
98
|
</div>
|
|
@@ -96,7 +105,7 @@ export default function LaunchPadWidgetsMock() {
|
|
|
96
105
|
</div>
|
|
97
106
|
{/* Column 4: Calendar + Video call shortcut */}
|
|
98
107
|
<div className={styles.col}>
|
|
99
|
-
<div className={[styles.w, styles['w-calendar']].join(' ')}>
|
|
108
|
+
<div className={[styles.w, styles['w-calendar']].join(' ')} style={ARRIVE(5)}>
|
|
100
109
|
<div className={styles.wHead}>
|
|
101
110
|
<div className={styles.h}></div><div className={styles.t}></div>
|
|
102
111
|
</div>
|
|
@@ -106,7 +115,7 @@ export default function LaunchPadWidgetsMock() {
|
|
|
106
115
|
))}
|
|
107
116
|
</div>
|
|
108
117
|
</div>
|
|
109
|
-
<div className={[styles.w, styles['w-video']].join(' ')}>
|
|
118
|
+
<div className={[styles.w, styles['w-video']].join(' ')} style={ARRIVE(6)}>
|
|
110
119
|
<div className={styles.wHead}>
|
|
111
120
|
<div className={styles.h}></div><div className={styles.t}></div>
|
|
112
121
|
</div>
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
* centre shows a settings layout with a colour-swatch row, a type
|
|
6
6
|
* specimen, and a component preview block. Left nav for theme /
|
|
7
7
|
* colours / typography / spacing / components / overrides.
|
|
8
|
+
*
|
|
9
|
+
* Animated (6s loop, --am-dur): the lavender swatch is selected (a
|
|
10
|
+
* cobalt focus ring pops on it), the component preview rethemes in a
|
|
11
|
+
* cascade (button, then status pill crossfade to lavender), and the
|
|
12
|
+
* type-scale bars restretch as if the theme changed the scale. The
|
|
13
|
+
* loop reverts by crossfading back. `running={false}` /
|
|
14
|
+
* prefers-reduced-motion show the resting cobalt/mint theme with no
|
|
15
|
+
* ring. Keyframes live in AppMock.module.css (`.nldesign` section).
|
|
8
16
|
*/
|
|
9
17
|
|
|
10
18
|
import React from 'react';
|
|
@@ -20,7 +28,7 @@ export default function NLDesignMock() {
|
|
|
20
28
|
<div className={styles.bell}></div>
|
|
21
29
|
<div className={styles.avatar}></div>
|
|
22
30
|
</div>
|
|
23
|
-
<div className={[styles.body, styles.openregister].filter(Boolean).join(' ')}>
|
|
31
|
+
<div className={[styles.body, styles.openregister, styles.nldesign].filter(Boolean).join(' ')}>
|
|
24
32
|
<div className={styles.nav}>
|
|
25
33
|
<div className={styles.navHead}><div className={styles.h}></div><div className={styles.l}></div></div>
|
|
26
34
|
{[true, false, false, false, false, false].map((active, i) => (
|
|
@@ -35,15 +43,16 @@ export default function NLDesignMock() {
|
|
|
35
43
|
<div className={styles.panel}>
|
|
36
44
|
<div className={styles.head}><div className={styles.title}></div></div>
|
|
37
45
|
<div style={{display: 'flex', gap: 6, flexWrap: 'wrap'}}>
|
|
46
|
+
{/* Index 3 (lavender) is the swatch the animation selects. */}
|
|
38
47
|
{['var(--c-blue-cobalt)','var(--c-orange-knvb)','var(--c-mint-500)','var(--c-lavender-500)','var(--c-forest-500)','var(--c-terracotta-500)','var(--c-cobalt-300)','var(--c-cobalt-100)'].map((bg, i) => (
|
|
39
|
-
<span key={i}
|
|
48
|
+
<span key={i} className={[styles.swatch, i === 3 && styles.swatchSel].filter(Boolean).join(' ')} style={{background: bg}}></span>
|
|
40
49
|
))}
|
|
41
50
|
</div>
|
|
42
51
|
</div>
|
|
43
52
|
{/* Type specimen */}
|
|
44
53
|
<div className={styles.panel}>
|
|
45
54
|
<div className={styles.head}><div className={styles.title}></div></div>
|
|
46
|
-
<div style={{display: 'flex', alignItems: 'baseline', gap: 8}}>
|
|
55
|
+
<div className={styles.typeRow} style={{display: 'flex', alignItems: 'baseline', gap: 8}}>
|
|
47
56
|
<div style={{height: 14, width: 18, background: 'var(--c-cobalt-900)', borderRadius: 1}}></div>
|
|
48
57
|
<div style={{height: 11, width: 14, background: 'var(--c-cobalt-700)', borderRadius: 1}}></div>
|
|
49
58
|
<div style={{height: 8, width: 12, background: 'var(--c-cobalt-400)', borderRadius: 1}}></div>
|
|
@@ -56,7 +65,9 @@ export default function NLDesignMock() {
|
|
|
56
65
|
{/* Component preview */}
|
|
57
66
|
<div className={styles.panel}>
|
|
58
67
|
<div className={styles.head}><div className={styles.title}></div></div>
|
|
59
|
-
|
|
68
|
+
{/* The .actions wrapper is required for .btn to pick up its
|
|
69
|
+
styles (the buttons rendered invisible without it). */}
|
|
70
|
+
<div className={styles.actions} style={{alignItems: 'center', flexWrap: 'wrap'}}>
|
|
60
71
|
<div className={styles.btn}></div>
|
|
61
72
|
<div className={styles.btn + ' ' + styles.ghost}></div>
|
|
62
73
|
<div className={styles.statusPill}><div className={styles.h}></div><div className={styles.t}></div></div>
|
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
* (lead, qualified, proposal, won, lost) plus a KPI strip on top
|
|
7
7
|
* (pipeline value, win rate, deals this week). Left nav for kanban /
|
|
8
8
|
* customers / contacts / deals / quotes.
|
|
9
|
+
*
|
|
10
|
+
* Animated (7s loop, --am-dur): a deal closes — the proposal-column
|
|
11
|
+
* card collapses, the won card scales in with its mint bar, and the
|
|
12
|
+
* pipeline-value KPI widens. Resets by opacity swap. `running={false}`
|
|
13
|
+
* / prefers-reduced-motion show the closed state (won card in place,
|
|
14
|
+
* proposal card gone). Keyframes live in AppMock.module.css (`.plq`
|
|
15
|
+
* section).
|
|
9
16
|
*/
|
|
10
17
|
|
|
11
18
|
import React from 'react';
|
|
@@ -21,7 +28,7 @@ export default function PipelinQMock() {
|
|
|
21
28
|
<div className={styles.bell}></div>
|
|
22
29
|
<div className={styles.avatar}></div>
|
|
23
30
|
</div>
|
|
24
|
-
<div className={[styles.body, styles.decidesk].filter(Boolean).join(' ')}>
|
|
31
|
+
<div className={[styles.body, styles.decidesk, styles.plq].filter(Boolean).join(' ')}>
|
|
25
32
|
<div className={styles.nav}>
|
|
26
33
|
<div className={styles.navHead}><div className={styles.h}></div><div className={styles.l}></div></div>
|
|
27
34
|
{[true, false, false, false, false].map((active, i) => (
|
|
@@ -71,9 +78,12 @@ export default function PipelinQMock() {
|
|
|
71
78
|
<div className={styles.col}>
|
|
72
79
|
<div className={[styles.card, styles.c].join(' ')}></div>
|
|
73
80
|
<div className={styles.card}></div>
|
|
74
|
-
|
|
81
|
+
{/* The deal being closed — collapses out of proposal. */}
|
|
82
|
+
<div className={[styles.card, styles.dealGone].join(' ')}></div>
|
|
75
83
|
</div>
|
|
76
84
|
<div className={styles.col}>
|
|
85
|
+
{/* …and lands here as the won card (mint bar). */}
|
|
86
|
+
<div className={[styles.card, styles.dealWon].join(' ')}></div>
|
|
77
87
|
<div className={[styles.card, styles.c].join(' ')}></div>
|
|
78
88
|
</div>
|
|
79
89
|
<div className={styles.col}>
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
* row, then a submission form card next to a message inbox list. No
|
|
7
7
|
* left nav — the visitor gets one page, not an admin surface. Tones:
|
|
8
8
|
* cobalt for the frame, mint for the delivered/confirmed states.
|
|
9
|
+
*
|
|
10
|
+
* Animated (8s loop, --am-dur): a visitor works the form — focus ring
|
|
11
|
+
* and greeked value fill each field in turn, the submit button
|
|
12
|
+
* presses, a mint confirmation band sweeps the form card, and the
|
|
13
|
+
* message lands as a new row (mint avatar) at the top of the inbox.
|
|
14
|
+
* `running={false}` / prefers-reduced-motion show the static end
|
|
15
|
+
* state: filled form, delivered row. Keyframes live in
|
|
16
|
+
* AppMock.module.css (`.portaliq` section). No orange.
|
|
9
17
|
*/
|
|
10
18
|
|
|
11
19
|
import React from 'react';
|
|
@@ -21,7 +29,7 @@ export default function PortaliqMock() {
|
|
|
21
29
|
{Array.from({length: 3}).map((_, i) => <div key={i} className={styles.icon}></div>)}
|
|
22
30
|
<div className={styles.avatar}></div>
|
|
23
31
|
</div>
|
|
24
|
-
<div className={[styles.body, styles.decidesk].filter(Boolean).join(' ')}>
|
|
32
|
+
<div className={[styles.body, styles.decidesk, styles.portaliq].filter(Boolean).join(' ')}>
|
|
25
33
|
<div className={styles.col}>
|
|
26
34
|
{/* Portal masthead */}
|
|
27
35
|
<div className={styles.head}>
|
|
@@ -32,24 +40,28 @@ export default function PortaliqMock() {
|
|
|
32
40
|
</div>
|
|
33
41
|
</div>
|
|
34
42
|
<div className={styles.panelRow} style={{gridTemplateColumns: '1.2fr 1fr'}}>
|
|
35
|
-
{/* Submission form card
|
|
36
|
-
|
|
43
|
+
{/* Submission form card. Each .field carries a .fieldFill
|
|
44
|
+
bar that greeks the typed value; the sweep is the mint
|
|
45
|
+
confirmation band after submit. */}
|
|
46
|
+
<div className={[styles.panel, styles.formPanel].join(' ')}>
|
|
37
47
|
<div className={styles.head}><div className={styles.title}></div></div>
|
|
38
48
|
<div className={styles.row + ' ' + styles.head} style={{width: '25%', height: 4}}></div>
|
|
39
|
-
<div
|
|
49
|
+
<div className={[styles.field, styles['field-1']].join(' ')}><span className={styles.fieldFill}></span></div>
|
|
40
50
|
<div className={styles.row + ' ' + styles.head} style={{width: '30%', height: 4}}></div>
|
|
41
|
-
<div
|
|
51
|
+
<div className={[styles.field, styles['field-2']].join(' ')}><span className={styles.fieldFill}></span></div>
|
|
42
52
|
<div className={styles.row + ' ' + styles.head} style={{width: '20%', height: 4}}></div>
|
|
43
|
-
<div
|
|
53
|
+
<div className={[styles.field, styles['field-3'], styles.tall].join(' ')}><span className={styles.fieldFill}></span></div>
|
|
44
54
|
<div className={styles.actions} style={{justifyContent: 'flex-end', marginTop: 2}}>
|
|
45
|
-
<div className={styles.btn}></div>
|
|
55
|
+
<div className={[styles.btn, styles.submit].join(' ')}></div>
|
|
46
56
|
</div>
|
|
57
|
+
<span className={styles.confirmSweep} aria-hidden="true"></span>
|
|
47
58
|
</div>
|
|
48
|
-
{/* Message inbox list
|
|
59
|
+
{/* Message inbox list — the .newItem row is the submitted
|
|
60
|
+
message arriving (mint avatar = delivered). */}
|
|
49
61
|
<div className={[styles.panel, styles['w-mail']].join(' ')}>
|
|
50
62
|
<div className={styles.head}><div className={styles.title}></div></div>
|
|
51
63
|
<div className={styles.list}>
|
|
52
|
-
<div className={styles.item}><div className={styles.av}></div><div className={styles.lines}><div className={styles.l1}></div><div className={styles.l2}></div></div></div>
|
|
64
|
+
<div className={[styles.item, styles.newItem].join(' ')}><div className={styles.av}></div><div className={styles.lines}><div className={styles.l1}></div><div className={styles.l2}></div></div></div>
|
|
53
65
|
<div className={styles.item}><div className={styles.av}></div><div className={styles.lines}><div className={styles.l1}></div><div className={styles.l2}></div></div></div>
|
|
54
66
|
<div className={styles.item}><div className={styles.av}></div><div className={styles.lines}><div className={styles.l1}></div><div className={styles.l2}></div></div></div>
|
|
55
67
|
<div className={styles.item}><div className={styles.av}></div><div className={styles.lines}><div className={styles.l1}></div><div className={styles.l2}></div></div></div>
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
* credential chip on the completed course. Left nav for courses /
|
|
7
7
|
* lessons / learners / certificates. Tone: forest — data you can
|
|
8
8
|
* trust a diploma to.
|
|
9
|
+
*
|
|
10
|
+
* Animated (6s loop, --am-dur): the six progress bars fill on a
|
|
11
|
+
* 250ms stagger (inline animation-delay), then the completed course's
|
|
12
|
+
* credential chip pops in with an overshoot. `running={false}` /
|
|
13
|
+
* prefers-reduced-motion show the filled bars and the chip. Keyframes
|
|
14
|
+
* live in AppMock.module.css (`.scholiq` section). No orange.
|
|
9
15
|
*/
|
|
10
16
|
|
|
11
17
|
import React from 'react';
|
|
@@ -30,7 +36,7 @@ export default function ScholiqMock() {
|
|
|
30
36
|
<div className={styles.bell}></div>
|
|
31
37
|
<div className={styles.avatar}></div>
|
|
32
38
|
</div>
|
|
33
|
-
<div className={[styles.body, styles.opencatalogi].filter(Boolean).join(' ')}>
|
|
39
|
+
<div className={[styles.body, styles.opencatalogi, styles.scholiq].filter(Boolean).join(' ')}>
|
|
34
40
|
<div className={styles.nav}>
|
|
35
41
|
<div className={styles.navHead}><div className={styles.h}></div><div className={styles.l}></div></div>
|
|
36
42
|
{[true, false, false, false].map((active, i) => (
|
|
@@ -54,14 +60,14 @@ export default function ScholiqMock() {
|
|
|
54
60
|
<div className={styles.ico}></div>
|
|
55
61
|
<div className={styles.row}></div>
|
|
56
62
|
<div className={styles.row + ' ' + styles.short}></div>
|
|
57
|
-
<div
|
|
58
|
-
<div style={{width: fill,
|
|
63
|
+
<div className={styles.progress}>
|
|
64
|
+
<div className={styles.progressFill} style={{width: fill, animationDelay: `${i * 250}ms`}}></div>
|
|
59
65
|
</div>
|
|
60
66
|
{done && (
|
|
61
67
|
/* Credential chip — course completed, certificate issued */
|
|
62
|
-
<div
|
|
63
|
-
<div
|
|
64
|
-
<div
|
|
68
|
+
<div className={styles.credChip}>
|
|
69
|
+
<div className={styles.credHex}></div>
|
|
70
|
+
<div className={styles.credBar}></div>
|
|
65
71
|
</div>
|
|
66
72
|
)}
|
|
67
73
|
</div>
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
* Five nodes: trigger → two parallel steps → approval (join) → end.
|
|
13
13
|
* A mint run-line travels the trigger → step → approval → end route
|
|
14
14
|
* on a slow loop, with a subtle halo pulse on each node as the line
|
|
15
|
-
* passes.
|
|
16
|
-
*
|
|
15
|
+
* passes. At the approval node the run HOLDS for ~600ms while an
|
|
16
|
+
* orange halo flashes — a human decides here. `prefers-reduced-motion:
|
|
17
|
+
* reduce` (and `running={false}`) renders the full route as a static
|
|
18
|
+
* mint path with no animation and no flash.
|
|
17
19
|
*
|
|
18
20
|
* Tokens only; no images; rectangles (the nodes *do* work, so no
|
|
19
|
-
* hexes);
|
|
21
|
+
* hexes); the approval hold flash is the single orange accent.
|
|
20
22
|
*
|
|
21
23
|
* Usage:
|
|
22
24
|
*
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
* transparent to layout.
|
|
10
10
|
*
|
|
11
11
|
* Tokens only. Node bars: mint = trigger, lavender = steps/approval,
|
|
12
|
-
* vermillion = end. The run-line is mint (--c-mint-500)
|
|
12
|
+
* vermillion = end. The run-line is mint (--c-mint-500). The run
|
|
13
|
+
* holds ~600ms at the approval node while an orange halo flashes
|
|
14
|
+
* there — "a human decides here" — and that flash is this
|
|
15
|
+
* component's single orange accent.
|
|
13
16
|
*/
|
|
14
17
|
|
|
15
18
|
.fm { display: contents; }
|
|
@@ -102,7 +105,13 @@
|
|
|
102
105
|
* travels the full route once per cycle. Animating the offset from
|
|
103
106
|
* 1.28 to 0 shifts the pattern forward exactly one period, so the
|
|
104
107
|
* loop is seamless (the segment exits the end node as its successor
|
|
105
|
-
* enters the trigger).
|
|
108
|
+
* enters the trigger).
|
|
109
|
+
*
|
|
110
|
+
* The offset keyframes are not linear end-to-end: the run reaches the
|
|
111
|
+
* approval node at 58% (offset 1.28 × (1 − 0.62) ≈ 0.487, the node
|
|
112
|
+
* sitting ~62% along the route) and HOLDS there for ~600ms (8.6% of
|
|
113
|
+
* the 7s cycle) — approval is where a human decides — before
|
|
114
|
+
* continuing to the end node. */
|
|
106
115
|
|
|
107
116
|
.fm .runline {
|
|
108
117
|
fill: none;
|
|
@@ -115,13 +124,17 @@
|
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
@keyframes fmRun {
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
0% { stroke-dashoffset: 1.28; }
|
|
128
|
+
58% { stroke-dashoffset: 0.487; } /* segment reaches the approval node */
|
|
129
|
+
66.6% { stroke-dashoffset: 0.487; } /* ~600ms hold: a human decides */
|
|
130
|
+
100% { stroke-dashoffset: 0; }
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
/* Halo pulse on the nodes the run-line passes. Delays approximate
|
|
123
|
-
where along the
|
|
124
|
-
approval
|
|
134
|
+
where along the (held) cycle each node sits: trigger 0%, step ~28%,
|
|
135
|
+
approval 58% (the hold), end ~87%. The approval halo is the
|
|
136
|
+
exception: it flashes KNVB orange for the length of the hold — the
|
|
137
|
+
component's one orange accent. */
|
|
125
138
|
.fm .halo {
|
|
126
139
|
fill: none;
|
|
127
140
|
stroke: var(--c-mint-500);
|
|
@@ -130,9 +143,13 @@
|
|
|
130
143
|
animation: fmPulse var(--fm-dur, 7s) linear infinite;
|
|
131
144
|
}
|
|
132
145
|
.fm .halo-0 { animation-delay: 0s; }
|
|
133
|
-
.fm .halo-1 { animation-delay: calc(var(--fm-dur, 7s) * 0.
|
|
134
|
-
.fm .halo-2 {
|
|
135
|
-
|
|
146
|
+
.fm .halo-1 { animation-delay: calc(var(--fm-dur, 7s) * 0.28); }
|
|
147
|
+
.fm .halo-2 {
|
|
148
|
+
stroke: var(--c-orange-knvb);
|
|
149
|
+
animation-name: fmPulseHold;
|
|
150
|
+
animation-delay: calc(var(--fm-dur, 7s) * 0.58);
|
|
151
|
+
}
|
|
152
|
+
.fm .halo-3 { animation-delay: calc(var(--fm-dur, 7s) * 0.87); }
|
|
136
153
|
|
|
137
154
|
@keyframes fmPulse {
|
|
138
155
|
0% { opacity: 0; }
|
|
@@ -141,6 +158,16 @@
|
|
|
141
158
|
100% { opacity: 0; }
|
|
142
159
|
}
|
|
143
160
|
|
|
161
|
+
/* The approval flash spans the whole ~600ms hold (8.6% of the cycle)
|
|
162
|
+
instead of the quick pass-through pulse. */
|
|
163
|
+
@keyframes fmPulseHold {
|
|
164
|
+
0% { opacity: 0; }
|
|
165
|
+
1.5% { opacity: 0.8; }
|
|
166
|
+
8% { opacity: 0.8; }
|
|
167
|
+
11% { opacity: 0; }
|
|
168
|
+
100% { opacity: 0; }
|
|
169
|
+
}
|
|
170
|
+
|
|
144
171
|
/* ---- Static renderings ----
|
|
145
172
|
*
|
|
146
173
|
* Both `running={false}` (the .static class) and the user's
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowMock.render.test.js — SSR-renders the real <FlowMock> and
|
|
3
|
+
* asserts the canvas structure the approval-hold animation relies on:
|
|
4
|
+
* the run-line with pathLength="1", the four halo rects (halo-2 being
|
|
5
|
+
* the approval flash), and the `running` prop's static class. Same
|
|
6
|
+
* esbuild harness as KanbanMock.render.test.js; CSS modules stubbed
|
|
7
|
+
* to identity.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const test = require('node:test');
|
|
13
|
+
const {before, after} = test;
|
|
14
|
+
const assert = require('node:assert/strict');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
const fs = require('node:fs/promises');
|
|
17
|
+
const {build} = require('esbuild');
|
|
18
|
+
const React = require('react');
|
|
19
|
+
const {renderToStaticMarkup} = require('react-dom/server');
|
|
20
|
+
|
|
21
|
+
const COMPONENT = path.resolve(__dirname, '..', 'FlowMock.jsx');
|
|
22
|
+
const PRESET_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
|
|
23
|
+
const SCRATCH = path.join(PRESET_ROOT, '.tmp-flow-mock-test');
|
|
24
|
+
|
|
25
|
+
const cssModuleStub = {
|
|
26
|
+
name: 'css-module-stub',
|
|
27
|
+
setup(b) {
|
|
28
|
+
b.onResolve({filter: /\.module\.css$/}, (args) => ({path: args.path, namespace: 'css-stub'}));
|
|
29
|
+
b.onLoad({filter: /.*/, namespace: 'css-stub'}, () => ({
|
|
30
|
+
contents: 'export default new Proxy({}, {get: (_, p) => p});',
|
|
31
|
+
loader: 'js',
|
|
32
|
+
}));
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let FlowMock;
|
|
37
|
+
|
|
38
|
+
before(async () => {
|
|
39
|
+
await fs.mkdir(SCRATCH, {recursive: true});
|
|
40
|
+
const tmpDir = await fs.mkdtemp(path.join(SCRATCH, 'run-'));
|
|
41
|
+
const outFile = path.join(tmpDir, 'bundle.cjs');
|
|
42
|
+
await build({
|
|
43
|
+
entryPoints: [COMPONENT],
|
|
44
|
+
outfile: outFile,
|
|
45
|
+
bundle: true,
|
|
46
|
+
format: 'cjs',
|
|
47
|
+
jsx: 'automatic',
|
|
48
|
+
jsxImportSource: 'react',
|
|
49
|
+
tsconfigRaw: {compilerOptions: {jsx: 'react-jsx', jsxImportSource: 'react'}},
|
|
50
|
+
platform: 'node',
|
|
51
|
+
external: ['react'],
|
|
52
|
+
plugins: [cssModuleStub],
|
|
53
|
+
logLevel: 'warning',
|
|
54
|
+
});
|
|
55
|
+
delete require.cache[require.resolve(outFile)];
|
|
56
|
+
FlowMock = require(outFile).default;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
after(async () => {
|
|
60
|
+
await fs.rm(SCRATCH, {recursive: true, force: true});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function render(props) {
|
|
64
|
+
return renderToStaticMarkup(React.createElement(FlowMock, props));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
test('SSR smoke: renders the canvas with the run-line on pathLength="1"', () => {
|
|
68
|
+
const html = render({});
|
|
69
|
+
assert.match(html, /class="canvas"/);
|
|
70
|
+
assert.match(html, /class="runline"[^>]*pathLength="1"/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('halos: four pulse rects, halo-2 (the approval hold flash) among them', () => {
|
|
74
|
+
const html = render({});
|
|
75
|
+
for (const i of [0, 1, 2, 3]) {
|
|
76
|
+
assert.match(html, new RegExp(`halo halo-${i}`), `missing halo-${i}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('running={false} renders the static frame class', () => {
|
|
81
|
+
assert.match(render({running: false}), /class="frame size-md static"/);
|
|
82
|
+
assert.doesNotMatch(render({}), /\bstatic\b/);
|
|
83
|
+
});
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
* Token-built abstract of the pipelinq kanban board with a looping
|
|
5
5
|
* drag animation: a cursor picks up the top lead card in the first
|
|
6
6
|
* column (the card lifts), drags it to the second column, drops it,
|
|
7
|
-
* and the column counts nudge (3→2, 2→3)
|
|
8
|
-
*
|
|
7
|
+
* and at the drop moment the column counts nudge (3→2, 2→3) and the
|
|
8
|
+
* per-lane € KPI tiles above the board shift the moved deal's value
|
|
9
|
+
* (€ 48k→€ 30k, € 27k→€ 45k; the third lane stays € 64k). Loops
|
|
10
|
+
* every ~7 seconds and resets cleanly at loop start. Pipelinq's
|
|
11
|
+
* hero/panel illustration.
|
|
9
12
|
*
|
|
10
13
|
* Named KanbanMock rather than PipelineMock because the preset
|
|
11
14
|
* already ships a `Pipeline` component (the hex-numbered process
|
|
@@ -48,8 +51,8 @@ function CardGreek({wide, dot}) {
|
|
|
48
51
|
|
|
49
52
|
function Count({from, to}) {
|
|
50
53
|
/* Two stacked digits crossfading on the shared timeline: `from`
|
|
51
|
-
shows until the drop (
|
|
52
|
-
next loop. Static renderings show `from`. */
|
|
54
|
+
shows until the drop settles (54%), `to` after it, back again for
|
|
55
|
+
the next loop. Static renderings show `from`. */
|
|
53
56
|
return (
|
|
54
57
|
<span className={styles.count}>
|
|
55
58
|
<span className={styles.countBefore}>{from}</span>
|
|
@@ -58,6 +61,28 @@ function Count({from, to}) {
|
|
|
58
61
|
);
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
function Kpi({from, to}) {
|
|
65
|
+
/* Small € tile above a lane. Reuses the count crossfade classes so
|
|
66
|
+
the value swaps at the same drop moment as the column counts and
|
|
67
|
+
shows `from` in static renderings. A `to` of undefined renders a
|
|
68
|
+
lane whose total never changes. */
|
|
69
|
+
return (
|
|
70
|
+
<div className={styles.kpi}>
|
|
71
|
+
<span className={styles.kpiBar} />
|
|
72
|
+
<span className={styles.kpiValue}>
|
|
73
|
+
{to != null ? (
|
|
74
|
+
<>
|
|
75
|
+
<span className={styles.countBefore}>{from}</span>
|
|
76
|
+
<span className={styles.countAfter}>{to}</span>
|
|
77
|
+
</>
|
|
78
|
+
) : (
|
|
79
|
+
<span className={styles.kpiStill}>{from}</span>
|
|
80
|
+
)}
|
|
81
|
+
</span>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
export default function KanbanMock({size = 'md', caption, running = true, className}) {
|
|
62
87
|
return (
|
|
63
88
|
<div className={styles.km}>
|
|
@@ -65,9 +90,14 @@ export default function KanbanMock({size = 'md', caption, running = true, classN
|
|
|
65
90
|
<div
|
|
66
91
|
className={[styles.frame, styles[`size-${size}`], !running && styles.static].filter(Boolean).join(' ')}
|
|
67
92
|
role="img"
|
|
68
|
-
aria-label="Abstract kanban board: a lead card is dragged from the first column to the second
|
|
93
|
+
aria-label="Abstract kanban board: a lead card is dragged from the first column to the second; the column counts and the lane value totals update at the drop."
|
|
69
94
|
>
|
|
70
95
|
<div className={styles.board}>
|
|
96
|
+
<div className={styles.kpis}>
|
|
97
|
+
<Kpi from="€ 48k" to="€ 30k" />
|
|
98
|
+
<Kpi from="€ 27k" to="€ 45k" />
|
|
99
|
+
<Kpi from="€ 64k" />
|
|
100
|
+
</div>
|
|
71
101
|
<div className={styles.col}>
|
|
72
102
|
<div className={styles.colHead}>
|
|
73
103
|
<span className={styles.colBar} />
|