@cueplusplus/ui 0.1.0 → 0.2.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 (83) hide show
  1. package/CHANGELOG.md +684 -0
  2. package/dist/brand/_chassis.d.ts +54 -0
  3. package/dist/brand/_chassis.js +81 -0
  4. package/dist/brand/_geometry.js +41 -0
  5. package/dist/brand/cue-logotype.d.ts +29 -0
  6. package/dist/brand/cue-logotype.js +60 -0
  7. package/dist/brand/cue-mark.d.ts +30 -0
  8. package/dist/brand/cue-mark.js +61 -0
  9. package/dist/brand/index.d.ts +5 -0
  10. package/dist/brand/plussie.d.ts +67 -0
  11. package/dist/brand/plussie.js +125 -0
  12. package/dist/chat/agent-pile.js +1 -1
  13. package/dist/chat/ask-box.js +2 -2
  14. package/dist/chat/delegation-card.js +1 -1
  15. package/dist/chat/message.js +2 -2
  16. package/dist/chrome/_edge-scroller.d.ts +45 -0
  17. package/dist/chrome/_edge-scroller.js +138 -0
  18. package/dist/chrome/_glyphs.js +19 -1
  19. package/dist/chrome/_tabs-scroll.d.ts +12 -7
  20. package/dist/chrome/_tabs-scroll.js +12 -7
  21. package/dist/chrome/app-bar.d.ts +103 -0
  22. package/dist/chrome/app-bar.js +175 -0
  23. package/dist/chrome/app-shell.d.ts +125 -0
  24. package/dist/chrome/app-shell.js +167 -0
  25. package/dist/chrome/footer.d.ts +75 -0
  26. package/dist/chrome/footer.js +68 -0
  27. package/dist/chrome/index.d.ts +5 -1
  28. package/dist/chrome/index.js +4 -1
  29. package/dist/chrome/navigation-menu.js +1 -1
  30. package/dist/chrome/page-shell.d.ts +1 -1
  31. package/dist/chrome/page-shell.js +15 -5
  32. package/dist/chrome/segmented-control.d.ts +19 -0
  33. package/dist/chrome/segmented-control.js +40 -21
  34. package/dist/chrome/tabs.js +21 -65
  35. package/dist/chrome/toolbar.d.ts +22 -0
  36. package/dist/chrome/toolbar.js +26 -5
  37. package/dist/configurator/_overrides.js +4 -2
  38. package/dist/configurator/configurator.js +1 -1
  39. package/dist/configurator/panel-sections.js +10 -4
  40. package/dist/dmx/channel-matrix.js +1 -1
  41. package/dist/forms/otp-field.js +1 -1
  42. package/dist/index.d.ts +15 -6
  43. package/dist/index.js +15 -9
  44. package/dist/instruments/data-table.js +1 -1
  45. package/dist/layout/carousel.js +1 -1
  46. package/dist/layout/container.d.ts +1 -1
  47. package/dist/layout/container.js +6 -3
  48. package/dist/layout/index.d.ts +3 -3
  49. package/dist/layout/index.js +3 -3
  50. package/dist/layout/pagination.js +1 -1
  51. package/dist/layout/sidebar.d.ts +23 -3
  52. package/dist/layout/sidebar.js +18 -7
  53. package/dist/midi/spectrum-visualizer.js +1 -1
  54. package/dist/midi/timeline-ruler.js +1 -1
  55. package/dist/overlays/command-palette.js +1 -1
  56. package/dist/overlays/dialog.js +1 -1
  57. package/dist/overlays/dropdown-menu.js +2 -2
  58. package/dist/overlays/hover-card.js +1 -1
  59. package/dist/overlays/index.js +1 -1
  60. package/dist/overlays/popover.js +1 -1
  61. package/dist/overlays/sheet.js +1 -1
  62. package/dist/overlays/toast.js +1 -1
  63. package/dist/primitives/index.d.ts +1 -1
  64. package/dist/primitives/index.js +1 -1
  65. package/dist/primitives/status-dot.d.ts +7 -0
  66. package/dist/primitives/status-dot.js +8 -1
  67. package/dist/styles.css +47 -1
  68. package/dist/system/density.d.ts +4 -1
  69. package/dist/system/density.js +12 -4
  70. package/dist/system/index.d.ts +2 -2
  71. package/dist/system/portal.d.ts +5 -5
  72. package/dist/system/portal.js +11 -6
  73. package/dist/system/prepaint.d.ts +15 -8
  74. package/dist/system/prepaint.js +26 -9
  75. package/dist/system/theme-provider.d.ts +36 -10
  76. package/dist/system/theme-provider.js +56 -18
  77. package/dist/system/use-density.d.ts +3 -3
  78. package/dist/system/use-density.js +16 -6
  79. package/dist/system/use-theme.d.ts +4 -2
  80. package/dist/system/use-theme.js +4 -2
  81. package/dist/theming/_presets.js +80 -5
  82. package/dist/theming/create-theme.js +13 -4
  83. package/package.json +4 -3
