@cueplusplus/ui 0.1.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 +705 -0
- package/dist/brand/_chassis.d.ts +54 -0
- package/dist/brand/_chassis.js +81 -0
- package/dist/brand/_geometry.js +41 -0
- package/dist/brand/cue-logotype.d.ts +29 -0
- package/dist/brand/cue-logotype.js +60 -0
- package/dist/brand/cue-mark.d.ts +30 -0
- package/dist/brand/cue-mark.js +61 -0
- package/dist/brand/index.d.ts +5 -0
- package/dist/brand/plussie.d.ts +67 -0
- package/dist/brand/plussie.js +125 -0
- package/dist/chat/agent-pile.js +1 -1
- package/dist/chat/ask-box.js +2 -2
- package/dist/chat/delegation-card.js +1 -1
- package/dist/chat/message.js +2 -2
- package/dist/chrome/_edge-scroller.d.ts +45 -0
- package/dist/chrome/_edge-scroller.js +149 -0
- package/dist/chrome/_glyphs.js +19 -1
- package/dist/chrome/_tabs-scroll.d.ts +13 -8
- package/dist/chrome/_tabs-scroll.js +13 -8
- package/dist/chrome/app-bar.d.ts +103 -0
- package/dist/chrome/app-bar.js +175 -0
- package/dist/chrome/app-shell.d.ts +125 -0
- package/dist/chrome/app-shell.js +167 -0
- package/dist/chrome/footer.d.ts +75 -0
- package/dist/chrome/footer.js +68 -0
- package/dist/chrome/index.d.ts +5 -1
- package/dist/chrome/index.js +4 -1
- package/dist/chrome/navigation-menu.js +1 -1
- package/dist/chrome/page-shell.d.ts +1 -1
- package/dist/chrome/page-shell.js +15 -5
- package/dist/chrome/segmented-control.d.ts +19 -0
- package/dist/chrome/segmented-control.js +40 -21
- package/dist/chrome/tabs.js +21 -65
- package/dist/chrome/toolbar.d.ts +22 -0
- package/dist/chrome/toolbar.js +26 -5
- package/dist/configurator/_overrides.js +22 -8
- package/dist/configurator/configurator.js +1 -1
- package/dist/configurator/panel-sections.js +10 -4
- package/dist/dmx/channel-matrix.js +1 -1
- package/dist/forms/otp-field.js +1 -1
- package/dist/index.d.ts +18 -9
- package/dist/index.js +16 -10
- package/dist/instruments/data-table.js +1 -1
- 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/layout/carousel.js +1 -1
- package/dist/layout/container.d.ts +1 -1
- package/dist/layout/container.js +6 -3
- package/dist/layout/index.d.ts +3 -3
- package/dist/layout/index.js +3 -3
- package/dist/layout/pagination.js +1 -1
- package/dist/layout/sidebar.d.ts +23 -3
- package/dist/layout/sidebar.js +18 -7
- package/dist/midi/spectrum-visualizer.js +1 -1
- package/dist/midi/timeline-ruler.js +1 -1
- package/dist/overlays/command-palette.js +1 -1
- package/dist/overlays/dialog.js +1 -1
- package/dist/overlays/dropdown-menu.js +2 -2
- package/dist/overlays/hover-card.js +1 -1
- package/dist/overlays/index.js +1 -1
- package/dist/overlays/popover.js +1 -1
- package/dist/overlays/sheet.js +1 -1
- package/dist/overlays/toast.js +1 -1
- package/dist/primitives/chip.d.ts +1 -1
- package/dist/primitives/index.d.ts +1 -1
- package/dist/primitives/index.js +1 -1
- package/dist/primitives/status-dot.d.ts +7 -0
- package/dist/primitives/status-dot.js +8 -1
- package/dist/styles.css +58 -2
- package/dist/system/density.d.ts +4 -1
- package/dist/system/density.js +12 -4
- package/dist/system/index.d.ts +4 -4
- package/dist/system/portal.d.ts +5 -5
- package/dist/system/portal.js +30 -11
- package/dist/system/prepaint.d.ts +34 -9
- package/dist/system/prepaint.js +48 -25
- package/dist/system/theme-provider.d.ts +48 -11
- package/dist/system/theme-provider.js +109 -35
- package/dist/system/use-density.d.ts +3 -3
- package/dist/system/use-density.js +16 -6
- package/dist/system/use-theme.d.ts +4 -2
- package/dist/system/use-theme.js +4 -2
- package/dist/theming/_presets.js +133 -5
- package/dist/theming/create-theme.d.ts +2 -2
- package/dist/theming/create-theme.js +37 -19
- 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 +13 -5
package/dist/system/prepaint.js
CHANGED
|
@@ -1,37 +1,60 @@
|
|
|
1
|
+
import { DEFAULT_DENSITY, DEFAULT_FONT, DENSITIES, FONTS, MODES, THEMES } from "@cueplusplus/tokens";
|
|
1
2
|
//#region src/system/prepaint.ts
|
|
3
|
+
/**
|
|
4
|
+
* Pre-paint stamping. Server-safe by design: no `"use client"`, no React, no
|
|
5
|
+
* DOM access at module scope — `app/layout.tsx` calls `prepaintScript()` on the
|
|
6
|
+
* server and inlines the result before hydration.
|
|
7
|
+
*/
|
|
2
8
|
/** localStorage key the ThemeProvider and the pre-paint script share by default. */
|
|
3
9
|
const DEFAULT_STORAGE_KEY = "cue-ui";
|
|
10
|
+
/** `<` and `>` escaped, so no value can close the host `<script>` element. */
|
|
11
|
+
const scriptSafe = (json) => json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
|
|
4
12
|
/**
|
|
5
13
|
* Embed a string in JavaScript source that will itself be embedded in HTML.
|
|
6
14
|
* `<` becomes `\u003c` so no value can close the host `<script>` element.
|
|
7
15
|
*/
|
|
8
16
|
function jsStringLiteral(value) {
|
|
9
|
-
return JSON.stringify(value)
|
|
17
|
+
return scriptSafe(JSON.stringify(value));
|
|
10
18
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
function prepaintScript(storageKeyOrOptions = DEFAULT_STORAGE_KEY, defaultTheme = "cue", defaultDensity = DEFAULT_DENSITY, defaultFont = DEFAULT_FONT) {
|
|
20
|
+
const options = typeof storageKeyOrOptions === "string" ? {
|
|
21
|
+
storageKey: storageKeyOrOptions,
|
|
22
|
+
defaults: {
|
|
23
|
+
theme: defaultTheme,
|
|
24
|
+
density: defaultDensity,
|
|
25
|
+
mode: "dark"
|
|
26
|
+
},
|
|
27
|
+
readStoredPreferences: true
|
|
28
|
+
} : storageKeyOrOptions;
|
|
29
|
+
if (options === null || typeof options !== "object" || options.defaults === void 0) throw new TypeError("prepaintScript options require a complete default triple");
|
|
30
|
+
const defaults = options.defaults;
|
|
31
|
+
if (defaults.theme === void 0 || defaults.density === void 0 || defaults.mode === void 0) throw new TypeError("prepaintScript options require a complete default triple");
|
|
32
|
+
if (!THEMES.includes(defaults.theme)) throw new TypeError("prepaintScript defaults contain an invalid theme");
|
|
33
|
+
if (!DENSITIES.includes(defaults.density)) throw new TypeError("prepaintScript defaults contain an invalid density");
|
|
34
|
+
if (!MODES.includes(defaults.mode)) throw new TypeError("prepaintScript defaults contain an invalid mode");
|
|
35
|
+
if (defaults.font !== void 0 && !FONTS.includes(defaults.font)) throw new TypeError("prepaintScript defaults contain an invalid font");
|
|
36
|
+
if (options.readStoredPreferences !== void 0 && typeof options.readStoredPreferences !== "boolean") throw new TypeError("prepaintScript readStoredPreferences must be boolean");
|
|
37
|
+
const storageKey = options.storageKey ?? "cue-ui";
|
|
38
|
+
if (typeof storageKey !== "string") throw new TypeError("prepaintScript storageKey must be a string");
|
|
39
|
+
const readStoredPreferences = options.readStoredPreferences ?? true;
|
|
40
|
+
const key = jsStringLiteral(storageKey);
|
|
41
|
+
const fallbackTheme = jsStringLiteral(defaults.theme);
|
|
42
|
+
const fallbackDensity = jsStringLiteral(defaults.density);
|
|
43
|
+
const fallbackMode = jsStringLiteral(defaults.mode);
|
|
44
|
+
const fallbackFont = jsStringLiteral(defaults.font ?? defaultFont);
|
|
45
|
+
const themes = scriptSafe(JSON.stringify([...THEMES]));
|
|
46
|
+
const densities = scriptSafe(JSON.stringify([...DENSITIES]));
|
|
47
|
+
const modes = scriptSafe(JSON.stringify([...MODES]));
|
|
48
|
+
/**
|
|
49
|
+
* The pairings, from the package that authors them, for the same reason.
|
|
50
|
+
*
|
|
51
|
+
* This is also the axis where an unvalidated read is worst: a stale or
|
|
52
|
+
* hand-edited `font` would stamp an attribute the stylesheet has no block
|
|
53
|
+
* for, `--cue-font-sans` would keep the base stack, and the page would render
|
|
54
|
+
* in a family the picker says is not selected.
|
|
55
|
+
*/
|
|
56
|
+
const pairings = scriptSafe(JSON.stringify([...FONTS]));
|
|
57
|
+
return `!function(){try{var k=${key},e=document.documentElement,s=null;` + (readStoredPreferences ? "try{var r=localStorage.getItem(k);s=r?JSON.parse(r):null}catch(_){}" : "") + `s=s&&typeof s=="object"?s:{};var t=${themes}.indexOf(s.theme)>-1?s.theme:${fallbackTheme};var d=${densities}.indexOf(s.density)>-1?s.density:${fallbackDensity};var f=${pairings}.indexOf(s.font)>-1?s.font:${fallbackFont};var m=${modes}.indexOf(s.mode)>-1?s.mode:${fallbackMode};e.setAttribute("data-theme",t);e.setAttribute("data-density",d);e.setAttribute("data-font",f);e.setAttribute("data-mode",m);e.style.colorScheme=m==="system"?"light dark":m}catch(_){}}()`;
|
|
35
58
|
}
|
|
36
59
|
//#endregion
|
|
37
60
|
export { DEFAULT_STORAGE_KEY, prepaintScript };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Density, Mode, ThemeName } from "@cueplusplus/tokens";
|
|
2
|
+
import { Density, FontName, Mode, ThemeName } from "@cueplusplus/tokens";
|
|
3
3
|
//#region src/system/theme-provider.d.ts
|
|
4
4
|
/** A mode that has been resolved to an actual palette — `"system"` never survives this far. */
|
|
5
5
|
type ResolvedMode = "dark" | "light";
|
|
@@ -11,6 +11,8 @@ interface ThemeContextValue {
|
|
|
11
11
|
density: Density;
|
|
12
12
|
/** Mode as chosen, including the literal `"system"`. */
|
|
13
13
|
mode: Mode;
|
|
14
|
+
/** Active font pairing. */
|
|
15
|
+
font: FontName;
|
|
14
16
|
/** Mode after resolving `"system"` against `prefers-color-scheme`. */
|
|
15
17
|
resolvedMode: ResolvedMode;
|
|
16
18
|
/** Switch theme and persist the new preference triple. */
|
|
@@ -19,22 +21,51 @@ interface ThemeContextValue {
|
|
|
19
21
|
setDensity: (density: Density) => void;
|
|
20
22
|
/** Switch mode (`"system"` included) and persist the new preference triple. */
|
|
21
23
|
setMode: (mode: Mode) => void;
|
|
24
|
+
/** Switch the font pairing and persist the new preference. */
|
|
25
|
+
setFont: (font: FontName) => void;
|
|
26
|
+
}
|
|
27
|
+
/** Provider-owned font-family overrides that detached portals must republish. */
|
|
28
|
+
interface FontFamilies {
|
|
29
|
+
/** Value for `--cue-font-sans`. */
|
|
30
|
+
sans?: string;
|
|
31
|
+
/** Value for `--cue-font-mono`. */
|
|
32
|
+
mono?: string;
|
|
22
33
|
}
|
|
23
34
|
interface ThemeProviderProps {
|
|
24
35
|
/** Initial theme preset. Defaults to `"cue"`. Changing it after mount adopts the new value. */
|
|
25
36
|
theme?: ThemeName;
|
|
26
|
-
/** Initial density level. Defaults to `"
|
|
37
|
+
/** Initial density level. Defaults to `"compact"`. */
|
|
27
38
|
density?: Density;
|
|
28
39
|
/** Initial mode. Defaults to `"dark"`; `"system"` tracks `prefers-color-scheme`. */
|
|
29
40
|
mode?: Mode;
|
|
41
|
+
/**
|
|
42
|
+
* Initial font pairing. Defaults to `"system"` — the platform's own faces,
|
|
43
|
+
* nothing downloaded, and the theme keeps whatever monospace it authored.
|
|
44
|
+
*
|
|
45
|
+
* A pairing that needs delivering is a set of *names*: this library ships no
|
|
46
|
+
* font files, and a pairing nobody delivers falls through its stack to the
|
|
47
|
+
* platform rather than failing. See `FONT_PAIRINGS[font].faces` for the
|
|
48
|
+
* custom properties an app assigns to make one resolve.
|
|
49
|
+
*
|
|
50
|
+
* **Root-level only.** There is no font island: a nested provider forwards
|
|
51
|
+
* this axis to the root, ignores this prop, and its `setFont` drives the root.
|
|
52
|
+
* A page that changed face halfway down is a page with a bug, and a specimen
|
|
53
|
+
* that genuinely wants one — a picker row, a docs page showing all eight —
|
|
54
|
+
* needs nothing from this library but `data-font` on a `<div>`.
|
|
55
|
+
*/
|
|
56
|
+
font?: FontName;
|
|
30
57
|
/** Multiplier on every text token, published as `--cue-font-scale`. Defaults to `1`. */
|
|
31
58
|
fontScale?: number;
|
|
59
|
+
/** Optional app-owned font stacks, published as inline CUE font custom properties. */
|
|
60
|
+
fontFamilies?: FontFamilies;
|
|
32
61
|
/**
|
|
33
|
-
* localStorage key holding `{ theme, density, mode }`. Defaults to `"cue-ui"`.
|
|
62
|
+
* localStorage key holding `{ theme, density, font, mode }`. Defaults to `"cue-ui"`.
|
|
34
63
|
* Only the outermost provider reads or writes it — a nested provider is an
|
|
35
64
|
* island whose explicit props are the point, not a second preference store.
|
|
36
65
|
*/
|
|
37
66
|
storageKey?: string;
|
|
67
|
+
/** Restore and write root preferences in localStorage. Defaults to `true`. */
|
|
68
|
+
persistPreferences?: boolean;
|
|
38
69
|
/** Stamp the single child element instead of rendering a wrapper `<div>`. */
|
|
39
70
|
asChild?: boolean;
|
|
40
71
|
/** Extra classes for the provider root. */
|
|
@@ -46,27 +77,33 @@ interface ThemeProviderProps {
|
|
|
46
77
|
}
|
|
47
78
|
/**
|
|
48
79
|
* Root of the theme system: stamps `data-theme`, `data-density` and `data-mode`
|
|
49
|
-
* so the token layer resolves,
|
|
50
|
-
* persisted user preference.
|
|
80
|
+
* so the token layer resolves, puts `data-font` on `<html>`, publishes
|
|
81
|
+
* `--cue-font-scale`, and owns the persisted user preference.
|
|
51
82
|
*
|
|
52
83
|
* Rendering: `<div data-cue-root data-theme data-density data-mode style={{ colorScheme, --cue-font-scale }}>`
|
|
53
|
-
* (or the single child when `asChild`). The `theme`/`density`/`mode` props are
|
|
84
|
+
* (or the single child when `asChild`). The `theme`/`density`/`font`/`mode` props are
|
|
54
85
|
* *initial* values — the provider holds the state so `setTheme` and friends can
|
|
55
86
|
* drive it — but a changed prop is adopted after mount, so a controlling parent
|
|
56
87
|
* still works.
|
|
57
88
|
*
|
|
89
|
+
* The font pairing is the one axis that never lands on this element: it has no
|
|
90
|
+
* island form, `<html>` is its only home, and restating it here would shadow the
|
|
91
|
+
* pre-paint stamp for the whole page on the first frame. See the `stamp` object
|
|
92
|
+
* below.
|
|
93
|
+
*
|
|
58
94
|
* Persistence and the pre-paint contract: setters write
|
|
59
|
-
* `{ theme, density, mode }` to `localStorage[storageKey]`, which is exactly what
|
|
95
|
+
* `{ theme, density, font, mode }` to `localStorage[storageKey]`, which is exactly what
|
|
60
96
|
* {@link prepaintScript} reads to stamp `<html>` before the first paint. The
|
|
61
97
|
* outermost provider keeps `<html>` in sync while the app runs (and restores the
|
|
62
98
|
* previous stamp on unmount) so the page ground, UA scrollbars and form controls
|
|
63
|
-
* follow the theme; nested providers never touch `<html
|
|
99
|
+
* follow the theme; nested providers never touch `<html>`, and never own the
|
|
100
|
+
* font pairing — `useTheme().font` and `setFont` inside one are the root's.
|
|
64
101
|
*
|
|
65
102
|
* @example
|
|
66
|
-
* <ThemeProvider theme="terminal" density="ultra-compact" mode="system">
|
|
103
|
+
* <ThemeProvider theme="terminal" density="ultra-compact" font="plex" mode="system">
|
|
67
104
|
* <App />
|
|
68
105
|
* </ThemeProvider>
|
|
69
106
|
*/
|
|
70
|
-
declare function ThemeProvider({ theme: themeProp, density: densityProp, mode: modeProp, fontScale, storageKey, asChild, className, style: styleProp, children }: ThemeProviderProps): React.JSX.Element;
|
|
107
|
+
declare function ThemeProvider({ theme: themeProp, density: densityProp, mode: modeProp, font: fontProp, fontScale, fontFamilies, storageKey, persistPreferences, asChild, className, style: styleProp, children }: ThemeProviderProps): React.JSX.Element;
|
|
71
108
|
//#endregion
|
|
72
|
-
export { ResolvedMode, ThemeContextValue, ThemeProvider, ThemeProviderProps };
|
|
109
|
+
export { FontFamilies, ResolvedMode, ThemeContextValue, ThemeProvider, ThemeProviderProps };
|
|
@@ -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, THEMES } from "@cueplusplus/tokens";
|
|
8
9
|
//#region src/system/theme-provider.tsx
|
|
9
|
-
const MODES = [
|
|
10
|
+
const MODES$1 = [
|
|
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
|
/**
|
|
@@ -33,6 +34,8 @@ const ThemeContext = React.createContext(null);
|
|
|
33
34
|
* Internal: not part of the public API.
|
|
34
35
|
*/
|
|
35
36
|
const FontScaleContext = React.createContext(null);
|
|
37
|
+
/** Effective font-family overrides from the nearest provider chain. */
|
|
38
|
+
const FontFamiliesContext = React.createContext(null);
|
|
36
39
|
function isOneOf(allowed, value) {
|
|
37
40
|
return typeof value === "string" && allowed.includes(value);
|
|
38
41
|
}
|
|
@@ -56,62 +59,91 @@ function writePreferences(storageKey, preferences) {
|
|
|
56
59
|
}
|
|
57
60
|
/**
|
|
58
61
|
* Root of the theme system: stamps `data-theme`, `data-density` and `data-mode`
|
|
59
|
-
* so the token layer resolves,
|
|
60
|
-
* persisted user preference.
|
|
62
|
+
* so the token layer resolves, puts `data-font` on `<html>`, publishes
|
|
63
|
+
* `--cue-font-scale`, and owns the persisted user preference.
|
|
61
64
|
*
|
|
62
65
|
* 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
|
|
66
|
+
* (or the single child when `asChild`). The `theme`/`density`/`font`/`mode` props are
|
|
64
67
|
* *initial* values — the provider holds the state so `setTheme` and friends can
|
|
65
68
|
* drive it — but a changed prop is adopted after mount, so a controlling parent
|
|
66
69
|
* still works.
|
|
67
70
|
*
|
|
71
|
+
* The font pairing is the one axis that never lands on this element: it has no
|
|
72
|
+
* island form, `<html>` is its only home, and restating it here would shadow the
|
|
73
|
+
* pre-paint stamp for the whole page on the first frame. See the `stamp` object
|
|
74
|
+
* below.
|
|
75
|
+
*
|
|
68
76
|
* Persistence and the pre-paint contract: setters write
|
|
69
|
-
* `{ theme, density, mode }` to `localStorage[storageKey]`, which is exactly what
|
|
77
|
+
* `{ theme, density, font, mode }` to `localStorage[storageKey]`, which is exactly what
|
|
70
78
|
* {@link prepaintScript} reads to stamp `<html>` before the first paint. The
|
|
71
79
|
* outermost provider keeps `<html>` in sync while the app runs (and restores the
|
|
72
80
|
* previous stamp on unmount) so the page ground, UA scrollbars and form controls
|
|
73
|
-
* follow the theme; nested providers never touch `<html
|
|
81
|
+
* follow the theme; nested providers never touch `<html>`, and never own the
|
|
82
|
+
* font pairing — `useTheme().font` and `setFont` inside one are the root's.
|
|
74
83
|
*
|
|
75
84
|
* @example
|
|
76
|
-
* <ThemeProvider theme="terminal" density="ultra-compact" mode="system">
|
|
85
|
+
* <ThemeProvider theme="terminal" density="ultra-compact" font="plex" mode="system">
|
|
77
86
|
* <App />
|
|
78
87
|
* </ThemeProvider>
|
|
79
88
|
*/
|
|
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
|
|
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 }) {
|
|
90
|
+
const ambient = React.useContext(ThemeContext);
|
|
91
|
+
const isRoot = ambient === null;
|
|
82
92
|
const [theme, setThemeState] = React.useState(themeProp);
|
|
83
93
|
const [density, setDensityState] = React.useState(densityProp);
|
|
84
94
|
const [mode, setModeState] = React.useState(modeProp);
|
|
95
|
+
const [rootFont, setRootFontState] = React.useState(() => {
|
|
96
|
+
if (!isRoot || !persistPreferences) return fontProp;
|
|
97
|
+
const stored = readPreferences(storageKey).font;
|
|
98
|
+
return isOneOf(FONT_NAMES, stored) ? stored : fontProp;
|
|
99
|
+
});
|
|
100
|
+
const font = isRoot ? rootFont : ambient.font;
|
|
85
101
|
const [systemMode, setSystemMode] = React.useState(DEFAULT_RESOLVED_MODE);
|
|
86
102
|
const resolvedMode = mode === "system" ? systemMode : mode;
|
|
87
103
|
const restoredKey = React.useRef(null);
|
|
104
|
+
const [restored, setRestored] = React.useState(false);
|
|
88
105
|
useIsomorphicLayoutEffect(() => {
|
|
89
|
-
if (!isRoot
|
|
106
|
+
if (!isRoot) return;
|
|
107
|
+
if (!persistPreferences) {
|
|
108
|
+
setRestored(true);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (restoredKey.current === storageKey) return;
|
|
90
112
|
restoredKey.current = storageKey;
|
|
113
|
+
setRestored(true);
|
|
91
114
|
const stored = readPreferences(storageKey);
|
|
92
|
-
if (
|
|
115
|
+
if (isOneOf(THEMES, stored.theme)) setThemeState(stored.theme);
|
|
93
116
|
if (isOneOf(DENSITY_LEVELS, stored.density)) setDensityState(stored.density);
|
|
94
|
-
if (isOneOf(
|
|
95
|
-
|
|
117
|
+
if (isOneOf(FONT_NAMES, stored.font)) setRootFontState(stored.font);
|
|
118
|
+
if (isOneOf(MODES$1, stored.mode)) setModeState(stored.mode);
|
|
119
|
+
}, [
|
|
120
|
+
isRoot,
|
|
121
|
+
persistPreferences,
|
|
122
|
+
storageKey
|
|
123
|
+
]);
|
|
96
124
|
const lastProps = React.useRef({
|
|
97
125
|
theme: themeProp,
|
|
98
126
|
density: densityProp,
|
|
99
|
-
mode: modeProp
|
|
127
|
+
mode: modeProp,
|
|
128
|
+
font: fontProp
|
|
100
129
|
});
|
|
101
130
|
React.useEffect(() => {
|
|
102
131
|
const previous = lastProps.current;
|
|
103
132
|
lastProps.current = {
|
|
104
133
|
theme: themeProp,
|
|
105
134
|
density: densityProp,
|
|
106
|
-
mode: modeProp
|
|
135
|
+
mode: modeProp,
|
|
136
|
+
font: fontProp
|
|
107
137
|
};
|
|
108
138
|
if (previous.theme !== themeProp) setThemeState(themeProp);
|
|
109
139
|
if (previous.density !== densityProp) setDensityState(densityProp);
|
|
110
140
|
if (previous.mode !== modeProp) setModeState(modeProp);
|
|
141
|
+
if (previous.font !== fontProp) setRootFontState(fontProp);
|
|
111
142
|
}, [
|
|
112
143
|
themeProp,
|
|
113
144
|
densityProp,
|
|
114
|
-
modeProp
|
|
145
|
+
modeProp,
|
|
146
|
+
fontProp
|
|
115
147
|
]);
|
|
116
148
|
useIsomorphicLayoutEffect(() => {
|
|
117
149
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
@@ -124,13 +156,15 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
124
156
|
const stateRef = React.useRef({
|
|
125
157
|
theme,
|
|
126
158
|
density,
|
|
127
|
-
mode
|
|
159
|
+
mode,
|
|
160
|
+
font
|
|
128
161
|
});
|
|
129
162
|
useIsomorphicLayoutEffect(() => {
|
|
130
163
|
stateRef.current = {
|
|
131
164
|
theme,
|
|
132
165
|
density,
|
|
133
|
-
mode
|
|
166
|
+
mode,
|
|
167
|
+
font
|
|
134
168
|
};
|
|
135
169
|
});
|
|
136
170
|
const persist = React.useCallback((patch) => {
|
|
@@ -139,9 +173,13 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
139
173
|
...patch
|
|
140
174
|
};
|
|
141
175
|
stateRef.current = next;
|
|
142
|
-
if (!isRoot) return;
|
|
176
|
+
if (!isRoot || !persistPreferences) return;
|
|
143
177
|
writePreferences(storageKey, next);
|
|
144
|
-
}, [
|
|
178
|
+
}, [
|
|
179
|
+
isRoot,
|
|
180
|
+
persistPreferences,
|
|
181
|
+
storageKey
|
|
182
|
+
]);
|
|
145
183
|
const setTheme = React.useCallback((next) => {
|
|
146
184
|
setThemeState(next);
|
|
147
185
|
persist({ theme: next });
|
|
@@ -154,25 +192,35 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
154
192
|
setModeState(next);
|
|
155
193
|
persist({ mode: next });
|
|
156
194
|
}, [persist]);
|
|
195
|
+
const rootSetFont = React.useCallback((next) => {
|
|
196
|
+
setRootFontState(next);
|
|
197
|
+
persist({ font: next });
|
|
198
|
+
}, [persist]);
|
|
199
|
+
/** Nested providers hand the axis straight up: one document, one pairing. */
|
|
200
|
+
const setFont = isRoot ? rootSetFont : ambient.setFont;
|
|
157
201
|
const originalStamp = React.useRef(null);
|
|
158
202
|
useIsomorphicLayoutEffect(() => {
|
|
159
|
-
if (!isRoot || typeof document === "undefined") return;
|
|
203
|
+
if (!isRoot || !restored || typeof document === "undefined") return;
|
|
160
204
|
const root = document.documentElement;
|
|
161
205
|
originalStamp.current ??= {
|
|
162
206
|
theme: root.getAttribute("data-theme"),
|
|
163
207
|
density: root.getAttribute("data-density"),
|
|
208
|
+
font: root.getAttribute("data-font"),
|
|
164
209
|
mode: root.getAttribute("data-mode"),
|
|
165
210
|
colorScheme: root.style.colorScheme
|
|
166
211
|
};
|
|
167
212
|
root.setAttribute("data-theme", theme);
|
|
168
213
|
root.setAttribute("data-density", density);
|
|
169
|
-
root.setAttribute("data-
|
|
170
|
-
root.
|
|
214
|
+
root.setAttribute("data-font", font);
|
|
215
|
+
root.setAttribute("data-mode", mode);
|
|
216
|
+
root.style.colorScheme = mode === "system" ? "light dark" : mode;
|
|
171
217
|
}, [
|
|
172
218
|
isRoot,
|
|
219
|
+
restored,
|
|
173
220
|
theme,
|
|
174
221
|
density,
|
|
175
|
-
|
|
222
|
+
font,
|
|
223
|
+
mode
|
|
176
224
|
]);
|
|
177
225
|
React.useEffect(() => () => {
|
|
178
226
|
const original = originalStamp.current;
|
|
@@ -185,6 +233,7 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
185
233
|
};
|
|
186
234
|
restore("data-theme", original.theme);
|
|
187
235
|
restore("data-density", original.density);
|
|
236
|
+
restore("data-font", original.font);
|
|
188
237
|
restore("data-mode", original.mode);
|
|
189
238
|
root.style.colorScheme = original.colorScheme;
|
|
190
239
|
}, []);
|
|
@@ -192,33 +241,55 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
192
241
|
theme,
|
|
193
242
|
density,
|
|
194
243
|
mode,
|
|
244
|
+
font,
|
|
195
245
|
resolvedMode,
|
|
196
246
|
setTheme,
|
|
197
247
|
setDensity,
|
|
198
|
-
setMode
|
|
248
|
+
setMode,
|
|
249
|
+
setFont
|
|
199
250
|
}), [
|
|
200
251
|
theme,
|
|
201
252
|
density,
|
|
202
253
|
mode,
|
|
254
|
+
font,
|
|
203
255
|
resolvedMode,
|
|
204
256
|
setTheme,
|
|
205
257
|
setDensity,
|
|
206
|
-
setMode
|
|
258
|
+
setMode,
|
|
259
|
+
setFont
|
|
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
|
|
207
274
|
]);
|
|
208
275
|
const style = React.useMemo(() => ({
|
|
209
|
-
colorScheme:
|
|
276
|
+
colorScheme: mode === "system" ? "light dark" : mode,
|
|
210
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 },
|
|
211
280
|
...styleProp
|
|
212
281
|
}), [
|
|
213
|
-
|
|
282
|
+
mode,
|
|
214
283
|
fontScale,
|
|
284
|
+
fontFamilies?.sans,
|
|
285
|
+
fontFamilies?.mono,
|
|
215
286
|
styleProp
|
|
216
287
|
]);
|
|
217
288
|
const stamp = {
|
|
218
289
|
"data-cue-root": "",
|
|
219
290
|
"data-theme": theme,
|
|
220
291
|
"data-density": density,
|
|
221
|
-
"data-mode":
|
|
292
|
+
"data-mode": mode
|
|
222
293
|
};
|
|
223
294
|
let root;
|
|
224
295
|
if (asChild) {
|
|
@@ -241,12 +312,15 @@ function ThemeProvider({ theme: themeProp = DEFAULT_THEME, density: densityProp
|
|
|
241
312
|
value: contextValue,
|
|
242
313
|
children: /* @__PURE__ */ jsx(FontScaleContext.Provider, {
|
|
243
314
|
value: fontScale,
|
|
244
|
-
children: /* @__PURE__ */ jsx(
|
|
245
|
-
value:
|
|
246
|
-
children:
|
|
315
|
+
children: /* @__PURE__ */ jsx(FontFamiliesContext.Provider, {
|
|
316
|
+
value: effectiveFontFamilies,
|
|
317
|
+
children: /* @__PURE__ */ jsx(DensityContext.Provider, {
|
|
318
|
+
value: density,
|
|
319
|
+
children: root
|
|
320
|
+
})
|
|
247
321
|
})
|
|
248
322
|
})
|
|
249
323
|
});
|
|
250
324
|
}
|
|
251
325
|
//#endregion
|
|
252
|
-
export { FontScaleContext, ThemeContext, ThemeProvider };
|
|
326
|
+
export { FontFamiliesContext, FontScaleContext, ThemeContext, ThemeProvider };
|
|
@@ -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 `"
|
|
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:`
|
|
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
|
|
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
|
-
|
|
17
|
+
"compact": {
|
|
18
18
|
sm: 1.25,
|
|
19
19
|
md: 1.5,
|
|
20
20
|
lg: 1.75
|
|
21
21
|
},
|
|
22
|
-
|
|
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 `"
|
|
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:`
|
|
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) ?? "
|
|
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
|
|
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,
|
|
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.
|
package/dist/system/use-theme.js
CHANGED
|
@@ -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,
|
|
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.
|