@cueplusplus/ui 0.18.2 → 0.20.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 +100 -0
- package/dist/brand/_motif-seed.js +20 -0
- package/dist/brand/gloss-object.d.ts +23 -0
- package/dist/brand/gloss-object.js +108 -0
- package/dist/brand/halftone.d.ts +23 -0
- package/dist/brand/halftone.js +54 -0
- package/dist/brand/index.d.ts +8 -1
- package/dist/brand/motif.d.ts +32 -0
- package/dist/brand/motif.js +71 -0
- package/dist/brand/particle-logo.d.ts +50 -0
- package/dist/brand/particle-logo.js +437 -0
- package/dist/brand/particle-mark.d.ts +29 -0
- package/dist/brand/particle-mark.js +137 -0
- package/dist/brand/ribbon.d.ts +20 -0
- package/dist/brand/ribbon.js +54 -0
- package/dist/brand/text-scramble.d.ts +42 -0
- package/dist/brand/text-scramble.js +160 -0
- package/dist/chat/delegation-card.js +2 -1
- package/dist/chat/message.js +12 -13
- package/dist/chrome/announcement-bar.d.ts +30 -0
- package/dist/chrome/announcement-bar.js +38 -0
- package/dist/chrome/app-bar.d.ts +4 -1
- package/dist/chrome/app-bar.js +7 -2
- package/dist/chrome/app-shell.js +8 -1
- package/dist/chrome/band.d.ts +3 -1
- package/dist/chrome/band.js +4 -3
- package/dist/chrome/hero.d.ts +61 -0
- package/dist/chrome/hero.js +108 -0
- package/dist/chrome/index.d.ts +6 -1
- package/dist/chrome/index.js +6 -1
- package/dist/chrome/keyword.d.ts +22 -0
- package/dist/chrome/keyword.js +28 -0
- package/dist/chrome/logo-strip.d.ts +23 -0
- package/dist/chrome/logo-strip.js +43 -0
- package/dist/chrome/page-shell.js +13 -1
- package/dist/chrome/stat-band.d.ts +32 -0
- package/dist/chrome/stat-band.js +39 -0
- package/dist/color/color-area.js +2 -1
- package/dist/dmx/channel-matrix.js +5 -3
- package/dist/dmx/dmx-strip.js +5 -3
- package/dist/elements/animated-number.js +4 -1
- package/dist/elements/replay.d.ts +1 -1
- package/dist/elements/replay.js +5 -2
- package/dist/elements/scroll-anchor.js +4 -1
- package/dist/forms/_chassis.d.ts +1 -1
- package/dist/forms/_chassis.js +2 -2
- package/dist/forms/env-var-input.js +2 -1
- package/dist/forms/file-upload.js +2 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +18 -2
- package/dist/instruments/data-table.js +3 -2
- package/dist/instruments/index.js +1 -1
- package/dist/instruments/stat.js +1 -1
- package/dist/instruments/table.js +1 -1
- package/dist/instruments/terminal-frame.js +5 -2
- package/dist/layout/grid.js +4 -2
- package/dist/layout/index.d.ts +2 -1
- package/dist/layout/index.js +2 -1
- package/dist/layout/preview.js +4 -188
- package/dist/layout/reveal.d.ts +44 -0
- package/dist/layout/reveal.js +106 -0
- package/dist/layout/sidebar.js +88 -52
- package/dist/lib/cn.js +23 -5
- package/dist/lib/particle-field-shaders.js +236 -0
- package/dist/lib/particle-field.js +210 -0
- package/dist/midi/spectrum-visualizer.js +2 -1
- package/dist/overlays/_surface.d.ts +2 -2
- package/dist/primitives/button.js +22 -3
- package/dist/primitives/chip.d.ts +1 -1
- package/dist/primitives/chip.js +63 -6
- package/dist/styles.css +26 -0
- package/dist/system/_scoped-pairing.js +18 -0
- package/dist/system/_stage-scope.js +199 -0
- package/dist/system/_style-check.js +1 -1
- package/dist/system/_style-chip.js +1 -1
- package/dist/system/_style-field-chassis.js +14 -1
- package/dist/system/_style-group-label.js +7 -3
- package/dist/system/_style-hero-title.js +41 -0
- package/dist/system/_style-kbd.js +1 -1
- package/dist/system/_style-label-ink.js +12 -5
- package/dist/system/_style-menu.js +1 -1
- package/dist/system/_style-page-title.js +1 -1
- package/dist/system/_style-segmented-control.js +1 -1
- package/dist/system/_style-skeleton.js +1 -1
- package/dist/system/_style-tab.js +1 -1
- package/dist/system/font-scope.d.ts +38 -0
- package/dist/system/font-scope.js +69 -0
- package/dist/system/frame-clock.d.ts +33 -0
- package/dist/system/frame-clock.js +55 -0
- package/dist/system/index.d.ts +4 -1
- package/dist/system/index.js +4 -1
- package/dist/system/portal.js +7 -1
- package/dist/system/still.d.ts +32 -0
- package/dist/system/still.js +60 -0
- package/manifest/components/alert-dialog.json +1 -1
- package/manifest/components/announcement-bar.json +82 -0
- package/manifest/components/app-bar.json +3 -2
- package/manifest/components/app-shell.json +3 -0
- package/manifest/components/autocomplete.json +2 -0
- package/manifest/components/band.json +2 -1
- package/manifest/components/bento.json +13 -13
- package/manifest/components/button.json +1 -0
- package/manifest/components/channel-matrix.json +1 -0
- package/manifest/components/chart-tooltip-content.json +1 -0
- package/manifest/components/checkbox-group.json +1 -1
- package/manifest/components/checkbox.json +1 -1
- package/manifest/components/chip.json +1 -0
- package/manifest/components/color-area.json +10 -0
- package/manifest/components/color-field.json +3 -0
- package/manifest/components/color-picker.json +3 -0
- package/manifest/components/combobox.json +2 -0
- package/manifest/components/command-palette.json +1 -0
- package/manifest/components/composer.json +3 -0
- package/manifest/components/context-menu.json +1 -1
- package/manifest/components/data-table-pagination.json +3 -0
- package/manifest/components/data-table-toolbar.json +3 -0
- package/manifest/components/date-field.json +3 -0
- package/manifest/components/date-picker.json +3 -0
- package/manifest/components/date-range-picker.json +3 -0
- package/manifest/components/delegation-card.json +6 -0
- package/manifest/components/density.json +6 -2
- package/manifest/components/dialog.json +1 -1
- package/manifest/components/dmx-bar.json +1 -1
- package/manifest/components/dmx-strip.json +1 -1
- package/manifest/components/drawer.json +1 -1
- package/manifest/components/dropdown-menu.json +1 -0
- package/manifest/components/env-var-input.json +3 -0
- package/manifest/components/file-upload.json +5 -0
- package/manifest/components/font-scope.json +82 -0
- package/manifest/components/footer.json +0 -1
- package/manifest/components/gloss-object.json +81 -0
- package/manifest/components/grid.json +6 -6
- package/manifest/components/halftone.json +79 -0
- package/manifest/components/hero.json +204 -0
- package/manifest/components/hover-card.json +1 -1
- package/manifest/components/input-group.json +3 -0
- package/manifest/components/input.json +3 -0
- package/manifest/components/kbd.json +1 -0
- package/manifest/components/keyword.json +67 -0
- package/manifest/components/ledger.json +5 -5
- package/manifest/components/logo-strip.json +93 -0
- package/manifest/components/markdown-text.json +3 -3
- package/manifest/components/menubar.json +1 -1
- package/manifest/components/message.json +8 -0
- package/manifest/components/motif.json +90 -0
- package/manifest/components/multi-select.json +2 -0
- package/manifest/components/musical-time-input.json +3 -0
- package/manifest/components/navigation-menu.json +1 -0
- package/manifest/components/number-field.json +3 -0
- package/manifest/components/otp-field.json +3 -0
- package/manifest/components/page-shell.json +5 -0
- package/manifest/components/particle-logo.json +140 -0
- package/manifest/components/particle-mark.json +75 -0
- package/manifest/components/password-input.json +3 -0
- package/manifest/components/popover.json +1 -1
- package/manifest/components/preview.json +10 -4
- package/manifest/components/radio-group.json +0 -1
- package/manifest/components/radio.json +0 -1
- package/manifest/components/reveal.json +106 -0
- package/manifest/components/ribbon.json +65 -0
- package/manifest/components/scrollable-tabs-list.json +1 -0
- package/manifest/components/scrub-input.json +3 -0
- package/manifest/components/search-input.json +3 -0
- package/manifest/components/segmented-control.json +1 -0
- package/manifest/components/select.json +3 -0
- package/manifest/components/sheet.json +1 -1
- package/manifest/components/sidebar.json +1 -0
- package/manifest/components/skeleton.json +1 -0
- package/manifest/components/spectrum-visualizer.json +10 -0
- package/manifest/components/stack.json +4 -4
- package/manifest/components/stat-band.json +68 -0
- package/manifest/components/stat.json +1 -1
- package/manifest/components/still-provider.json +60 -0
- package/manifest/components/switch.json +0 -1
- package/manifest/components/table.json +2 -2
- package/manifest/components/tabs.json +1 -0
- package/manifest/components/tags-input.json +2 -0
- package/manifest/components/text-scramble.json +90 -0
- package/manifest/components/textarea.json +3 -0
- package/manifest/components/theme-configurator.json +1 -0
- package/manifest/components/theme-provider.json +10 -4
- package/manifest/components/time-field.json +3 -0
- package/manifest/components/toast.json +1 -0
- package/manifest/components/tooltip.json +1 -1
- package/manifest/fixtures.json +18 -0
- package/manifest/manifest.json +368 -80
- package/manifest/tokens.json +93 -3
- package/package.json +6 -6
- package/dist/system/_paint-ground-pattern.js +0 -30
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { usePublish, useRead } from "./published.js";
|
|
3
|
+
import { AmbientThemeContext, DensityContext, PaintContext, RootAxesContext, StyleSetContext, rootAxes } from "./density.js";
|
|
4
|
+
import { OverridesContext } from "./overrides.js";
|
|
5
|
+
import { ScopedPairingContext } from "./_scoped-pairing.js";
|
|
6
|
+
import { NO_PAIRING_SEED, PairingSeedContext, ThemeContext } from "./theme-provider.js";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
//#region src/system/_stage-scope.tsx
|
|
10
|
+
/**
|
|
11
|
+
* The attributes the stage carries — and, when nothing is asked, none at all.
|
|
12
|
+
*
|
|
13
|
+
* Inheriting is the *absence* of attributes, which is why every branch below
|
|
14
|
+
* is conditional. Three branches add a second attribute, each for a reason in
|
|
15
|
+
* the stylesheets rather than in taste.
|
|
16
|
+
*
|
|
17
|
+
* A theme's light block is the compound selector
|
|
18
|
+
* `[data-theme="t"][data-mode="light"]`, so a mode with no theme restates the
|
|
19
|
+
* page's theme on the same element. Read the other way, a theme with no mode
|
|
20
|
+
* repeats the page's mode. Otherwise the stage would match the theme's dark
|
|
21
|
+
* block under a light page, while every popup opened on it came up light:
|
|
22
|
+
* popups are stamped from the context `StageScope` publishes, which carries
|
|
23
|
+
* the page's mode.
|
|
24
|
+
*
|
|
25
|
+
* A theme's density overrides are addressed through `data-cue-theme`, the
|
|
26
|
+
* stamp `<Density>` writes, so a density names the nearest theme the same way.
|
|
27
|
+
*
|
|
28
|
+
* A stage that ends up naming a theme and no rung — its own theme, or the
|
|
29
|
+
* page's restated beside a mode (`scoped`, below) — restates the rung in force
|
|
30
|
+
* around it, as `data-density` with the theme as `data-cue-theme` (spec
|
|
31
|
+
* 2026-09-26-looks-fidelity §4.1.1). A theme's rung retunes are keyed on the
|
|
32
|
+
* theme and the rung on one element, or a rung below the theme, so a stage that
|
|
33
|
+
* re-themes with no `data-density` of its own inherits the rung already
|
|
34
|
+
* resolved against the PAGE's theme, and its own theme's retune of that rung
|
|
35
|
+
* never applies. `/looks` opens every stage this way: a theme and no rung. It
|
|
36
|
+
* is what a nested `ThemeProvider` that names a theme already does — theme,
|
|
37
|
+
* rung and mode together. The rung is the one `DensityContext` carries: `null`
|
|
38
|
+
* outside every provider, where nothing is restated, and never `useDensity()`'s
|
|
39
|
+
* `compact` fallback, which would invent one.
|
|
40
|
+
*
|
|
41
|
+
* The style set adds no second attribute, and that is the difference worth
|
|
42
|
+
* writing down beside the three that do: a `[data-style]` block is a single
|
|
43
|
+
* attribute selector naming no other axis, so a set stamped alone matches on
|
|
44
|
+
* its own.
|
|
45
|
+
*
|
|
46
|
+
* But a stage that re-themes, re-modes or re-faces restates the set and the pack
|
|
47
|
+
* in force around it (`around`), for a reason in custom properties rather than
|
|
48
|
+
* in selectors. Their blocks declare names whose values read those axes — a
|
|
49
|
+
* pack's stroke stops are the theme's borders, a set's band rule and ground are
|
|
50
|
+
* the theme's border and surface — and a custom property's `var()`s are
|
|
51
|
+
* substituted where it is declared. Declared only on the page, they arrive at
|
|
52
|
+
* the stage already resolved against the page's theme and pairing; restated on
|
|
53
|
+
* the stage, beside its own `data-theme` or `data-font`, they resolve against
|
|
54
|
+
* those. A rung alone changes no ink and restates nothing. `around` is `null`
|
|
55
|
+
* until the stage may restate: under a root that persists, not before the stage
|
|
56
|
+
* has mounted, when the set and pack in force are the root's props rather than
|
|
57
|
+
* the visitor's (see `useRestateReady`).
|
|
58
|
+
*
|
|
59
|
+
* The page's mode and rung, restated beside a stage theme — and the page's
|
|
60
|
+
* theme, restated beside a stage mode or as a stage rung's `data-cue-theme` —
|
|
61
|
+
* wait for the same mount on the same terms (tracking plan F13): under a
|
|
62
|
+
* persisting root each is the root's, whose value on the server is its prop
|
|
63
|
+
* while `<html>` carries the visitor's. So until then `followed` holds it back,
|
|
64
|
+
* and `data-cue-follows` names it instead — the theme stylesheets' blocks each
|
|
65
|
+
* have a twin keyed on `<html>`'s value and that marker (a followed rung
|
|
66
|
+
* included: the base rung's twin in `axes.css`, and each theme's retune of it
|
|
67
|
+
* keyed on the stage's own `data-theme`), and a rung node with no
|
|
68
|
+
* `data-cue-theme` is reached by the rung rules' structural form, so the stage
|
|
69
|
+
* paints the visitor's theme, mode and rung from the first frame (see
|
|
70
|
+
* `useFollowedAxes`). A theme, mode or rung the stage names, or one named
|
|
71
|
+
* between it and the root, is stamped as it always was. A followed rung takes
|
|
72
|
+
* its `data-cue-theme` with it: the stamp addresses a `data-density` on the
|
|
73
|
+
* same element, and there is none to address until the rung is stamped.
|
|
74
|
+
*/
|
|
75
|
+
function stageAttributes(asked, page, around, aroundDensity, followed) {
|
|
76
|
+
const attributes = {};
|
|
77
|
+
const scoped = asked.theme ?? (asked.mode === void 0 ? null : page?.theme ?? null);
|
|
78
|
+
if (scoped !== null && (asked.theme !== void 0 || !followed.theme)) attributes["data-theme"] = scoped;
|
|
79
|
+
const moded = asked.mode ?? (asked.theme === void 0 ? void 0 : page?.mode);
|
|
80
|
+
if (moded !== void 0 && !followed.mode) attributes["data-mode"] = moded;
|
|
81
|
+
if (asked.density !== void 0) {
|
|
82
|
+
attributes["data-density"] = asked.density;
|
|
83
|
+
const nearest = asked.theme ?? page?.theme ?? null;
|
|
84
|
+
if (nearest !== null && (asked.theme !== void 0 || !followed.theme)) attributes["data-cue-theme"] = nearest;
|
|
85
|
+
} else if (scoped !== null && aroundDensity !== null && !followed.density) {
|
|
86
|
+
attributes["data-density"] = aroundDensity;
|
|
87
|
+
if (asked.theme !== void 0 || !followed.theme) attributes["data-cue-theme"] = scoped;
|
|
88
|
+
}
|
|
89
|
+
if (followed.marker !== null) attributes["data-cue-follows"] = followed.marker;
|
|
90
|
+
if (asked.font !== void 0) attributes["data-font"] = asked.font;
|
|
91
|
+
if (asked.styleSet !== void 0) attributes["data-style"] = asked.styleSet;
|
|
92
|
+
if (asked.paint !== void 0) attributes["data-paint"] = asked.paint;
|
|
93
|
+
const restates = around !== null && (scoped !== null || moded !== void 0 || asked.font !== void 0);
|
|
94
|
+
if (restates && asked.styleSet === void 0 && around.styleSet !== null) attributes["data-style"] = around.styleSet;
|
|
95
|
+
if (restates && asked.paint === void 0 && around.paint !== null) attributes["data-paint"] = around.paint;
|
|
96
|
+
return attributes;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The contexts the stage republishes, so an overlay opened on it follows it.
|
|
100
|
+
*
|
|
101
|
+
* `useCuePortalProps()` stamps a portal container from `ThemeContext` and
|
|
102
|
+
* `DensityContext`; a popup mounts on `<body>` and inherits nothing from the
|
|
103
|
+
* stage's DOM. So the stage's subtree reads a theme context with the stage's
|
|
104
|
+
* theme and mode, and a density context with the stage's rung. The tools and
|
|
105
|
+
* footer are outside this subtree, so what they open follows the page. A theme
|
|
106
|
+
* the provider did not register leaves `manifest` null, which is what
|
|
107
|
+
* `useTheme()` already reports for a name nobody registered.
|
|
108
|
+
*
|
|
109
|
+
* The style set travels with the density for the same reason and by the same
|
|
110
|
+
* route: `useCuePortalProps()` stamps a portal container from `StyleSetContext`,
|
|
111
|
+
* and a popup opened on the stage inherits nothing from the stage's DOM.
|
|
112
|
+
*
|
|
113
|
+
* And the pairing travels in the theme context, because that is where the
|
|
114
|
+
* portal stamp reads `data-font` from (`theme?.font`). It used not to: the
|
|
115
|
+
* stage stamped `data-font` on its own element and republished nothing, so a
|
|
116
|
+
* popover opened on a `plex` stage came up in the root's pairing — a limit the
|
|
117
|
+
* `font` prop's docblock stated rather than a rule anybody chose. A pairing on
|
|
118
|
+
* the stage is the stage's, exactly as its theme is; `setFont` read here still
|
|
119
|
+
* drives the root, because the pairing's setter has no island form.
|
|
120
|
+
*/
|
|
121
|
+
function StageScope({ theme, mode, density, font, styleSet, paint, children }) {
|
|
122
|
+
const outer = useRead(ThemeContext);
|
|
123
|
+
const outerOverrides = useRead(OverridesContext);
|
|
124
|
+
const outerDensity = useRead(DensityContext);
|
|
125
|
+
const outerAmbient = useRead(AmbientThemeContext);
|
|
126
|
+
const outerStyleSet = useRead(StyleSetContext);
|
|
127
|
+
const outerPaint = useRead(PaintContext);
|
|
128
|
+
const value = React.useMemo(() => {
|
|
129
|
+
if (outer === null || theme === void 0 && mode === void 0 && font === void 0) return outer;
|
|
130
|
+
const scoped = theme ?? outer.theme;
|
|
131
|
+
return {
|
|
132
|
+
...outer,
|
|
133
|
+
theme: scoped,
|
|
134
|
+
manifest: scoped === outer.theme ? outer.manifest : null,
|
|
135
|
+
mode: mode ?? outer.mode,
|
|
136
|
+
font: font ?? outer.font,
|
|
137
|
+
resolvedMode: mode === "dark" || mode === "light" ? mode : outer.resolvedMode
|
|
138
|
+
};
|
|
139
|
+
}, [
|
|
140
|
+
outer,
|
|
141
|
+
theme,
|
|
142
|
+
mode,
|
|
143
|
+
font
|
|
144
|
+
]);
|
|
145
|
+
const stamped = theme !== void 0 || mode !== void 0;
|
|
146
|
+
const inputs = [
|
|
147
|
+
theme,
|
|
148
|
+
mode,
|
|
149
|
+
density,
|
|
150
|
+
font,
|
|
151
|
+
styleSet,
|
|
152
|
+
paint,
|
|
153
|
+
React.useContext(ThemeContext),
|
|
154
|
+
React.useContext(OverridesContext),
|
|
155
|
+
React.useContext(DensityContext),
|
|
156
|
+
React.useContext(AmbientThemeContext),
|
|
157
|
+
React.useContext(StyleSetContext),
|
|
158
|
+
React.useContext(PaintContext)
|
|
159
|
+
];
|
|
160
|
+
const publishedAmbient = usePublish(theme ?? outerAmbient, inputs);
|
|
161
|
+
const publishedDensity = usePublish(density ?? outerDensity, inputs);
|
|
162
|
+
const publishedStyleSet = usePublish(styleSet ?? outerStyleSet, inputs);
|
|
163
|
+
const publishedPaint = usePublish(paint ?? outerPaint, inputs);
|
|
164
|
+
const publishedOverrides = usePublish(stamped ? null : outerOverrides, inputs);
|
|
165
|
+
const publishedValue = usePublish(value, inputs);
|
|
166
|
+
const outerPairingSeed = React.useContext(PairingSeedContext);
|
|
167
|
+
const outerScopedPairing = React.useContext(ScopedPairingContext);
|
|
168
|
+
const outerRootAxes = React.useContext(RootAxesContext);
|
|
169
|
+
return /* @__PURE__ */ jsx(RootAxesContext.Provider, {
|
|
170
|
+
value: rootAxes(outerRootAxes.theme && theme === void 0, outerRootAxes.mode && mode === void 0, outerRootAxes.density && density === void 0),
|
|
171
|
+
children: /* @__PURE__ */ jsx(AmbientThemeContext.Provider, {
|
|
172
|
+
value: publishedAmbient,
|
|
173
|
+
children: /* @__PURE__ */ jsx(DensityContext.Provider, {
|
|
174
|
+
value: publishedDensity,
|
|
175
|
+
children: /* @__PURE__ */ jsx(StyleSetContext.Provider, {
|
|
176
|
+
value: publishedStyleSet,
|
|
177
|
+
children: /* @__PURE__ */ jsx(PaintContext.Provider, {
|
|
178
|
+
value: publishedPaint,
|
|
179
|
+
children: /* @__PURE__ */ jsx(OverridesContext.Provider, {
|
|
180
|
+
value: publishedOverrides,
|
|
181
|
+
children: /* @__PURE__ */ jsx(PairingSeedContext.Provider, {
|
|
182
|
+
value: font === void 0 ? outerPairingSeed : NO_PAIRING_SEED,
|
|
183
|
+
children: /* @__PURE__ */ jsx(ScopedPairingContext.Provider, {
|
|
184
|
+
value: font !== void 0 || outerScopedPairing,
|
|
185
|
+
children: /* @__PURE__ */ jsx(ThemeContext.Provider, {
|
|
186
|
+
value: publishedValue,
|
|
187
|
+
children
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
export { StageScope, stageAttributes };
|
|
@@ -26,7 +26,7 @@ const CHECK_EDGE = "border-[color:var(--cue-style-check-edge,var(--cue-border))]
|
|
|
26
26
|
* box is 18px on a phone and 16px on a desktop, rounded at 0.4 of its control
|
|
27
27
|
* corner (4px). The `sm` box keeps its `icon-sm`.
|
|
28
28
|
*/
|
|
29
|
-
const CHECK_RADIUS = "rounded-[var(--cue-style-check-radius,calc(var(--cue-radius-control)*var(--cue-radius-scale)))]";
|
|
29
|
+
const CHECK_RADIUS = "rounded-[var(--cue-style-check-radius,calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)))]";
|
|
30
30
|
/** @see CHECK_RADIUS */
|
|
31
31
|
const CHECK_SIZE_MD = "size-[var(--cue-style-check-size,var(--cue-icon-md))]";
|
|
32
32
|
/**
|
|
@@ -69,7 +69,7 @@ const CHIP_TRACKING = "tracking-[var(--cue-style-chip-tracking,var(--cue-trackin
|
|
|
69
69
|
/** @see CHIP_CASE — the `tag` variant's tracking; its own closed `0.06em` by default. */
|
|
70
70
|
const CHIP_TRACKING_TAG = "tracking-[var(--cue-style-chip-tracking,0.06em)]";
|
|
71
71
|
/** @see CHIP_CASE — the chip's radius; what `rounded-control` compiles to by default. */
|
|
72
|
-
const CHIP_RADIUS = "rounded-[var(--cue-style-chip-radius,calc(var(--cue-radius-control)*var(--cue-radius-scale)))]";
|
|
72
|
+
const CHIP_RADIUS = "rounded-[var(--cue-style-chip-radius,calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)))]";
|
|
73
73
|
/** @see CHIP_CASE — each tone's fill: `var(--cue-style-chip-fill, 10%)` of the tone over transparent. */
|
|
74
74
|
const CHIP_FILL = {
|
|
75
75
|
accent: "bg-[color:color-mix(in_oklab,var(--cue-accent)_var(--cue-style-chip-fill,10%),transparent)]",
|
|
@@ -24,6 +24,19 @@ const FIELD_PAD_SM = "px-[calc(var(--cue-space-3)*var(--cue-style-field-pad,1))]
|
|
|
24
24
|
const FIELD_PAD_MD = "px-[calc(var(--cue-space-4)*var(--cue-style-field-pad,1))]";
|
|
25
25
|
/** The `lg` rung's inline padding. */
|
|
26
26
|
const FIELD_PAD_LG = "px-[calc(var(--cue-space-5)*var(--cue-style-field-pad,1))]";
|
|
27
|
+
/**
|
|
28
|
+
* A text-entry field's corner (2026-10-04): `style-field-radius`, and the
|
|
29
|
+
* control's own radius — a set's `style-control-radius` or the density's rung,
|
|
30
|
+
* times the scale, exactly what `rounded-control` resolves to — when a set names
|
|
31
|
+
* none. A set whose buttons are pills (Prism, Ledger) names it so its fields,
|
|
32
|
+
* textareas, drop zones and data panes are rounded rectangles rather than pills;
|
|
33
|
+
* every other set draws what it drew before. Spelled through `--cue-*` names,
|
|
34
|
+
* as `_style-check.ts`'s corner is, and never through a `--radius-*` reader.
|
|
35
|
+
*/
|
|
36
|
+
const FIELD_RADIUS = "rounded-[var(--cue-style-field-radius,calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)))]";
|
|
37
|
+
/** The same corner on one side, for a shape that tightens only its tail (a message bubble). */
|
|
38
|
+
const FIELD_RADIUS_BR = "rounded-br-[var(--cue-style-field-radius,calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)))]";
|
|
39
|
+
const FIELD_RADIUS_BL = "rounded-bl-[var(--cue-style-field-radius,calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)))]";
|
|
27
40
|
/** The edge under a pointer that can hover. */
|
|
28
41
|
const FIELD_HOVER_EDGE = "hover:border-[color:var(--cue-style-field-hover-edge,transparent)]";
|
|
29
42
|
/** An invalid field's edge: `danger`, the class the chassis drew before round 6. */
|
|
@@ -35,4 +48,4 @@ const INVALID_EDGE = "data-[invalid]:border-danger";
|
|
|
35
48
|
*/
|
|
36
49
|
const SELECT_HOVER_EDGE = "hover:not-data-disabled:border-[color:var(--cue-style-field-hover-edge,var(--cue-border-strong))]";
|
|
37
50
|
//#endregion
|
|
38
|
-
export { FIELD_HOVER_EDGE, FIELD_PAD_LG, FIELD_PAD_MD, FIELD_PAD_SM, INVALID_EDGE, SELECT_HOVER_EDGE };
|
|
51
|
+
export { FIELD_HOVER_EDGE, FIELD_PAD_LG, FIELD_PAD_MD, FIELD_PAD_SM, FIELD_RADIUS, FIELD_RADIUS_BL, FIELD_RADIUS_BR, INVALID_EDGE, SELECT_HOVER_EDGE };
|
|
@@ -4,21 +4,25 @@
|
|
|
4
4
|
* `style-group-label-size`, `style-group-label-ink`): a menu's and a command
|
|
5
5
|
* palette's group labels. A menu's falls back through `style-label-size` and
|
|
6
6
|
* `style-label-ink` to the rung and ink it drew; the palette's, which read no
|
|
7
|
-
* label name, to its `text-micro
|
|
7
|
+
* label name, to its `text-micro`. coss.com/ui sets both at
|
|
8
8
|
* 12px in `fg-muted` (P163.label-size, P152.group-label, P152.group-label-ink),
|
|
9
9
|
* where its field labels are 14px `fg`.
|
|
10
10
|
*
|
|
11
11
|
* **Optional** (spec §3.0). Its own module for docgen's reason: one token per
|
|
12
12
|
* file, so only the components that read it are credited with it.
|
|
13
13
|
*
|
|
14
|
+
* Both inks fall back to `fg-muted`, not `fg-subtle`: a group label is label
|
|
15
|
+
* text, held to 4.5:1 (structural Looks, spec 2026-10-02 §4.1; see
|
|
16
|
+
* `_style-label-ink.ts`).
|
|
17
|
+
*
|
|
14
18
|
* @internal Not part of the public API.
|
|
15
19
|
*/
|
|
16
20
|
const GROUP_LABEL_SIZE = "text-[length:var(--cue-style-group-label-size,var(--cue-style-label-size,var(--cue-text-label)))]";
|
|
17
21
|
/** @see GROUP_LABEL_SIZE */
|
|
18
|
-
const GROUP_LABEL_INK = "text-[color:var(--cue-style-group-label-ink,var(--cue-style-label-ink,var(--cue-fg-
|
|
22
|
+
const GROUP_LABEL_INK = "text-[color:var(--cue-style-group-label-ink,var(--cue-style-label-ink,var(--cue-fg-muted)))]";
|
|
19
23
|
/** @see GROUP_LABEL_SIZE — the palette's, which read no label name. */
|
|
20
24
|
const GROUP_LABEL_SIZE_MICRO = "text-[length:var(--cue-style-group-label-size,var(--cue-text-micro))]";
|
|
21
25
|
/** @see GROUP_LABEL_SIZE */
|
|
22
|
-
const GROUP_LABEL_INK_SUBTLE = "text-[color:var(--cue-style-group-label-ink,var(--cue-fg-
|
|
26
|
+
const GROUP_LABEL_INK_SUBTLE = "text-[color:var(--cue-style-group-label-ink,var(--cue-fg-muted))]";
|
|
23
27
|
//#endregion
|
|
24
28
|
export { GROUP_LABEL_INK, GROUP_LABEL_INK_SUBTLE, GROUP_LABEL_SIZE, GROUP_LABEL_SIZE_MICRO };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/system/_style-hero-title.ts
|
|
2
|
+
/**
|
|
3
|
+
* The `Hero` title's voice: the style set's **display** rung, falling back to
|
|
4
|
+
* the page title's voice (`_style-page-title.ts`) name by name, and through it
|
|
5
|
+
* to the class the page title replaced.
|
|
6
|
+
*
|
|
7
|
+
* Spec 2026-10-02-structural-looks §16 (revision 8). A hero is a page's display
|
|
8
|
+
* line, and the sources say so: Tender's opens at 48px where its page titles
|
|
9
|
+
* are 32px, Porcelain's at 48px against 36px, Zine's at 96px. The display rung
|
|
10
|
+
* is where every measured set already records that size, so the hero reads it
|
|
11
|
+
* first. `style-hero-title-size` sits in front of the size for the one source
|
|
12
|
+
* whose hero is larger than its display rung: Graphite's is 56px, and its
|
|
13
|
+
* display rung is its 46px section heading.
|
|
14
|
+
*
|
|
15
|
+
* | property | reads, outermost first |
|
|
16
|
+
* |-------------------------|------------------------|
|
|
17
|
+
* | font-family | `style-display-face` → `style-page-title-face` → `var(--cue-font-mono)` |
|
|
18
|
+
* | font-size | `style-hero-title-size` → `style-display` → `style-page-title-size` → `var(--cue-text-title)` |
|
|
19
|
+
* | font-weight | `style-display-weight` → `style-page-title-weight` → `500` |
|
|
20
|
+
* | letter-spacing | `style-tracking-display` → `style-page-title-tracking` → `normal` |
|
|
21
|
+
* | line-height | `style-leading-display` → `style-page-title-leading` → `inherit` |
|
|
22
|
+
* | font-variation-settings | `style-display-variation` → `normal` |
|
|
23
|
+
* | color | `style-heading-ink` → `var(--cue-fg)` |
|
|
24
|
+
* | text-transform | `style-display-case` → `none` |
|
|
25
|
+
*
|
|
26
|
+
* **Who moves.** A set that declares none of the display names draws its hero
|
|
27
|
+
* exactly as before, because each chain ends in the page title's reader. The
|
|
28
|
+
* four structural sets of phase 3 declare both rungs equal. `editorial` and
|
|
29
|
+
* `marketing` declare a display size and no page-title size, so their heroes
|
|
30
|
+
* grow to it (22 → 52px and 22 → 64px at scale 1); that is what a display rung
|
|
31
|
+
* is for. `tests/browser/hero-voice.spec.ts` pins every set.
|
|
32
|
+
*
|
|
33
|
+
* **Its own module**, for the reason `_style-page-title.ts` gives: `@repo/docgen`
|
|
34
|
+
* attributes a token to a component by the file its class string lives in, and
|
|
35
|
+
* the display names belong to the hero.
|
|
36
|
+
*
|
|
37
|
+
* @internal Not part of the public API.
|
|
38
|
+
*/
|
|
39
|
+
const HERO_TITLE_VOICE = "font-[family-name:var(--cue-style-display-face,var(--cue-style-page-title-face,var(--cue-font-mono)))] text-[length:var(--cue-style-hero-title-size,var(--cue-style-display,var(--cue-style-page-title-size,var(--cue-text-title))))] font-[number:var(--cue-style-display-weight,var(--cue-style-page-title-weight,500))] tracking-[var(--cue-style-tracking-display,var(--cue-style-page-title-tracking,normal))] leading-[var(--cue-style-leading-display,var(--cue-style-page-title-leading,inherit))] [font-variation-settings:var(--cue-style-display-variation,normal)] text-[color:var(--cue-style-heading-ink,var(--cue-fg))] [text-transform:var(--cue-style-display-case,none)] [overflow-wrap:anywhere]";
|
|
40
|
+
//#endregion
|
|
41
|
+
export { HERO_TITLE_VOICE };
|
|
@@ -41,7 +41,7 @@ const KBD_SIZE = "text-[length:var(--cue-style-kbd-size,var(--cue-style-chip-siz
|
|
|
41
41
|
* where its key caps stay 12px. It falls back through the chip's size, so a
|
|
42
42
|
* Look that names only the chip's keeps its key caps beside it.
|
|
43
43
|
*/
|
|
44
|
-
const KBD_BOX = "h-[var(--cue-style-kbd-height,var(--cue-chip-h))] min-w-[var(--cue-style-kbd-height,var(--cue-chip-h))] px-[var(--cue-style-kbd-pad,var(--cue-space-2))] rounded-[var(--cue-style-kbd-radius,calc(var(--cue-radius-control)*var(--cue-radius-scale)))]";
|
|
44
|
+
const KBD_BOX = "h-[var(--cue-style-kbd-height,var(--cue-chip-h))] min-w-[var(--cue-style-kbd-height,var(--cue-chip-h))] px-[var(--cue-style-kbd-pad,var(--cue-space-2))] rounded-[var(--cue-style-kbd-radius,calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)))]";
|
|
45
45
|
/**
|
|
46
46
|
* A key's leading (round 6, spec §11, lane R6.P: `style-kbd-leading`), falling
|
|
47
47
|
* back to the `leading-none` it drew. coss.com/ui's keys set 12px on a 16px line
|
|
@@ -12,9 +12,16 @@
|
|
|
12
12
|
* once, which is why the name exists.
|
|
13
13
|
*
|
|
14
14
|
* **Optional, so each reader keeps its own fallback** (spec §3.0, F-3): under
|
|
15
|
-
* no set, `console` and every set that declines the name this resolves
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* no set, `console` and every set that declines the name this resolves at the
|
|
16
|
+
* label itself, so a nested theme island still repaints it.
|
|
17
|
+
*
|
|
18
|
+
* **Both fall back to `fg-muted`** (structural Looks, spec 2026-10-02 §4.1).
|
|
19
|
+
* A label names what sits beside it, so it is held to 4.5:1 like body text,
|
|
20
|
+
* and the quieter `fg-subtle` the second reader spent is a hint's ink, not a
|
|
21
|
+
* label's: under Classic it drew stat labels, the logo strip's caption and a
|
|
22
|
+
* sidebar section's label at 2.4:1 in dark and 3.1:1 in light, on the
|
|
23
|
+
* rendered sweep. `LABEL_INK_SUBTLE` keeps its name so the readers that spent
|
|
24
|
+
* `text-fg-subtle` stay findable.
|
|
18
25
|
*
|
|
19
26
|
* **Its own module**, for the reason `_style-control-face.ts` gives: one
|
|
20
27
|
* token per file, so `@repo/docgen` credits exactly the components that read
|
|
@@ -23,7 +30,7 @@
|
|
|
23
30
|
* @internal Not part of the public API.
|
|
24
31
|
*/
|
|
25
32
|
const LABEL_INK = "text-[color:var(--cue-style-label-ink,var(--cue-fg-muted))]";
|
|
26
|
-
/** @see LABEL_INK — for a reader that spent `text-fg-subtle` before this existed. */
|
|
27
|
-
const LABEL_INK_SUBTLE =
|
|
33
|
+
/** @see LABEL_INK — for a reader that spent `text-fg-subtle` before this existed; it falls back to `fg-muted` too. */
|
|
34
|
+
const LABEL_INK_SUBTLE = LABEL_INK;
|
|
28
35
|
//#endregion
|
|
29
36
|
export { LABEL_INK, LABEL_INK_SUBTLE };
|
|
@@ -25,7 +25,7 @@ const MENU_ITEM_INK = "text-[color:var(--cue-style-quiet-ink,var(--cue-fg-muted)
|
|
|
25
25
|
* rows read the same name with a `0px` fallback (`_style-option.ts`), since
|
|
26
26
|
* they drew no corner.
|
|
27
27
|
*/
|
|
28
|
-
const MENU_ITEM_RADIUS = "rounded-[var(--cue-style-menu-item-radius,calc(var(--cue-radius-control)*var(--cue-radius-scale)))]";
|
|
28
|
+
const MENU_ITEM_RADIUS = "rounded-[var(--cue-style-menu-item-radius,calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)))]";
|
|
29
29
|
/**
|
|
30
30
|
* A menu popup's minimum width (round 6, spec §11, lane R6.P:
|
|
31
31
|
* `style-menu-min-width`), falling back to the `10rem` it drew. coss.com/ui's
|
|
@@ -43,6 +43,6 @@
|
|
|
43
43
|
*
|
|
44
44
|
* @internal Not part of the public API.
|
|
45
45
|
*/
|
|
46
|
-
const PAGE_TITLE_VOICE = "font-[family-name:var(--cue-style-page-title-face,var(--cue-font-mono))] text-[length:var(--cue-style-page-title-size,var(--cue-text-title))] font-[number:var(--cue-style-page-title-weight,500)] tracking-[var(--cue-style-page-title-tracking,normal)] leading-[var(--cue-style-page-title-leading,inherit)] [font-variation-settings:var(--cue-style-display-variation,normal)] text-[color:var(--cue-style-heading-ink,var(--cue-fg))] [overflow-wrap:anywhere]";
|
|
46
|
+
const PAGE_TITLE_VOICE = "font-[family-name:var(--cue-style-page-title-face,var(--cue-font-mono))] text-[length:var(--cue-style-page-title-size,var(--cue-text-title))] font-[number:var(--cue-style-page-title-weight,500)] tracking-[var(--cue-style-page-title-tracking,normal)] leading-[var(--cue-style-page-title-leading,inherit)] [font-variation-settings:var(--cue-style-display-variation,normal)] text-[color:var(--cue-style-heading-ink,var(--cue-fg))] [text-transform:var(--cue-style-display-case,none)] [overflow-wrap:anywhere]";
|
|
47
47
|
//#endregion
|
|
48
48
|
export { PAGE_TITLE_VOICE };
|
|
@@ -72,7 +72,7 @@ const SEGMENTED_TRACK = "bg-[color:var(--cue-style-segment-track,var(--cue-surfa
|
|
|
72
72
|
/** The overflow chevrons' ground: the track's colour over an opaque `surface-1`. */
|
|
73
73
|
const SEGMENTED_CHEVRON_GROUND = "bg-surface-1 bg-[image:linear-gradient(var(--cue-style-segment-track,transparent),var(--cue-style-segment-track,transparent))]";
|
|
74
74
|
/** A segment's corners, nested inside the track's inset. */
|
|
75
|
-
const SEGMENT_RADIUS = "rounded-[calc(var(--cue-radius-control)*var(--cue-radius-scale)-var(--cue-style-segment-inset,0px))]";
|
|
75
|
+
const SEGMENT_RADIUS = "rounded-[calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)-var(--cue-style-segment-inset,0px))]";
|
|
76
76
|
/** `SegmentedControl`'s pressed segment. */
|
|
77
77
|
const SEGMENT_PRESSED = "data-[pressed]:bg-[color:var(--cue-style-segment-ground,var(--cue-accent-soft))] data-[pressed]:text-[color:var(--cue-style-segment-ink,var(--cue-accent))] [:where(&[data-pressed])]:font-[number:var(--cue-style-segment-weight,400)] [:where(&[data-pressed])]:shadow-[inset_0_0_0_var(--cue-border-width)_var(--cue-style-segment-edge,transparent),0_calc(2px*var(--cue-paint-shadow-scale))_calc(4px*var(--cue-paint-shadow-scale))_var(--cue-paint-rest-shade,transparent)]";
|
|
78
78
|
/**
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @internal Not part of the public API.
|
|
12
12
|
*/
|
|
13
|
-
const SKELETON_RADIUS = "rounded-[var(--cue-style-skeleton-radius,calc(var(--cue-radius-control)*var(--cue-radius-scale)))]";
|
|
13
|
+
const SKELETON_RADIUS = "rounded-[var(--cue-style-skeleton-radius,calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)))]";
|
|
14
14
|
//#endregion
|
|
15
15
|
export { SKELETON_RADIUS };
|
|
@@ -60,7 +60,7 @@ const TAB_TRACK = "bg-[color:var(--cue-style-segment-track,transparent)] p-[var(
|
|
|
60
60
|
* Porcelain's idle tabs are 500 too — falling back to 400. A caller's
|
|
61
61
|
* `font-*` class on a tab replaces it through `tailwind-merge`.
|
|
62
62
|
*/
|
|
63
|
-
const TAB_SHAPE = "rounded-t-[calc(var(--cue-style-tab-radius,calc(var(--cue-radius-control)*var(--cue-radius-scale)+var(--cue-style-segment-inset,0px)))-var(--cue-style-segment-inset,0px))] rounded-b-[calc(var(--cue-style-tab-radius,var(--cue-style-segment-inset,0px))-var(--cue-style-segment-inset,0px))] font-[number:var(--cue-style-segment-weight,400)]";
|
|
63
|
+
const TAB_SHAPE = "rounded-t-[calc(var(--cue-style-tab-radius,calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)+var(--cue-style-segment-inset,0px)))-var(--cue-style-segment-inset,0px))] rounded-b-[calc(var(--cue-style-tab-radius,var(--cue-style-segment-inset,0px))-var(--cue-style-segment-inset,0px))] font-[number:var(--cue-style-segment-weight,400)]";
|
|
64
64
|
/** The list's bottom rule, as a width. */
|
|
65
65
|
const TAB_RULE = "border-b-[length:var(--cue-style-tab-rule,1px)]";
|
|
66
66
|
/** The active tab's underline ink, on its `::after`. */
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { FontName } from "@cueplusplus/theme-base";
|
|
3
|
+
//#region src/system/font-scope.d.ts
|
|
4
|
+
interface FontScopeProps extends Omit<React.ComponentPropsWithoutRef<"div">, "children"> {
|
|
5
|
+
/** The pairing this subtree is set in. */
|
|
6
|
+
font: FontName;
|
|
7
|
+
/** What is set in it. Every overlay opened inside it is set in it too. */
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A subtree set in another type pairing — and everything it opens with it.
|
|
12
|
+
*
|
|
13
|
+
* `ThemeProvider`'s `font` is root-level only: the pairing has no island, and a
|
|
14
|
+
* nested provider forwards it to the root. `data-font` on a `<div>` is enough
|
|
15
|
+
* for the subtree's own tokens, and nothing more: a dialog or menu opened inside
|
|
16
|
+
* it mounts on `<body>`, stamped from the root's pairing, and unclassed text
|
|
17
|
+
* keeps the family `<body>` computed, because a computed family does not follow
|
|
18
|
+
* a token. `FontScope` does all three: it stamps `data-font`, asks for the
|
|
19
|
+
* family again on its own element, and republishes the pairing so every portal
|
|
20
|
+
* opened inside it is stamped with it and asks for the family too. It is what
|
|
21
|
+
* `Preview` does for a stage that names a pairing, as a component of its own.
|
|
22
|
+
*
|
|
23
|
+
* It never calls `setFont`, so the root's pairing — and the visitor's stored
|
|
24
|
+
* one — never moves. A gallery frame that draws a page in a Look of its own is
|
|
25
|
+
* the case it exists for (structural Looks, spec 2026-10-02 §6.2).
|
|
26
|
+
*
|
|
27
|
+
* The set and pack in force around it are restated beside `data-font`, once it
|
|
28
|
+
* may restate, because their names read the pairing and a custom property's
|
|
29
|
+
* `var()`s are substituted where it is declared.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* <FontScope font="inter-source">
|
|
33
|
+
* <Dialog.Root>…</Dialog.Root>
|
|
34
|
+
* </FontScope>
|
|
35
|
+
*/
|
|
36
|
+
declare const FontScope: React.ForwardRefExoticComponent<FontScopeProps & React.RefAttributes<HTMLDivElement>>;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { FontScope, FontScopeProps };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { useRead } from "./published.js";
|
|
4
|
+
import { DensityContext, PaintContext, StyleSetContext, useFollowedAxes, useRestateReady } from "./density.js";
|
|
5
|
+
import { ThemeContext } from "./theme-provider.js";
|
|
6
|
+
import { StageScope, stageAttributes } from "./_stage-scope.js";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
//#region src/system/font-scope.tsx
|
|
10
|
+
/**
|
|
11
|
+
* A subtree set in another type pairing — and everything it opens with it.
|
|
12
|
+
*
|
|
13
|
+
* `ThemeProvider`'s `font` is root-level only: the pairing has no island, and a
|
|
14
|
+
* nested provider forwards it to the root. `data-font` on a `<div>` is enough
|
|
15
|
+
* for the subtree's own tokens, and nothing more: a dialog or menu opened inside
|
|
16
|
+
* it mounts on `<body>`, stamped from the root's pairing, and unclassed text
|
|
17
|
+
* keeps the family `<body>` computed, because a computed family does not follow
|
|
18
|
+
* a token. `FontScope` does all three: it stamps `data-font`, asks for the
|
|
19
|
+
* family again on its own element, and republishes the pairing so every portal
|
|
20
|
+
* opened inside it is stamped with it and asks for the family too. It is what
|
|
21
|
+
* `Preview` does for a stage that names a pairing, as a component of its own.
|
|
22
|
+
*
|
|
23
|
+
* It never calls `setFont`, so the root's pairing — and the visitor's stored
|
|
24
|
+
* one — never moves. A gallery frame that draws a page in a Look of its own is
|
|
25
|
+
* the case it exists for (structural Looks, spec 2026-10-02 §6.2).
|
|
26
|
+
*
|
|
27
|
+
* The set and pack in force around it are restated beside `data-font`, once it
|
|
28
|
+
* may restate, because their names read the pairing and a custom property's
|
|
29
|
+
* `var()`s are substituted where it is declared.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* <FontScope font="inter-source">
|
|
33
|
+
* <Dialog.Root>…</Dialog.Root>
|
|
34
|
+
* </FontScope>
|
|
35
|
+
*/
|
|
36
|
+
const FontScope = React.forwardRef(function FontScope({ font, className, children, ...elementProps }, ref) {
|
|
37
|
+
const outer = useRead(ThemeContext);
|
|
38
|
+
const aroundStyleSet = useRead(StyleSetContext);
|
|
39
|
+
const aroundPaint = useRead(PaintContext);
|
|
40
|
+
const aroundDensity = useRead(DensityContext);
|
|
41
|
+
const restateReady = useRestateReady(aroundStyleSet !== null || aroundPaint !== null);
|
|
42
|
+
const followed = useFollowedAxes({
|
|
43
|
+
theme: false,
|
|
44
|
+
mode: false,
|
|
45
|
+
density: false
|
|
46
|
+
});
|
|
47
|
+
const attributes = stageAttributes({ font }, outer, restateReady ? {
|
|
48
|
+
styleSet: aroundStyleSet,
|
|
49
|
+
paint: aroundPaint
|
|
50
|
+
} : null, aroundDensity, followed);
|
|
51
|
+
return /* @__PURE__ */ jsx("div", {
|
|
52
|
+
ref,
|
|
53
|
+
"data-slot": "font-scope",
|
|
54
|
+
...elementProps,
|
|
55
|
+
...attributes,
|
|
56
|
+
className: cn("font-sans", className),
|
|
57
|
+
children: /* @__PURE__ */ jsx(StageScope, {
|
|
58
|
+
theme: void 0,
|
|
59
|
+
mode: void 0,
|
|
60
|
+
density: void 0,
|
|
61
|
+
font,
|
|
62
|
+
styleSet: void 0,
|
|
63
|
+
paint: void 0,
|
|
64
|
+
children
|
|
65
|
+
})
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
//#endregion
|
|
69
|
+
export { FontScope };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/system/frame-clock.d.ts
|
|
2
|
+
interface FrameClockOptions {
|
|
3
|
+
/** Frames per second. Defaults to `30`, cueplusplus.com's `VIDEO_FPS`. */
|
|
4
|
+
fps?: number;
|
|
5
|
+
/** How many frames the loop holds. */
|
|
6
|
+
durationInFrames: number;
|
|
7
|
+
/** Start again at frame 0 after the last. Defaults to `true`; `false` holds the last frame. */
|
|
8
|
+
loop?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Whether the clock runs. Defaults to `true`. Pass `false` while the
|
|
11
|
+
* animation is off screen: a paused clock schedules nothing and, resumed,
|
|
12
|
+
* continues from the frame it stopped on.
|
|
13
|
+
*/
|
|
14
|
+
playing?: boolean;
|
|
15
|
+
/** The frame shown while held still (a thumbnail, or reduced motion). Defaults to the middle of the loop. */
|
|
16
|
+
stillFrame?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A frame counter for a loop illustration: what Remotion's `useCurrentFrame`
|
|
20
|
+
* gave cueplusplus.com's service animations, without Remotion (spec
|
|
21
|
+
* 2026-10-04-cue-site-verbatim §4.4). Frame maths written against
|
|
22
|
+
* `useCurrentFrame` ports unchanged.
|
|
23
|
+
*
|
|
24
|
+
* Driven by `requestAnimationFrame` against elapsed time, so a slow frame never
|
|
25
|
+
* slows the animation down. It never advances while paused or still, and held
|
|
26
|
+
* still it returns `stillFrame` and schedules nothing.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const frame = useFrameClock({ durationInFrames: 300, playing: onScreen });
|
|
30
|
+
*/
|
|
31
|
+
declare function useFrameClock({ fps, durationInFrames, loop, playing, stillFrame }: FrameClockOptions): number;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { FrameClockOptions, useFrameClock };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { usePrefersReducedMotion } from "../instruments/_motion.js";
|
|
3
|
+
import { useStill } from "./still.js";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
//#region src/system/frame-clock.ts
|
|
6
|
+
/**
|
|
7
|
+
* A frame counter for a loop illustration: what Remotion's `useCurrentFrame`
|
|
8
|
+
* gave cueplusplus.com's service animations, without Remotion (spec
|
|
9
|
+
* 2026-10-04-cue-site-verbatim §4.4). Frame maths written against
|
|
10
|
+
* `useCurrentFrame` ports unchanged.
|
|
11
|
+
*
|
|
12
|
+
* Driven by `requestAnimationFrame` against elapsed time, so a slow frame never
|
|
13
|
+
* slows the animation down. It never advances while paused or still, and held
|
|
14
|
+
* still it returns `stillFrame` and schedules nothing.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const frame = useFrameClock({ durationInFrames: 300, playing: onScreen });
|
|
18
|
+
*/
|
|
19
|
+
function useFrameClock({ fps = 30, durationInFrames, loop = true, playing = true, stillFrame }) {
|
|
20
|
+
const held = useStill();
|
|
21
|
+
const reduced = usePrefersReducedMotion();
|
|
22
|
+
const still = held || reduced;
|
|
23
|
+
const seat = stillFrame ?? Math.floor(durationInFrames / 2);
|
|
24
|
+
const [frame, setFrame] = React.useState(0);
|
|
25
|
+
const elapsed = React.useRef(0);
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
if (still || !playing || durationInFrames <= 0) return;
|
|
28
|
+
let last = performance.now();
|
|
29
|
+
let handle = requestAnimationFrame(function step() {
|
|
30
|
+
const now = performance.now();
|
|
31
|
+
elapsed.current += now - last;
|
|
32
|
+
last = now;
|
|
33
|
+
const total = Math.floor(elapsed.current * fps / 1e3);
|
|
34
|
+
setFrame(loop ? total % durationInFrames : Math.min(total, durationInFrames - 1));
|
|
35
|
+
handle = requestAnimationFrame(step);
|
|
36
|
+
});
|
|
37
|
+
const onVisibility = () => {
|
|
38
|
+
if (!document.hidden) last = performance.now();
|
|
39
|
+
};
|
|
40
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
41
|
+
return () => {
|
|
42
|
+
cancelAnimationFrame(handle);
|
|
43
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
44
|
+
};
|
|
45
|
+
}, [
|
|
46
|
+
still,
|
|
47
|
+
playing,
|
|
48
|
+
fps,
|
|
49
|
+
durationInFrames,
|
|
50
|
+
loop
|
|
51
|
+
]);
|
|
52
|
+
return still ? seat : frame;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { useFrameClock };
|
package/dist/system/index.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ import { ThemeRegistry } from "./vocabulary.js";
|
|
|
7
7
|
import { useDensities, useFonts, useThemes } from "./theme-registry.js";
|
|
8
8
|
import { ControlSize, useControlHeight, useDensity } from "./use-density.js";
|
|
9
9
|
import { useTheme } from "./use-theme.js";
|
|
10
|
+
import { StillProvider, StillProviderProps, useStill } from "./still.js";
|
|
11
|
+
import { FrameClockOptions, useFrameClock } from "./frame-clock.js";
|
|
12
|
+
import { FontScope, FontScopeProps } from "./font-scope.js";
|
|
10
13
|
import { DEFAULT_PAINT, DEFAULT_STYLE_SET, PAINTS, Paint, STYLE_SETS, StyleSet } from "@cueplusplus/tokens";
|
|
11
14
|
import { Density as DensityLevel, DensityEntry, FontEntry, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
|
|
12
|
-
export { type ControlSize, CuePortalFrame, type CuePortalFrameProps, DEFAULT_PAINT, DEFAULT_STORAGE_KEY, DEFAULT_STYLE_SET, Density, type DensityEntry, type DensityLevel, type DensityProps, type FontEntry, type FontFamilies, type FontName, type Mode, PAINTS, type Paint, type PersistedPreferences, type PrepaintDefaults, type PrepaintOptions, type ResolvedMode, STYLE_SETS, type StyleSet, type ThemeContextValue, type ThemeManifest, type ThemeName, ThemeProvider, type ThemeProviderProps, type ThemeRegistry, type TokenOverrides, isSafeTokenValue, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
|
|
15
|
+
export { type ControlSize, CuePortalFrame, type CuePortalFrameProps, DEFAULT_PAINT, DEFAULT_STORAGE_KEY, DEFAULT_STYLE_SET, Density, type DensityEntry, type DensityLevel, type DensityProps, type FontEntry, type FontFamilies, type FontName, FontScope, type FontScopeProps, type FrameClockOptions, type Mode, PAINTS, type Paint, type PersistedPreferences, type PrepaintDefaults, type PrepaintOptions, type ResolvedMode, STYLE_SETS, StillProvider, type StillProviderProps, type StyleSet, type ThemeContextValue, type ThemeManifest, type ThemeName, ThemeProvider, type ThemeProviderProps, type ThemeRegistry, type TokenOverrides, isSafeTokenValue, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useFrameClock, useStill, useTheme, useThemes };
|
package/dist/system/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StillProvider, useStill } from "./still.js";
|
|
1
2
|
import { Density } from "./density.js";
|
|
2
3
|
import { isSafeTokenValue } from "./overrides.js";
|
|
3
4
|
import { DEFAULT_STORAGE_KEY, prepaintScript } from "./prepaint.js";
|
|
@@ -6,5 +7,7 @@ import { ThemeProvider } from "./theme-provider.js";
|
|
|
6
7
|
import { CuePortalFrame, useCuePortalProps } from "./portal.js";
|
|
7
8
|
import { useControlHeight, useDensity } from "./use-density.js";
|
|
8
9
|
import { useTheme } from "./use-theme.js";
|
|
10
|
+
import { useFrameClock } from "./frame-clock.js";
|
|
11
|
+
import { FontScope } from "./font-scope.js";
|
|
9
12
|
import { DEFAULT_PAINT, DEFAULT_STYLE_SET, PAINTS, STYLE_SETS } from "@cueplusplus/tokens";
|
|
10
|
-
export { CuePortalFrame, DEFAULT_PAINT, DEFAULT_STORAGE_KEY, DEFAULT_STYLE_SET, Density, PAINTS, STYLE_SETS, ThemeProvider, isSafeTokenValue, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
|
|
13
|
+
export { CuePortalFrame, DEFAULT_PAINT, DEFAULT_STORAGE_KEY, DEFAULT_STYLE_SET, Density, FontScope, PAINTS, STYLE_SETS, StillProvider, ThemeProvider, isSafeTokenValue, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useFrameClock, useStill, useTheme, useThemes };
|