@@ -1,19 +1,20 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.js";
4
- import { DENSITY_LEVELS, DensityContext } from "./density.js";
4
+ import { DEFAULT_DENSITY as DEFAULT_DENSITY$1, DENSITY_LEVELS, DensityContext } from "./density.js";
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
9
  //#region src/system/theme-provider.tsx
9
10
  const MODES = [
10
11
  "dark",
11
12
  "light",
12
13
  "system"
13
14
  ];
15
+ const FONT_NAMES = FONTS;
14
16
  /** Dark-first portfolio defaults; the same triple bare `:root` in `theme.css` paints. */
15
17
  const DEFAULT_THEME = "cue";
16
- const DEFAULT_DENSITY = "normal";
17
18
  const DEFAULT_MODE = "dark";
18
19
  const DEFAULT_RESOLVED_MODE = "dark";
19
20
  /**
@@ -56,62 +57,82 @@ function writePreferences(storageKey, preferences) {
56
57
  }
57
58
  /**
58
59
  * Root of the theme system: stamps `data-theme`, `data-density` and `data-mode`
59
- * so the token layer resolves, publishes `--cue-font-scale`, and owns the
60
- * persisted user preference.
60
+ * so the token layer resolves, puts `data-font` on `<html>`, publishes
61
+ * `--cue-font-scale`, and owns the persisted user preference.
61
62
  *
62
63
  * Rendering: `<div data-cue-root data-theme data-density data-mode style={{ colorScheme, --cue-font-scale }}>`
63
- * (or the single child when `asChild`). The `theme`/`density`/`mode` props are
64
+ * (or the single child when `asChild`). The `theme`/`density`/`font`/`mode` props are
64
65
  * *initial* values — the provider holds the state so `setTheme` and friends can
65
66
  * drive it — but a changed prop is adopted after mount, so a controlling parent
66
67
  * still works.
67
68
  *
69
+ * The font pairing is the one axis that never lands on this element: it has no
70
+ * island form, `<html>` is its only home, and restating it here would shadow the
71
+ * pre-paint stamp for the whole page on the first frame. See the `stamp` object
72
+ * below.
73
+ *
68
74
  * Persistence and the pre-paint contract: setters write
69
- * `{ theme, density, mode }` to `localStorage[storageKey]`, which is exactly what
75
+ * `{ theme, density, font, mode }` to `localStorage[storageKey]`, which is exactly what
70
76
  * {@link prepaintScript} reads to stamp `<html>` before the first paint. The
71
77
  * outermost provider keeps `<html>` in sync while the app runs (and restores the
72
78
  * previous stamp on unmount) so the page ground, UA scrollbars and form controls
73
- * follow the theme; nested providers never touch `<html>`.
79
+ * follow the theme; nested providers never touch `<html>`, and never own the
80
+ * font pairing — `useTheme().font` and `setFont` inside one are the root's.
74
81
  *
75
82
  * @example
76
- * <ThemeProvider theme="terminal" density="ultra-compact" mode="system">
83
+ * <ThemeProvider theme="terminal" density="ultra-compact" font="plex" mode="system">
77
84
  * <App />
78
85
  * </ThemeProvider>
79
86
  */
