@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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { headingTag } from "./_ledger.js";
|
|
3
|
+
import { LedgerDisclosure } from "./_ledger-disclosure.js";
|
|
2
4
|
import * as React from "react";
|
|
3
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
6
|
//#region src/instruments/ledger.tsx
|
|
@@ -12,24 +14,68 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
12
14
|
* micro-label in a dense list reads as a second kind of emphasis.
|
|
13
15
|
*/
|
|
14
16
|
const LEDGER_LABEL = "font-mono text-(length:--cue-text-label) font-normal tracking-[0.15em] text-fg-subtle uppercase";
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The disclosure button a collapsible heading holds.
|
|
19
|
+
*
|
|
20
|
+
* Worn *with* {@link LEDGER_LABEL}, which the heading around it is already
|
|
21
|
+
* wearing. The repetition is deliberate: a `<button>` carries the UA's own font
|
|
22
|
+
* rather than its parent's, and while Tailwind's preflight resets that to
|
|
23
|
+
* `font: inherit`, a component whose micro-label silently becomes 13px Arial if
|
|
24
|
+
* a consumer drops preflight is a component with a very confusing bug report.
|
|
25
|
+
* One constant applied twice cannot drift; two recipes can.
|
|
26
|
+
*
|
|
27
|
+
* `w-full` so the whole width of the heading is the hit area rather than the
|
|
28
|
+
* word itself, and the focus ring is inset (`-outline-offset-2`) for the same
|
|
29
|
+
* reason `_data-row.ts` gives: these headings dock at the top of clipped panes,
|
|
30
|
+
* where an outset ring on the first one is cut off by the pane's own rim.
|
|
31
|
+
*/
|
|
32
|
+
const LEDGER_TOGGLE = "flex w-full min-w-0 cursor-pointer items-baseline gap-(--cue-space-2) text-left outline-none focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-accent";
|
|
17
33
|
/**
|
|
18
34
|
* A grouped run of dense rows under sticky headings: the ledger idiom.
|
|
19
35
|
*
|
|
20
|
-
* The flattest of the three surfaces this package ships over one row model
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
36
|
+
* The flattest of the three surfaces this package ships over one row model:
|
|
37
|
+
* headings, slugs, rows. What it owns above all is the document outline. Tiers
|
|
38
|
+
* and groups are real `<h2>` / `<h3>` elements at caller-chosen levels, because
|
|
39
|
+
* in a list of two hundred rows the heading list *is* how a screen-reader user
|
|
40
|
+
* navigates.
|
|
41
|
+
*
|
|
42
|
+
* ## Folding: the accordion pattern, opt-in, and why it lives here
|
|
43
|
+
*
|
|
44
|
+
* This component used to refuse disclosure outright, on the grounds that *a
|
|
45
|
+
* ledger which folds is a tree with worse semantics*. That ruling was right
|
|
46
|
+
* about rows and wrong about places, and the distinction is the whole of
|
|
47
|
+
* {@link LedgerTierProps.collapsible}:
|
|
26
48
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
49
|
+
* - **`DataTree` folds a row.** Its `role="treeitem"` rows carry their own
|
|
50
|
+
* `aria-expanded`, its chevron is `role="presentation"` because in tree
|
|
51
|
+
* semantics the row owns its expansion, and a tier and a subgroup drawn
|
|
52
|
+
* through it stop being places and become rows. A consumer who measured this
|
|
53
|
+
* exact shape through `DataTree` counted **zero** `h2`/`h3` elements and zero
|
|
54
|
+
* `button[aria-expanded]`, which is `DataTree` working correctly and still
|
|
55
|
+
* not being what a grouped document needs.
|
|
56
|
+
* - **`Ledger` folds a place.** A tier is already an `<h2>`, which is already
|
|
57
|
+
* the right element for it, so the disclosure goes *inside* the heading as a
|
|
58
|
+
* real `<button aria-expanded aria-controls>` — the accordion pattern, and
|
|
59
|
+
* the same one `Table.GroupRow`'s note has always pointed at for a table that
|
|
60
|
+
* genuinely needs to fold. The outline is untouched: the heading list of a
|
|
61
|
+
* folding ledger and a plain one are identical, which is what lets a page
|
|
62
|
+
* offer both idioms over one dataset without the outline moving under a
|
|
63
|
+
* reader who switches.
|
|
30
64
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
65
|
+
* So it is one component with two forms rather than a `DisclosureLedger` beside
|
|
66
|
+
* it: everything a folding ledger needs — the gutter track, the sticky offsets,
|
|
67
|
+
* the label recipe, the type contract — is this component, and a sibling would
|
|
68
|
+
* have been a copy of all of it wrapped around one `<button>`.
|
|
69
|
+
*
|
|
70
|
+
* Expansion is controllable and stores nothing. *Which* places a reader has
|
|
71
|
+
* folded is a fact about the reader; consumers keep that set themselves and
|
|
72
|
+
* pass it back in. A folded run of rows is hidden rather than unmounted, so a
|
|
73
|
+
* group that keeps its own fold inside a tier still keeps it after the tier has
|
|
74
|
+
* been shut and opened over it.
|
|
75
|
+
*
|
|
76
|
+
* Static markup — no `"use client"` — **until a heading is asked to fold**, at
|
|
77
|
+
* which point that heading and its rows become a small client island and the
|
|
78
|
+
* rest of the ledger keeps rendering on the server.
|
|
33
79
|
*
|
|
34
80
|
* @example
|
|
35
81
|
* <Ledger.Root className="[--cue-data-row-cols:1fr_8rem_auto]">
|
|
@@ -42,6 +88,18 @@ const headingTag = (level) => level === 2 ? "h2" : level === 3 ? "h3" : "h4";
|
|
|
42
88
|
* </Ledger.Group>
|
|
43
89
|
* </Ledger.Tier>
|
|
44
90
|
* </Ledger.Root>
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* // Folded places are the reader's, so the reader's storage holds them.
|
|
94
|
+
* <Ledger.Tier
|
|
95
|
+
* heading="Ours"
|
|
96
|
+
* count={13}
|
|
97
|
+
* collapsible
|
|
98
|
+
* expanded={!folded.includes("ours")}
|
|
99
|
+
* onExpandedChange={(open) => remember("ours", open)}
|
|
100
|
+
* >
|
|
101
|
+
* <Ledger.Group label="released">…</Ledger.Group>
|
|
102
|
+
* </Ledger.Tier>
|
|
45
103
|
*/
|
|
46
104
|
const Ledger = {
|
|
47
105
|
Root: React.forwardRef(function LedgerRoot({ className, containment = true, ...elementProps }, ref) {
|
|
@@ -52,53 +110,85 @@ const Ledger = {
|
|
|
52
110
|
...elementProps
|
|
53
111
|
});
|
|
54
112
|
}),
|
|
55
|
-
Tier: React.forwardRef(function LedgerTier({ className, heading, count, sticky = true, headingLevel = 2, note, children, ...elementProps }, ref) {
|
|
113
|
+
Tier: React.forwardRef(function LedgerTier({ className, heading, count, sticky = true, headingLevel = 2, note, collapsible = false, expanded, defaultExpanded = true, onExpandedChange, children, ...elementProps }, ref) {
|
|
56
114
|
const Heading = headingTag(headingLevel);
|
|
57
|
-
|
|
115
|
+
const words = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
116
|
+
/* @__PURE__ */ jsx("span", {
|
|
117
|
+
"data-slot": "ledger-tier-label",
|
|
118
|
+
children: heading
|
|
119
|
+
}),
|
|
120
|
+
count === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
121
|
+
"data-slot": "ledger-tier-count",
|
|
122
|
+
className: "tabular-nums",
|
|
123
|
+
children: count
|
|
124
|
+
}),
|
|
125
|
+
note === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
126
|
+
"data-slot": "ledger-tier-note",
|
|
127
|
+
className: "tracking-normal normal-case",
|
|
128
|
+
children: note
|
|
129
|
+
})
|
|
130
|
+
] });
|
|
131
|
+
const ground = sticky ? "sticky top-0 z-2 bg-bg" : null;
|
|
132
|
+
return /* @__PURE__ */ jsx("section", {
|
|
58
133
|
ref,
|
|
59
134
|
"data-slot": "ledger-tier",
|
|
60
135
|
className: cn("flex flex-col", className),
|
|
61
136
|
...elementProps,
|
|
62
|
-
children:
|
|
137
|
+
children: heading === void 0 ? children : collapsible ? /* @__PURE__ */ jsx(LedgerDisclosure, {
|
|
138
|
+
headingLevel,
|
|
139
|
+
headingSlot: "ledger-tier-heading",
|
|
140
|
+
headingClassName: cn(LEDGER_LABEL, "py-(--cue-pad-row-y)", ground),
|
|
141
|
+
toggleSlot: "ledger-tier-toggle",
|
|
142
|
+
toggleClassName: cn(LEDGER_LABEL, LEDGER_TOGGLE),
|
|
143
|
+
panelSlot: "ledger-tier-rows",
|
|
144
|
+
panelClassName: "flex flex-col",
|
|
145
|
+
label: words,
|
|
146
|
+
expanded,
|
|
147
|
+
defaultExpanded,
|
|
148
|
+
onExpandedChange,
|
|
149
|
+
children
|
|
150
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Heading, {
|
|
63
151
|
"data-slot": "ledger-tier-heading",
|
|
64
|
-
className: cn(LEDGER_LABEL, "flex items-baseline gap-(--cue-space-2) py-(--cue-pad-row-y)",
|
|
65
|
-
children:
|
|
66
|
-
|
|
67
|
-
"data-slot": "ledger-tier-label",
|
|
68
|
-
children: heading
|
|
69
|
-
}),
|
|
70
|
-
count === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
71
|
-
"data-slot": "ledger-tier-count",
|
|
72
|
-
className: "tabular-nums",
|
|
73
|
-
children: count
|
|
74
|
-
}),
|
|
75
|
-
note === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
76
|
-
"data-slot": "ledger-tier-note",
|
|
77
|
-
className: "tracking-normal normal-case",
|
|
78
|
-
children: note
|
|
79
|
-
})
|
|
80
|
-
]
|
|
81
|
-
}), children]
|
|
152
|
+
className: cn(LEDGER_LABEL, "flex items-baseline gap-(--cue-space-2) py-(--cue-pad-row-y)", ground),
|
|
153
|
+
children: words
|
|
154
|
+
}), children] })
|
|
82
155
|
});
|
|
83
156
|
}),
|
|
84
|
-
Group: React.forwardRef(function LedgerGroup({ className, label, headingLevel = 3, sticky = true, children, ...elementProps }, ref) {
|
|
157
|
+
Group: React.forwardRef(function LedgerGroup({ className, label, headingLevel = 3, sticky = true, collapsible = false, expanded, defaultExpanded = true, onExpandedChange, children, ...elementProps }, ref) {
|
|
85
158
|
const Heading = headingTag(headingLevel);
|
|
86
159
|
const labelled = label !== void 0;
|
|
160
|
+
const ground = sticky ? "sticky self-start [top:var(--cue-ledger-slug-top,2.5rem)] bg-bg" : null;
|
|
87
161
|
return /* @__PURE__ */ jsx("div", {
|
|
88
162
|
ref,
|
|
89
163
|
"data-slot": "ledger-group",
|
|
90
164
|
"data-labelled": labelled ? "" : void 0,
|
|
91
165
|
className: cn(labelled ? "grid [grid-template-columns:var(--cue-ledger-gutter,7rem)_minmax(0,1fr)]" : "flex flex-col", className),
|
|
92
166
|
...elementProps,
|
|
93
|
-
children: labelled ?
|
|
167
|
+
children: !labelled ? children : collapsible ? /* @__PURE__ */ jsx(LedgerDisclosure, {
|
|
168
|
+
headingLevel,
|
|
169
|
+
headingSlot: "ledger-group-label",
|
|
170
|
+
headingClassName: cn(LEDGER_LABEL, "min-w-0 py-(--cue-pad-row-y) pr-(--cue-space-3)", ground),
|
|
171
|
+
toggleSlot: "ledger-group-toggle",
|
|
172
|
+
toggleClassName: cn(LEDGER_LABEL, LEDGER_TOGGLE),
|
|
173
|
+
panelSlot: "ledger-group-rows",
|
|
174
|
+
panelClassName: "flex min-w-0 flex-col",
|
|
175
|
+
label: /* @__PURE__ */ jsx("span", {
|
|
176
|
+
className: "min-w-0 truncate",
|
|
177
|
+
children: label
|
|
178
|
+
}),
|
|
179
|
+
expanded,
|
|
180
|
+
defaultExpanded,
|
|
181
|
+
onExpandedChange,
|
|
182
|
+
children
|
|
183
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Heading, {
|
|
94
184
|
"data-slot": "ledger-group-label",
|
|
95
|
-
className: cn(LEDGER_LABEL, "min-w-0 truncate py-(--cue-pad-row-y) pr-(--cue-space-3)",
|
|
185
|
+
className: cn(LEDGER_LABEL, "min-w-0 truncate py-(--cue-pad-row-y) pr-(--cue-space-3)", ground),
|
|
96
186
|
children: label
|
|
97
187
|
}), /* @__PURE__ */ jsx("div", {
|
|
98
188
|
"data-slot": "ledger-group-rows",
|
|
99
189
|
className: "flex min-w-0 flex-col",
|
|
100
190
|
children
|
|
101
|
-
})] })
|
|
191
|
+
})] })
|
|
102
192
|
});
|
|
103
193
|
}),
|
|
104
194
|
Empty: React.forwardRef(function LedgerEmpty({ className, ...elementProps }, ref) {
|
|
@@ -49,7 +49,11 @@ const PianoKeyboard = React.forwardRef(function PianoKeyboard({ className, range
|
|
|
49
49
|
const [drag, setDrag] = React.useState(null);
|
|
50
50
|
const settled = React.useMemo(() => keyboardWindow(range, octaves), [range, octaves]);
|
|
51
51
|
const view = drag?.window ?? settled;
|
|
52
|
-
const
|
|
52
|
+
const { start: viewStart, end: viewEnd } = view;
|
|
53
|
+
const layout = React.useMemo(() => keyboardLayout({
|
|
54
|
+
start: viewStart,
|
|
55
|
+
end: viewEnd
|
|
56
|
+
}), [viewStart, viewEnd]);
|
|
53
57
|
const lit = React.useMemo(() => new Set(highlight ?? []), [highlight]);
|
|
54
58
|
const setEdge = (edge, note) => {
|
|
55
59
|
const next = edge === "start" ? {
|
|
@@ -30,7 +30,7 @@ import * as React from "react";
|
|
|
30
30
|
* <span className={chipVariants({ variant: "tag" })}>mcp</span>
|
|
31
31
|
*/
|
|
32
32
|
declare const chipVariants: (props?: ({
|
|
33
|
-
tone?: "
|
|
33
|
+
tone?: "info" | "warn" | "danger" | "accent" | "ok" | "busy" | "neutral" | null | undefined;
|
|
34
34
|
variant?: "outline" | "tinted" | "tag" | null | undefined;
|
|
35
35
|
interactive?: boolean | null | undefined;
|
|
36
36
|
} & ClassProp) | undefined) => string;
|
package/dist/styles.css
CHANGED
|
@@ -3,13 +3,35 @@
|
|
|
3
3
|
* Consumer app CSS:
|
|
4
4
|
* @import "tailwindcss";
|
|
5
5
|
* @import "@cueplusplus/ui/styles.css";
|
|
6
|
+
* @import "@cueplusplus/theme-cue/theme.css"; (or any theme package)
|
|
6
7
|
*
|
|
7
8
|
* Tailwind v4 does not scan node_modules, so the library registers its own
|
|
8
9
|
* sources: `@source "./"` resolves relative to THIS file, i.e. the shipped
|
|
9
10
|
* dist/, which is where the class strings live after the build.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
/* Two lines, and the first one is the release's whole behaviour change.
|
|
14
|
+
*
|
|
15
|
+
* It used to be `@cueplusplus/tokens/theme.css`, which carried ten palettes and
|
|
16
|
+
* painted `cue` on a bare `:root`. A palette is a package now, so what this
|
|
17
|
+
* library ships is the *blank* base: `theme-base/base.css` declares every colour
|
|
18
|
+
* token at a neutral default and imports `@cueplusplus/tokens/axes.css` for the
|
|
19
|
+
* geometry and type axes. That is why there is no third line — `base.css`
|
|
20
|
+
* carries the axes with it, and `tailwind.css` maps utilities onto token names
|
|
21
|
+
* and pulls nothing.
|
|
22
|
+
*
|
|
23
|
+
* An app that imports this and registers no theme therefore paints greys and one
|
|
24
|
+
* desaturated accent, rather than somebody's brand. To get a palette back:
|
|
25
|
+
* install a theme package, import its stylesheet after this one, and pass its
|
|
26
|
+
* manifest to the provider —
|
|
27
|
+
*
|
|
28
|
+
* @import "@cueplusplus/ui/styles.css";
|
|
29
|
+
* @import "@cueplusplus/theme-cue/theme.css";
|
|
30
|
+
*
|
|
31
|
+
* with `<ThemeProvider themes={[cue]} theme="cue">`. Order matters and is not an
|
|
32
|
+
* accident: `[data-theme]` and `:root` tie at (0,1,0), so a theme's block wins
|
|
33
|
+
* by being declared later. */
|
|
34
|
+
@import "@cueplusplus/theme-base/base.css";
|
|
13
35
|
@import "@cueplusplus/tokens/tailwind.css";
|
|
14
36
|
|
|
15
37
|
@source "./";
|
package/dist/system/density.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Density } from "@cueplusplus/
|
|
2
|
+
import { Density } from "@cueplusplus/theme-base";
|
|
3
3
|
//#region src/system/density.d.ts
|
|
4
4
|
interface DensityProps {
|
|
5
5
|
/**
|
|
@@ -17,14 +17,23 @@ interface DensityProps {
|
|
|
17
17
|
/**
|
|
18
18
|
* Re-scope any subtree to a different density level.
|
|
19
19
|
*
|
|
20
|
-
* Renders a plain `<div data-density="…">`: the geometry
|
|
21
|
-
* re-declared by the attribute selector and inherit
|
|
22
|
-
* needs no JS. The matching React context is
|
|
23
|
-
* JS reads (`useDensity()`,
|
|
24
|
-
*
|
|
20
|
+
* Renders a plain `<div data-density="…" data-cue-theme="…">`: the geometry
|
|
21
|
+
* custom properties are re-declared by the attribute selector and inherit
|
|
22
|
+
* down, so nesting is free and needs no JS. The matching React context is
|
|
23
|
+
* published alongside for the rare JS reads (`useDensity()`,
|
|
24
|
+
* `useControlHeight()`, portal stamping) — the DOM attributes, not the
|
|
25
|
+
* context, are what actually restyle the tree.
|
|
25
26
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
27
|
+
* `data-cue-theme` carries the nearest `<ThemeProvider>`'s name, or is
|
|
28
|
+
* omitted where there is none. It is read by nothing this island does —
|
|
29
|
+
* theme and mode are deliberately *not* re-scoped here, density is the only
|
|
30
|
+
* axis an island may override (spec §6) — it exists so a theme's own
|
|
31
|
+
* generated CSS can address exactly this island regardless of how many
|
|
32
|
+
* *other* themed providers sit between it and the one it belongs to. A CSS
|
|
33
|
+
* ancestor selector can only ask "does some ancestor carry theme `x`", never
|
|
34
|
+
* "is `x` the *nearest* one"; this stamp answers that question the way
|
|
35
|
+
* `useContext` already does, at render time, and hands the answer to CSS as
|
|
36
|
+
* an attribute rather than leaving it unanswerable there.
|
|
28
37
|
*
|
|
29
38
|
* @example
|
|
30
39
|
* <Density density="ultra-compact">
|
package/dist/system/density.js
CHANGED
|
@@ -10,14 +10,6 @@ import { jsx } from "react/jsx-runtime";
|
|
|
10
10
|
* but the level a screen gets when nobody chose is the dense one.
|
|
11
11
|
*/
|
|
12
12
|
const DEFAULT_DENSITY = "compact";
|
|
13
|
-
/** The five levels, as a runtime array for validating persisted input. */
|
|
14
|
-
const DENSITY_LEVELS = [
|
|
15
|
-
"ultra-compact",
|
|
16
|
-
"compact",
|
|
17
|
-
"normal",
|
|
18
|
-
"large",
|
|
19
|
-
"ultra-large"
|
|
20
|
-
];
|
|
21
13
|
/**
|
|
22
14
|
* Nearest ambient density. `null` means "nothing has stamped a level yet", which
|
|
23
15
|
* `useDensity()` reports as {@link DEFAULT_DENSITY}.
|
|
@@ -26,16 +18,45 @@ const DENSITY_LEVELS = [
|
|
|
26
18
|
*/
|
|
27
19
|
const DensityContext = React.createContext(null);
|
|
28
20
|
/**
|
|
21
|
+
* Nearest ambient theme's name, published for exactly one reader: `Density`
|
|
22
|
+
* below, which stamps it onto its island as `data-cue-theme` so a theme's
|
|
23
|
+
* CSS can tell *nearest* theme from *any* theme in the ancestor chain (see
|
|
24
|
+
* `densityRules`/`themeSelector`'s comment). `use-density.ts`'s measuring
|
|
25
|
+
* probe stamps the same attribute from the identical value, read through
|
|
26
|
+
* `ThemeContext` there instead, because it already imports that module with
|
|
27
|
+
* no cycle.
|
|
28
|
+
*
|
|
29
|
+
* Defined here, not in `theme-provider.tsx`, and re-exported from nowhere
|
|
30
|
+
* else. `theme-provider.tsx` already imports `DensityContext` from this
|
|
31
|
+
* module; a context this small living over there and imported back here
|
|
32
|
+
* would cycle the two modules. `null` outside a provider, the same as
|
|
33
|
+
* `ThemeContext`'s own theme field would be if it existed with no provider
|
|
34
|
+
* to require — `Density` used bare, with nothing above it, stamps nothing.
|
|
35
|
+
*
|
|
36
|
+
* Internal: `Density` is the only reader. Not `useTheme()` — that throws
|
|
37
|
+
* outside a provider, and this island has no reason to.
|
|
38
|
+
*/
|
|
39
|
+
const AmbientThemeContext = React.createContext(null);
|
|
40
|
+
/**
|
|
29
41
|
* Re-scope any subtree to a different density level.
|
|
30
42
|
*
|
|
31
|
-
* Renders a plain `<div data-density="…">`: the geometry
|
|
32
|
-
* re-declared by the attribute selector and inherit
|
|
33
|
-
* needs no JS. The matching React context is
|
|
34
|
-
* JS reads (`useDensity()`,
|
|
35
|
-
*
|
|
43
|
+
* Renders a plain `<div data-density="…" data-cue-theme="…">`: the geometry
|
|
44
|
+
* custom properties are re-declared by the attribute selector and inherit
|
|
45
|
+
* down, so nesting is free and needs no JS. The matching React context is
|
|
46
|
+
* published alongside for the rare JS reads (`useDensity()`,
|
|
47
|
+
* `useControlHeight()`, portal stamping) — the DOM attributes, not the
|
|
48
|
+
* context, are what actually restyle the tree.
|
|
36
49
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
50
|
+
* `data-cue-theme` carries the nearest `<ThemeProvider>`'s name, or is
|
|
51
|
+
* omitted where there is none. It is read by nothing this island does —
|
|
52
|
+
* theme and mode are deliberately *not* re-scoped here, density is the only
|
|
53
|
+
* axis an island may override (spec §6) — it exists so a theme's own
|
|
54
|
+
* generated CSS can address exactly this island regardless of how many
|
|
55
|
+
* *other* themed providers sit between it and the one it belongs to. A CSS
|
|
56
|
+
* ancestor selector can only ask "does some ancestor carry theme `x`", never
|
|
57
|
+
* "is `x` the *nearest* one"; this stamp answers that question the way
|
|
58
|
+
* `useContext` already does, at render time, and hands the answer to CSS as
|
|
59
|
+
* an attribute rather than leaving it unanswerable there.
|
|
39
60
|
*
|
|
40
61
|
* @example
|
|
41
62
|
* <Density density="ultra-compact">
|
|
@@ -43,10 +64,12 @@ const DensityContext = React.createContext(null);
|
|
|
43
64
|
* </Density>
|
|
44
65
|
*/
|
|
45
66
|
function Density({ density, children, className, style }) {
|
|
67
|
+
const theme = React.useContext(AmbientThemeContext);
|
|
46
68
|
return /* @__PURE__ */ jsx(DensityContext.Provider, {
|
|
47
69
|
value: density,
|
|
48
70
|
children: /* @__PURE__ */ jsx("div", {
|
|
49
71
|
"data-density": density,
|
|
72
|
+
"data-cue-theme": theme ?? void 0,
|
|
50
73
|
className,
|
|
51
74
|
style,
|
|
52
75
|
children
|
|
@@ -54,4 +77,4 @@ function Density({ density, children, className, style }) {
|
|
|
54
77
|
});
|
|
55
78
|
}
|
|
56
79
|
//#endregion
|
|
57
|
-
export {
|
|
80
|
+
export { AmbientThemeContext, DEFAULT_DENSITY, Density, DensityContext };
|
package/dist/system/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { TokenOverrides } from "./overrides.js";
|
|
1
2
|
import { FontFamilies, ResolvedMode, ThemeContextValue, ThemeProvider, ThemeProviderProps } from "./theme-provider.js";
|
|
2
3
|
import { Density, DensityProps } from "./density.js";
|
|
3
4
|
import { CuePortalFrame, CuePortalFrameProps, useCuePortalProps } from "./portal.js";
|
|
4
5
|
import { DEFAULT_STORAGE_KEY, PersistedPreferences, PrepaintDefaults, PrepaintOptions, prepaintScript } from "./prepaint.js";
|
|
6
|
+
import { ThemeRegistry } from "./vocabulary.js";
|
|
7
|
+
import { useDensities, useFonts, useThemes } from "./theme-registry.js";
|
|
5
8
|
import { ControlSize, useControlHeight, useDensity } from "./use-density.js";
|
|
6
9
|
import { useTheme } from "./use-theme.js";
|
|
7
|
-
import { Density as DensityLevel, FontName, Mode, ThemeName } from "@cueplusplus/
|
|
8
|
-
export { type ControlSize, CuePortalFrame, type CuePortalFrameProps, DEFAULT_STORAGE_KEY, Density, type DensityLevel, type DensityProps, type FontFamilies, type FontName, type Mode, type PersistedPreferences, type PrepaintDefaults, type PrepaintOptions, type ResolvedMode, type ThemeContextValue, type ThemeName, ThemeProvider, type ThemeProviderProps, prepaintScript, useControlHeight, useCuePortalProps, useDensity, useTheme };
|
|
10
|
+
import { Density as DensityLevel, DensityEntry, FontEntry, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
|
|
11
|
+
export { type ControlSize, CuePortalFrame, type CuePortalFrameProps, DEFAULT_STORAGE_KEY, Density, type DensityEntry, type DensityLevel, type DensityProps, type FontEntry, type FontFamilies, type FontName, type Mode, type PersistedPreferences, type PrepaintDefaults, type PrepaintOptions, type ResolvedMode, type ThemeContextValue, type ThemeManifest, type ThemeName, ThemeProvider, type ThemeProviderProps, type ThemeRegistry, type TokenOverrides, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
|
package/dist/system/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Density } from "./density.js";
|
|
2
2
|
import { DEFAULT_STORAGE_KEY, prepaintScript } from "./prepaint.js";
|
|
3
|
+
import { useDensities, useFonts, useThemes } from "./theme-registry.js";
|
|
3
4
|
import { ThemeProvider } from "./theme-provider.js";
|
|
4
5
|
import { CuePortalFrame, useCuePortalProps } from "./portal.js";
|
|
5
6
|
import { useControlHeight, useDensity } from "./use-density.js";
|
|
6
7
|
import { useTheme } from "./use-theme.js";
|
|
7
|
-
export { CuePortalFrame, DEFAULT_STORAGE_KEY, Density, ThemeProvider, prepaintScript, useControlHeight, useCuePortalProps, useDensity, useTheme };
|
|
8
|
+
export { CuePortalFrame, DEFAULT_STORAGE_KEY, Density, ThemeProvider, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import "react";
|
|
2
|
+
import { ColorToken, GeometryToken, ThemeManifest } from "@cueplusplus/theme-base";
|
|
3
|
+
//#region src/system/overrides.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A typed edit over the active theme.
|
|
6
|
+
*
|
|
7
|
+
* Not `ThemeOverrides`: `@cueplusplus/ui/configurator` already exports a type
|
|
8
|
+
* by that name — the serialised edit snapshot its panel writes — and the root
|
|
9
|
+
* barrel re-exports `./system` wholesale, so two public types under one name in
|
|
10
|
+
* one package would be an alias every consumer of both subpaths has to write.
|
|
11
|
+
* This one overrides *tokens*, which is what it is named for.
|
|
12
|
+
*
|
|
13
|
+
* Every key is checked against the token contracts, so a typo is a compile
|
|
14
|
+
* error rather than a custom property nothing reads.
|
|
15
|
+
*/
|
|
16
|
+
interface TokenOverrides {
|
|
17
|
+
/**
|
|
18
|
+
* Colour tokens, per block. Only the block the resolved mode is in is
|
|
19
|
+
* emitted, so a provider under `mode="light"` never publishes the dark edit —
|
|
20
|
+
* which is what makes the two independent rather than a merge.
|
|
21
|
+
*/
|
|
22
|
+
colors?: {
|
|
23
|
+
dark?: Partial<Record<ColorToken, string>>;
|
|
24
|
+
light?: Partial<Record<ColorToken, string>>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Geometry, per rung. The key is a rung name — one of the base five, or one a
|
|
28
|
+
* registered theme adds — and the value only the tokens that move.
|
|
29
|
+
*/
|
|
30
|
+
densities?: Partial<Record<string, Partial<Record<GeometryToken, string>>>>;
|
|
31
|
+
/**
|
|
32
|
+
* Font stacks. These write `--cue-font-sans` / `--cue-font-mono` /
|
|
33
|
+
* `--cue-font-display` directly, and so shadow the pairing on `data-font` for
|
|
34
|
+
* the subtree — see `ThemeProviderProps.overrides`.
|
|
35
|
+
*/
|
|
36
|
+
fonts?: Partial<{
|
|
37
|
+
sans: string;
|
|
38
|
+
mono: string;
|
|
39
|
+
display: string;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { TokenOverrides };
|