@conduction/docusaurus-preset 3.31.0 → 3.33.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 +6 -2
- package/package.json +1 -1
- package/src/components/BuildMock/BuildMock.jsx +101 -0
- package/src/components/BuildMock/BuildMock.module.css +290 -0
- package/src/components/DetailHero/DetailHero.jsx +68 -16
- package/src/components/DetailHero/DetailHero.module.css +31 -1
- package/src/components/FlowMock/FlowMock.jsx +151 -0
- package/src/components/FlowMock/FlowMock.module.css +164 -0
- package/src/components/KanbanMock/KanbanMock.jsx +149 -0
- package/src/components/KanbanMock/KanbanMock.module.css +325 -0
- package/src/components/KanbanMock/__tests__/KanbanMock.render.test.js +114 -0
- package/src/components/LeafMock/LeafMock.jsx +173 -0
- package/src/components/LeafMock/LeafMock.module.css +237 -0
- package/src/components/RotatingCards/RotatingCards.module.css +5 -2
- package/src/components/Showcase/Showcase.module.css +5 -2
- package/src/components/WidgetShelf/WidgetShelf.jsx +346 -14
- package/src/components/WidgetShelf/WidgetShelf.module.css +276 -5
- package/src/components/WidgetShelf/__tests__/WidgetShelf.render.test.js +149 -0
- package/src/components/index.js +4 -0
- package/src/components/primitives/SectionHead.module.css +6 -0
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* <WidgetShelf /> styles. Header +
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* <WidgetShelf /> styles. Header + widget-preview cards. Each card
|
|
3
|
+
* has a tinted panel containing the widget mock, with title +
|
|
4
|
+
* description below.
|
|
5
|
+
*
|
|
6
|
+
* Two layouts: the default single-row carousel (slow marquee, track
|
|
7
|
+
* duplicated for a seamless loop, paused on hover/focus) and the
|
|
8
|
+
* static responsive grid (carousel={false}, and what
|
|
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.
|
|
5
21
|
*/
|
|
6
22
|
|
|
7
23
|
.shelf {
|
|
@@ -11,7 +27,11 @@
|
|
|
11
27
|
font-family: var(--conduction-typography-font-family-body);
|
|
12
28
|
}
|
|
13
29
|
|
|
14
|
-
|
|
30
|
+
/* The head runs full width so the title can fill the line; only the
|
|
31
|
+
lede (body text) keeps the 70ch reading measure. Capping the whole
|
|
32
|
+
head at 70ch forced titles into an early wrap, which the base
|
|
33
|
+
h1-h6 `text-wrap: balance` then split into two even lines. */
|
|
34
|
+
.head { margin: 0 0 var(--space-8); }
|
|
15
35
|
.eyebrow {
|
|
16
36
|
display: inline-flex; align-items: center; gap: var(--space-2);
|
|
17
37
|
font-family: var(--conduction-typography-font-family-code);
|
|
@@ -23,8 +43,9 @@
|
|
|
23
43
|
.title {
|
|
24
44
|
font-size: 36px; font-weight: 700; letter-spacing: -0.02em;
|
|
25
45
|
line-height: 1.1; color: var(--c-cobalt-900); margin: 0 0 var(--space-3);
|
|
46
|
+
text-wrap: pretty; /* run full width; no artificial two-line balance */
|
|
26
47
|
}
|
|
27
|
-
.lede { font-size: 17px; line-height: 1.5; color: var(--c-cobalt-700); margin: 0; }
|
|
48
|
+
.lede { font-size: 17px; line-height: 1.5; color: var(--c-cobalt-700); margin: 0; max-width: 70ch; }
|
|
28
49
|
|
|
29
50
|
.grid {
|
|
30
51
|
display: grid;
|
|
@@ -34,6 +55,126 @@
|
|
|
34
55
|
.cols-3 { grid-template-columns: repeat(3, 1fr); }
|
|
35
56
|
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }
|
|
36
57
|
|
|
58
|
+
/* ---- Carousel (default) ----
|
|
59
|
+
*
|
|
60
|
+
* Marquee mechanics: the track holds two identical groups of cards
|
|
61
|
+
* and translates from 0 to -50% on a slow linear loop; because each
|
|
62
|
+
* group carries a trailing padding equal to the card gap, -50% of
|
|
63
|
+
* the track lands exactly one group later and the loop is seamless.
|
|
64
|
+
* The duplicate group is aria-hidden in the JSX so screen readers
|
|
65
|
+
* hear each widget once. Hover or keyboard focus anywhere in the
|
|
66
|
+
* viewport pauses the animation. */
|
|
67
|
+
|
|
68
|
+
.viewport {
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
/* Soft fade at both edges so cards enter/exit gently. */
|
|
71
|
+
-webkit-mask-image: linear-gradient(to right, transparent, black 48px, black calc(100% - 48px), transparent);
|
|
72
|
+
mask-image: linear-gradient(to right, transparent, black 48px, black calc(100% - 48px), transparent);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.track {
|
|
76
|
+
display: flex;
|
|
77
|
+
width: max-content;
|
|
78
|
+
animation: wsMarquee var(--ws-speed, 45s) linear infinite;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.viewport:hover .track,
|
|
82
|
+
.viewport:focus-within .track {
|
|
83
|
+
animation-play-state: paused;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.group {
|
|
87
|
+
display: flex;
|
|
88
|
+
gap: var(--space-5);
|
|
89
|
+
padding-right: var(--space-5);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.carousel .card {
|
|
93
|
+
width: 340px;
|
|
94
|
+
flex: 0 0 auto;
|
|
95
|
+
}
|
|
96
|
+
@media (max-width: 700px) { .carousel .card { width: 280px; } }
|
|
97
|
+
|
|
98
|
+
@keyframes wsMarquee {
|
|
99
|
+
from { transform: translateX(0); }
|
|
100
|
+
to { transform: translateX(-50%); }
|
|
101
|
+
}
|
|
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
|
+
|
|
153
|
+
/* Reduced motion: drop the duplicate copy and render the first group
|
|
154
|
+
as the static wrapping grid — identical to carousel={false}. The
|
|
155
|
+
.cols-2 / .cols-3 class on the group supplies the column template. */
|
|
156
|
+
@media (prefers-reduced-motion: reduce) {
|
|
157
|
+
.viewport {
|
|
158
|
+
overflow: visible;
|
|
159
|
+
-webkit-mask-image: none;
|
|
160
|
+
mask-image: none;
|
|
161
|
+
}
|
|
162
|
+
.controls { display: none; }
|
|
163
|
+
.track { animation: none; display: block; width: 100%; transform: none !important; }
|
|
164
|
+
.group {
|
|
165
|
+
display: grid;
|
|
166
|
+
gap: var(--space-5);
|
|
167
|
+
padding-right: 0;
|
|
168
|
+
width: 100%;
|
|
169
|
+
}
|
|
170
|
+
/* After .group so its display wins the equal-specificity contest. */
|
|
171
|
+
.group.dup { display: none; }
|
|
172
|
+
.carousel .card { width: auto; }
|
|
173
|
+
}
|
|
174
|
+
@media (prefers-reduced-motion: reduce) and (max-width: 900px) {
|
|
175
|
+
.group { grid-template-columns: 1fr; }
|
|
176
|
+
}
|
|
177
|
+
|
|
37
178
|
.card {
|
|
38
179
|
background: white;
|
|
39
180
|
border: 1px solid var(--c-cobalt-100);
|
|
@@ -71,3 +212,133 @@
|
|
|
71
212
|
margin: 0;
|
|
72
213
|
padding: var(--space-2) var(--space-5) var(--space-5);
|
|
73
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
|
+
});
|
package/src/components/index.js
CHANGED
|
@@ -77,6 +77,10 @@ export {default as ComposeBlock} from './ComposeBlock/ComposeBlock.jsx';
|
|
|
77
77
|
export {default as AppsGrid} from './AppsGrid/AppsGrid.jsx';
|
|
78
78
|
export {default as AppMock} from './AppMock/AppMock.jsx';
|
|
79
79
|
export {default as WidgetMock} from './WidgetMock/WidgetMock.jsx';
|
|
80
|
+
export {default as FlowMock} from './FlowMock/FlowMock.jsx';
|
|
81
|
+
export {default as LeafMock} from './LeafMock/LeafMock.jsx';
|
|
82
|
+
export {default as BuildMock} from './BuildMock/BuildMock.jsx';
|
|
83
|
+
export {default as KanbanMock} from './KanbanMock/KanbanMock.jsx';
|
|
80
84
|
export {default as SidebarMock} from './SidebarMock/SidebarMock.jsx';
|
|
81
85
|
export {default as MockScene} from './MockScene/MockScene.jsx';
|
|
82
86
|
export {default as IntegrationIcon} from './IntegrationIcon/IntegrationIcon.jsx';
|
|
@@ -27,6 +27,12 @@
|
|
|
27
27
|
letter-spacing: -0.02em;
|
|
28
28
|
color: var(--c-cobalt-900);
|
|
29
29
|
margin: 0;
|
|
30
|
+
/* Section titles run the full line before wrapping. The kit's base
|
|
31
|
+
h1-h6 rule sets `text-wrap: balance`, which splits a title into
|
|
32
|
+
two near-equal lines even when it fits comfortably on one; the
|
|
33
|
+
PO decision is that section titles fill the available width.
|
|
34
|
+
`pretty` keeps orphan control without the artificial balance. */
|
|
35
|
+
text-wrap: pretty;
|
|
30
36
|
}
|
|
31
37
|
@media (max-width: 700px) { .title { font-size: 32px; } }
|
|
32
38
|
|