80
- function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp = DEFAULT_DENSITY, mode: modeProp = DEFAULT_MODE, fontScale = 1, storageKey = DEFAULT_STORAGE_KEY, asChild = false, className, style: styleProp, children }) {
81
- const isRoot = React.useContext(ThemeContext) === null;
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 }) {
88
+ const ambient = React.useContext(ThemeContext);
89
+ const isRoot = ambient === null;
82
90
  const [theme, setThemeState] = React.useState(themeProp);
83
91
  const [density, setDensityState] = React.useState(densityProp);
84
92
  const [mode, setModeState] = React.useState(modeProp);
93
+ const [rootFont, setRootFontState] = React.useState(() => {
94
+ if (!isRoot) return fontProp;
95
+ const stored = readPreferences(storageKey).font;
96
+ return isOneOf(FONT_NAMES, stored) ? stored : fontProp;
97
+ });
98
+ const font = isRoot ? rootFont : ambient.font;
85
99
  const [systemMode, setSystemMode] = React.useState(DEFAULT_RESOLVED_MODE);
86
100
  const resolvedMode = mode === "system" ? systemMode : mode;
87
101
  const restoredKey = React.useRef(null);
102
+ const [restored, setRestored] = React.useState(false);
88
103
  useIsomorphicLayoutEffect(() => {
89
104
  if (!isRoot || restoredKey.current === storageKey) return;
90
105
  restoredKey.current = storageKey;
106
+ setRestored(true);
91
107
  const stored = readPreferences(storageKey);
92
108
  if (typeof stored.theme === "string" && stored.theme.length > 0) setThemeState(stored.theme);
93
109
  if (isOneOf(DENSITY_LEVELS, stored.density)) setDensityState(stored.density);
110
+ if (isOneOf(FONT_NAMES, stored.font)) setRootFontState(stored.font);
94
111
  if (isOneOf(MODES, stored.mode)) setModeState(stored.mode);
95
112
  }, [isRoot, storageKey]);
96
113
  const lastProps = React.useRef({
97
114
  theme: themeProp,
98
115
  density: densityProp,
99
- mode: modeProp
116
+ mode: modeProp,
117
+ font: fontProp
100
118
  });
101
119
  React.useEffect(() => {
102
120
  const previous = lastProps.current;
103
121
  lastProps.current = {
104
122
  theme: themeProp,
105
123
  density: densityProp,
106
- mode: modeProp
124
+ mode: modeProp,
125
+ font: fontProp
107
126
  };
108
127
  if (previous.theme !== themeProp) setThemeState(themeProp);
109
128
  if (previous.density !== densityProp) setDensityState(densityProp);
110
129
  if (previous.mode !== modeProp) setModeState(modeProp);
130
+ if (previous.font !== fontProp) setRootFontState(fontProp);
111
131
  }, [
112
132
  themeProp,
113
133
  densityProp,
114
- modeProp
134
+ modeProp,
135
+ fontProp
115
136
  ]);
116
137
  useIsomorphicLayoutEffect(() => {
117
138
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
@@ -124,13 +145,15 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
124
145
  const stateRef = React.useRef({
125
146
  theme,
126
147
  density,
127
- mode
148
+ mode,
149
+ font
128
150
  });
129
151
  useIsomorphicLayoutEffect(() => {
130
152
  stateRef.current = {
131
153
  theme,
132
154
  density,
133
- mode
155
+ mode,
156
+ font
134
157
  };
135
158
  });
136
159
  const persist = React.useCallback((patch) => {
@@ -154,24 +177,34 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
154
177
  setModeState(next);
155
178
  persist({ mode: next });
156
179
  }, [persist]);
180
+ const rootSetFont = React.useCallback((next) => {
181
+ setRootFontState(next);
182
+ persist({ font: next });
183
+ }, [persist]);
184
+ /** Nested providers hand the axis straight up: one document, one pairing. */
185
+ const setFont = isRoot ? rootSetFont : ambient.setFont;
157
186
  const originalStamp = React.useRef(null);
158
187
  useIsomorphicLayoutEffect(() => {
159
- if (!isRoot || typeof document === "undefined") return;
188
+ if (!isRoot || !restored || typeof document === "undefined") return;
160
189
  const root = document.documentElement;
161
190
  originalStamp.current ??= {
162
191
  theme: root.getAttribute("data-theme"),
163
192
  density: root.getAttribute("data-density"),
193
+ font: root.getAttribute("data-font"),
164
194
  mode: root.getAttribute("data-mode"),
165
195
  colorScheme: root.style.colorScheme
166
196
  };
167
197
  root.setAttribute("data-theme", theme);
168
198
  root.setAttribute("data-density", density);
199
+ root.setAttribute("data-font", font);
169
200
  root.setAttribute("data-mode", resolvedMode);
170
201
  root.style.colorScheme = resolvedMode;
171
202
  }, [
172
203
  isRoot,
204
+ restored,
173
205
  theme,
174
206
  density,
207
+ font,
175
208
  resolvedMode
176
209
  ]);
177
210
  React.useEffect(() => () => {
@@ -185,6 +218,7 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
185
218
  };
186
219
  restore("data-theme", original.theme);
187
220
  restore("data-density", original.density);
221
+ restore("data-font", original.font);
188
222
  restore("data-mode", original.mode);
189
223
  root.style.colorScheme = original.colorScheme;
190
224
  }, []);
@@ -192,18 +226,22 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
192
226
  theme,
193
227
  density,
194
228
  mode,
229
+ font,
195
230
  resolvedMode,
196
231
  setTheme,
197
232
  setDensity,
198
- setMode
233
+ setMode,
234
+ setFont
199
235
  }), [
200
236
  theme,
201
237
  density,
202
238
  mode,
239
+ font,
203
240
  resolvedMode,
204
241
  setTheme,
205
242
  setDensity,
206
- setMode
243
+ setMode,
244
+ setFont
207
245
  ]);
