@cueplusplus/ui 0.2.0 → 0.3.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/CHANGELOG.md +21 -0
- package/dist/brand/_chassis.d.ts +1 -1
- package/dist/brand/cue-mark.d.ts +1 -1
- package/dist/brand/cue-mark.js +1 -1
- package/dist/chrome/_edge-scroller.js +12 -1
- package/dist/chrome/_tabs-scroll.d.ts +1 -1
- package/dist/chrome/_tabs-scroll.js +1 -1
- package/dist/configurator/_overrides.js +18 -6
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/instruments/index.d.ts +2 -2
- package/dist/instruments/index.js +2 -2
- package/dist/instruments/table.d.ts +14 -1
- package/dist/instruments/table.js +20 -1
- package/dist/primitives/chip.d.ts +1 -1
- package/dist/styles.css +11 -1
- package/dist/system/index.d.ts +3 -3
- package/dist/system/portal.js +19 -5
- package/dist/system/prepaint.d.ts +20 -2
- package/dist/system/prepaint.js +39 -33
- package/dist/system/theme-provider.d.ts +13 -2
- package/dist/system/theme-provider.js +56 -20
- package/dist/theming/_presets.js +53 -0
- package/dist/theming/create-theme.d.ts +2 -2
- package/dist/theming/create-theme.js +24 -15
- package/dist/theming/serialize.d.ts +1 -1
- package/dist/theming/serialize.js +25 -1
- package/manifest/components/accordion.json +154 -0
- package/manifest/components/agent-pile.json +116 -0
- package/manifest/components/alert-dialog.json +190 -0
- package/manifest/components/app-bar.json +244 -0
- package/manifest/components/app-shell.json +291 -0
- package/manifest/components/app-window-frame.json +102 -0
- package/manifest/components/ask-box.json +209 -0
- package/manifest/components/autocomplete.json +197 -0
- package/manifest/components/avatar-group.json +72 -0
- package/manifest/components/avatar.json +95 -0
- package/manifest/components/breadcrumb.json +74 -0
- package/manifest/components/button-group.json +74 -0
- package/manifest/components/button.json +125 -0
- package/manifest/components/calendar.json +65 -0
- package/manifest/components/card.json +140 -0
- package/manifest/components/carousel.json +200 -0
- package/manifest/components/channel-matrix.json +173 -0
- package/manifest/components/chart-container.json +111 -0
- package/manifest/components/chart-ramp.json +72 -0
- package/manifest/components/chart-swatch.json +65 -0
- package/manifest/components/chart-tooltip-content.json +114 -0
- package/manifest/components/checkbox-group.json +68 -0
- package/manifest/components/checkbox.json +93 -0
- package/manifest/components/chip.json +132 -0
- package/manifest/components/collapsible.json +120 -0
- package/manifest/components/color-area.json +59 -0
- package/manifest/components/color-field.json +79 -0
- package/manifest/components/color-picker.json +175 -0
- package/manifest/components/color-slider.json +75 -0
- package/manifest/components/color-swatch.json +68 -0
- package/manifest/components/colors-section.json +66 -0
- package/manifest/components/combobox.json +198 -0
- package/manifest/components/command-palette.json +177 -0
- package/manifest/components/composer.json +176 -0
- package/manifest/components/container.json +60 -0
- package/manifest/components/context-menu.json +121 -0
- package/manifest/components/copy-button.json +93 -0
- package/manifest/components/cue-logotype.json +96 -0
- package/manifest/components/cue-mark.json +97 -0
- package/manifest/components/cue-portal-frame.json +55 -0
- package/manifest/components/data-table-pagination.json +83 -0
- package/manifest/components/data-table-toolbar.json +104 -0
- package/manifest/components/date-field.json +80 -0
- package/manifest/components/date-picker.json +191 -0
- package/manifest/components/date-range-picker.json +191 -0
- package/manifest/components/delegation-card.json +144 -0
- package/manifest/components/density.json +58 -0
- package/manifest/components/description-list.json +84 -0
- package/manifest/components/dialog.json +198 -0
- package/manifest/components/disclosure.json +109 -0
- package/manifest/components/dmx-bar.json +94 -0
- package/manifest/components/dmx-strip.json +101 -0
- package/manifest/components/drawer.json +179 -0
- package/manifest/components/dropdown-menu.json +229 -0
- package/manifest/components/empty-state.json +90 -0
- package/manifest/components/env-var-input.json +150 -0
- package/manifest/components/export-dialog.json +84 -0
- package/manifest/components/eyebrow.json +56 -0
- package/manifest/components/field-description.json +63 -0
- package/manifest/components/field-error.json +77 -0
- package/manifest/components/field-label.json +63 -0
- package/manifest/components/field.json +79 -0
- package/manifest/components/file-upload.json +159 -0
- package/manifest/components/flow-background.json +50 -0
- package/manifest/components/flow-controls.json +50 -0
- package/manifest/components/footer.json +112 -0
- package/manifest/components/grid.json +81 -0
- package/manifest/components/group-bar.json +102 -0
- package/manifest/components/hover-card.json +151 -0
- package/manifest/components/icon-button.json +88 -0
- package/manifest/components/info-tip.json +98 -0
- package/manifest/components/input-group.json +91 -0
- package/manifest/components/input.json +89 -0
- package/manifest/components/item.json +129 -0
- package/manifest/components/kbd.json +52 -0
- package/manifest/components/link.json +81 -0
- package/manifest/components/log-viewer.json +139 -0
- package/manifest/components/menubar.json +157 -0
- package/manifest/components/message-list.json +100 -0
- package/manifest/components/message.json +116 -0
- package/manifest/components/meter.json +114 -0
- package/manifest/components/multi-select.json +190 -0
- package/manifest/components/musical-time-input.json +113 -0
- package/manifest/components/navigation-menu.json +226 -0
- package/manifest/components/node-card.json +109 -0
- package/manifest/components/node-handle.json +69 -0
- package/manifest/components/number-field.json +107 -0
- package/manifest/components/otp-field.json +128 -0
- package/manifest/components/page-shell.json +96 -0
- package/manifest/components/pagination.json +92 -0
- package/manifest/components/panel-header.json +77 -0
- package/manifest/components/panel.json +71 -0
- package/manifest/components/password-input.json +119 -0
- package/manifest/components/piano-keyboard.json +120 -0
- package/manifest/components/plussie.json +111 -0
- package/manifest/components/popover.json +193 -0
- package/manifest/components/preset-section.json +73 -0
- package/manifest/components/progress.json +95 -0
- package/manifest/components/radio-group.json +73 -0
- package/manifest/components/radio.json +80 -0
- package/manifest/components/rating.json +141 -0
- package/manifest/components/resizable.json +88 -0
- package/manifest/components/row.json +109 -0
- package/manifest/components/scroll-area.json +91 -0
- package/manifest/components/scrollable-tabs-list.json +83 -0
- package/manifest/components/scrub-input.json +200 -0
- package/manifest/components/seam-cell.json +75 -0
- package/manifest/components/seam-grid.json +75 -0
- package/manifest/components/seam-list.json +56 -0
- package/manifest/components/search-input.json +134 -0
- package/manifest/components/section-header.json +112 -0
- package/manifest/components/segmented-control.json +138 -0
- package/manifest/components/select.json +194 -0
- package/manifest/components/separator.json +53 -0
- package/manifest/components/shape-section.json +66 -0
- package/manifest/components/sheet.json +186 -0
- package/manifest/components/sidebar.json +198 -0
- package/manifest/components/signal-edge.json +56 -0
- package/manifest/components/skeleton.json +46 -0
- package/manifest/components/slider.json +89 -0
- package/manifest/components/sparkline.json +76 -0
- package/manifest/components/spectrum-visualizer.json +115 -0
- package/manifest/components/spinner.json +61 -0
- package/manifest/components/stack.json +96 -0
- package/manifest/components/stat.json +87 -0
- package/manifest/components/status-bar.json +58 -0
- package/manifest/components/status-dot.json +78 -0
- package/manifest/components/stepper.json +89 -0
- package/manifest/components/switch.json +87 -0
- package/manifest/components/table-scroll-region.json +69 -0
- package/manifest/components/table.json +155 -0
- package/manifest/components/tabs.json +145 -0
- package/manifest/components/tags-input.json +190 -0
- package/manifest/components/terminal-frame.json +119 -0
- package/manifest/components/textarea.json +76 -0
- package/manifest/components/theme-configurator.json +124 -0
- package/manifest/components/theme-provider.json +134 -0
- package/manifest/components/threshold-rail.json +160 -0
- package/manifest/components/time-field.json +80 -0
- package/manifest/components/timeline-ruler.json +139 -0
- package/manifest/components/timeline.json +115 -0
- package/manifest/components/title-bar.json +82 -0
- package/manifest/components/toast.json +130 -0
- package/manifest/components/toggle-group.json +106 -0
- package/manifest/components/toggle.json +109 -0
- package/manifest/components/token-editor.json +90 -0
- package/manifest/components/toolbar.json +163 -0
- package/manifest/components/tooltip.json +164 -0
- package/manifest/components/tree-visibility-toggle.json +84 -0
- package/manifest/components/tree.json +140 -0
- package/manifest/components/two-step-button.json +113 -0
- package/manifest/components/universe-grid.json +127 -0
- package/manifest/components/usage-chart.json +124 -0
- package/manifest/manifest.json +2142 -0
- package/manifest/tokens.json +138 -0
- package/package.json +11 -4
|
@@ -5,9 +5,9 @@ import { DEFAULT_DENSITY as DEFAULT_DENSITY$1, DENSITY_LEVELS, DensityContext }
|
|
|
5
5
|
import { DEFAULT_STORAGE_KEY } from "./prepaint.js";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
import { jsx } from "react/jsx-runtime";
|
|
8
|
-
import { DEFAULT_FONT, FONTS } from "@cueplusplus/tokens";
|
|
8
|
+
import { DEFAULT_FONT, FONTS, THEMES } from "@cueplusplus/tokens";
|
|
9
9
|
//#region src/system/theme-provider.tsx
|
|
10
|
-
const MODES = [
|
|
10
|
+
const MODES$1 = [
|
|
11
11
|
"dark",
|
|
12
12
|
"light",
|
|
13
13
|
"system"
|
|
@@ -34,6 +34,8 @@ const ThemeContext = React.createContext(null);
|
|
|
34
34
|
* Internal: not part of the public API.
|
|
35
35
|
*/
|
|
36
36
|
const FontScaleContext = React.createContext(null);
|
|
37
|
+
/** Effective font-family overrides from the nearest provider chain. */
|
|
38
|
+
const FontFamiliesContext = React.createContext(null);
|
|
37
39
|
function isOneOf(allowed, value) {
|
|
38
40
|
return typeof value === "string" && allowed.includes(value);
|
|
39
41
|
}
|
|
@@ -84,14 +86,14 @@ function writePreferences(storageKey, preferences) {
|
|
|
84
86
|
* <App />
|
|
85
87
|
* </ThemeProvider>
|
|
86
88
|
*/
|
|
87
|
-
function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp = DEFAULT_DENSITY$1, mode: modeProp = DEFAULT_MODE, font: fontProp = DEFAULT_FONT, fontScale = 1, storageKey = DEFAULT_STORAGE_KEY, asChild = false, className, style: styleProp, children }) {
|
|
89
|
+
function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp = DEFAULT_DENSITY$1, mode: modeProp = DEFAULT_MODE, font: fontProp = DEFAULT_FONT, fontScale = 1, fontFamilies, storageKey = DEFAULT_STORAGE_KEY, persistPreferences = true, asChild = false, className, style: styleProp, children }) {
|
|
88
90
|
const ambient = React.useContext(ThemeContext);
|
|
89
91
|
const isRoot = ambient === null;
|
|
90
92
|
const [theme, setThemeState] = React.useState(themeProp);
|
|
91
93
|
const [density, setDensityState] = React.useState(densityProp);
|
|
92
94
|
const [mode, setModeState] = React.useState(modeProp);
|
|
93
95
|
const [rootFont, setRootFontState] = React.useState(() => {
|
|
94
|
-
if (!isRoot) return fontProp;
|
|
96
|
+
if (!isRoot || !persistPreferences) return fontProp;
|
|
95
97
|
const stored = readPreferences(storageKey).font;
|
|
96
98
|
return isOneOf(FONT_NAMES, stored) ? stored : fontProp;
|
|
97
99
|
});
|
|
@@ -101,15 +103,24 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
101
103
|
const restoredKey = React.useRef(null);
|
|
102
104
|
const [restored, setRestored] = React.useState(false);
|
|
103
105
|
useIsomorphicLayoutEffect(() => {
|
|
104
|
-
if (!isRoot
|
|
106
|
+
if (!isRoot) return;
|
|
107
|
+
if (!persistPreferences) {
|
|
108
|
+
setRestored(true);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (restoredKey.current === storageKey) return;
|
|
105
112
|
restoredKey.current = storageKey;
|
|
106
113
|
setRestored(true);
|
|
107
114
|
const stored = readPreferences(storageKey);
|
|
108
|
-
if (
|
|
115
|
+
if (isOneOf(THEMES, stored.theme)) setThemeState(stored.theme);
|
|
109
116
|
if (isOneOf(DENSITY_LEVELS, stored.density)) setDensityState(stored.density);
|
|
110
117
|
if (isOneOf(FONT_NAMES, stored.font)) setRootFontState(stored.font);
|
|
111
|
-
if (isOneOf(MODES, stored.mode)) setModeState(stored.mode);
|
|
112
|
-
}, [
|
|
118
|
+
if (isOneOf(MODES$1, stored.mode)) setModeState(stored.mode);
|
|
119
|
+
}, [
|
|
120
|
+
isRoot,
|
|
121
|
+
persistPreferences,
|
|
122
|
+
storageKey
|
|
123
|
+
]);
|
|
113
124
|
const lastProps = React.useRef({
|
|
114
125
|
theme: themeProp,
|
|
115
126
|
density: densityProp,
|
|
@@ -162,9 +173,13 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
162
173
|
...patch
|
|
163
174
|
};
|
|
164
175
|
stateRef.current = next;
|
|
165
|
-
if (!isRoot) return;
|
|
176
|
+
if (!isRoot || !persistPreferences) return;
|
|
166
177
|
writePreferences(storageKey, next);
|
|
167
|
-
}, [
|
|
178
|
+
}, [
|
|
179
|
+
isRoot,
|
|
180
|
+
persistPreferences,
|
|
181
|
+
storageKey
|
|
182
|
+
]);
|
|
168
183
|
const setTheme = React.useCallback((next) => {
|
|
169
184
|
setThemeState(next);
|
|
170
185
|
persist({ theme: next });
|
|
@@ -197,15 +212,15 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
197
212
|
root.setAttribute("data-theme", theme);
|
|
198
213
|
root.setAttribute("data-density", density);
|
|
199
214
|
root.setAttribute("data-font", font);
|
|
200
|
-
root.setAttribute("data-mode",
|
|
201
|
-
root.style.colorScheme =
|
|
215
|
+
root.setAttribute("data-mode", mode);
|
|
216
|
+
root.style.colorScheme = mode === "system" ? "light dark" : mode;
|
|
202
217
|
}, [
|
|
203
218
|
isRoot,
|
|
204
219
|
restored,
|
|
205
220
|
theme,
|
|
206
221
|
density,
|
|
207
222
|
font,
|
|
208
|
-
|
|
223
|
+
mode
|
|
209
224
|
]);
|
|
210
225
|
React.useEffect(() => () => {
|
|
211
226
|
const original = originalStamp.current;
|
|
@@ -243,20 +258,38 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
243
258
|
setMode,
|
|
244
259
|
setFont
|
|
245
260
|
]);
|
|
261
|
+
const parentFontFamilies = React.useContext(FontFamiliesContext);
|
|
262
|
+
const effectiveFontFamilies = React.useMemo(() => {
|
|
263
|
+
const sans = fontFamilies?.sans ?? parentFontFamilies?.sans;
|
|
264
|
+
const mono = fontFamilies?.mono ?? parentFontFamilies?.mono;
|
|
265
|
+
return sans === void 0 && mono === void 0 ? null : {
|
|
266
|
+
sans,
|
|
267
|
+
mono
|
|
268
|
+
};
|
|
269
|
+
}, [
|
|
270
|
+
fontFamilies?.sans,
|
|
271
|
+
fontFamilies?.mono,
|
|
272
|
+
parentFontFamilies?.sans,
|
|
273
|
+
parentFontFamilies?.mono
|
|
274
|
+
]);
|
|
246
275
|
const style = React.useMemo(() => ({
|
|
247
|
-
colorScheme:
|
|
276
|
+
colorScheme: mode === "system" ? "light dark" : mode,
|
|
248
277
|
"--cue-font-scale": fontScale,
|
|
278
|
+
...fontFamilies?.sans === void 0 ? null : { "--cue-font-sans": fontFamilies.sans },
|
|
279
|
+
...fontFamilies?.mono === void 0 ? null : { "--cue-font-mono": fontFamilies.mono },
|
|
249
280
|
...styleProp
|
|
250
281
|
}), [
|
|
251
|
-
|
|
282
|
+
mode,
|
|
252
283
|
fontScale,
|
|
284
|
+
fontFamilies?.sans,
|
|
285
|
+
fontFamilies?.mono,
|
|
253
286
|
styleProp
|
|
254
287
|
]);
|
|
255
288
|
const stamp = {
|
|
256
289
|
"data-cue-root": "",
|
|
257
290
|
"data-theme": theme,
|
|
258
291
|
"data-density": density,
|
|
259
|
-
"data-mode":
|
|
292
|
+
"data-mode": mode
|
|
260
293
|
};
|
|
261
294
|
let root;
|
|
262
295
|
if (asChild) {
|
|
@@ -279,12 +312,15 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
279
312
|
value: contextValue,
|
|
280
313
|
children: /* @__PURE__ */ jsx(FontScaleContext.Provider, {
|
|
281
314
|
value: fontScale,
|
|
282
|
-
children: /* @__PURE__ */ jsx(
|
|
283
|
-
value:
|
|
284
|
-
children:
|
|
315
|
+
children: /* @__PURE__ */ jsx(FontFamiliesContext.Provider, {
|
|
316
|
+
value: effectiveFontFamilies,
|
|
317
|
+
children: /* @__PURE__ */ jsx(DensityContext.Provider, {
|
|
318
|
+
value: density,
|
|
319
|
+
children: root
|
|
320
|
+
})
|
|
285
321
|
})
|
|
286
322
|
})
|
|
287
323
|
});
|
|
288
324
|
}
|
|
289
325
|
//#endregion
|
|
290
|
-
export { FontScaleContext, ThemeContext, ThemeProvider };
|
|
326
|
+
export { FontFamiliesContext, FontScaleContext, ThemeContext, ThemeProvider };
|
package/dist/theming/_presets.js
CHANGED
|
@@ -454,6 +454,59 @@ const THEME_PRESETS = {
|
|
|
454
454
|
},
|
|
455
455
|
fonts: { "font-mono": "\"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" }
|
|
456
456
|
},
|
|
457
|
+
requestport: {
|
|
458
|
+
dark: {
|
|
459
|
+
"bg": "#0a0d0e",
|
|
460
|
+
"sunken": "#07090a",
|
|
461
|
+
"surface-1": "#101416",
|
|
462
|
+
"surface-2": "#151a1d",
|
|
463
|
+
"surface-3": "#1a2024",
|
|
464
|
+
"fg": "#f0eee9",
|
|
465
|
+
"fg-muted": "#a3a6a8",
|
|
466
|
+
"fg-subtle": "#81868a",
|
|
467
|
+
"border": "#293034",
|
|
468
|
+
"border-strong": "#61686d",
|
|
469
|
+
"border-overlay": "#3f484e",
|
|
470
|
+
"accent": "#e66a45",
|
|
471
|
+
"accent-hover": "#f07a53",
|
|
472
|
+
"accent-fg": "#160d09",
|
|
473
|
+
"ok": "#63b482",
|
|
474
|
+
"busy": "#9a8cdb",
|
|
475
|
+
"warn": "#d6a65a",
|
|
476
|
+
"danger": "#dc6d69",
|
|
477
|
+
"info": "#6eb7d9",
|
|
478
|
+
"selection": "rgba(230,106,69,0.18)",
|
|
479
|
+
"focus": "#e66a45",
|
|
480
|
+
"data-ground": "#000000",
|
|
481
|
+
"scrim": "rgba(0,0,0,0.72)"
|
|
482
|
+
},
|
|
483
|
+
light: {
|
|
484
|
+
"bg": "#f4f3ef",
|
|
485
|
+
"sunken": "#e7e6e1",
|
|
486
|
+
"surface-1": "#faf9f6",
|
|
487
|
+
"surface-2": "#ffffff",
|
|
488
|
+
"surface-3": "#deddd8",
|
|
489
|
+
"fg": "#1a1d1e",
|
|
490
|
+
"fg-muted": "#53595c",
|
|
491
|
+
"fg-subtle": "#62696d",
|
|
492
|
+
"border": "#d0d2cf",
|
|
493
|
+
"border-strong": "#7f8689",
|
|
494
|
+
"border-overlay": "#aeb4b5",
|
|
495
|
+
"accent": "#b8492b",
|
|
496
|
+
"accent-hover": "#963b23",
|
|
497
|
+
"accent-fg": "#ffffff",
|
|
498
|
+
"ok": "#247746",
|
|
499
|
+
"busy": "#604aaa",
|
|
500
|
+
"warn": "#8a5b00",
|
|
501
|
+
"danger": "#aa2f2b",
|
|
502
|
+
"info": "#1d6587",
|
|
503
|
+
"selection": "rgba(184,73,43,0.18)",
|
|
504
|
+
"focus": "#b8492b",
|
|
505
|
+
"data-ground": "#000000",
|
|
506
|
+
"scrim": "rgba(20,18,15,0.45)"
|
|
507
|
+
},
|
|
508
|
+
fonts: { "font-mono": "\"Geist Mono\", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" }
|
|
509
|
+
},
|
|
457
510
|
quotamate: {
|
|
458
511
|
dark: {
|
|
459
512
|
"bg": "#0e0e12",
|
|
@@ -30,7 +30,7 @@ declare const DEFAULT_THEME_BASE: ThemeName;
|
|
|
30
30
|
/**
|
|
31
31
|
* How far the sunken ground sits below the page, in oklch lightness.
|
|
32
32
|
*
|
|
33
|
-
* Small on purpose: sunken is a well, not a second theme. Across the
|
|
33
|
+
* Small on purpose: sunken is a well, not a second theme. Across the ten
|
|
34
34
|
* shipped presets the authored gap runs −0.011 to −0.031.
|
|
35
35
|
*/
|
|
36
36
|
declare const SUNKEN_LIGHTNESS_STEP = -0.02;
|
|
@@ -75,7 +75,7 @@ declare const ACCENT_FG_MINIMUM = 4.5;
|
|
|
75
75
|
* extreme rather than to the arithmetic mirror: `1 − L` of a 0.16 near-black is a
|
|
76
76
|
* 0.84 mid-grey, which is not a page. Compressing instead maps the ladder of
|
|
77
77
|
* dark grounds onto a narrow paper band — a deeper dark theme gets a cleaner
|
|
78
|
-
* white page — and lands within 0.01 of all
|
|
78
|
+
* white page — and lands within 0.01 of all ten authored light presets.
|
|
79
79
|
*/
|
|
80
80
|
declare const LIGHT_GROUND_COMPRESSION = 0.1;
|
|
81
81
|
interface ThemeAnchors {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { formatColorString } from "../color/_convert.js";
|
|
2
2
|
import { BASE_FONT_STACKS, DEFAULT_DENSITY_LEVEL, DENSITY_RADIUS_SCALE, RESOLVED_MONO, THEME_COLOR_TOKENS, THEME_PRESETS } from "./_presets.js";
|
|
3
3
|
import { WCAG_AA_TEXT, contrastRatio, contrastReport } from "./contrast.js";
|
|
4
|
-
import { assertCssValue, assertThemeName, derivedTokens, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
|
|
4
|
+
import { assertCssValue, assertThemeName, derivedTokens, serializeSystemModeAliases, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
|
|
5
5
|
import { clampChroma, converter, formatHex, inGamut, parse } from "culori";
|
|
6
6
|
//#region src/theming/create-theme.ts
|
|
7
7
|
/**
|
|
@@ -32,7 +32,7 @@ const DEFAULT_THEME_BASE = "cue";
|
|
|
32
32
|
/**
|
|
33
33
|
* How far the sunken ground sits below the page, in oklch lightness.
|
|
34
34
|
*
|
|
35
|
-
* Small on purpose: sunken is a well, not a second theme. Across the
|
|
35
|
+
* Small on purpose: sunken is a well, not a second theme. Across the ten
|
|
36
36
|
* shipped presets the authored gap runs −0.011 to −0.031.
|
|
37
37
|
*/
|
|
38
38
|
const SUNKEN_LIGHTNESS_STEP = -.02;
|
|
@@ -81,7 +81,7 @@ const ACCENT_FG_MINIMUM = WCAG_AA_TEXT;
|
|
|
81
81
|
* extreme rather than to the arithmetic mirror: `1 − L` of a 0.16 near-black is a
|
|
82
82
|
* 0.84 mid-grey, which is not a page. Compressing instead maps the ladder of
|
|
83
83
|
* dark grounds onto a narrow paper band — a deeper dark theme gets a cleaner
|
|
84
|
-
* white page — and lands within 0.01 of all
|
|
84
|
+
* white page — and lands within 0.01 of all ten authored light presets.
|
|
85
85
|
*/
|
|
86
86
|
const LIGHT_GROUND_COMPRESSION = .1;
|
|
87
87
|
/** Lightness a derived light colour steps down by while hunting for contrast. */
|
|
@@ -400,7 +400,7 @@ function createTheme(anchors) {
|
|
|
400
400
|
const derived = Object.entries(derivedTokens());
|
|
401
401
|
const fonts = resolveFonts(anchors, preset.fonts);
|
|
402
402
|
const radius = anchors.radiusScale === void 0 ? null : radiusBlocks(name, anchors.radiusScale);
|
|
403
|
-
const
|
|
403
|
+
const darkPaletteSections = [
|
|
404
404
|
{ declarations: colorDeclarations(dark.colors) },
|
|
405
405
|
{
|
|
406
406
|
comment: DERIVED_COMMENT,
|
|
@@ -409,25 +409,26 @@ function createTheme(anchors) {
|
|
|
409
409
|
{
|
|
410
410
|
comment: FONT_COMMENT,
|
|
411
411
|
declarations: fonts
|
|
412
|
-
}
|
|
413
|
-
...radius === null ? [] : [{
|
|
414
|
-
comment: SHAPE_COMMENT,
|
|
415
|
-
declarations: radius.base
|
|
416
|
-
}]
|
|
412
|
+
}
|
|
417
413
|
];
|
|
414
|
+
const darkSections = [...darkPaletteSections, ...radius === null ? [] : [{
|
|
415
|
+
comment: SHAPE_COMMENT,
|
|
416
|
+
declarations: radius.base
|
|
417
|
+
}]];
|
|
418
|
+
const lightSections = light === null ? null : [{ declarations: colorDeclarations(light.colors) }, {
|
|
419
|
+
comment: DERIVED_COMMENT,
|
|
420
|
+
declarations: derived
|
|
421
|
+
}];
|
|
418
422
|
const blocks = [
|
|
419
423
|
{
|
|
420
424
|
selector: themeSelector(name),
|
|
421
425
|
colorScheme: "dark",
|
|
422
426
|
sections: darkSections
|
|
423
427
|
},
|
|
424
|
-
...
|
|
428
|
+
...lightSections === null ? [] : [{
|
|
425
429
|
selector: themeSelector(name, { mode: "light" }),
|
|
426
430
|
colorScheme: "light",
|
|
427
|
-
sections:
|
|
428
|
-
comment: DERIVED_COMMENT,
|
|
429
|
-
declarations: derived
|
|
430
|
-
}]
|
|
431
|
+
sections: lightSections
|
|
431
432
|
}],
|
|
432
433
|
...radius?.blocks ?? []
|
|
433
434
|
];
|
|
@@ -441,7 +442,15 @@ function createTheme(anchors) {
|
|
|
441
442
|
}]];
|
|
442
443
|
return {
|
|
443
444
|
name,
|
|
444
|
-
css: serializeThemeCss(blocks, { banner: `@cueplusplus/ui — theme "${name}"` }),
|
|
445
|
+
css: `${serializeThemeCss(blocks, { banner: `@cueplusplus/ui — theme "${name}"` }).trimEnd()}\n\n${serializeSystemModeAliases(name, [{
|
|
446
|
+
mode: "dark",
|
|
447
|
+
colorScheme: "light dark",
|
|
448
|
+
sections: darkPaletteSections
|
|
449
|
+
}, {
|
|
450
|
+
mode: "light",
|
|
451
|
+
colorScheme: "light dark",
|
|
452
|
+
sections: lightSections ?? darkPaletteSections
|
|
453
|
+
}])}\n`,
|
|
445
454
|
tokens: Object.fromEntries(darkSections.flatMap((section) => section.declarations)),
|
|
446
455
|
light: light === null ? null : Object.fromEntries([...colorDeclarations(light.colors), ...derived]),
|
|
447
456
|
report: contrastReport(inputs)
|
|
@@ -110,7 +110,7 @@ interface ThemeBlock {
|
|
|
110
110
|
/** The selector the rule declares under. */
|
|
111
111
|
selector: string;
|
|
112
112
|
/** Emitted as `color-scheme`, ahead of the custom properties. Omit to skip it. */
|
|
113
|
-
colorScheme?: "dark" | "light";
|
|
113
|
+
colorScheme?: "dark" | "light" | "light dark";
|
|
114
114
|
/** The declarations, grouped into commented sections. */
|
|
115
115
|
sections: readonly ThemeBlockSection[];
|
|
116
116
|
}
|
|
@@ -148,6 +148,30 @@ function serializeThemeBlock(block) {
|
|
|
148
148
|
}
|
|
149
149
|
return `${block.selector} {\n${lines.join("\n")}\n}`;
|
|
150
150
|
}
|
|
151
|
+
/** Indent a complete rule once so it can live inside an at-rule. */
|
|
152
|
+
function indentRule(rule) {
|
|
153
|
+
return rule.split("\n").map((line) => ` ${line}`).join("\n");
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Alias resolved dark/light palettes onto the preserved logical `system` mode.
|
|
157
|
+
*
|
|
158
|
+
* Both `createTheme()` and the configurator call this function. Keeping the
|
|
159
|
+
* selector and media-query spelling here prevents exported overrides from
|
|
160
|
+
* drifting away from generated custom themes.
|
|
161
|
+
*
|
|
162
|
+
* @internal Used by package-owned theme serializers; not part of the public
|
|
163
|
+
* `@cueplusplus/ui/theming` barrel.
|
|
164
|
+
*/
|
|
165
|
+
function serializeSystemModeAliases(theme, aliases) {
|
|
166
|
+
const name = assertThemeName(theme);
|
|
167
|
+
return aliases.map(({ mode, colorScheme, sections }) => {
|
|
168
|
+
return `@media (prefers-color-scheme: ${mode}) {\n${indentRule(serializeThemeBlock({
|
|
169
|
+
selector: `[data-theme="${name}"][data-mode="system"]`,
|
|
170
|
+
colorScheme,
|
|
171
|
+
sections
|
|
172
|
+
}))}\n}`;
|
|
173
|
+
}).join("\n\n");
|
|
174
|
+
}
|
|
151
175
|
/**
|
|
152
176
|
* A whole generated stylesheet: banner, then one rule per block.
|
|
153
177
|
*
|
|
@@ -162,4 +186,4 @@ function serializeThemeCss(blocks, options = {}) {
|
|
|
162
186
|
return `${[...options.banner === void 0 ? [] : [`/*! ${options.banner}\n * Generated by createTheme(). Do not edit by hand.\n */`], ...rules].join("\n\n")}\n`;
|
|
163
187
|
}
|
|
164
188
|
//#endregion
|
|
165
|
-
export { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, THEME_NAME_PATTERN, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
|
|
189
|
+
export { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, THEME_NAME_PATTERN, assertCssValue, assertThemeName, derivedTokens, serializeSystemModeAliases, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Accordion",
|
|
3
|
+
"slug": "accordion",
|
|
4
|
+
"group": "layout",
|
|
5
|
+
"importPath": "@cueplusplus/ui",
|
|
6
|
+
"peerDependencies": [],
|
|
7
|
+
"clientOnly": true,
|
|
8
|
+
"description": "A stack of sections, one (or several) open at a time.\n\nWraps Base UI's `Accordion`, so the disclosure ARIA, keyboard handling and\nopen-state bookkeeping are the primitive's. This layer adds the console skin:\nhairline separation, mono summary rows on the control ladder, and a height\ntransition that respects `prefers-reduced-motion`.",
|
|
9
|
+
"props": [],
|
|
10
|
+
"typeReferences": [],
|
|
11
|
+
"subcomponents": [
|
|
12
|
+
"Root",
|
|
13
|
+
"Item",
|
|
14
|
+
"Header",
|
|
15
|
+
"Trigger",
|
|
16
|
+
"Panel"
|
|
17
|
+
],
|
|
18
|
+
"parts": [
|
|
19
|
+
{
|
|
20
|
+
"name": "Root",
|
|
21
|
+
"usage": "Accordion.Root",
|
|
22
|
+
"description": "Groups the sections and owns which of them are open.",
|
|
23
|
+
"props": [
|
|
24
|
+
{
|
|
25
|
+
"name": "className",
|
|
26
|
+
"type": "string",
|
|
27
|
+
"required": false,
|
|
28
|
+
"defaultValue": null,
|
|
29
|
+
"description": "Extra classes for the accordion frame."
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"typeReferences": [
|
|
33
|
+
"Omit<BaseAccordion.Root.Props, \"className\">"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "Item",
|
|
38
|
+
"usage": "Accordion.Item",
|
|
39
|
+
"description": "One section.\n\nSeparation is a hairline the section draws above itself, dropped on the first\none — so a stack shows rules *between* sections and none against the rim of\nwhatever contains them. Same rule as `Row`, for the same reason.",
|
|
40
|
+
"props": [
|
|
41
|
+
{
|
|
42
|
+
"name": "className",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"required": false,
|
|
45
|
+
"defaultValue": null,
|
|
46
|
+
"description": "Extra classes for the section."
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"typeReferences": [
|
|
50
|
+
"Omit<BaseAccordion.Item.Props, \"className\">"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "Header",
|
|
55
|
+
"usage": "Accordion.Header",
|
|
56
|
+
"description": "The heading that labels a section's panel. An `<h3>` with no type of its own.",
|
|
57
|
+
"props": [
|
|
58
|
+
{
|
|
59
|
+
"name": "className",
|
|
60
|
+
"type": "string",
|
|
61
|
+
"required": false,
|
|
62
|
+
"defaultValue": null,
|
|
63
|
+
"description": "Extra classes for the heading."
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"typeReferences": [
|
|
67
|
+
"Omit<BaseAccordion.Header.Props, \"className\">"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "Trigger",
|
|
72
|
+
"usage": "Accordion.Trigger",
|
|
73
|
+
"description": "The summary row that opens a section.\n\nA mono row on the control ladder, padded to the same gutters as a `Row`, with\na caret that rotates off the trigger's own `data-panel-open` — nothing in\nReact has to know the section is open for the arrow to turn.",
|
|
74
|
+
"props": [
|
|
75
|
+
{
|
|
76
|
+
"name": "className",
|
|
77
|
+
"type": "string",
|
|
78
|
+
"required": false,
|
|
79
|
+
"defaultValue": null,
|
|
80
|
+
"description": "Extra classes for the summary row."
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"typeReferences": [
|
|
84
|
+
"Omit<BaseAccordion.Trigger.Props, \"className\">"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "Panel",
|
|
89
|
+
"usage": "Accordion.Panel",
|
|
90
|
+
"description": "A section's body.\n\nHeight is animated from `--accordion-panel-height`, which Base UI measures.\nThe animating element carries no padding — padding is part of the box height,\nso it would be counted into the `0 → measured` transition and make the panel\njump at both ends — hence the inner body element.",
|
|
91
|
+
"props": [
|
|
92
|
+
{
|
|
93
|
+
"name": "className",
|
|
94
|
+
"type": "string",
|
|
95
|
+
"required": false,
|
|
96
|
+
"defaultValue": null,
|
|
97
|
+
"description": "Extra classes for the animating box. Padding belongs on `bodyClassName`."
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "bodyClassName",
|
|
101
|
+
"type": "string",
|
|
102
|
+
"required": false,
|
|
103
|
+
"defaultValue": null,
|
|
104
|
+
"description": "Extra classes for the padded body inside it."
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"typeReferences": [
|
|
108
|
+
"Omit<BaseAccordion.Panel.Props, \"className\">"
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"variants": {},
|
|
113
|
+
"defaultVariants": {},
|
|
114
|
+
"tokensUsed": [
|
|
115
|
+
"--cue-accent",
|
|
116
|
+
"--cue-border",
|
|
117
|
+
"--cue-control-md",
|
|
118
|
+
"--cue-fg",
|
|
119
|
+
"--cue-fg-muted",
|
|
120
|
+
"--cue-fg-subtle",
|
|
121
|
+
"--cue-font-mono",
|
|
122
|
+
"--cue-icon-sm",
|
|
123
|
+
"--cue-pad-row-x",
|
|
124
|
+
"--cue-space-1",
|
|
125
|
+
"--cue-space-3",
|
|
126
|
+
"--cue-space-4",
|
|
127
|
+
"--cue-text-ui"
|
|
128
|
+
],
|
|
129
|
+
"summary": "A stack of sections, one (or several) open at a time.",
|
|
130
|
+
"examples": [
|
|
131
|
+
{
|
|
132
|
+
"title": "Usage",
|
|
133
|
+
"code": "<Accordion.Root>\n <Accordion.Item value=\"routing\">\n <Accordion.Header><Accordion.Trigger>routing</Accordion.Trigger></Accordion.Header>\n <Accordion.Panel>…</Accordion.Panel>\n </Accordion.Item>\n</Accordion.Root>\n<Accordion.Root multiple defaultValue={[\"routing\"]}>…</Accordion.Root>",
|
|
134
|
+
"language": "tsx"
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
"status": "todo-docs",
|
|
138
|
+
"url": "/docs/components/accordion",
|
|
139
|
+
"mdUrl": "/docs/components/accordion.md",
|
|
140
|
+
"jsonUrl": "/r/components/accordion.json",
|
|
141
|
+
"specimens": [
|
|
142
|
+
{
|
|
143
|
+
"title": "Accordion",
|
|
144
|
+
"group": "layout",
|
|
145
|
+
"components": [
|
|
146
|
+
"Accordion"
|
|
147
|
+
],
|
|
148
|
+
"code": "<Accordion.Root className=\"w-full max-w-[36rem]\" defaultValue={[\"patch\"]}>\n <Accordion.Item value=\"patch\">\n <Accordion.Header>\n <Accordion.Trigger>Patch</Accordion.Trigger>\n </Accordion.Header>\n <Accordion.Panel>48 fixtures across 4 universes.</Accordion.Panel>\n </Accordion.Item>\n <Accordion.Item value=\"cues\">\n <Accordion.Header>\n <Accordion.Trigger>Cues</Accordion.Trigger>\n </Accordion.Header>\n <Accordion.Panel>12 cues, last edited 4 minutes ago.</Accordion.Panel>\n </Accordion.Item>\n <Accordion.Item value=\"network\" disabled>\n <Accordion.Header>\n <Accordion.Trigger>Network</Accordion.Trigger>\n </Accordion.Header>\n <Accordion.Panel>Art-Net on 10.0.0.42.</Accordion.Panel>\n </Accordion.Item>\n</Accordion.Root>",
|
|
149
|
+
"note": "Hairline-separated items with mono summary rows. Several can be open at once.",
|
|
150
|
+
"interaction": "the whole header row is the trigger; the panel animates its height and stills under prefers-reduced-motion."
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"todo": true
|
|
154
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "AgentPile",
|
|
3
|
+
"slug": "agent-pile",
|
|
4
|
+
"group": "chat",
|
|
5
|
+
"importPath": "@cueplusplus/ui",
|
|
6
|
+
"peerDependencies": [],
|
|
7
|
+
"clientOnly": true,
|
|
8
|
+
"description": "The roster of agents working on a thread, stacked into one object.\n\nThree states ride on each avatar, and none of them is colour-only: the\norchestrator wears a crown, a working agent gets a spinning arc, and an agent\nwaiting on the user gets a `?` badge — each of them also spelled into the\navatar's accessible name, so a screen reader hears \"Researcher · working\"\nrather than nothing at all.\n\nAvatars are outlined in the agent's colour rather than filled with it. A fill\nwould put arbitrary-hue ink on an arbitrary-hue ground at 20px; the outline\ntreatment is the same tinted-chip recipe the rest of the system uses and\nstays legible on every theme.",
|
|
9
|
+
"props": [
|
|
10
|
+
{
|
|
11
|
+
"name": "agents",
|
|
12
|
+
"type": "readonly AgentPileEntry[]",
|
|
13
|
+
"required": true,
|
|
14
|
+
"defaultValue": null,
|
|
15
|
+
"description": "The roster, in the order it should read. Renders nothing when empty."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "root",
|
|
19
|
+
"type": "string",
|
|
20
|
+
"required": false,
|
|
21
|
+
"defaultValue": null,
|
|
22
|
+
"description": "Slug of the orchestrator: takes the theme accent and wears the crown."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "selected",
|
|
26
|
+
"type": "string",
|
|
27
|
+
"required": false,
|
|
28
|
+
"defaultValue": null,
|
|
29
|
+
"description": "Slug of the lane currently open — that avatar gets a ring."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "onSelect",
|
|
33
|
+
"type": "((slug: string) => void)",
|
|
34
|
+
"required": false,
|
|
35
|
+
"defaultValue": null,
|
|
36
|
+
"description": "Called with a slug when an avatar is pressed. Omit for a read-only pile."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "max",
|
|
40
|
+
"type": "number",
|
|
41
|
+
"required": false,
|
|
42
|
+
"defaultValue": null,
|
|
43
|
+
"description": "Show at most this many; the rest collapse into a `+N` chip."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "fanned",
|
|
47
|
+
"type": "boolean",
|
|
48
|
+
"required": false,
|
|
49
|
+
"defaultValue": null,
|
|
50
|
+
"description": "Force the fan open or shut. Omit to let hover and focus decide."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "size",
|
|
54
|
+
"type": "\"sm\" | \"md\" | \"lg\"",
|
|
55
|
+
"required": false,
|
|
56
|
+
"defaultValue": "md",
|
|
57
|
+
"description": "Rung of the control ladder. Defaults to `\"md\"`."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "aria-label",
|
|
61
|
+
"type": "string",
|
|
62
|
+
"required": false,
|
|
63
|
+
"defaultValue": null,
|
|
64
|
+
"description": "Accessible name for the group. Required in practice — name the roster."
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"typeReferences": [
|
|
68
|
+
"Omit<React.ComponentPropsWithoutRef<\"div\">, \"onSelect\">"
|
|
69
|
+
],
|
|
70
|
+
"subcomponents": [],
|
|
71
|
+
"parts": [],
|
|
72
|
+
"variants": {},
|
|
73
|
+
"defaultVariants": {},
|
|
74
|
+
"tokensUsed": [
|
|
75
|
+
"--cue-accent",
|
|
76
|
+
"--cue-accent-fg",
|
|
77
|
+
"--cue-bg",
|
|
78
|
+
"--cue-border",
|
|
79
|
+
"--cue-control-lg",
|
|
80
|
+
"--cue-control-md",
|
|
81
|
+
"--cue-control-sm",
|
|
82
|
+
"--cue-fg-muted",
|
|
83
|
+
"--cue-font-mono",
|
|
84
|
+
"--cue-icon-sm",
|
|
85
|
+
"--cue-surface-2",
|
|
86
|
+
"--cue-surface-3",
|
|
87
|
+
"--cue-text-label",
|
|
88
|
+
"--cue-text-micro",
|
|
89
|
+
"--cue-text-ui"
|
|
90
|
+
],
|
|
91
|
+
"summary": "The roster of agents working on a thread, stacked into one object.",
|
|
92
|
+
"examples": [
|
|
93
|
+
{
|
|
94
|
+
"title": "Usage",
|
|
95
|
+
"code": "<AgentPile aria-label=\"Agents\" agents={roster} root=\"chief\" onSelect={openLane} />\n<AgentPile aria-label=\"Agents\" agents={roster} max={5} fanned />",
|
|
96
|
+
"language": "tsx"
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"status": "todo-docs",
|
|
100
|
+
"url": "/docs/components/agent-pile",
|
|
101
|
+
"mdUrl": "/docs/components/agent-pile.md",
|
|
102
|
+
"jsonUrl": "/r/components/agent-pile.json",
|
|
103
|
+
"specimens": [
|
|
104
|
+
{
|
|
105
|
+
"title": "AgentPile",
|
|
106
|
+
"group": "chat",
|
|
107
|
+
"components": [
|
|
108
|
+
"AgentPile"
|
|
109
|
+
],
|
|
110
|
+
"code": "<Specimens>\n {([\"sm\", \"md\", \"lg\"] as const).map((size) => (\n <Specimen key={size} label={size}>\n <AgentPile agents={AGENTS} root=\"orchestrator\" size={size} />\n </Specimen>\n ))}\n <Specimen label=\"max 4\">\n <AgentPile agents={AGENTS} root=\"orchestrator\" max={4} />\n </Specimen>\n <Specimen label=\"not fanned\">\n <AgentPile agents={AGENTS} root=\"orchestrator\" fanned={false} />\n </Specimen>\n <Specimen label=\"selected\">\n <AgentPile agents={AGENTS} root=\"orchestrator\" selected=\"patcher\" />\n </Specimen>\n</Specimens>",
|
|
111
|
+
"note": "Faces at −12px overlap, fanning to +4px on hover. The crown marks the orchestrator, an arc marks work in flight and a `?` marks an agent waiting on an answer.",
|
|
112
|
+
"interaction": "every face is a button with the agent's name; the pile is one tab stop and arrow keys move inside it."
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"todo": true
|
|
116
|
+
}
|