@cueplusplus/ui 0.7.0 → 0.9.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 +272 -0
- package/README.md +49 -0
- package/dist/chat/message-list.js +2 -1
- package/dist/configurator/_export.d.ts +1 -1
- package/dist/configurator/_export.js +53 -13
- package/dist/configurator/_overrides.d.ts +25 -6
- package/dist/configurator/_overrides.js +30 -17
- package/dist/configurator/configurator.js +8 -3
- package/dist/configurator/panel-sections.js +43 -13
- package/dist/elements/command-palette.js +1 -1
- package/dist/elements/flow-graph.js +2 -2
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/surfaces.js +4 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -1
- package/dist/instruments/_ledger-disclosure.js +103 -0
- package/dist/instruments/_ledger.d.ts +21 -0
- package/dist/instruments/_ledger.js +5 -0
- package/dist/instruments/ledger.d.ts +112 -13
- package/dist/instruments/ledger.js +128 -38
- package/dist/midi/piano-keyboard.js +5 -1
- package/dist/primitives/chip.d.ts +1 -1
- package/dist/styles.css +23 -1
- package/dist/system/density.d.ts +17 -8
- package/dist/system/density.js +39 -16
- package/dist/system/index.d.ts +5 -2
- package/dist/system/index.js +2 -1
- package/dist/system/overrides.d.ts +43 -0
- package/dist/system/overrides.js +238 -0
- package/dist/system/portal.d.ts +4 -2
- package/dist/system/portal.js +34 -3
- package/dist/system/prepaint.d.ts +58 -7
- package/dist/system/prepaint.js +72 -20
- package/dist/system/theme-provider.d.ts +133 -8
- package/dist/system/theme-provider.js +203 -72
- package/dist/system/theme-registry.d.ts +53 -0
- package/dist/system/theme-registry.js +66 -0
- package/dist/system/use-density.d.ts +13 -5
- package/dist/system/use-density.js +142 -13
- package/dist/system/use-theme.d.ts +5 -3
- package/dist/system/use-theme.js +5 -3
- package/dist/system/vocabulary.d.ts +15 -0
- package/dist/system/vocabulary.js +111 -0
- package/dist/theming/contrast.d.ts +2 -122
- package/dist/theming/contrast.js +2 -194
- package/dist/theming/create-theme.d.ts +37 -11
- package/dist/theming/create-theme.js +54 -17
- package/dist/theming/index.d.ts +3 -4
- package/dist/theming/index.js +3 -4
- package/dist/theming/serialize.d.ts +24 -11
- package/dist/theming/serialize.js +16 -18
- package/manifest/components/colors-section.json +2 -3
- package/manifest/components/cue-portal-frame.json +1 -1
- package/manifest/components/data-tree.json +1 -0
- package/manifest/components/density.json +1 -1
- package/manifest/components/export-dialog.json +0 -3
- package/manifest/components/ledger.json +82 -7
- package/manifest/components/preset-section.json +2 -3
- package/manifest/components/shape-section.json +2 -3
- package/manifest/components/theme-configurator.json +0 -3
- package/manifest/components/theme-provider.json +24 -9
- package/manifest/components/token-editor.json +0 -3
- package/manifest/manifest.json +148 -27
- package/manifest/tokens.json +121 -11
- package/package.json +15 -6
- package/dist/theming/_presets.d.ts +0 -11
- package/dist/theming/_presets.js +0 -678
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,277 @@
|
|
|
1
1
|
# @cueplusplus/ui
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5eb0da2: The ten colour presets leave `@cueplusplus/tokens` and become packages of their
|
|
8
|
+
own. `@cueplusplus/ui/styles.css` now ships the blank base instead of a palette,
|
|
9
|
+
so a theme is something you install, import and register — the same way a
|
|
10
|
+
third-party theme always should have been, and now is.
|
|
11
|
+
|
|
12
|
+
`@cueplusplus/tokens` keeps the two axes it actually owns: the five density
|
|
13
|
+
rungs and the eight font pairings, plus the contracts, `base.json` and the
|
|
14
|
+
primitives a theme source aliases. It emits `axes.css` and no colour at all.
|
|
15
|
+
Each preset is `@cueplusplus/theme-<name>` — `cue`, `dusk`, `hivehub`, `luma`,
|
|
16
|
+
`quotamate`, `requestport`, `signal`, `snuffle`, `terminal`, `venu` — carrying
|
|
17
|
+
the stylesheet it always carried, byte for byte: nothing about any shipped
|
|
18
|
+
palette moved, and a frozen snapshot of what `tokens@0.8.0` emitted is committed
|
|
19
|
+
so that stays checkable rather than merely asserted.
|
|
20
|
+
|
|
21
|
+
## Migrating
|
|
22
|
+
|
|
23
|
+
**The unattributed default stops being `cue`.** An app that imports
|
|
24
|
+
`@cueplusplus/ui/styles.css` and registers nothing now paints the blank base.
|
|
25
|
+
Nothing errors and nothing falls back: a `data-theme` value is styled by
|
|
26
|
+
whichever stylesheet declares it, and with no theme package imported, none does.
|
|
27
|
+
|
|
28
|
+
Ten custom properties move in dark and seven in light. Measured on a root
|
|
29
|
+
stamped `data-theme="cue"`, which is what every `ThemeProvider` stamped before
|
|
30
|
+
this release and still stamps, so this is the delta for an app that changes
|
|
31
|
+
nothing:
|
|
32
|
+
|
|
33
|
+
| token | dark: `cue` → blank base | light: `cue` → blank base |
|
|
34
|
+
| ---------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------ |
|
|
35
|
+
| `--cue-accent` | `#ffffff` → `#8a93a6` | `#0a0a0a` → `#3f4759` |
|
|
36
|
+
| `--cue-accent-hover` | `rgba(255,255,255,0.85)` → `#9aa3b6` | `rgba(0,0,0,0.85)` → `#333a4a` |
|
|
37
|
+
| `--cue-accent-soft` | follows `--cue-accent`: it is a 14% `color-mix` of it | same |
|
|
38
|
+
| `--cue-focus` | `#ffffff` → `#8a93a6` | `#0a0a0a` → `#3f4759` |
|
|
39
|
+
| `--cue-font-theme-mono` | JetBrains Mono → nothing; the base declares no theme mono | same as dark |
|
|
40
|
+
| `--cue-font-mono` | resolves through the line above, so JetBrains Mono → the platform stack | same as dark |
|
|
41
|
+
| `--cue-fg` | `#f5f5f7` → `#f5f5f5` | unchanged |
|
|
42
|
+
| `--cue-hair`, `--cue-hair-strong`, `--cue-row-hover` | follow `--cue-fg`: each is a `color-mix` of it (6%, 12%, 3.5%) | unchanged |
|
|
43
|
+
| `--cue-accent-fg` | unchanged | `#fcfcfc` → `#ffffff` |
|
|
44
|
+
|
|
45
|
+
The last four rows are the ones an eye catches and a list of "the accent and the
|
|
46
|
+
focus ring" would not have mentioned: four of the ten dark changes and the one
|
|
47
|
+
light change are tokens no theme file names directly, because they are mixes of
|
|
48
|
+
two that do.
|
|
49
|
+
|
|
50
|
+
To get `cue` back, exactly as it was:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pnpm add @cueplusplus/theme-cue
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```css
|
|
57
|
+
@import "@cueplusplus/ui/styles.css";
|
|
58
|
+
@import "@cueplusplus/theme-cue/theme.css";
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import cue from "@cueplusplus/theme-cue";
|
|
63
|
+
|
|
64
|
+
<ThemeProvider themes={[cue]} theme="cue">
|
|
65
|
+
{children}
|
|
66
|
+
</ThemeProvider>;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The import order is load-bearing: a `[data-theme]` block and the base's bare
|
|
70
|
+
`:root` tie at (0,1,0), so the one declared later wins. Importing the theme
|
|
71
|
+
before `styles.css` paints the blank base over it. Importing the package from
|
|
72
|
+
its root, rather than the `manifest.json` subpath, also closes `ThemeName` to
|
|
73
|
+
the themes you installed, so a misspelt `theme=` is a type error.
|
|
74
|
+
|
|
75
|
+
Then the rest:
|
|
76
|
+
|
|
77
|
+
- **`@cueplusplus/tokens/theme.css` is a deprecated alias** of `axes.css` for
|
|
78
|
+
one minor. It still resolves and still delivers the geometry and type axes, so
|
|
79
|
+
a stylesheet naming it keeps building — it simply carries no palette. Change
|
|
80
|
+
the line to `@cueplusplus/tokens/axes.css`, or drop it: `ui/styles.css` already
|
|
81
|
+
brings the axes in through `@cueplusplus/theme-base/base.css`.
|
|
82
|
+
- **Gone from `@cueplusplus/tokens`:** the `themes/*.css` and
|
|
83
|
+
`registry/theme-*.json` subpaths, and the `THEMES`, `ThemeName`,
|
|
84
|
+
`THEME_SUPPORTS_LIGHT` and `DEFAULT_THEME` exports. `ThemeName` now comes from
|
|
85
|
+
`@cueplusplus/theme-base` (re-exported by `@cueplusplus/ui`) and widens with
|
|
86
|
+
each theme package installed, rather than naming the ten that happened to be
|
|
87
|
+
compiled in; whether a theme ships a light block is `supportsLight` on its own
|
|
88
|
+
`manifest.json`; and there is no default palette to name, because the library
|
|
89
|
+
contains none.
|
|
90
|
+
- **`@cueplusplus/tokens`' root entry is now `dist/tokens.js`**, with
|
|
91
|
+
`dist/tokens.d.ts` beside it, where it used to be the TypeScript source
|
|
92
|
+
`dist/tokens.ts`. The import specifier, the exported names and their values
|
|
93
|
+
are unchanged — `import { DENSITIES } from "@cueplusplus/tokens"` is the same
|
|
94
|
+
line it was — but the file behind it is one Node can execute. It could not
|
|
95
|
+
before: Node refuses to strip types for anything under `node_modules`, so the
|
|
96
|
+
package worked in a workspace and failed from a registry install, taking
|
|
97
|
+
`@cueplusplus/theme-base` and the `cue-theme` CLI with it. If you added
|
|
98
|
+
`@cueplusplus/tokens` to your bundler's transpile list to work around that
|
|
99
|
+
(Next.js' `transpilePackages`, or the equivalent), take it out.
|
|
100
|
+
- **`@cueplusplus/tokens` gains `base.json`'s `fonts.handshake`**: the three
|
|
101
|
+
literals of the monospace resolution (`font-pairing-mono`, `font-theme-mono`,
|
|
102
|
+
and the `var()` that resolves them), published as data so a theme built
|
|
103
|
+
anywhere spells them the same way this build does.
|
|
104
|
+
- **`@cueplusplus/ui`'s `THEME_NAME_PATTERN` and `RESOLVED_MONO` are now
|
|
105
|
+
re-exports** of `@cueplusplus/theme-base`'s. Same names, same values, one
|
|
106
|
+
definition instead of three.
|
|
107
|
+
- **`/r/tokens.json`'s `themes` are objects**, not strings:
|
|
108
|
+
`{ name, package, supportsLight, densities }`. A consumer reading the registry
|
|
109
|
+
for a theme list wants to know which package delivers it.
|
|
110
|
+
- **Every published tarball now carries a `README.md`.** On a registry the
|
|
111
|
+
package page _is_ the README, and `tokens`, `ui` and `brand-tokens` shipped
|
|
112
|
+
without one; `packages/release/test/publishable-packages.test.mjs` holds all
|
|
113
|
+
fifteen to it now, beside the same check for `CHANGELOG.md`.
|
|
114
|
+
- **`@cueplusplus/ui` depends on `@cueplusplus/theme-base@^1`**, a range rather
|
|
115
|
+
than a pin, deliberately: two copies of `theme-base` in one tree would break
|
|
116
|
+
the type registry, because a theme's `declare module` augments one copy while
|
|
117
|
+
`ui`'s re-exported `ThemeName` resolves against the other, and neither errors.
|
|
118
|
+
|
|
119
|
+
- 1b98830: `ThemeProvider` takes `themes`, an ordered array of theme manifests, and the runtime reads
|
|
120
|
+
everything about a theme from its manifest instead of from a copy inside this package. Types open:
|
|
121
|
+
`ThemeName`, `DensityLevel` (`Density` on `@cueplusplus/theme-base`) and `FontName` are now `@cueplusplus/theme-base`'s, which a theme package
|
|
122
|
+
widens by declaration merging — `"foo"` is a valid theme in an app that installed
|
|
123
|
+
`@cueplusplus/theme-foo`, and `ThemeName` is `string` in one that installed none. Three hooks —
|
|
124
|
+
`useThemes()`, `useDensities()`, `useFonts()` — return what the active theme offers. Every place
|
|
125
|
+
that validated a persisted name against a frozen list validates against the registry. A typed
|
|
126
|
+
`overrides` prop layers colours, fonts and densities over the active theme, with a development
|
|
127
|
+
contrast report.
|
|
128
|
+
|
|
129
|
+
## Migrating
|
|
130
|
+
- **`themes` is optional for this release.** An app that passes none gets the blank base and the
|
|
131
|
+
base axes, with one development warning. Pass `themes={[cue]}` from `@cueplusplus/theme-cue`
|
|
132
|
+
when it publishes. With nothing registered there is no theme vocabulary to be out of, so
|
|
133
|
+
`ThemeProvider` and `prepaintScript` no longer reject a persisted or default theme name they do
|
|
134
|
+
not recognise: a well-formed name is stamped as-is. Pass `themes` to close the vocabulary again.
|
|
135
|
+
- **`createTheme({ base })` takes a manifest, not a name.** `createTheme({ name, base: "terminal" })`
|
|
136
|
+
becomes `createTheme({ name, base: terminal })` with `import terminal from "@cueplusplus/theme-terminal"`.
|
|
137
|
+
With no `base` it starts from the blank base. `DEFAULT_THEME_BASE` is removed.
|
|
138
|
+
- **`createTheme`'s radius rules now emit both selector forms** — `[data-theme="x"] [data-density="y"]`
|
|
139
|
+
beside `[data-theme="x"][data-density="y"]` — so a `<Density>` island and `useControlHeight`'s probe
|
|
140
|
+
get the crossed value too. A byte-comparison against the old output will differ on those lines.
|
|
141
|
+
- **`ThemeScope` from `@cueplusplus/ui/configurator` gains `manifest: ThemeManifest | null`.** Every
|
|
142
|
+
function that takes a scope needs it — `baseValue`, `resolveValue`, `resolvedTokens`, `setOverride`,
|
|
143
|
+
`clearOverride`, `isOverridden`, `scopeKey`, `exportName`, `buildExports({ scope })` and
|
|
144
|
+
`<ExportDialog scope>`. Pass the active manifest (`useTheme().manifest`) or `null` for the blank
|
|
145
|
+
base. The panel's `create-theme` export now emits an import of the base theme's manifest instead
|
|
146
|
+
of `base: "<name>"`.
|
|
147
|
+
- **`overrides` is typed `TokenOverrides`**, exported from `@cueplusplus/ui` and `@cueplusplus/ui/system`.
|
|
148
|
+
It is unrelated to `ThemeOverrides` on `@cueplusplus/ui/configurator`, which is still the
|
|
149
|
+
configurator's edit snapshot.
|
|
150
|
+
- `THEME_COLOR_TOKENS` is now exactly `COLOR_CONTRACT` from `@cueplusplus/tokens`: the same 26 names
|
|
151
|
+
in the same order.
|
|
152
|
+
- **`ThemeProvider` opens on the active theme's `densities.default` and `fontPairings.default`** when
|
|
153
|
+
you pass no `density` or `font` — the rung and pairing a theme declares it prefers. Pass one to
|
|
154
|
+
override it for every theme. No first-party theme declares either, so nothing shipped moves.
|
|
155
|
+
`prepaintScript` opens on the same tier: `defaults.density` and `defaults.font` are both optional
|
|
156
|
+
in the options form, and an absent one falls to that theme's declared preference rather than to the
|
|
157
|
+
library's base rung, so the blocking script stamps exactly what the provider is about to commit
|
|
158
|
+
with no work on your side. Passing `defaults.density` or `defaults.font` is the same statement as
|
|
159
|
+
passing the prop — do both, or neither. One consequence for the positional form: its fourth
|
|
160
|
+
argument is now validated like the other three, so `prepaintScript(key, theme, density, font)` with
|
|
161
|
+
a pairing outside the base eight throws the same `defaults.font … is not offered by theme …` the
|
|
162
|
+
options form gives, where before the script silently fell back to `system`.
|
|
163
|
+
- **Three types 16 public signatures name are now exported** from `@cueplusplus/ui` and
|
|
164
|
+
`@cueplusplus/ui/system`: `ThemeManifest`, `DensityEntry` and `FontEntry`. They appear in
|
|
165
|
+
`ThemeProviderProps.themes`, `PrepaintOptions.themes`, `ThemeContextValue.manifest`,
|
|
166
|
+
`ThemeRegistry`, `useThemes()`, `useDensities()`, `useFonts()` and `ThemeScope.manifest`, so
|
|
167
|
+
`@cueplusplus/ui` re-exports them: an app that names one of those types in a signature of its own
|
|
168
|
+
need not reach past the package it installed.
|
|
169
|
+
- **`ThemeRegistry` is exported** from `@cueplusplus/ui/system` — the shape the three registry hooks
|
|
170
|
+
read: the registered manifests, the first one's name, and the rungs and pairings the _active_ theme
|
|
171
|
+
offers.
|
|
172
|
+
- **`RESOLVED_MONO` is exported** from `@cueplusplus/ui/theming`: the `var()` chain every theme block
|
|
173
|
+
sets `--cue-font-mono` to, pairing first. A tool that writes theme CSS beside `serializeThemeCss`
|
|
174
|
+
needs the same string.
|
|
175
|
+
- **`themeSelector`'s options gain `pair?: boolean`**, which emits the descendant form
|
|
176
|
+
`[data-theme="x"] [data-density="y"]` beside the compound one, as more than one selector when
|
|
177
|
+
`density` is set. Neither a `<Density>` island nor `useControlHeight`'s `document.body` probe
|
|
178
|
+
carries `data-theme` beside its `data-density`, so this is what reaches them. This function, and
|
|
179
|
+
`@cueplusplus/theme-tools`' `cue-theme build`, now emit three forms per rung instead of two — an
|
|
180
|
+
exact match, a structural fallback, and the unchanged compound — so that a rung a theme adds or
|
|
181
|
+
retunes no longer applies inside a nested provider of another theme: not through a `<Density>`
|
|
182
|
+
island, not through the nested provider's own root opening on a rung the outer theme happens to
|
|
183
|
+
retune, and not even when that outer theme is _re-entered_ after a detour through a different one
|
|
184
|
+
several levels in, which an ancestor-only structural check cannot always tell apart from the
|
|
185
|
+
outer detour alone. **`<Density>` and the internal measuring probe now also render
|
|
186
|
+
`data-cue-theme`**, stamped from the nearest `<ThemeProvider>` (or omitted where there is none) —
|
|
187
|
+
an inert attribute nothing in this library reads back except the exact-match selector above, which
|
|
188
|
+
needs it to resolve "nearest theme" the way `useContext` already does, rather than the "does some
|
|
189
|
+
ancestor carry this theme" a pure CSS selector is limited to. All three forms are wrapped in
|
|
190
|
+
`:where()`, which contributes no specificity, so together they tie with the `overrides` prop's own
|
|
191
|
+
document-scoped density rule at (0,2,0) — as they always did — rather than outranking it.
|
|
192
|
+
|
|
193
|
+
- e587341: `contrastReport()` now measures the focus ring.
|
|
194
|
+
|
|
195
|
+
`CONTRAST_REQUIREMENTS` gains a second tier: nine **advisory** pairs holding
|
|
196
|
+
`--cue-focus` and `--cue-danger` — the two roles a form control paints as a
|
|
197
|
+
ring, and therefore WCAG 1.4.11 non-text contrast surfaces — to 3:1 against
|
|
198
|
+
every ground one can be painted over. `ContrastRequirement` carries
|
|
199
|
+
`advisory?: boolean`, `ContrastReport` carries `advisories` beside `failures`,
|
|
200
|
+
and `passes` still counts only the eleven required pairs. No theme that was
|
|
201
|
+
clean yesterday fails today; every theme now gets told about its ring.
|
|
202
|
+
|
|
203
|
+
The gap was known and the gate for it lived in a test file, which protected the
|
|
204
|
+
ten shipped presets and nobody who calls `createTheme()`. The dark block takes
|
|
205
|
+
the accent into `--cue-focus` verbatim, so
|
|
206
|
+
`createTheme({ name: "acme", base: snuffle, accent: "#3b3f8f" })` — the manifest
|
|
207
|
+
imported from `@cueplusplus/theme-snuffle`, and a brand colour straight into the
|
|
208
|
+
anchor, which is the documented use — came back
|
|
209
|
+
`passes: true` with twenty-two checks, none of which named `focus`, over a ring
|
|
210
|
+
measuring 2.01:1 down to 1.55:1 on the five grounds it can sit on. The same call
|
|
211
|
+
now returns those five pairs in `report.advisories`, each with its reason. It is
|
|
212
|
+
the failure 0.6.0 fixed in `luma`, `venu` and `hivehub` at 2.45:1, and a report
|
|
213
|
+
is the only place a consumer would ever meet it: an invisible focus ring is
|
|
214
|
+
invisible in code review and invisible in a screenshot taken with a mouse.
|
|
215
|
+
|
|
216
|
+
All ten shipped presets clear the new tier in both modes — two hundred ring
|
|
217
|
+
measurements, the tightest `terminal.dark --cue-danger` on `--cue-surface-3` at
|
|
218
|
+
3.13:1 — and the preset gate now runs through `contrastReport()` rather than
|
|
219
|
+
through its own copy of the same loop.
|
|
220
|
+
|
|
221
|
+
`danger/bg` is not restated in the ring tier: the status pairs have always held
|
|
222
|
+
the red against the page at this same floor, as a required pair, and one
|
|
223
|
+
measurement gets one id.
|
|
224
|
+
|
|
225
|
+
**If you iterate `CONTRAST_REQUIREMENTS` or render `report.checks`,** the array
|
|
226
|
+
goes from 11 entries to 20 and a two-block report from 22 checks to 40. Filter
|
|
227
|
+
on `advisory` to keep the old set.
|
|
228
|
+
|
|
229
|
+
### Patch Changes
|
|
230
|
+
|
|
231
|
+
- cf80a87: `FlowGraph` draws an edge faint until both of the nodes it joins have been
|
|
232
|
+
reached, which is what its `edges` prop has always said it does.
|
|
233
|
+
|
|
234
|
+
The behaviour was documented, published and unimplemented: the prop's
|
|
235
|
+
description — "An edge dims until both of its ends are visible" — ships in
|
|
236
|
+
`manifest/components/flow-graph.json` and in the type declarations, the
|
|
237
|
+
`transition-opacity duration-500` was already on the path, and the component
|
|
238
|
+
computed whether an edge was live and then never used the answer. Every edge
|
|
239
|
+
painted at full strength from the first frame, so a graph revealing itself
|
|
240
|
+
through `visibleCount` showed all of its connections before it had any of its
|
|
241
|
+
nodes.
|
|
242
|
+
|
|
243
|
+
Found by turning the lint gate on: the unused binding was the first thing it
|
|
244
|
+
reported.
|
|
245
|
+
|
|
246
|
+
- 2737aec: `@cueplusplus/tokens` exports the contracts and the axes as data, so a theme
|
|
247
|
+
built outside this repository can resolve over the same base: `COLOR_CONTRACT`,
|
|
248
|
+
`GEOMETRY_CONTRACT` and `FONT_TOKENS` as typed tuples, `base.json` (every
|
|
249
|
+
density's geometry, the pairings' stacks, the derived templates, the defaults),
|
|
250
|
+
`primitives.tokens.json` (the tier-1 file a theme source aliases), and
|
|
251
|
+
`axes.css` — `theme.css` with the theme axis removed. Nothing that exists today
|
|
252
|
+
moves; `theme.css` is unchanged.
|
|
253
|
+
|
|
254
|
+
`@cueplusplus/ui/theming` keeps every export it had. The contrast arithmetic now
|
|
255
|
+
lives in `@cueplusplus/theme-base` and is re-exported here; `ui` gains that
|
|
256
|
+
package as a dependency. No behaviour changes.
|
|
257
|
+
|
|
258
|
+
- Updated dependencies [cf80a87]
|
|
259
|
+
- Updated dependencies [5eb0da2]
|
|
260
|
+
- Updated dependencies [814c859]
|
|
261
|
+
- Updated dependencies [2737aec]
|
|
262
|
+
- @cueplusplus/tokens@0.9.0
|
|
263
|
+
- @cueplusplus/theme-base@1.0.0
|
|
264
|
+
|
|
265
|
+
## 0.8.0
|
|
266
|
+
|
|
267
|
+
### Minor Changes
|
|
268
|
+
|
|
269
|
+
- 4c7b963: `Ledger.Tier` and `Ledger.Group` take `collapsible`: the heading stays the `h2`/`h3` element it always was and gains a real `button[aria-expanded aria-controls]` inside it, which folds the run of rows below away. The heading outline is untouched, so a page that offers the same dataset folded and unfolded does not move a screen-reader user's heading list. Expansion is controlled or uncontrolled — `expanded`, `defaultExpanded`, `onExpandedChange` — and the library stores nothing: which places a reader folded is the caller's to keep. A folded run of rows is hidden rather than unmounted, so a group holding its own fold inside a tier still holds it after the tier above has been shut and opened again. A ledger nobody asks to fold renders exactly as before, with no client boundary.
|
|
270
|
+
|
|
271
|
+
### Patch Changes
|
|
272
|
+
|
|
273
|
+
- @cueplusplus/tokens@0.8.0
|
|
274
|
+
|
|
3
275
|
## 0.7.0
|
|
4
276
|
|
|
5
277
|
### Minor Changes
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @cueplusplus/ui
|
|
2
|
+
|
|
3
|
+
Components for console software: dense, keyboard-first interfaces where a screen is mostly data.
|
|
4
|
+
Base UI underneath for behaviour and accessibility, Tailwind v4 on top for the paint.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
pnpm add @cueplusplus/ui @cueplusplus/tokens @cueplusplus/theme-cue @base-ui/react
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```css
|
|
11
|
+
@import "tailwindcss";
|
|
12
|
+
@import "@cueplusplus/ui/styles.css";
|
|
13
|
+
@import "@cueplusplus/theme-cue/theme.css";
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import cue from "@cueplusplus/theme-cue";
|
|
18
|
+
import { ThemeProvider } from "@cueplusplus/ui/system";
|
|
19
|
+
|
|
20
|
+
<ThemeProvider themes={[cue]} theme="cue" density="compact" mode="system">
|
|
21
|
+
<App />
|
|
22
|
+
</ThemeProvider>;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The order of those three `@import`s is load-bearing, and the `themes` prop is not optional in
|
|
26
|
+
practice: this library contains no palette. Install and register a `@cueplusplus/theme-*` package,
|
|
27
|
+
or every surface paints `@cueplusplus/theme-base`'s blank base — greys, one desaturated accent,
|
|
28
|
+
the platform monospace — with nothing erroring to tell you.
|
|
29
|
+
|
|
30
|
+
Common components come from the root entry. Groups that need an optional peer have a subpath of
|
|
31
|
+
their own (`/charts`, `/date`, `/flow`, `/color`, `/agent-runtime`, and the rest), so importing a
|
|
32
|
+
button never pulls a charting library into your bundle.
|
|
33
|
+
|
|
34
|
+
## The machine-readable half
|
|
35
|
+
|
|
36
|
+
This package ships its own manifest: `@cueplusplus/ui/manifest.json` is an index of every
|
|
37
|
+
component with a SHA-256 for each companion document, and
|
|
38
|
+
`@cueplusplus/ui/manifest/components/<name>.json` is one component in full — props, variants, the
|
|
39
|
+
tokens it paints with, and the hand-written notes on when it is the wrong choice. An agent or a
|
|
40
|
+
code generator should read that rather than guess a prop name.
|
|
41
|
+
|
|
42
|
+
The same surfaces are published at <https://ui.cueplusplus.com>, with the docs site, the live
|
|
43
|
+
kitchen sink and the CUE++ agent skills beside them.
|
|
44
|
+
|
|
45
|
+
## Full documentation
|
|
46
|
+
|
|
47
|
+
- Installing and using it from another project: [`docs/CONSUMING.md`](https://github.com/cueplusplus/product-design-system/blob/main/docs/CONSUMING.md)
|
|
48
|
+
- Every component, with props and a live example: <https://ui.cueplusplus.com/docs/components>
|
|
49
|
+
- Theming, density and the `--cue-*` contract: <https://ui.cueplusplus.com/docs/theming>
|
|
@@ -42,6 +42,7 @@ const MessageList = React.forwardRef(function MessageList({ className, children,
|
|
|
42
42
|
const [behind, setBehind] = React.useState(false);
|
|
43
43
|
const lastHeightRef = React.useRef(0);
|
|
44
44
|
const count = React.Children.count(children);
|
|
45
|
+
const isEmpty = count === 0;
|
|
45
46
|
const previousCountRef = React.useRef(count);
|
|
46
47
|
const jumpToLatest = React.useCallback(() => {
|
|
47
48
|
const element = scrollerRef.current;
|
|
@@ -90,7 +91,7 @@ const MessageList = React.forwardRef(function MessageList({ className, children,
|
|
|
90
91
|
}, [
|
|
91
92
|
follow,
|
|
92
93
|
pinned,
|
|
93
|
-
|
|
94
|
+
isEmpty
|
|
94
95
|
]);
|
|
95
96
|
const onScroll = React.useCallback(() => {
|
|
96
97
|
const element = scrollerRef.current;
|
|
@@ -61,7 +61,7 @@ interface BuildExportsOptions {
|
|
|
61
61
|
* @param name - What the user typed, if anything.
|
|
62
62
|
* @returns A usable CSS identifier.
|
|
63
63
|
* @example
|
|
64
|
-
* exportName({ theme: "terminal", mode: "dark" }); // → "terminal-custom"
|
|
64
|
+
* exportName({ theme: "terminal", mode: "dark", manifest: terminal }); // → "terminal-custom"
|
|
65
65
|
*/
|
|
66
66
|
declare function exportName(scope: ThemeScope, name?: string): string;
|
|
67
67
|
/**
|
|
@@ -43,7 +43,7 @@ const DTCG_ALIASES = { density: "density-factor" };
|
|
|
43
43
|
* @param name - What the user typed, if anything.
|
|
44
44
|
* @returns A usable CSS identifier.
|
|
45
45
|
* @example
|
|
46
|
-
* exportName({ theme: "terminal", mode: "dark" }); // → "terminal-custom"
|
|
46
|
+
* exportName({ theme: "terminal", mode: "dark", manifest: terminal }); // → "terminal-custom"
|
|
47
47
|
*/
|
|
48
48
|
function exportName(scope, name) {
|
|
49
49
|
const proposed = name?.trim() ?? "";
|
|
@@ -51,13 +51,22 @@ function exportName(scope, name) {
|
|
|
51
51
|
}
|
|
52
52
|
/** A JSON string, printed the way a hand-written source file would print it. */
|
|
53
53
|
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Both modes of a scope, so an export can carry the palette the user is not
|
|
56
|
+
* looking at.
|
|
57
|
+
*
|
|
58
|
+
* Spread rather than rebuilt: a fresh literal would have to restate `manifest`,
|
|
59
|
+
* and the shape of that mistake is a `null` that resolves the off-mode palette
|
|
60
|
+
* to the blank base — so the `dtcg` and `registry` exports would carry the right
|
|
61
|
+
* colours for the mode on screen and the neutral ramp for the other one, with
|
|
62
|
+
* nothing to see until somebody installed the theme.
|
|
63
|
+
*/
|
|
55
64
|
function bothModes(scope) {
|
|
56
65
|
return [{
|
|
57
|
-
|
|
66
|
+
...scope,
|
|
58
67
|
mode: "dark"
|
|
59
68
|
}, {
|
|
60
|
-
|
|
69
|
+
...scope,
|
|
61
70
|
mode: "light"
|
|
62
71
|
}];
|
|
63
72
|
}
|
|
@@ -71,8 +80,11 @@ function movedTokens(overrides, scope, kind) {
|
|
|
71
80
|
* ② The DTCG token file.
|
|
72
81
|
*
|
|
73
82
|
* A whole theme rather than a diff: the point of this format is that the result
|
|
74
|
-
* can be dropped into `
|
|
75
|
-
*
|
|
83
|
+
* can be dropped into a theme package's `src/` — `cue-theme init` scaffolds one,
|
|
84
|
+
* and `packages/theme-<name>/src/<name>.dark.tokens.json` is the shape — or into
|
|
85
|
+
* anyone else's DTCG pipeline, and built, which a patch file cannot. (It used to
|
|
86
|
+
* say `packages/tokens/src/themes/`; that directory is gone, and a palette is a
|
|
87
|
+
* package now.) Groups follow the sources —
|
|
76
88
|
* `color` for the palette, `type` for the stacks, `density` for the multipliers
|
|
77
89
|
* — and appear only when they carry something.
|
|
78
90
|
*/
|
|
@@ -121,9 +133,25 @@ function dtcgFile(overrides, scope, name) {
|
|
|
121
133
|
function quote(value) {
|
|
122
134
|
return value.includes("\"") && !value.includes("'") && !value.includes("\\") ? `'${value}'` : JSON.stringify(value);
|
|
123
135
|
}
|
|
136
|
+
/** What a JavaScript binding may be called, and what a printed key may go unquoted as. */
|
|
137
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
124
138
|
/** One `key: "value"` line of an object literal, quoting the key only when it must. */
|
|
125
139
|
function property(key, value, indent) {
|
|
126
|
-
return `${indent}${
|
|
140
|
+
return `${indent}${IDENTIFIER.test(key) ? key : JSON.stringify(key)}: ${quote(value)},`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* What to call the imported base manifest in the snippet.
|
|
144
|
+
*
|
|
145
|
+
* The theme's own name where that is a legal binding — `terminal` reads as what
|
|
146
|
+
* it is — and `base` where it is not (a theme may be called `acme-dark`, which
|
|
147
|
+
* is a fine `data-theme` value and not an identifier). The suffix exists for the
|
|
148
|
+
* one collision this can produce: a user who names their export exactly what the
|
|
149
|
+
* base theme is called would otherwise get `const terminal = createTheme({ base:
|
|
150
|
+
* terminal })`, which is a snippet that does not run.
|
|
151
|
+
*/
|
|
152
|
+
function baseIdentifier(theme, exported) {
|
|
153
|
+
const proposed = IDENTIFIER.test(theme) ? theme : "base";
|
|
154
|
+
return proposed === exported ? `${proposed}Base` : proposed;
|
|
127
155
|
}
|
|
128
156
|
/**
|
|
129
157
|
* ③ The `createTheme()` call.
|
|
@@ -134,7 +162,17 @@ function property(key, value, indent) {
|
|
|
134
162
|
* a snippet that quietly loses half an edit is worse than no snippet.
|
|
135
163
|
*/
|
|
136
164
|
function createThemeSnippet(overrides, scope, name) {
|
|
137
|
-
const
|
|
165
|
+
const exported = IDENTIFIER.test(name) ? name : "theme";
|
|
166
|
+
const base = scope.manifest;
|
|
167
|
+
let baseImport = null;
|
|
168
|
+
let baseAnchor = null;
|
|
169
|
+
if (base !== null) {
|
|
170
|
+
const ident = baseIdentifier(scope.theme, exported);
|
|
171
|
+
baseImport = `import ${ident} from ${JSON.stringify(base.package)};`;
|
|
172
|
+
baseAnchor = ` base: ${ident},`;
|
|
173
|
+
}
|
|
174
|
+
const lines = [` name: ${JSON.stringify(name)},`];
|
|
175
|
+
if (baseAnchor !== null) lines.push(baseAnchor);
|
|
138
176
|
const unrepresentable = [];
|
|
139
177
|
for (const [anchor, token] of DIRECT_ANCHORS) if (isOverridden(overrides, scope, token)) lines.push(property(anchor, resolveValue(overrides, scope, token), " "));
|
|
140
178
|
const surfaces = SURFACE_ANCHORS.filter((token) => isOverridden(overrides, scope, token));
|
|
@@ -167,17 +205,19 @@ function createThemeSnippet(overrides, scope, name) {
|
|
|
167
205
|
if (anchored.has(moved)) continue;
|
|
168
206
|
unrepresentable.push(`${moved}: ${resolveValue(overrides, scope, moved)}`);
|
|
169
207
|
}
|
|
170
|
-
const
|
|
208
|
+
const notes = [];
|
|
209
|
+
if (base === null) notes.push(`// no manifest is registered for "${scope.theme}"; this call starts from the blank base.`);
|
|
210
|
+
if (unrepresentable.length > 0) notes.push("// createTheme() derives these rather than taking them as anchors, so they are not", "// in the call above. Ship the CSS export alongside it — that one is lossless.", ...unrepresentable.map((entry) => `// ${entry}`));
|
|
211
|
+
const footer = notes.length === 0 ? "" : [
|
|
171
212
|
"",
|
|
172
|
-
|
|
173
|
-
"// in the call above. Ship the CSS export alongside it — that one is lossless.",
|
|
174
|
-
...unrepresentable.map((entry) => `// ${entry}`),
|
|
213
|
+
...notes,
|
|
175
214
|
""
|
|
176
215
|
].join("\n");
|
|
177
216
|
return [
|
|
178
217
|
"import { createTheme } from \"@cueplusplus/ui/theming\";",
|
|
218
|
+
...baseImport === null ? [] : [baseImport],
|
|
179
219
|
"",
|
|
180
|
-
`export const ${
|
|
220
|
+
`export const ${exported} = createTheme({`,
|
|
181
221
|
...lines,
|
|
182
222
|
"});",
|
|
183
223
|
footer
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ResolvedMode } from "../system/theme-provider.js";
|
|
2
|
-
import { Density
|
|
2
|
+
import { Density } from "@cueplusplus/tokens";
|
|
3
|
+
import { ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
|
|
3
4
|
//#region src/configurator/_overrides.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* The override model behind the theme configurator: what a user may move, where
|
|
@@ -68,6 +69,18 @@ interface ThemeScope {
|
|
|
68
69
|
theme: ThemeName;
|
|
69
70
|
/** The mode as resolved — `"system"` never reaches here. */
|
|
70
71
|
mode: ResolvedMode;
|
|
72
|
+
/**
|
|
73
|
+
* The active theme's manifest, or `null` for the blank base.
|
|
74
|
+
*
|
|
75
|
+
* Required, not optional, and that is the point. {@link baseValue} branches on
|
|
76
|
+
* `manifest === null` to mean *the blank base*; an absent field would take
|
|
77
|
+
* that branch by accident, and normalising `undefined` to `null` would make
|
|
78
|
+
* every caller that has not been updated resolve blank-base colours under a
|
|
79
|
+
* name that is not the blank base — silently, and only visibly wrong to
|
|
80
|
+
* somebody comparing swatches. A compile error naming the property is the
|
|
81
|
+
* right way for a stale caller to find out. Pass `useTheme().manifest`.
|
|
82
|
+
*/
|
|
83
|
+
manifest: ThemeManifest | null;
|
|
71
84
|
}
|
|
72
85
|
/** Every override the user has made, ready to serialize. Treat as immutable. */
|
|
73
86
|
interface ThemeOverrides {
|
|
@@ -117,15 +130,21 @@ declare function isSafeCssValue(value: string): boolean;
|
|
|
117
130
|
/**
|
|
118
131
|
* The value a token has before the user touches anything.
|
|
119
132
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
133
|
+
* Everything a theme actually declares comes from `theme-base`'s `resolve()`,
|
|
134
|
+
* which is the one implementation of the layering rule — the theme's block for
|
|
135
|
+
* this mode, the theme's own font override under the base stack — so the panel
|
|
136
|
+
* and the runtime cannot disagree about what "shipped" means. A `null` manifest
|
|
137
|
+
* is the blank base, which resolves to the neutral ramp rather than to nothing:
|
|
138
|
+
* every scope answers, including one naming a theme no manifest was passed for.
|
|
139
|
+
* The multipliers are 1 by definition, because what the panel edits is the
|
|
140
|
+
* multiplier and not the ladder underneath it.
|
|
123
141
|
*
|
|
124
|
-
* @param scope - Theme and
|
|
142
|
+
* @param scope - Theme, mode and manifest being edited.
|
|
125
143
|
* @param token - A bare token name or a `--cue-*` property.
|
|
126
144
|
* @returns The shipped value, as a CSS string.
|
|
127
145
|
* @example
|
|
128
|
-
* baseValue({ theme: "terminal", mode: "dark" }, "accent");
|
|
146
|
+
* baseValue({ theme: "terminal", mode: "dark", manifest: terminal }, "accent");
|
|
147
|
+
* // → "hsl(180 100% 50%)"
|
|
129
148
|
*/
|
|
130
149
|
declare function baseValue(scope: ThemeScope, token: string): string;
|
|
131
150
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { THEME_NAME_PATTERN as THEME_NAME_PATTERN$1, assertCssValue, serializeSystemModeAliases, serializeThemeBlock, tokenProperty } from "../theming/serialize.js";
|
|
2
|
+
import { DEFAULT_DENSITY_LEVEL, DENSITY_RADIUS_SCALE, THEME_COLOR_TOKENS } from "../theming/create-theme.js";
|
|
3
|
+
import { resolve } from "@cueplusplus/theme-base";
|
|
3
4
|
//#region src/configurator/_overrides.ts
|
|
4
5
|
/**
|
|
5
6
|
* The override model behind the theme configurator: what a user may move, where
|
|
@@ -55,11 +56,10 @@ const OVERRIDE_BANNER = "@cueplusplus/ui — theme configurator overrides";
|
|
|
55
56
|
/**
|
|
56
57
|
* The unitless density multiplier each level publishes.
|
|
57
58
|
*
|
|
58
|
-
* A copy of `packages/tokens/src/density/*.tokens.json
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* the first number that has drifted.
|
|
59
|
+
* A copy of `packages/tokens/src/density/*.tokens.json`: the tokens package
|
|
60
|
+
* emits CSS, and a value this module has to *multiply* cannot be read out of a
|
|
61
|
+
* stylesheet. `test/configurator/overrides.test.ts` re-reads the DTCG sources
|
|
62
|
+
* and fails on the first number that has drifted.
|
|
63
63
|
*/
|
|
64
64
|
const DENSITY_FACTOR = {
|
|
65
65
|
"ultra-compact": .85,
|
|
@@ -120,13 +120,21 @@ function tokenKind(token) {
|
|
|
120
120
|
function scopeKey(scope) {
|
|
121
121
|
return `${scope.theme}/${scope.mode}`;
|
|
122
122
|
}
|
|
123
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* The inverse of {@link scopeKey}, rejecting anything that would not be safe in
|
|
125
|
+
* a selector.
|
|
126
|
+
*
|
|
127
|
+
* A bare `{ theme, mode }` rather than a {@link ThemeScope}: a storage key
|
|
128
|
+
* carries a *name*, and this module has no registry to look a manifest up in.
|
|
129
|
+
* Both callers below want only the selector halves. A caller that needs the
|
|
130
|
+
* manifest attaches it from the registry it holds.
|
|
131
|
+
*/
|
|
124
132
|
function parseScopeKey(key) {
|
|
125
133
|
const slash = key.lastIndexOf("/");
|
|
126
134
|
if (slash === -1) return null;
|
|
127
135
|
const theme = key.slice(0, slash);
|
|
128
136
|
const mode = key.slice(slash + 1);
|
|
129
|
-
if (!THEME_NAME_PATTERN.test(theme)) return null;
|
|
137
|
+
if (!THEME_NAME_PATTERN$1.test(theme)) return null;
|
|
130
138
|
if (mode !== "dark" && mode !== "light") return null;
|
|
131
139
|
return {
|
|
132
140
|
theme,
|
|
@@ -157,23 +165,28 @@ function isSafeCssValue(value) {
|
|
|
157
165
|
/**
|
|
158
166
|
* The value a token has before the user touches anything.
|
|
159
167
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
168
|
+
* Everything a theme actually declares comes from `theme-base`'s `resolve()`,
|
|
169
|
+
* which is the one implementation of the layering rule — the theme's block for
|
|
170
|
+
* this mode, the theme's own font override under the base stack — so the panel
|
|
171
|
+
* and the runtime cannot disagree about what "shipped" means. A `null` manifest
|
|
172
|
+
* is the blank base, which resolves to the neutral ramp rather than to nothing:
|
|
173
|
+
* every scope answers, including one naming a theme no manifest was passed for.
|
|
174
|
+
* The multipliers are 1 by definition, because what the panel edits is the
|
|
175
|
+
* multiplier and not the ladder underneath it.
|
|
163
176
|
*
|
|
164
|
-
* @param scope - Theme and
|
|
177
|
+
* @param scope - Theme, mode and manifest being edited.
|
|
165
178
|
* @param token - A bare token name or a `--cue-*` property.
|
|
166
179
|
* @returns The shipped value, as a CSS string.
|
|
167
180
|
* @example
|
|
168
|
-
* baseValue({ theme: "terminal", mode: "dark" }, "accent");
|
|
181
|
+
* baseValue({ theme: "terminal", mode: "dark", manifest: terminal }, "accent");
|
|
182
|
+
* // → "hsl(180 100% 50%)"
|
|
169
183
|
*/
|
|
170
184
|
function baseValue(scope, token) {
|
|
171
185
|
const property = tokenProperty(token);
|
|
172
186
|
const name = property.slice(6);
|
|
173
|
-
const preset = THEME_PRESETS[scope.theme];
|
|
174
187
|
switch (tokenKind(property)) {
|
|
175
|
-
case "color": return (
|
|
176
|
-
case "font": return
|
|
188
|
+
case "color": return resolve(scope.manifest, { mode: scope.mode }).colors[name] ?? "";
|
|
189
|
+
case "font": return resolve(scope.manifest, { mode: scope.mode }).fonts[name] ?? "";
|
|
177
190
|
case "scaled":
|
|
178
191
|
case "inline": return "1";
|
|
179
192
|
case "flat": return BASE_TRACKING_LABEL;
|
|
@@ -56,11 +56,16 @@ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
|
56
56
|
* <ThemeConfigurator position="docked-right" onChange={(tokens) => report(tokens)} />
|
|
57
57
|
*/
|
|
58
58
|
function ThemeConfigurator({ position: positionProp, defaultPosition = "floating", onPositionChange, defaultCollapsed = false, storageKey = DEFAULT_CONFIGURATOR_STORAGE_KEY, onChange, zoomShortcuts = true, title = "Theme", className }) {
|
|
59
|
-
const { theme, resolvedMode } = useTheme();
|
|
59
|
+
const { theme, resolvedMode, manifest } = useTheme();
|
|
60
60
|
const scope = React.useMemo(() => ({
|
|
61
61
|
theme,
|
|
62
|
-
mode: resolvedMode
|
|
63
|
-
|
|
62
|
+
mode: resolvedMode,
|
|
63
|
+
manifest
|
|
64
|
+
}), [
|
|
65
|
+
theme,
|
|
66
|
+
resolvedMode,
|
|
67
|
+
manifest
|
|
68
|
+
]);
|
|
64
69
|
const [snapshot, setSnapshot] = React.useState(() => {
|
|
65
70
|
const initial = defaultSnapshot();
|
|
66
71
|
return {
|