@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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"importPath": "@cueplusplus/ui",
|
|
6
6
|
"peerDependencies": [],
|
|
7
7
|
"clientOnly": false,
|
|
8
|
-
"description": "A grouped run of dense rows under sticky headings: the ledger idiom.\n\nThe flattest of the three surfaces this package ships over one row model,
|
|
8
|
+
"description": "A grouped run of dense rows under sticky headings: the ledger idiom.\n\nThe flattest of the three surfaces this package ships over one row model:\nheadings, slugs, rows. What it owns above all is the document outline. Tiers\nand groups are real `<h2>` / `<h3>` elements at caller-chosen levels, because\nin a list of two hundred rows the heading list *is* how a screen-reader user\nnavigates.\n\n## Folding: the accordion pattern, opt-in, and why it lives here\n\nThis component used to refuse disclosure outright, on the grounds that *a\nledger which folds is a tree with worse semantics*. That ruling was right\nabout rows and wrong about places, and the distinction is the whole of\n{@link LedgerTierProps.collapsible}:\n\n- **`DataTree` folds a row.** Its `role=\"treeitem\"` rows carry their own\n `aria-expanded`, its chevron is `role=\"presentation\"` because in tree\n semantics the row owns its expansion, and a tier and a subgroup drawn\n through it stop being places and become rows. A consumer who measured this\n exact shape through `DataTree` counted **zero** `h2`/`h3` elements and zero\n `button[aria-expanded]`, which is `DataTree` working correctly and still\n not being what a grouped document needs.\n- **`Ledger` folds a place.** A tier is already an `<h2>`, which is already\n the right element for it, so the disclosure goes *inside* the heading as a\n real `<button aria-expanded aria-controls>` — the accordion pattern, and\n the same one `Table.GroupRow`'s note has always pointed at for a table that\n genuinely needs to fold. The outline is untouched: the heading list of a\n folding ledger and a plain one are identical, which is what lets a page\n offer both idioms over one dataset without the outline moving under a\n reader who switches.\n\nSo it is one component with two forms rather than a `DisclosureLedger` beside\nit: everything a folding ledger needs — the gutter track, the sticky offsets,\nthe label recipe, the type contract — is this component, and a sibling would\nhave been a copy of all of it wrapped around one `<button>`.\n\nExpansion is controllable and stores nothing. *Which* places a reader has\nfolded is a fact about the reader; consumers keep that set themselves and\npass it back in. A folded run of rows is hidden rather than unmounted, so a\ngroup that keeps its own fold inside a tier still keeps it after the tier has\nbeen shut and opened over it.\n\nStatic markup — no `\"use client\"` — **until a heading is asked to fold**, at\nwhich point that heading and its rows become a small client island and the\nrest of the ledger keeps rendering on the server.",
|
|
9
9
|
"props": [],
|
|
10
10
|
"typeReferences": [],
|
|
11
11
|
"subcomponents": [
|
|
@@ -71,6 +71,34 @@
|
|
|
71
71
|
"required": false,
|
|
72
72
|
"defaultValue": null,
|
|
73
73
|
"description": "A qualifier printed after the name — a date, a source, a state."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "collapsible",
|
|
77
|
+
"type": "boolean",
|
|
78
|
+
"required": false,
|
|
79
|
+
"defaultValue": "false",
|
|
80
|
+
"description": "Put a disclosure in the tier's heading that folds its rows away. Defaults\nto `false`, and a tier with no `heading` has nothing to fold from, so it\nignores this and renders as it always has.\n\n**This is the accordion pattern, not the tree pattern.** What folds is the\nheading — a *place* — and the `<h2>` stays exactly where it was with a real\n`<button aria-expanded aria-controls>` inside it. The rows below are hidden\nwhile it is shut, which takes them out of the accessibility tree and out of\nthe layout but keeps whatever state they hold. If what you are folding is a\n*row*, and the thing under it is that row's children rather than that\nplace's contents, you want `DataTree` and its `treeitem` semantics instead."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "expanded",
|
|
84
|
+
"type": "boolean",
|
|
85
|
+
"required": false,
|
|
86
|
+
"defaultValue": null,
|
|
87
|
+
"description": "Whether the tier is open, when the caller owns the state.\n\n**Persistence is the caller's.** Which places a reader has folded is a fact\nabout the reader, not about the list, and it belongs wherever that consumer\nkeeps the rest of them — `localStorage`, a URL, a profile. This library\nstores nothing. Pass this with {@link LedgerTierProps.onExpandedChange};\nomit both and the tier keeps its own state from\n{@link LedgerTierProps.defaultExpanded}. Ignored unless `collapsible`."
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "defaultExpanded",
|
|
91
|
+
"type": "boolean",
|
|
92
|
+
"required": false,
|
|
93
|
+
"defaultValue": "true",
|
|
94
|
+
"description": "Whether an uncontrolled tier starts open. Defaults to `true`.\n\nOpen, because a ledger whose content is folded away on first paint has\nhidden the thing the reader came for; the reader folds what they are done\nwith. Ignored unless `collapsible`, and ignored entirely once\n{@link LedgerTierProps.expanded} is passed."
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "onExpandedChange",
|
|
98
|
+
"type": "((expanded: boolean) => void)",
|
|
99
|
+
"required": false,
|
|
100
|
+
"defaultValue": null,
|
|
101
|
+
"description": "Called with the tier's next state each time the reader presses the\ndisclosure — `true` when it is being opened, `false` when it is being shut.\n\nFires in both the controlled and the uncontrolled form, so a consumer can\nrecord what was folded without also having to own the state."
|
|
74
102
|
}
|
|
75
103
|
],
|
|
76
104
|
"typeReferences": [
|
|
@@ -102,6 +130,34 @@
|
|
|
102
130
|
"required": false,
|
|
103
131
|
"defaultValue": "true",
|
|
104
132
|
"description": "Dock the slug under the tier heading while the group's rows scroll past.\nDefaults to `true`.\n\nThe offset is a custom property, `--cue-ledger-slug-top` (default `2.5rem`):\nhow far down the slug docks depends on how tall the tier heading above it\nrenders, which is a function of the density and the type the *caller*\nconfigured. Set it on the ledger — `className=\"[--cue-ledger-slug-top:2rem]\"` —\nrather than measuring at runtime."
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "collapsible",
|
|
136
|
+
"type": "boolean",
|
|
137
|
+
"required": false,
|
|
138
|
+
"defaultValue": "false",
|
|
139
|
+
"description": "Put a disclosure in the group's slug that folds its rows away. Defaults to\n`false`, and an unlabelled group has no slug to fold from, so it ignores\nthis. The same accordion pattern the tier takes — see\n{@link LedgerTierProps.collapsible} for why it is not the tree pattern."
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name": "expanded",
|
|
143
|
+
"type": "boolean",
|
|
144
|
+
"required": false,
|
|
145
|
+
"defaultValue": null,
|
|
146
|
+
"description": "Whether the group is open, when the caller owns the state. Persistence is\nthe caller's; see {@link LedgerTierProps.expanded}."
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "defaultExpanded",
|
|
150
|
+
"type": "boolean",
|
|
151
|
+
"required": false,
|
|
152
|
+
"defaultValue": "true",
|
|
153
|
+
"description": "Whether an uncontrolled group starts open. Defaults to `true`."
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"name": "onExpandedChange",
|
|
157
|
+
"type": "((expanded: boolean) => void)",
|
|
158
|
+
"required": false,
|
|
159
|
+
"defaultValue": null,
|
|
160
|
+
"description": "Called with the group's next state each time the reader presses the slug."
|
|
105
161
|
}
|
|
106
162
|
],
|
|
107
163
|
"typeReferences": [
|
|
@@ -121,11 +177,13 @@
|
|
|
121
177
|
"variants": {},
|
|
122
178
|
"defaultVariants": {},
|
|
123
179
|
"tokensUsed": [
|
|
180
|
+
"--cue-accent",
|
|
124
181
|
"--cue-bg",
|
|
125
182
|
"--cue-data-row-cols",
|
|
126
183
|
"--cue-fg",
|
|
127
184
|
"--cue-fg-subtle",
|
|
128
185
|
"--cue-font-mono",
|
|
186
|
+
"--cue-icon-sm",
|
|
129
187
|
"--cue-ledger-gutter",
|
|
130
188
|
"--cue-ledger-slug-top",
|
|
131
189
|
"--cue-pad-row-x",
|
|
@@ -135,7 +193,7 @@
|
|
|
135
193
|
"--cue-text-label",
|
|
136
194
|
"--cue-text-ui"
|
|
137
195
|
],
|
|
138
|
-
"summary": "Grouped dense rows under sticky tier headings and gutter slugs, with
|
|
196
|
+
"summary": "Grouped dense rows under sticky tier headings and gutter slugs, with an opt-in disclosure that folds a heading without leaving the outline.",
|
|
139
197
|
"examples": [
|
|
140
198
|
{
|
|
141
199
|
"title": "Tiers, groups, rows",
|
|
@@ -147,9 +205,14 @@
|
|
|
147
205
|
"code": "<Ledger.Tier>\n <Ledger.Group>\n <DataRow.Root>…</DataRow.Root>\n </Ledger.Group>\n</Ledger.Tier>",
|
|
148
206
|
"language": "tsx"
|
|
149
207
|
},
|
|
208
|
+
{
|
|
209
|
+
"title": "Folded places, persisted by the caller",
|
|
210
|
+
"code": "<Ledger.Tier\n heading=\"Ours\"\n count={13}\n collapsible\n expanded={!folded.includes(\"ours\")}\n onExpandedChange={(open) => remember(\"ours\", open)}\n>\n <Ledger.Group label=\"released\" collapsible defaultExpanded={false}>\n <DataRow.Root>…</DataRow.Root>\n </Ledger.Group>\n</Ledger.Tier>",
|
|
211
|
+
"language": "tsx"
|
|
212
|
+
},
|
|
150
213
|
{
|
|
151
214
|
"title": "Usage",
|
|
152
|
-
"code": "<Ledger.Root className=\"[--cue-data-row-cols:1fr_8rem_auto]\">\n <Ledger.Tier heading=\"Ours\" count={12}>\n <Ledger.Group label=\"released\">\n <DataRow.Root interactive onActivate={open}>…</DataRow.Root>\n </Ledger.Group>\n <Ledger.Group label=\"lab\">\n <Ledger.Empty>Nothing yet.</Ledger.Empty>\n </Ledger.Group>\n </Ledger.Tier>\n</Ledger.Root>",
|
|
215
|
+
"code": "<Ledger.Root className=\"[--cue-data-row-cols:1fr_8rem_auto]\">\n <Ledger.Tier heading=\"Ours\" count={12}>\n <Ledger.Group label=\"released\">\n <DataRow.Root interactive onActivate={open}>…</DataRow.Root>\n </Ledger.Group>\n <Ledger.Group label=\"lab\">\n <Ledger.Empty>Nothing yet.</Ledger.Empty>\n </Ledger.Group>\n </Ledger.Tier>\n</Ledger.Root>\n// Folded places are the reader's, so the reader's storage holds them.\n<Ledger.Tier\n heading=\"Ours\"\n count={13}\n collapsible\n expanded={!folded.includes(\"ours\")}\n onExpandedChange={(open) => remember(\"ours\", open)}\n>\n <Ledger.Group label=\"released\">…</Ledger.Group>\n</Ledger.Tier>",
|
|
153
216
|
"language": "tsx"
|
|
154
217
|
}
|
|
155
218
|
],
|
|
@@ -158,16 +221,18 @@
|
|
|
158
221
|
"mdUrl": "/docs/components/ledger.md",
|
|
159
222
|
"jsonUrl": "/r/components/ledger.json",
|
|
160
223
|
"whenToUse": [
|
|
161
|
-
"A long
|
|
162
|
-
"A surface whose groups should be reachable by heading, since the tiers and slugs are real `h2`/`h3` elements."
|
|
224
|
+
"A long grouped list: a corpus by source, a patch by direction, a run log by day.",
|
|
225
|
+
"A surface whose groups should be reachable by heading, since the tiers and slugs are real `h2`/`h3` elements.",
|
|
226
|
+
"A grouped list the reader folds *by place* — `collapsible` puts a real `button[aria-expanded]` inside the heading and leaves the `h2`/`h3` outline exactly as it was."
|
|
163
227
|
],
|
|
164
228
|
"whenNotToUse": [
|
|
165
|
-
"A hierarchy
|
|
229
|
+
"A hierarchy whose *rows* fold into their own children. Use `DataTree`, where the row is the treeitem and owns its expansion.",
|
|
166
230
|
"Rows meant to be compared down a named column. Use a grouped `Table`, where the columns are named once in a `thead`.",
|
|
167
231
|
"A handful of rows in a panel. Use `Row` inside `Panel`; a tier heading over three rows is furniture with nothing to organise."
|
|
168
232
|
],
|
|
169
233
|
"commonMistakes": [
|
|
170
|
-
"
|
|
234
|
+
"Reaching for `DataTree` to fold a grouped list. A tree turns every tier and subgroup into a `treeitem` row and leaves zero headings behind — if the thing folding is a *place*, `collapsible` is the prop.",
|
|
235
|
+
"Storing the folded set inside the component. Which places a reader folded is a fact about the reader: pass `expanded` and `onExpandedChange` and keep it wherever the rest of that reader's preferences live.",
|
|
171
236
|
"Framing each group in a `Panel`. A card per group turns one list into a stack of little tables and breaks the top-to-bottom scan.",
|
|
172
237
|
"Leaving `--cue-ledger-slug-top` at its default after changing the density or the heading type, which docks the slug over the tier heading instead of under it.",
|
|
173
238
|
"Passing an `empty` string as a prop. `Ledger.Empty` takes the words because \"this place is empty\" and \"your filter emptied it\" are different sentences."
|
|
@@ -182,6 +247,16 @@
|
|
|
182
247
|
"code": "<Panel className=\"w-full\">\n <div className=\"max-h-[18rem] overflow-y-auto\">\n <Ledger.Root className={PATCH_TRACKS}>\n <Ledger.Tier heading=\"Patch\" count={PATCH_DEVICES.length} note=\"polled 18:04\">\n {PATCH_GROUPS.map((group) => (\n <Ledger.Group key={group.id} label={group.label}>\n {group.rows.map((device) => (\n <DataRow.Root key={device.id} interactive>\n <PatchRowSlots device={device} />\n </DataRow.Root>\n ))}\n </Ledger.Group>\n ))}\n <Ledger.Group label=\"spare\">\n <Ledger.Empty>Nothing yet.</Ledger.Empty>\n </Ledger.Group>\n </Ledger.Tier>\n </Ledger.Root>\n </div>\n</Panel>",
|
|
183
248
|
"note": "The same rows as tiers and slugs: sticky headings, a gutter for the group name, and no disclosure anywhere — the ledger does not collapse.",
|
|
184
249
|
"interaction": "the tier heading and the group slug dock as the rows scroll under them; scroll the pane to see it."
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"title": "Ledger — collapsible",
|
|
253
|
+
"group": "instruments",
|
|
254
|
+
"components": [
|
|
255
|
+
"Ledger"
|
|
256
|
+
],
|
|
257
|
+
"code": "<Panel className=\"w-full\">\n <div className=\"max-h-[18rem] overflow-y-auto\">\n <Ledger.Root className={PATCH_TRACKS}>\n <Ledger.Tier\n heading=\"Patch\"\n count={PATCH_DEVICES.length}\n collapsible\n expanded={patchOpen}\n onExpandedChange={setPatchOpen}\n >\n {PATCH_GROUPS.map((group, index) => (\n <Ledger.Group\n key={group.id}\n label={group.label}\n collapsible\n defaultExpanded={index === 0}\n >\n {group.rows.map((device) => (\n <DataRow.Root key={device.id} interactive>\n <PatchRowSlots device={device} />\n </DataRow.Root>\n ))}\n </Ledger.Group>\n ))}\n </Ledger.Tier>\n </Ledger.Root>\n </div>\n</Panel>",
|
|
258
|
+
"note": "The same ledger with `collapsible` on the tier and on one group. The disclosure is a real `button[aria-expanded aria-controls]` *inside* the `h2`/`h3`, so the heading outline is identical to the bench above it. A folded run of rows is hidden rather than unmounted — out of the layout and out of the accessibility tree, but still holding its state, which is what lets a group keep its own fold while the tier above it is shut and reopened. Which places are folded is the caller's to keep; this bench keeps it in React state and nothing is stored by the library.",
|
|
259
|
+
"interaction": "press a tier heading or a group slug to fold it; the chevron turns, the sibling group stays open, and Tab reaches each disclosure as a real button with Enter and Space. Fold the first group, then fold and reopen the tier over it: the group is still folded."
|
|
185
260
|
}
|
|
186
261
|
]
|
|
187
262
|
}
|
|
@@ -45,17 +45,16 @@
|
|
|
45
45
|
"--cue-fg-muted",
|
|
46
46
|
"--cue-fg-subtle",
|
|
47
47
|
"--cue-font-mono",
|
|
48
|
-
"--cue-font-pairing-mono",
|
|
49
48
|
"--cue-font-sans",
|
|
50
49
|
"--cue-font-scale",
|
|
51
|
-
"--cue-font-theme-mono",
|
|
52
50
|
"--cue-radius-control",
|
|
53
51
|
"--cue-radius-scale",
|
|
54
52
|
"--cue-space-1",
|
|
55
53
|
"--cue-space-2",
|
|
56
54
|
"--cue-space-4",
|
|
57
55
|
"--cue-text-label",
|
|
58
|
-
"--cue-text-micro"
|
|
56
|
+
"--cue-text-micro",
|
|
57
|
+
"--cue-text-ui"
|
|
59
58
|
],
|
|
60
59
|
"summary": "Section 1 — the four choices that are not token edits at all.",
|
|
61
60
|
"examples": [
|
|
@@ -38,17 +38,16 @@
|
|
|
38
38
|
"--cue-fg-muted",
|
|
39
39
|
"--cue-fg-subtle",
|
|
40
40
|
"--cue-font-mono",
|
|
41
|
-
"--cue-font-pairing-mono",
|
|
42
41
|
"--cue-font-sans",
|
|
43
42
|
"--cue-font-scale",
|
|
44
|
-
"--cue-font-theme-mono",
|
|
45
43
|
"--cue-radius-control",
|
|
46
44
|
"--cue-radius-scale",
|
|
47
45
|
"--cue-space-1",
|
|
48
46
|
"--cue-space-2",
|
|
49
47
|
"--cue-space-4",
|
|
50
48
|
"--cue-text-label",
|
|
51
|
-
"--cue-text-micro"
|
|
49
|
+
"--cue-text-micro",
|
|
50
|
+
"--cue-text-ui"
|
|
52
51
|
],
|
|
53
52
|
"summary": "Section 3 — the geometry and type knobs, which belong to the *density* axis rather than to the theme.",
|
|
54
53
|
"examples": [
|
|
@@ -89,14 +89,11 @@
|
|
|
89
89
|
"--cue-fg-muted",
|
|
90
90
|
"--cue-fg-subtle",
|
|
91
91
|
"--cue-font-mono",
|
|
92
|
-
"--cue-font-pairing-mono",
|
|
93
92
|
"--cue-font-sans",
|
|
94
93
|
"--cue-font-scale",
|
|
95
|
-
"--cue-font-theme-mono",
|
|
96
94
|
"--cue-icon-sm",
|
|
97
95
|
"--cue-radius-control",
|
|
98
96
|
"--cue-radius-overlay",
|
|
99
|
-
"--cue-radius-scale",
|
|
100
97
|
"--cue-scrim",
|
|
101
98
|
"--cue-space-1",
|
|
102
99
|
"--cue-space-2",
|
|
@@ -5,21 +5,28 @@
|
|
|
5
5
|
"importPath": "@cueplusplus/ui",
|
|
6
6
|
"peerDependencies": [],
|
|
7
7
|
"clientOnly": true,
|
|
8
|
-
"description": "Root of the theme system: stamps `data-theme`, `data-density` and `data-mode`\nso the token layer resolves, puts `data-font` on `<html>`, publishes\n`--cue-font-scale`,
|
|
8
|
+
"description": "Root of the theme system: stamps `data-theme`, `data-density` and `data-mode`\nso the token layer resolves, puts `data-font` on `<html>`, publishes\n`--cue-font-scale`, owns the persisted user preference, and publishes the\nregistry the three axis hooks read.\n\nRendering: `<div data-cue-root data-theme data-density data-mode style={{ colorScheme, --cue-font-scale }}>`\n(or the single child when `asChild`). The `theme`/`density`/`font`/`mode` props are\n*initial* values — the provider holds the state so `setTheme` and friends can\ndrive it — but a changed prop is adopted after mount, so a controlling parent\nstill works.\n\nThe font pairing is the one axis that never lands on this element: it has no\nisland form, `<html>` is its only home, and restating it here would shadow the\npre-paint stamp for the whole page on the first frame. See the `stamp` object\nbelow.\n\nPersistence and the pre-paint contract: setters write\n`{ theme, density, font, mode }` to `localStorage[storageKey]`, which is exactly what\n{@link prepaintScript } reads to stamp `<html>` before the first paint. The\noutermost provider keeps `<html>` in sync while the app runs (and restores the\nprevious stamp on unmount) so the page ground, UA scrollbars and form controls\nfollow the theme; nested providers never touch `<html>`, and never own the\nfont pairing — `useTheme().font` and `setFont` inside one are the root's.",
|
|
9
9
|
"props": [
|
|
10
|
+
{
|
|
11
|
+
"name": "themes",
|
|
12
|
+
"type": "readonly ThemeManifest[]",
|
|
13
|
+
"required": false,
|
|
14
|
+
"defaultValue": null,
|
|
15
|
+
"description": "The registry: an ordered array of theme manifests, the first of which is\nthe default theme.\n\nA prop rather than a side effect, so the server render and the client\nrender are handed the same array and nothing depends on which module an app\nhappened to import first. Each entry is a `manifest.json` from a\n`@cueplusplus/theme-<name>` package; this library never discovers a theme by\nitself, because which themes a product ships is an application decision.\n\nOptional for one release, defaulting to `[]` with a development warning. An\napp that registers nothing gets the base axes and no registry: any\nwell-formed name is accepted as a theme (so a visitor's stored preference\nstill restores on first paint), density and font validate against the base\nladder and pairings, and `useTheme().manifest` is `null`. What it *paints*\nis a separate question with a separate answer — whichever `[data-theme]`\nblocks its stylesheets declare, which is the blank base only when none of\nthem declares the stamped name.\n\nA nested provider that passes none inherits the ambient registry.\n\nOne disclosure to be aware of: {@link prepaintScript } inlines the\nregistered **names** — and each theme's `supportsLight` flag, and the names\nof any rung or pairing it adds — into the blocking script in every page's\nHTML, because first paint has to judge a stored preference before any\nmodule loads. No colours, no geometry, no package names, and only what an\napp passes here. But an app that registers\na per-customer theme is publishing that customer's name to every visitor\nwho views source, so register per-customer themes per response rather than\nglobally."
|
|
16
|
+
},
|
|
10
17
|
{
|
|
11
18
|
"name": "theme",
|
|
12
|
-
"type": "
|
|
19
|
+
"type": "string",
|
|
13
20
|
"required": false,
|
|
14
|
-
"defaultValue":
|
|
15
|
-
"description": "Initial theme preset. Defaults to `\"cue\"
|
|
21
|
+
"defaultValue": null,
|
|
22
|
+
"description": "Initial theme preset. Defaults to the first registered manifest's name, or\n`\"cue\"` with nothing registered. Changing it after mount adopts the new value."
|
|
16
23
|
},
|
|
17
24
|
{
|
|
18
25
|
"name": "density",
|
|
19
26
|
"type": "\"normal\" | \"large\" | \"compact\" | \"ultra-compact\" | \"ultra-large\"",
|
|
20
27
|
"required": false,
|
|
21
|
-
"defaultValue":
|
|
22
|
-
"description": "Initial density level.
|
|
28
|
+
"defaultValue": null,
|
|
29
|
+
"description": "Initial density level.\n\nDefaults to the rung the active theme's manifest names in\n`densities.default`, and to `\"compact\"` when it names none — spec §5's\nrule, and the same one `resolve()` applies when no rung is wanted, so a\ntheme opens on the rung it prefers whether it is read from JS or painted\nby its own stylesheet. Naming a rung here overrides that for every theme:\na consumer who named one named it deliberately.\n\nChanging it after mount adopts the new value; dropping it does not — a\nparent that stops naming a rung is not asking to move back to the theme's."
|
|
23
30
|
},
|
|
24
31
|
{
|
|
25
32
|
"name": "mode",
|
|
@@ -32,8 +39,8 @@
|
|
|
32
39
|
"name": "font",
|
|
33
40
|
"type": "\"source\" | \"system\" | \"geist\" | \"inter\" | \"plex\" | \"roboto\" | \"apple\" | \"office\"",
|
|
34
41
|
"required": false,
|
|
35
|
-
"defaultValue":
|
|
36
|
-
"description": "Initial font pairing.
|
|
42
|
+
"defaultValue": null,
|
|
43
|
+
"description": "Initial font pairing.\n\nDefaults to the pairing the active theme's manifest names in\n`fontPairings.default` — §5's rule, the density prop's rule one axis over\n— and to `\"system\"` when it names none: the platform's own faces, nothing\ndownloaded, and the theme keeps whatever monospace it authored.\n\nA pairing that needs delivering is a set of *names*: this library ships no\nfont files, and a pairing nobody delivers falls through its stack to the\nplatform rather than failing. See `FONT_PAIRINGS[font].faces` for the\ncustom properties an app assigns to make one resolve.\n\n**Root-level only.** There is no font island: a nested provider forwards\nthis axis to the root, ignores this prop, and its `setFont` drives the root.\nA page that changed face halfway down is a page with a bug, and a specimen\nthat genuinely wants one — a picker row, a docs page showing all eight —\nneeds nothing from this library but `data-font` on a `<div>`."
|
|
37
44
|
},
|
|
38
45
|
{
|
|
39
46
|
"name": "fontScale",
|
|
@@ -49,6 +56,13 @@
|
|
|
49
56
|
"defaultValue": null,
|
|
50
57
|
"description": "Optional app-owned font stacks, published as inline CUE font custom properties."
|
|
51
58
|
},
|
|
59
|
+
{
|
|
60
|
+
"name": "overrides",
|
|
61
|
+
"type": "TokenOverrides",
|
|
62
|
+
"required": false,
|
|
63
|
+
"defaultValue": null,
|
|
64
|
+
"description": "Typed token edits layered over the active theme: colours per mode, geometry\nper rung, and the three font stacks.\n\nFor the band of edits that sit below \"publish a theme package\": one accent\nfor a tenant, a stack the app already loads, a rung with two more pixels in\na touch build. Anything larger belongs in a `@cueplusplus/theme-<name>`\npackage, where a build measures it; anything smaller than this is a\n`!important` in a stray stylesheet, which outranks the token layer\neverywhere at once.\n\n**Colours and fonts are inline on this element**, so they inherit down the\nsubtree, beat every stylesheet without `!important`, and are re-stamped\nonto portal containers — which mount on `<body>` and inherit nothing from\nhere. They do not leak *out* of this provider: a nested provider's colour\nedit is scoped to its own subtree, and to portals opened from inside it.\n\n**Densities are one document-scoped `<style>` element**, because a rung is\nselected by attribute rather than inherited, and because\n`useControlHeight()`'s measuring probe hangs off `document.body`, where\na subtree-scoped rule would not reach it — leaving the measured height and\nthe painted control disagreeing by exactly the override. The consequence is\nworth knowing before you nest one: a nested provider's `densities` edit\nreaches the whole page, exactly as a theme's own rung rules do. Colours and\nfonts in the same object do not.\n\nAnd it cuts the other way as well, which is the half that surprises: two\nproviders editing the same rung produce two document-scoped rules of equal\nspecificity, so the **later** one in document order wins everywhere — and\nthe later one is the outer provider's, because a nested provider renders\ninside it. A nested `densities` edit does not merely leak out; an ancestor\nthat edits the same rung overrules it *inside the nested subtree too*. If\nan island needs geometry of its own, it needs a rung of its own — a name no\nancestor is editing — not the same rung with different numbers.\n\nIts rules are `[data-density=\"x\"][data-density=\"x\"]` — (0,2,0), tying a\ntheme's own `[data-theme=\"t\"] [data-density=\"x\"]` and winning on source\norder — so the precedence is: **`overrides`, then the configurator's\npersisted snapshot, then the theme, then base.**\n\n**A font override shadows the pairing for this subtree.** `data-font` is\nthe document's axis and this element deliberately never restates it (see\nthe note by `stamp` below), but `overrides.fonts` writes the resolved\n`--cue-font-*` properties directly, which is a stronger claim than the\nattribute and is the point: this is how an app says \"this product's face,\nwhatever pairing the visitor picked\".\n\nIn development the resulting palette is measured, and any required contrast\npair the edit broke — or made worse — is named on the console."
|
|
65
|
+
},
|
|
52
66
|
{
|
|
53
67
|
"name": "storageKey",
|
|
54
68
|
"type": "string",
|
|
@@ -98,6 +112,7 @@
|
|
|
98
112
|
"variants": {},
|
|
99
113
|
"defaultVariants": {},
|
|
100
114
|
"tokensUsed": [
|
|
115
|
+
"--cue-font-",
|
|
101
116
|
"--cue-font-mono",
|
|
102
117
|
"--cue-font-sans",
|
|
103
118
|
"--cue-font-scale"
|
|
@@ -111,7 +126,7 @@
|
|
|
111
126
|
},
|
|
112
127
|
{
|
|
113
128
|
"title": "Usage",
|
|
114
|
-
"code": "<ThemeProvider theme=\"terminal\" density=\"ultra-compact\" font=\"plex\" mode=\"system\">\n <App />\n</ThemeProvider>",
|
|
129
|
+
"code": "// `cue` and `terminal` are the `manifest.json` each theme package ships, read\n// from the `…/manifest.json` subpath of `@cueplusplus/theme-cue` and\n// `@cueplusplus/theme-terminal`. Written that way round on purpose: a literal\n// import statement in this comment reads, to every import-graph gate in this\n// repository, as `ui` depending on a theme package — which is the one thing\n// it may not do.\n<ThemeProvider themes={[cue, terminal]} theme=\"terminal\" density=\"ultra-compact\" font=\"plex\" mode=\"system\">\n <App />\n</ThemeProvider>",
|
|
115
130
|
"language": "tsx"
|
|
116
131
|
}
|
|
117
132
|
],
|
|
@@ -64,11 +64,8 @@
|
|
|
64
64
|
"--cue-fg-muted",
|
|
65
65
|
"--cue-fg-subtle",
|
|
66
66
|
"--cue-font-mono",
|
|
67
|
-
"--cue-font-pairing-mono",
|
|
68
67
|
"--cue-font-sans",
|
|
69
68
|
"--cue-font-scale",
|
|
70
|
-
"--cue-font-theme-mono",
|
|
71
|
-
"--cue-radius-scale",
|
|
72
69
|
"--cue-space-2",
|
|
73
70
|
"--cue-space-4",
|
|
74
71
|
"--cue-text-label",
|