@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.
Files changed (183) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/brand/_chassis.d.ts +1 -1
  3. package/dist/brand/cue-mark.d.ts +1 -1
  4. package/dist/brand/cue-mark.js +1 -1
  5. package/dist/chrome/_edge-scroller.js +12 -1
  6. package/dist/chrome/_tabs-scroll.d.ts +1 -1
  7. package/dist/chrome/_tabs-scroll.js +1 -1
  8. package/dist/configurator/_overrides.js +18 -6
  9. package/dist/index.d.ts +4 -4
  10. package/dist/index.js +2 -2
  11. package/dist/instruments/index.d.ts +2 -2
  12. package/dist/instruments/index.js +2 -2
  13. package/dist/instruments/table.d.ts +14 -1
  14. package/dist/instruments/table.js +20 -1
  15. package/dist/primitives/chip.d.ts +1 -1
  16. package/dist/styles.css +11 -1
  17. package/dist/system/index.d.ts +3 -3
  18. package/dist/system/portal.js +19 -5
  19. package/dist/system/prepaint.d.ts +20 -2
  20. package/dist/system/prepaint.js +39 -33
  21. package/dist/system/theme-provider.d.ts +13 -2
  22. package/dist/system/theme-provider.js +56 -20
  23. package/dist/theming/_presets.js +53 -0
  24. package/dist/theming/create-theme.d.ts +2 -2
  25. package/dist/theming/create-theme.js +24 -15
  26. package/dist/theming/serialize.d.ts +1 -1
  27. package/dist/theming/serialize.js +25 -1
  28. package/manifest/components/accordion.json +154 -0
  29. package/manifest/components/agent-pile.json +116 -0
  30. package/manifest/components/alert-dialog.json +190 -0
  31. package/manifest/components/app-bar.json +244 -0
  32. package/manifest/components/app-shell.json +291 -0
  33. package/manifest/components/app-window-frame.json +102 -0
  34. package/manifest/components/ask-box.json +209 -0
  35. package/manifest/components/autocomplete.json +197 -0
  36. package/manifest/components/avatar-group.json +72 -0
  37. package/manifest/components/avatar.json +95 -0
  38. package/manifest/components/breadcrumb.json +74 -0
  39. package/manifest/components/button-group.json +74 -0
  40. package/manifest/components/button.json +125 -0
  41. package/manifest/components/calendar.json +65 -0
  42. package/manifest/components/card.json +140 -0
  43. package/manifest/components/carousel.json +200 -0
  44. package/manifest/components/channel-matrix.json +173 -0
  45. package/manifest/components/chart-container.json +111 -0
  46. package/manifest/components/chart-ramp.json +72 -0
  47. package/manifest/components/chart-swatch.json +65 -0
  48. package/manifest/components/chart-tooltip-content.json +114 -0
  49. package/manifest/components/checkbox-group.json +68 -0
  50. package/manifest/components/checkbox.json +93 -0
  51. package/manifest/components/chip.json +132 -0
  52. package/manifest/components/collapsible.json +120 -0
  53. package/manifest/components/color-area.json +59 -0
  54. package/manifest/components/color-field.json +79 -0
  55. package/manifest/components/color-picker.json +175 -0
  56. package/manifest/components/color-slider.json +75 -0
  57. package/manifest/components/color-swatch.json +68 -0
  58. package/manifest/components/colors-section.json +66 -0
  59. package/manifest/components/combobox.json +198 -0
  60. package/manifest/components/command-palette.json +177 -0
  61. package/manifest/components/composer.json +176 -0
  62. package/manifest/components/container.json +60 -0
  63. package/manifest/components/context-menu.json +121 -0
  64. package/manifest/components/copy-button.json +93 -0
  65. package/manifest/components/cue-logotype.json +96 -0
  66. package/manifest/components/cue-mark.json +97 -0
  67. package/manifest/components/cue-portal-frame.json +55 -0
  68. package/manifest/components/data-table-pagination.json +83 -0
  69. package/manifest/components/data-table-toolbar.json +104 -0
  70. package/manifest/components/date-field.json +80 -0
  71. package/manifest/components/date-picker.json +191 -0
  72. package/manifest/components/date-range-picker.json +191 -0
  73. package/manifest/components/delegation-card.json +144 -0
  74. package/manifest/components/density.json +58 -0
  75. package/manifest/components/description-list.json +84 -0
  76. package/manifest/components/dialog.json +198 -0
  77. package/manifest/components/disclosure.json +109 -0
  78. package/manifest/components/dmx-bar.json +94 -0
  79. package/manifest/components/dmx-strip.json +101 -0
  80. package/manifest/components/drawer.json +179 -0
  81. package/manifest/components/dropdown-menu.json +229 -0
  82. package/manifest/components/empty-state.json +90 -0
  83. package/manifest/components/env-var-input.json +150 -0
  84. package/manifest/components/export-dialog.json +84 -0
  85. package/manifest/components/eyebrow.json +56 -0
  86. package/manifest/components/field-description.json +63 -0
  87. package/manifest/components/field-error.json +77 -0
  88. package/manifest/components/field-label.json +63 -0
  89. package/manifest/components/field.json +79 -0
  90. package/manifest/components/file-upload.json +159 -0
  91. package/manifest/components/flow-background.json +50 -0
  92. package/manifest/components/flow-controls.json +50 -0
  93. package/manifest/components/footer.json +112 -0
  94. package/manifest/components/grid.json +81 -0
  95. package/manifest/components/group-bar.json +102 -0
  96. package/manifest/components/hover-card.json +151 -0
  97. package/manifest/components/icon-button.json +88 -0
  98. package/manifest/components/info-tip.json +98 -0
  99. package/manifest/components/input-group.json +91 -0
  100. package/manifest/components/input.json +89 -0
  101. package/manifest/components/item.json +129 -0
  102. package/manifest/components/kbd.json +52 -0
  103. package/manifest/components/link.json +81 -0
  104. package/manifest/components/log-viewer.json +139 -0
  105. package/manifest/components/menubar.json +157 -0
  106. package/manifest/components/message-list.json +100 -0
  107. package/manifest/components/message.json +116 -0
  108. package/manifest/components/meter.json +114 -0
  109. package/manifest/components/multi-select.json +190 -0
  110. package/manifest/components/musical-time-input.json +113 -0
  111. package/manifest/components/navigation-menu.json +226 -0
  112. package/manifest/components/node-card.json +109 -0
  113. package/manifest/components/node-handle.json +69 -0
  114. package/manifest/components/number-field.json +107 -0
  115. package/manifest/components/otp-field.json +128 -0
  116. package/manifest/components/page-shell.json +96 -0
  117. package/manifest/components/pagination.json +92 -0
  118. package/manifest/components/panel-header.json +77 -0
  119. package/manifest/components/panel.json +71 -0
  120. package/manifest/components/password-input.json +119 -0
  121. package/manifest/components/piano-keyboard.json +120 -0
  122. package/manifest/components/plussie.json +111 -0
  123. package/manifest/components/popover.json +193 -0
  124. package/manifest/components/preset-section.json +73 -0
  125. package/manifest/components/progress.json +95 -0
  126. package/manifest/components/radio-group.json +73 -0
  127. package/manifest/components/radio.json +80 -0
  128. package/manifest/components/rating.json +141 -0
  129. package/manifest/components/resizable.json +88 -0
  130. package/manifest/components/row.json +109 -0
  131. package/manifest/components/scroll-area.json +91 -0
  132. package/manifest/components/scrollable-tabs-list.json +83 -0
  133. package/manifest/components/scrub-input.json +200 -0
  134. package/manifest/components/seam-cell.json +75 -0
  135. package/manifest/components/seam-grid.json +75 -0
  136. package/manifest/components/seam-list.json +56 -0
  137. package/manifest/components/search-input.json +134 -0
  138. package/manifest/components/section-header.json +112 -0
  139. package/manifest/components/segmented-control.json +138 -0
  140. package/manifest/components/select.json +194 -0
  141. package/manifest/components/separator.json +53 -0
  142. package/manifest/components/shape-section.json +66 -0
  143. package/manifest/components/sheet.json +186 -0
  144. package/manifest/components/sidebar.json +198 -0
  145. package/manifest/components/signal-edge.json +56 -0
  146. package/manifest/components/skeleton.json +46 -0
  147. package/manifest/components/slider.json +89 -0
  148. package/manifest/components/sparkline.json +76 -0
  149. package/manifest/components/spectrum-visualizer.json +115 -0
  150. package/manifest/components/spinner.json +61 -0
  151. package/manifest/components/stack.json +96 -0
  152. package/manifest/components/stat.json +87 -0
  153. package/manifest/components/status-bar.json +58 -0
  154. package/manifest/components/status-dot.json +78 -0
  155. package/manifest/components/stepper.json +89 -0
  156. package/manifest/components/switch.json +87 -0
  157. package/manifest/components/table-scroll-region.json +69 -0
  158. package/manifest/components/table.json +155 -0
  159. package/manifest/components/tabs.json +145 -0
  160. package/manifest/components/tags-input.json +190 -0
  161. package/manifest/components/terminal-frame.json +119 -0
  162. package/manifest/components/textarea.json +76 -0
  163. package/manifest/components/theme-configurator.json +124 -0
  164. package/manifest/components/theme-provider.json +134 -0
  165. package/manifest/components/threshold-rail.json +160 -0
  166. package/manifest/components/time-field.json +80 -0
  167. package/manifest/components/timeline-ruler.json +139 -0
  168. package/manifest/components/timeline.json +115 -0
  169. package/manifest/components/title-bar.json +82 -0
  170. package/manifest/components/toast.json +130 -0
  171. package/manifest/components/toggle-group.json +106 -0
  172. package/manifest/components/toggle.json +109 -0
  173. package/manifest/components/token-editor.json +90 -0
  174. package/manifest/components/toolbar.json +163 -0
  175. package/manifest/components/tooltip.json +164 -0
  176. package/manifest/components/tree-visibility-toggle.json +84 -0
  177. package/manifest/components/tree.json +140 -0
  178. package/manifest/components/two-step-button.json +113 -0
  179. package/manifest/components/universe-grid.json +127 -0
  180. package/manifest/components/usage-chart.json +124 -0
  181. package/manifest/manifest.json +2142 -0
  182. package/manifest/tokens.json +138 -0
  183. 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 || restoredKey.current === storageKey) return;
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 (typeof stored.theme === "string" && stored.theme.length > 0) setThemeState(stored.theme);
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
- }, [isRoot, storageKey]);
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
- }, [isRoot, storageKey]);
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", resolvedMode);
201
- root.style.colorScheme = resolvedMode;
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
- resolvedMode
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: resolvedMode,
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
- resolvedMode,
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": resolvedMode
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(DensityContext.Provider, {
283
- value: density,
284
- children: root
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 };
@@ -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 eight
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 eight authored light presets.
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 eight
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 eight authored light presets.
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 darkSections = [
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
- ...light === null ? [] : [{
428
+ ...lightSections === null ? [] : [{
425
429
  selector: themeSelector(name, { mode: "light" }),
426
430
  colorScheme: "light",
427
- sections: [{ declarations: colorDeclarations(light.colors) }, {
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
+ }