208
246
  const style = React.useMemo(() => ({
209
247
  colorScheme: resolvedMode,
@@ -4,11 +4,11 @@ import { Density } from "@cueplusplus/tokens";
4
4
  type ControlSize = "sm" | "md" | "lg";
5
5
  /**
6
6
  * The density level that actually applies here: the nearest `<Density>` island,
7
- * else the `<ThemeProvider>` level, else `"normal"`.
7
+ * else the `<ThemeProvider>` level, else `"compact"`.
8
8
  *
9
9
  * This is a JS read for code that cannot use CSS (virtualised row heights,
10
10
  * canvas drawing, layout math). Styling should use the density tokens or the
11
- * `density-ultra:` / `density-large:` variants instead.
11
+ * per-rung variants (`density-ultra-compact:` … `density-ultra-large:`) instead.
12
12
  *
13
13
  * @returns The applicable density level.
14
14
  */
@@ -26,7 +26,7 @@ declare function useDensity(): Density;
26
26
  * @param size - Which rung to measure. Defaults to `"md"`.
27
27
  * @returns Height in CSS pixels.
28
28
  * @example
29
- * const rowHeight = useControlHeight("sm"); // → 20 at normal density
29
+ * const rowHeight = useControlHeight("sm"); // → 20 at compact density
30
30
  */
31
31
  declare function useControlHeight(size?: ControlSize): number;
32
32
  //#endregion
@@ -14,15 +14,25 @@ const CONTROL_REM = {
14
14
  md: 1.25,
15
15
  lg: 1.5
16
16
  },
17
- normal: {
17
+ "compact": {
18
18
  sm: 1.25,
19
19
  md: 1.5,
20
20
  lg: 1.75
21
21
  },
22
- large: {
22
+ "normal": {
23
23
  sm: 1.75,
24
24
  md: 2,
25
25
  lg: 2.25
26
+ },
27
+ "large": {
28
+ sm: 2.25,
29
+ md: 2.5,
30
+ lg: 2.75
31
+ },
32
+ "ultra-large": {
33
+ sm: 2.75,
34
+ md: 3,
35
+ lg: 3.25
26
36
  }
27
37
  };
28
38
  /** Root font size assumed before the first measurement, so SSR and hydration agree. */
@@ -46,16 +56,16 @@ function measureControlHeight(density, size) {
46
56
  }
47
57
  /**
48
58
  * The density level that actually applies here: the nearest `<Density>` island,
49
- * else the `<ThemeProvider>` level, else `"normal"`.
59
+ * else the `<ThemeProvider>` level, else `"compact"`.
50
60
  *
51
61
  * This is a JS read for code that cannot use CSS (virtualised row heights,
52
62
  * canvas drawing, layout math). Styling should use the density tokens or the
53
- * `density-ultra:` / `density-large:` variants instead.
63
+ * per-rung variants (`density-ultra-compact:` … `density-ultra-large:`) instead.
54
64
  *
55
65
  * @returns The applicable density level.
56
66
  */
57
67
  function useDensity() {
58
- return React.useContext(DensityContext) ?? "normal";
68
+ return React.useContext(DensityContext) ?? "compact";
59
69
  }
60
70
  /**
61
71
  * The pixel height of a control at the ambient density — what a virtualiser or a
@@ -70,7 +80,7 @@ function useDensity() {
70
80
  * @param size - Which rung to measure. Defaults to `"md"`.
71
81
  * @returns Height in CSS pixels.
72
82
  * @example
73
- * const rowHeight = useControlHeight("sm"); // → 20 at normal density
83
+ * const rowHeight = useControlHeight("sm"); // → 20 at compact density
74
84
  */
75
85
  function useControlHeight(size = "md") {
76
86
  const density = useDensity();
@@ -3,10 +3,12 @@ import { ThemeContextValue } from "./theme-provider.js";
3
3
  /**
4
4
  * Read and drive the ambient theme state.
5
5
  *
6
- * Returns `{ theme, density, mode, resolvedMode, setTheme, setDensity, setMode }`.
6
+ * Returns `{ theme, density, mode, font, resolvedMode, setTheme, setDensity,
7
+ * setMode, setFont }`.
7
8
  * `density` here is the *provider's* level — a nested `<Density>` island does not
8
9
  * change it; use `useDensity()` when you want the level that actually applies
9
- * where you are.
10
+ * where you are. `font` is the pairing the provider holds; it has no island, so
11
+ * it is always the one in force.
10
12
  *
11
13
  * @throws If called outside a `<ThemeProvider>`. Theme state is never guessed:
12
14
  * a missing provider is a wiring bug, not a fallback situation.
@@ -5,10 +5,12 @@ import * as React from "react";
5
5
  /**
6
6
  * Read and drive the ambient theme state.
7
7
  *
8
- * Returns `{ theme, density, mode, resolvedMode, setTheme, setDensity, setMode }`.
8
+ * Returns `{ theme, density, mode, font, resolvedMode, setTheme, setDensity,
9
+ * setMode, setFont }`.
9
10
  * `density` here is the *provider's* level — a nested `<Density>` island does not
10
11
  * change it; use `useDensity()` when you want the level that actually applies
11
- * where you are.
12
+ * where you are. `font` is the pairing the provider holds; it has no island, so
13
+ * it is always the one in force.
12
14
  *
13
15
  * @throws If called outside a `<ThemeProvider>`. Theme state is never guessed:
14
16
  * a missing provider is a wiring bug, not a fallback situation.
@@ -453,6 +453,59 @@ const THEME_PRESETS = {
453
453
  "scrim": "rgba(0,0,0,0.35)"
454
454
  },
455
455
  fonts: { "font-mono": "\"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" }
456
+ },
457
+ quotamate: {
458
+ dark: {
459
+ "bg": "#0e0e12",
460
+ "sunken": "#09090c",
461
+ "surface-1": "#15151a",
462
+ "surface-2": "#212127",
463
+ "surface-3": "#2b2b31",
464
+ "fg": "#f4f4f6",
465
+ "fg-muted": "#8b8b95",
466
+ "fg-subtle": "#62626b",
467
+ "border": "#2b2b31",
468
+ "border-strong": "#3d3d45",
469
+ "border-overlay": "#3d3d45",
470
+ "accent": "#00e5e5",
471
+ "accent-hover": "#33ffff",
472
+ "accent-fg": "#0e0e12",
473
+ "ok": "#22d967",
474
+ "busy": "#00e5e5",
475
+ "warn": "#ffab1a",
476
+ "danger": "#e13d3d",
477
+ "info": "#4a9eff",
478
+ "selection": "rgba(0,229,229,0.18)",
479
+ "focus": "#00e5e5",
480
+ "data-ground": "#000000",
481
+ "scrim": "rgba(0,0,0,0.55)"
482
+ },
483
+ light: {
484
+ "bg": "#fcfcfd",
485
+ "sunken": "#f1f1f4",
486
+ "surface-1": "#ffffff",
487
+ "surface-2": "#f4f4f6",
488
+ "surface-3": "#e7e7ec",
489
+ "fg": "#18181b",
490
+ "fg-muted": "#6e6e78",
491
+ "fg-subtle": "#9a9aa4",
492
+ "border": "#e2e2e8",
493
+ "border-strong": "#c9c9d2",
494
+ "border-overlay": "#c9c9d2",
495
+ "accent": "#0d7d7d",
496
+ "accent-hover": "#0a6767",
497
+ "accent-fg": "#ffffff",
498
+ "ok": "#17914a",
499
+ "busy": "#0d7d7d",
500
+ "warn": "#b46a00",
501
+ "danger": "#c22d2d",
502
+ "info": "#1f6fd4",
503
+ "selection": "rgba(13,125,125,0.16)",
504
+ "focus": "#0d7d7d",
505
+ "data-ground": "#ffffff",
506
+ "scrim": "rgba(0,0,0,0.55)"
507
+ },
508
+ fonts: { "font-mono": "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace" }
456
509
  }
457
510
  };
458
511
  /**
@@ -461,11 +514,16 @@ const THEME_PRESETS = {
461
514
  * has to beat, and `:root` carries the *default* theme's font override. A silent
462
515
  * theme would inherit cue's JetBrains Mono instead of the base stack, so every
463
516
  * block says what it owns. Mirrors `build.mjs`'s `THEME_FONTS` handling.
517
+ *
518
+ * Every entry is a generic or a face the platform ships. The sans stack named
519
+ * `Geist` until 2026-08-19 and no webfont was ever loaded to make it resolve, so
520
+ * it was a name most visitors silently fell straight past; Geist is now the head
521
+ * of the `geist` font pairing, which an app can actually deliver.
464
522
  */
465
523
  const BASE_FONT_STACKS = {
466
- "font-sans": "Geist, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", \"Helvetica Neue\", sans-serif",
524
+ "font-sans": "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", \"Helvetica Neue\", sans-serif",
467
525
  "font-mono": "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace",
468
- "font-display": "Geist, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif"
526
+ "font-display": "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif"
469
527
  };
470
528
  /**
471
529
  * The radius multiplier each density level contributes.
@@ -478,10 +536,27 @@ const BASE_FONT_STACKS = {
478
536
  */
479
537
  const DENSITY_RADIUS_SCALE = {
480
538
  "ultra-compact": .75,
539
+ "compact": 1,
481
540
  "normal": 1,
482
- "large": 1
541
+ "large": 1,
542
+ "ultra-large": 1
483
543
  };
484
544
  /** The default density, whose radius scale the uncrossed theme block carries. */
485
- const DEFAULT_DENSITY_LEVEL = "normal";
545
+ const DEFAULT_DENSITY_LEVEL = "compact";
546
+ /**
547
+ * What every theme block sets `--cue-font-mono` to.
548
+ *
549
+ * Neither the theme axis nor the font axis writes the shared token: each
550
+ * publishes its own half (`--cue-font-theme-mono` and `--cue-font-pairing-mono`)
551
+ * and this is the resolution, pairing first. It has to be a `var()` chain rather
552
+ * than a value because the two attributes land on different elements — a
553
+ * provider stamps `data-theme` on a `<div>` inside the `<html>` the pre-paint
554
+ * script stamped `data-font` on, and for an inherited property the nearer
555
+ * declaration would otherwise win whatever the stylesheet's order is.
556
+ *
557
+ * Mirrors `packages/tokens/build.mjs`'s `RESOLVED_MONO`, and
558
+ * `test/theming/presets.test.ts` fails when the two drift.
559
+ */
560
+ const RESOLVED_MONO = "var(--cue-font-pairing-mono, var(--cue-font-theme-mono))";
486
561
  //#endregion
487
- export { BASE_FONT_STACKS, DEFAULT_DENSITY_LEVEL, DENSITY_RADIUS_SCALE, THEME_COLOR_TOKENS, THEME_PRESETS };
562
+ export { BASE_FONT_STACKS, DEFAULT_DENSITY_LEVEL, DENSITY_RADIUS_SCALE, RESOLVED_MONO, THEME_COLOR_TOKENS, THEME_PRESETS };
@@ -1,5 +1,5 @@
1
1
  import { formatColorString } from "../color/_convert.js";
2
- import { BASE_FONT_STACKS, DEFAULT_DENSITY_LEVEL, DENSITY_RADIUS_SCALE, THEME_COLOR_TOKENS, THEME_PRESETS } from "./_presets.js";
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
4
  import { assertCssValue, assertThemeName, derivedTokens, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
5
5
  import { clampChroma, converter, formatHex, inGamut, parse } from "culori";
@@ -321,14 +321,23 @@ function resolveLight(dark, preset) {
321
321
  provisional
322
322
  };
323
323
  }
324
- /** The font declarations a theme block carries. */
324
+ /**
325
+ * The font declarations a theme block carries.
326
+ *
327
+ * The monospace lands on `--cue-font-theme-mono`, not on `--cue-font-mono`, and
328
+ * the shared token is restated as the resolution of the two axes' halves —
329
+ * exactly what a shipped preset's block does. A generated theme that wrote the
330
+ * shared token directly would beat a `[data-font]` pairing stamped on any
331
+ * ancestor, purely by sitting nearer to the text, and a consumer's own preset
332
+ * would be the one preset in the system the typeface picker could not move.
333
+ */
325
334
  function resolveFonts(anchors, preset) {
326
335
  const fonts = anchors.fonts ?? {};
327
336
  const declared = [];
328
337
  if (fonts.sans !== void 0) declared.push(["font-sans", fonts.sans]);
329
- declared.push(["font-mono", fonts.mono ?? lookup(preset, "font-mono") ?? BASE_FONT_STACKS["font-mono"]]);
338
+ declared.push(["font-theme-mono", fonts.mono ?? lookup(preset, "font-mono") ?? BASE_FONT_STACKS["font-mono"]]);
330
339
  if (fonts.display !== void 0) declared.push(["font-display", fonts.display]);
331
- return declared.map(([name, value]) => [tokenProperty(name), assertCssValue(name, value)]);
340
+ return [...declared.map(([name, value]) => [tokenProperty(name), assertCssValue(name, value)]), [tokenProperty("font-mono"), RESOLVED_MONO]];
332
341
  }
333
342
  /** Round a product of two scales to something a stylesheet can print. */
334
343
  const scaled = (a, b) => String(Number((a * b).toFixed(4)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueplusplus/ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "CUE++ design system components: Tailwind v4 styled wrappers over Base UI, driven by @cueplusplus/tokens.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -107,13 +107,14 @@
107
107
  }
108
108
  },
109
109
  "files": [
110
- "dist"
110
+ "dist",
111
+ "CHANGELOG.md"
111
112
  ],
112
113
  "dependencies": {
113
114
  "clsx": "^2.1.1",
114
115
  "culori": "^4.0.2",
115
116
  "tailwind-merge": "^3.6.0",
116
- "@cueplusplus/tokens": "0.1.0"
117
+ "@cueplusplus/tokens": "0.2.0"
117
118
  },
118
119
  "peerDependencies": {
119
120
  "@base-ui/react": "^1.7.0",