@unpunnyfuns/swatchbook-blocks 1.2.0 → 2.0.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/README.md +5 -3
- package/dist/host-e2nSv-sA.mjs +791 -0
- package/dist/host-e2nSv-sA.mjs.map +1 -0
- package/dist/host.d.mts +57 -0
- package/dist/host.mjs +2 -0
- package/dist/index.d.mts +190 -348
- package/dist/index.mjs +485 -1100
- package/dist/index.mjs.map +1 -1
- package/dist/registry-Dm25Go94.d.mts +244 -0
- package/dist/style.css +237 -240
- package/package.json +6 -2
package/dist/index.mjs
CHANGED
|
@@ -1,201 +1,18 @@
|
|
|
1
1
|
import './style.css';
|
|
2
|
-
import { COLOR_FORMATS } from "
|
|
3
|
-
import {
|
|
4
|
-
import { createContext, memo, useCallback, useContext, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState
|
|
2
|
+
import { C as COLOR_FORMATS, S as BorderSample, _ as cssVarAsNumber, a as mergePresenters, b as useRootFontSize, c as ShadowSample, d as OpacitySwatch, f as MotionSample, g as FontWeightSpecimen, h as GradientSwatch, i as PresenterContext, l as TypeSpecimen, m as usePrefersReducedMotion, n as useProjectSource, p as resolveMotionSpec, r as DEFAULT_PRESENTERS, s as usePresenter, u as StrokeSample, v as FontFamilySpecimen, w as formatColor, x as ColorSwatch, y as DimensionSample } from "./host-e2nSv-sA.mjs";
|
|
3
|
+
import { parseColor } from "@unpunnyfuns/swatchbook-core/format-color";
|
|
4
|
+
import { createContext, memo, useCallback, useContext, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { formatDimension, formatSubColor, formatTokenValue, formatTokenValue as formatTokenValue$2 } from "@unpunnyfuns/swatchbook-core/token-value-css";
|
|
7
|
+
import cx from "clsx";
|
|
8
|
+
import { dataAttr } from "@unpunnyfuns/swatchbook-core/data-attr";
|
|
5
9
|
import { getVariance, listPaths, resolveAllWithProvenanceAt } from "@unpunnyfuns/swatchbook-core/graph";
|
|
6
10
|
import { cssVarRef } from "@unpunnyfuns/swatchbook-core/css-var";
|
|
7
11
|
import { SWATCHBOOK_STYLE_ELEMENT_ID, ensureStyleElement } from "@unpunnyfuns/swatchbook-core/style-element";
|
|
8
12
|
import { tupleToName } from "@unpunnyfuns/swatchbook-core/themes";
|
|
9
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
import { dataAttr } from "@unpunnyfuns/swatchbook-core/data-attr";
|
|
11
13
|
import { matchPath } from "@unpunnyfuns/swatchbook-core/match-path";
|
|
12
14
|
import { fuzzyFilter } from "@unpunnyfuns/swatchbook-core/fuzzy";
|
|
13
|
-
import cx from "clsx";
|
|
14
15
|
import { useWindowVirtualizer } from "@tanstack/react-virtual";
|
|
15
|
-
//#region src/internal/channel.ts
|
|
16
|
-
let channel = null;
|
|
17
|
-
const pending = /* @__PURE__ */ new Set();
|
|
18
|
-
/**
|
|
19
|
-
* Host adapter API. The single most load-bearing function in the host
|
|
20
|
-
* contract — the addon calls it once at preview init.
|
|
21
|
-
*
|
|
22
|
-
* Inject the host channel. Runs any subscribers that queued before the
|
|
23
|
-
* channel existed, then clears the queue. The host contract does NOT dedupe:
|
|
24
|
-
* on HMR a subscriber's module re-runs {@link onChannel}, which re-attaches
|
|
25
|
-
* immediately once the channel is set, so each subscriber MUST guard its own
|
|
26
|
-
* attach to stay single-wired.
|
|
27
|
-
*/
|
|
28
|
-
function registerChannel(next) {
|
|
29
|
-
channel = next;
|
|
30
|
-
for (const attach of pending) attach(next);
|
|
31
|
-
pending.clear();
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Host adapter API — see {@link registerChannel}.
|
|
35
|
-
*
|
|
36
|
-
* Attach a subscriber to the host channel now if one is registered, else queue
|
|
37
|
-
* it to run the moment {@link registerChannel} fires. Blocks call this at
|
|
38
|
-
* module load; the queue absorbs the gap until the addon injects the channel.
|
|
39
|
-
*/
|
|
40
|
-
function onChannel(attach) {
|
|
41
|
-
if (channel) attach(channel);
|
|
42
|
-
else pending.add(attach);
|
|
43
|
-
}
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/internal/channel-globals.ts
|
|
46
|
-
const AXES_GLOBAL_KEY = "swatchbookAxes";
|
|
47
|
-
const COLOR_FORMAT_GLOBAL_KEY = "swatchbookColorFormat";
|
|
48
|
-
let snapshot$1 = {
|
|
49
|
-
axes: null,
|
|
50
|
-
format: null
|
|
51
|
-
};
|
|
52
|
-
const listeners$1 = /* @__PURE__ */ new Set();
|
|
53
|
-
let subscribed$1 = false;
|
|
54
|
-
function isColorFormat(value) {
|
|
55
|
-
return typeof value === "string" && COLOR_FORMATS.includes(value);
|
|
56
|
-
}
|
|
57
|
-
function attach$1(channel) {
|
|
58
|
-
if (subscribed$1) return;
|
|
59
|
-
subscribed$1 = true;
|
|
60
|
-
let lastFingerprint = "";
|
|
61
|
-
const onGlobals = (payload) => {
|
|
62
|
-
const globals = payload.globals;
|
|
63
|
-
if (!globals) return;
|
|
64
|
-
const incomingAxes = globals[AXES_GLOBAL_KEY];
|
|
65
|
-
const incomingFormat = globals[COLOR_FORMAT_GLOBAL_KEY];
|
|
66
|
-
const nextAxes = incomingAxes && typeof incomingAxes === "object" ? incomingAxes : snapshot$1.axes;
|
|
67
|
-
const nextFormat = isColorFormat(incomingFormat) ? incomingFormat : snapshot$1.format;
|
|
68
|
-
const fingerprint = `${nextFormat ?? ""}|${nextAxes ? JSON.stringify(nextAxes) : ""}`;
|
|
69
|
-
if (fingerprint === lastFingerprint) return;
|
|
70
|
-
lastFingerprint = fingerprint;
|
|
71
|
-
snapshot$1 = {
|
|
72
|
-
axes: nextAxes,
|
|
73
|
-
format: nextFormat
|
|
74
|
-
};
|
|
75
|
-
for (const cb of listeners$1) cb();
|
|
76
|
-
};
|
|
77
|
-
channel.on("globalsUpdated", onGlobals);
|
|
78
|
-
channel.on("updateGlobals", onGlobals);
|
|
79
|
-
channel.on("setGlobals", onGlobals);
|
|
80
|
-
}
|
|
81
|
-
onChannel(attach$1);
|
|
82
|
-
function subscribe$2(cb) {
|
|
83
|
-
listeners$1.add(cb);
|
|
84
|
-
return () => {
|
|
85
|
-
listeners$1.delete(cb);
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function getSnapshot$1() {
|
|
89
|
-
return snapshot$1;
|
|
90
|
-
}
|
|
91
|
-
function getServerSnapshot$1() {
|
|
92
|
-
return snapshot$1;
|
|
93
|
-
}
|
|
94
|
-
function useChannelGlobals() {
|
|
95
|
-
return useSyncExternalStore(subscribe$2, getSnapshot$1, getServerSnapshot$1);
|
|
96
|
-
}
|
|
97
|
-
//#endregion
|
|
98
|
-
//#region src/internal/channel-tokens.ts
|
|
99
|
-
/**
|
|
100
|
-
* Host adapter API — same integration tier as {@link BlockChannel} /
|
|
101
|
-
* {@link registerChannel} / {@link onChannel}, distinct from the
|
|
102
|
-
* components/hooks/`SwatchbookProvider` surface MDX/story authors use.
|
|
103
|
-
* Most consumers never touch this; it exists for whoever builds the next
|
|
104
|
-
* host integration.
|
|
105
|
-
*
|
|
106
|
-
* Live token snapshot backed by the addon's preview dev-time HMR event.
|
|
107
|
-
*
|
|
108
|
-
* The initial snapshot is *injected* by the addon preview via
|
|
109
|
-
* {@link registerTokenSource} rather than imported from the addon's
|
|
110
|
-
* `virtual:swatchbook/tokens` build artifact — so blocks carries no
|
|
111
|
-
* dependency on that module and imports cleanly standalone (outside
|
|
112
|
-
* Storybook, in unit tests, in the docs site). Until something registers
|
|
113
|
-
* a source, blocks render from empty defaults.
|
|
114
|
-
*
|
|
115
|
-
* For dev-time updates this module subscribes to `TOKENS_UPDATED_EVENT`
|
|
116
|
-
* on Storybook's channel (which the addon preview re-broadcasts from its
|
|
117
|
-
* own HMR listener) and exposes the latest snapshot via
|
|
118
|
-
* `useSyncExternalStore`, so hooks re-render in place on each token save.
|
|
119
|
-
*/
|
|
120
|
-
const TOKENS_UPDATED_EVENT = "swatchbook/tokens-updated";
|
|
121
|
-
let snapshot = {
|
|
122
|
-
axes: [],
|
|
123
|
-
presets: [],
|
|
124
|
-
diagnostics: [],
|
|
125
|
-
css: "",
|
|
126
|
-
cssVarPrefix: "",
|
|
127
|
-
indicators: {},
|
|
128
|
-
listing: {},
|
|
129
|
-
tokenGraph: {
|
|
130
|
-
nodes: {},
|
|
131
|
-
axes: [],
|
|
132
|
-
axisDefaults: {},
|
|
133
|
-
axisContexts: {}
|
|
134
|
-
},
|
|
135
|
-
defaultTuple: {},
|
|
136
|
-
defaultColorFormat: "hex",
|
|
137
|
-
version: 0
|
|
138
|
-
};
|
|
139
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
140
|
-
let subscribed = false;
|
|
141
|
-
function applyPatch(patch) {
|
|
142
|
-
snapshot = {
|
|
143
|
-
axes: patch.axes ?? snapshot.axes,
|
|
144
|
-
presets: patch.presets ?? snapshot.presets,
|
|
145
|
-
diagnostics: patch.diagnostics ?? snapshot.diagnostics,
|
|
146
|
-
css: patch.css ?? snapshot.css,
|
|
147
|
-
cssVarPrefix: patch.cssVarPrefix ?? snapshot.cssVarPrefix,
|
|
148
|
-
indicators: patch.indicators ?? snapshot.indicators,
|
|
149
|
-
listing: patch.listing ?? snapshot.listing,
|
|
150
|
-
tokenGraph: patch.tokenGraph ?? snapshot.tokenGraph,
|
|
151
|
-
defaultTuple: patch.defaultTuple ?? snapshot.defaultTuple,
|
|
152
|
-
defaultColorFormat: patch.defaultColorFormat ?? snapshot.defaultColorFormat,
|
|
153
|
-
version: snapshot.version + 1
|
|
154
|
-
};
|
|
155
|
-
for (const cb of listeners) cb();
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Host adapter API — same integration tier as {@link registerChannel}.
|
|
159
|
-
*
|
|
160
|
-
* Seed the initial token snapshot. The addon preview calls this once at
|
|
161
|
-
* init with the build-time `virtual:swatchbook/tokens` data. Keeping the
|
|
162
|
-
* virtual-module read on the addon side (the package that owns it) lets
|
|
163
|
-
* blocks import cleanly without it. No-op fields fall back to the current
|
|
164
|
-
* snapshot, so a partial source is safe.
|
|
165
|
-
*/
|
|
166
|
-
function registerTokenSource(source) {
|
|
167
|
-
applyPatch(source);
|
|
168
|
-
}
|
|
169
|
-
function attach(channel) {
|
|
170
|
-
if (subscribed) return;
|
|
171
|
-
subscribed = true;
|
|
172
|
-
channel.on(TOKENS_UPDATED_EVENT, (payload) => {
|
|
173
|
-
applyPatch(payload);
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
onChannel(attach);
|
|
177
|
-
function subscribe$1(cb) {
|
|
178
|
-
listeners.add(cb);
|
|
179
|
-
return () => {
|
|
180
|
-
listeners.delete(cb);
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
function getSnapshot() {
|
|
184
|
-
return snapshot;
|
|
185
|
-
}
|
|
186
|
-
function getServerSnapshot() {
|
|
187
|
-
return snapshot;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Host adapter API — same integration tier as {@link registerChannel}. Most
|
|
191
|
-
* consumers read project data through `useSwatchbookData()` /
|
|
192
|
-
* `SwatchbookProvider` instead; this is the internal subscription
|
|
193
|
-
* `useProject()`'s fallback path uses to read the live channel-fed snapshot.
|
|
194
|
-
*/
|
|
195
|
-
function useTokenSnapshot() {
|
|
196
|
-
return useSyncExternalStore(subscribe$1, getSnapshot, getServerSnapshot);
|
|
197
|
-
}
|
|
198
|
-
//#endregion
|
|
199
16
|
//#region src/contexts.ts
|
|
200
17
|
/**
|
|
201
18
|
* Context carrying the full {@link ProjectSnapshot}. `null` sentinel lets
|
|
@@ -235,12 +52,13 @@ function useActiveAxes() {
|
|
|
235
52
|
*
|
|
236
53
|
* Sits in the middle of the precedence chain a block resolves via
|
|
237
54
|
* `colorFormat ?? useColorFormat()`: a block's own `colorFormat` prop wins
|
|
238
|
-
* over this context, which wins over the active
|
|
55
|
+
* over this context, which wins over the active source's
|
|
239
56
|
* `defaultColorFormat` (from `Config.defaultColorFormat`). "Active
|
|
240
|
-
*
|
|
241
|
-
* `SwatchbookProvider` when a story decorator mounted one,
|
|
242
|
-
*
|
|
243
|
-
* provider) read through `useProject()`'s
|
|
57
|
+
* source" is whichever one is actually feeding the render:
|
|
58
|
+
* `SwatchbookProvider`'s snapshot when a story decorator mounted one,
|
|
59
|
+
* otherwise the ambient `ProjectSource` (`#/host.ts`) that MDX-embedded
|
|
60
|
+
* blocks (no `<Story/>`, no provider) read through `useProject()`'s
|
|
61
|
+
* fallback path.
|
|
244
62
|
*
|
|
245
63
|
* Runs through plain React context rather than Storybook's `useGlobals` so
|
|
246
64
|
* per-story seeded globals flow through on first render and the same hook
|
|
@@ -249,223 +67,17 @@ function useActiveAxes() {
|
|
|
249
67
|
*/
|
|
250
68
|
const ColorFormatContext = createContext(null);
|
|
251
69
|
/**
|
|
252
|
-
* Resolves the color-display format from the context/snapshot/
|
|
253
|
-
* chain: `ColorFormatContext` →
|
|
254
|
-
* `'hex'`. Composing blocks
|
|
255
|
-
*
|
|
70
|
+
* Resolves the color-display format from the context/snapshot/source
|
|
71
|
+
* chain: `ColorFormatContext` → provider snapshot's `defaultColorFormat`
|
|
72
|
+
* → ambient source's `defaultColorFormat` → `'hex'`. Composing blocks
|
|
73
|
+
* read `colorFormat ?? useColorFormat()` to give their own `colorFormat`
|
|
74
|
+
* prop top precedence over this chain.
|
|
256
75
|
*/
|
|
257
76
|
function useColorFormat() {
|
|
258
77
|
const contextValue = useContext(ColorFormatContext);
|
|
259
|
-
const channelGlobals = useChannelGlobals();
|
|
260
78
|
const snapshot = useContext(SwatchbookContext);
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
return contextValue ?? channelGlobals.format ?? activeDefault ?? "hex";
|
|
264
|
-
}
|
|
265
|
-
//#endregion
|
|
266
|
-
//#region src/internal/use-project.ts
|
|
267
|
-
function computeVarianceByPath(graph) {
|
|
268
|
-
if (!graph) return {};
|
|
269
|
-
const out = {};
|
|
270
|
-
for (const path of listPaths(graph)) out[path] = getVariance(graph, path);
|
|
271
|
-
return out;
|
|
272
|
-
}
|
|
273
|
-
function ensureStylesheet(css) {
|
|
274
|
-
ensureStyleElement(SWATCHBOOK_STYLE_ELEMENT_ID, css);
|
|
275
|
-
}
|
|
276
|
-
function defaultTuple(axes) {
|
|
277
|
-
const out = {};
|
|
278
|
-
for (const axis of axes) out[axis.name] = axis.default;
|
|
279
|
-
return out;
|
|
280
|
-
}
|
|
281
|
-
function makeResolveAt(graph) {
|
|
282
|
-
if (!graph) return () => ({});
|
|
283
|
-
return (tuple) => resolveAllWithProvenanceAt(graph, tuple);
|
|
284
|
-
}
|
|
285
|
-
function snapshotResolveAt(snapshot) {
|
|
286
|
-
if (snapshot.resolveAt) return snapshot.resolveAt;
|
|
287
|
-
return makeResolveAt(snapshot.tokenGraph);
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Reads project data either from a mounted {@link SwatchbookProvider}
|
|
291
|
-
* (preferred — the addon's preview decorator installs one around every
|
|
292
|
-
* story) or, when no provider is present, from the virtual module plus
|
|
293
|
-
* Storybook globals directly.
|
|
294
|
-
*
|
|
295
|
-
* The provider-less path is what makes the hook safe to call from MDX
|
|
296
|
-
* doc blocks and autodocs renders where no story is active. It
|
|
297
|
-
* self-mounts the injected snapshot's per-theme CSS and tracks the active
|
|
298
|
-
* tuple via the injected channel's `globalsUpdated` event, since a
|
|
299
|
-
* story-scoped globals hook only works while a story is rendering.
|
|
300
|
-
*/
|
|
301
|
-
function useProject() {
|
|
302
|
-
const snapshot = useOptionalSwatchbookData();
|
|
303
|
-
const axes = snapshot?.axes;
|
|
304
|
-
const activeAxes = snapshot?.activeAxes;
|
|
305
|
-
const activeTheme = snapshot?.activeTheme;
|
|
306
|
-
const diagnostics = snapshot?.diagnostics;
|
|
307
|
-
const cssVarPrefix = snapshot?.cssVarPrefix;
|
|
308
|
-
const indicators = snapshot?.indicators;
|
|
309
|
-
const listing = snapshot?.listing;
|
|
310
|
-
const tokenGraph = snapshot?.tokenGraph;
|
|
311
|
-
const resolveAt = useMemo(() => {
|
|
312
|
-
if (!snapshot) return null;
|
|
313
|
-
return snapshotResolveAt(snapshot);
|
|
314
|
-
}, [tokenGraph, activeTheme]);
|
|
315
|
-
const derivedVarianceByPath = useMemo(() => computeVarianceByPath(tokenGraph), [tokenGraph]);
|
|
316
|
-
const providerData = useMemo(() => {
|
|
317
|
-
if (!snapshot || !resolveAt || !axes || !activeAxes) return null;
|
|
318
|
-
return {
|
|
319
|
-
activeTheme: activeTheme ?? "",
|
|
320
|
-
activeAxes,
|
|
321
|
-
axes,
|
|
322
|
-
resolved: resolveAt(activeAxes),
|
|
323
|
-
diagnostics: diagnostics ?? [],
|
|
324
|
-
cssVarPrefix: cssVarPrefix ?? "",
|
|
325
|
-
indicators: indicators ?? {},
|
|
326
|
-
listing: listing ?? {},
|
|
327
|
-
varianceByPath: derivedVarianceByPath,
|
|
328
|
-
resolveAt
|
|
329
|
-
};
|
|
330
|
-
}, [
|
|
331
|
-
snapshot,
|
|
332
|
-
resolveAt,
|
|
333
|
-
axes,
|
|
334
|
-
activeTheme,
|
|
335
|
-
activeAxes,
|
|
336
|
-
diagnostics,
|
|
337
|
-
cssVarPrefix,
|
|
338
|
-
indicators,
|
|
339
|
-
listing,
|
|
340
|
-
derivedVarianceByPath,
|
|
341
|
-
tokenGraph
|
|
342
|
-
]);
|
|
343
|
-
const fallback = useVirtualModuleFallback(snapshot === null);
|
|
344
|
-
return providerData ?? fallback;
|
|
345
|
-
}
|
|
346
|
-
function useVirtualModuleFallback(enabled) {
|
|
347
|
-
const contextThemeName = useActiveTheme();
|
|
348
|
-
const contextAxes = useActiveAxes();
|
|
349
|
-
const channelGlobals = useChannelGlobals();
|
|
350
|
-
const tokens = useTokenSnapshot();
|
|
351
|
-
useEffect(() => {
|
|
352
|
-
if (!enabled) return;
|
|
353
|
-
ensureStylesheet(tokens.css);
|
|
354
|
-
}, [enabled, tokens.css]);
|
|
355
|
-
const activeAxes = useMemo(() => {
|
|
356
|
-
return Object.keys(contextAxes).length > 0 ? { ...contextAxes } : channelGlobals.axes ?? defaultTuple(tokens.axes);
|
|
357
|
-
}, [
|
|
358
|
-
contextAxes,
|
|
359
|
-
channelGlobals.axes,
|
|
360
|
-
tokens.axes
|
|
361
|
-
]);
|
|
362
|
-
const activeTheme = contextThemeName || tupleToName(tokens.axes, activeAxes);
|
|
363
|
-
const resolveAt = useMemo(() => makeResolveAt(tokens.tokenGraph), [tokens.tokenGraph]);
|
|
364
|
-
const fallbackVarianceByPath = useMemo(() => computeVarianceByPath(tokens.tokenGraph), [tokens.tokenGraph]);
|
|
365
|
-
return useMemo(() => ({
|
|
366
|
-
activeTheme,
|
|
367
|
-
activeAxes,
|
|
368
|
-
axes: tokens.axes,
|
|
369
|
-
resolved: resolveAt(activeAxes),
|
|
370
|
-
diagnostics: tokens.diagnostics,
|
|
371
|
-
cssVarPrefix: tokens.cssVarPrefix,
|
|
372
|
-
indicators: tokens.indicators,
|
|
373
|
-
listing: tokens.listing,
|
|
374
|
-
varianceByPath: fallbackVarianceByPath,
|
|
375
|
-
resolveAt
|
|
376
|
-
}), [
|
|
377
|
-
activeTheme,
|
|
378
|
-
activeAxes,
|
|
379
|
-
tokens.axes,
|
|
380
|
-
tokens.diagnostics,
|
|
381
|
-
tokens.cssVarPrefix,
|
|
382
|
-
tokens.indicators,
|
|
383
|
-
tokens.listing,
|
|
384
|
-
fallbackVarianceByPath,
|
|
385
|
-
resolveAt
|
|
386
|
-
]);
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Resolve a token's CSS var reference, preferring the authoritative name
|
|
390
|
-
* emitted by `@terrazzo/plugin-css` (as recorded by
|
|
391
|
-
* `@terrazzo/plugin-token-listing` in the snapshot's `listing` field).
|
|
392
|
-
* Falls back to `cssVarRef` when the listing lacks an entry for this
|
|
393
|
-
* path — covers non-resolver projects, hand-built snapshots, and any
|
|
394
|
-
* listing-plugin miss.
|
|
395
|
-
*/
|
|
396
|
-
function resolveCssVar(path, project) {
|
|
397
|
-
const listed = project.listing[path]?.names?.["css"];
|
|
398
|
-
if (listed) return `var(${listed})`;
|
|
399
|
-
return cssVarRef(path, project.cssVarPrefix);
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Resolve a color value's display string + gamut flag, preferring the
|
|
403
|
-
* listing's `previewValue` when the user's active color-format matches
|
|
404
|
-
* plugin-css's output (hex). For any other format we fall back to
|
|
405
|
-
* `formatColor` so the toolbar's inspection modes (rgb / hsl / oklch /
|
|
406
|
-
* raw) keep working — the listing has only one canonical format.
|
|
407
|
-
*
|
|
408
|
-
* Pass `path === undefined` when resolving a sub-color inside a composite
|
|
409
|
-
* (shadow / border / gradient stop): composites' `previewValue` covers
|
|
410
|
-
* the whole token's rendering, not the individual channel, so there's no
|
|
411
|
-
* listing entry to key against.
|
|
412
|
-
*/
|
|
413
|
-
function resolveColorValue(path, raw, colorFormat, project) {
|
|
414
|
-
if (path !== void 0 && colorFormat === "hex") {
|
|
415
|
-
const listed = project.listing[path]?.previewValue;
|
|
416
|
-
if (typeof listed === "string") return {
|
|
417
|
-
value: listed,
|
|
418
|
-
outOfGamut: false
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
return formatColor(raw, colorFormat);
|
|
422
|
-
}
|
|
423
|
-
//#endregion
|
|
424
|
-
//#region src/border-preview/BorderSample.tsx
|
|
425
|
-
/**
|
|
426
|
-
* Pure derivation of a single border token's sample data from resolved
|
|
427
|
-
* project data. Extracted so it is unit-testable without React or a store.
|
|
428
|
-
*/
|
|
429
|
-
function deriveBorderSample(path, project) {
|
|
430
|
-
return { cssVar: resolveCssVar(path, project) };
|
|
431
|
-
}
|
|
432
|
-
/** Pure presentation for a single border token's sample. Renders from plain props. */
|
|
433
|
-
function BorderSampleView({ cssVar }) {
|
|
434
|
-
return /* @__PURE__ */ jsx("div", {
|
|
435
|
-
className: "sb-border-sample",
|
|
436
|
-
style: { border: cssVar },
|
|
437
|
-
"aria-hidden": true
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
function BorderSample({ path }) {
|
|
441
|
-
const { cssVar } = deriveBorderSample(path, useProject());
|
|
442
|
-
return /* @__PURE__ */ jsx(BorderSampleView, { cssVar });
|
|
443
|
-
}
|
|
444
|
-
//#endregion
|
|
445
|
-
//#region src/internal/composite-sample-format.ts
|
|
446
|
-
/**
|
|
447
|
-
* Display a composite sub-field dimension (shadow offset / blur / spread,
|
|
448
|
-
* border width, …) in the preview tables. Renders `—` for a missing
|
|
449
|
-
* sub-field and falls back to JSON for shapes it doesn't recognize.
|
|
450
|
-
*
|
|
451
|
-
* Distinct from `format-token-value`'s internal `formatDimension`, which
|
|
452
|
-
* formats a token's top-level value and has no `—` placeholder — these are
|
|
453
|
-
* the per-layer sample formatters shared by `ShadowPreview` + `BorderPreview`.
|
|
454
|
-
*/
|
|
455
|
-
function formatDimension$1(raw) {
|
|
456
|
-
if (raw == null) return "—";
|
|
457
|
-
if (typeof raw === "number") return String(raw);
|
|
458
|
-
if (typeof raw === "string") return raw;
|
|
459
|
-
if (typeof raw === "object") {
|
|
460
|
-
const v = raw;
|
|
461
|
-
if (typeof v.value === "number" && typeof v.unit === "string") return `${v.value}${v.unit}`;
|
|
462
|
-
}
|
|
463
|
-
return JSON.stringify(raw);
|
|
464
|
-
}
|
|
465
|
-
/** Display a composite sub-field color via the active format; `—` when absent. */
|
|
466
|
-
function formatSubColor(raw, format) {
|
|
467
|
-
if (raw == null) return "—";
|
|
468
|
-
return formatColor(raw, format).value;
|
|
79
|
+
const source = useProjectSource();
|
|
80
|
+
return contextValue ?? snapshot?.defaultColorFormat ?? source.defaultColorFormat ?? "hex";
|
|
469
81
|
}
|
|
470
82
|
//#endregion
|
|
471
83
|
//#region src/internal/data-attr.ts
|
|
@@ -622,6 +234,163 @@ function toDisplayable(v) {
|
|
|
622
234
|
return String(v ?? "");
|
|
623
235
|
}
|
|
624
236
|
//#endregion
|
|
237
|
+
//#region src/internal/use-project.ts
|
|
238
|
+
function computeVarianceByPath(graph) {
|
|
239
|
+
if (!graph) return {};
|
|
240
|
+
const out = {};
|
|
241
|
+
for (const path of listPaths(graph)) out[path] = getVariance(graph, path);
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
function ensureStylesheet(css) {
|
|
245
|
+
ensureStyleElement(SWATCHBOOK_STYLE_ELEMENT_ID, css);
|
|
246
|
+
}
|
|
247
|
+
function defaultTuple(axes) {
|
|
248
|
+
const out = {};
|
|
249
|
+
for (const axis of axes) out[axis.name] = axis.default;
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
function makeResolveAt(graph) {
|
|
253
|
+
if (!graph) return () => ({});
|
|
254
|
+
return (tuple) => resolveAllWithProvenanceAt(graph, tuple);
|
|
255
|
+
}
|
|
256
|
+
function snapshotResolveAt(snapshot) {
|
|
257
|
+
if (snapshot.resolveAt) return snapshot.resolveAt;
|
|
258
|
+
return makeResolveAt(snapshot.tokenGraph);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Reads project data either from a mounted {@link SwatchbookProvider}
|
|
262
|
+
* (preferred: the addon's preview decorator installs one around every
|
|
263
|
+
* story) or, when no provider is present, from the ambient project source
|
|
264
|
+
* a host pushes into via `registerProjectSource` (`#/host.ts`).
|
|
265
|
+
*
|
|
266
|
+
* The provider-less path is what makes the hook safe to call from MDX
|
|
267
|
+
* doc blocks and autodocs renders where no story is active. It
|
|
268
|
+
* self-mounts the source's per-theme CSS and tracks the active tuple via
|
|
269
|
+
* the source's `activeAxes`, since a story-scoped globals hook only works
|
|
270
|
+
* while a story is rendering.
|
|
271
|
+
*/
|
|
272
|
+
function useProject() {
|
|
273
|
+
const snapshot = useOptionalSwatchbookData();
|
|
274
|
+
const axes = snapshot?.axes;
|
|
275
|
+
const activeAxes = snapshot?.activeAxes;
|
|
276
|
+
const activeTheme = snapshot?.activeTheme;
|
|
277
|
+
const diagnostics = snapshot?.diagnostics;
|
|
278
|
+
const cssVarPrefix = snapshot?.cssVarPrefix;
|
|
279
|
+
const indicators = snapshot?.indicators;
|
|
280
|
+
const listing = snapshot?.listing;
|
|
281
|
+
const tokenGraph = snapshot?.tokenGraph;
|
|
282
|
+
const resolveAt = useMemo(() => {
|
|
283
|
+
if (!snapshot) return null;
|
|
284
|
+
return snapshotResolveAt(snapshot);
|
|
285
|
+
}, [tokenGraph, activeTheme]);
|
|
286
|
+
const derivedVarianceByPath = useMemo(() => computeVarianceByPath(tokenGraph), [tokenGraph]);
|
|
287
|
+
const providerData = useMemo(() => {
|
|
288
|
+
if (!snapshot || !resolveAt || !axes || !activeAxes) return null;
|
|
289
|
+
return {
|
|
290
|
+
activeTheme: activeTheme ?? "",
|
|
291
|
+
activeAxes,
|
|
292
|
+
axes,
|
|
293
|
+
resolved: resolveAt(activeAxes),
|
|
294
|
+
diagnostics: diagnostics ?? [],
|
|
295
|
+
cssVarPrefix: cssVarPrefix ?? "",
|
|
296
|
+
indicators: indicators ?? {},
|
|
297
|
+
listing: listing ?? {},
|
|
298
|
+
varianceByPath: derivedVarianceByPath,
|
|
299
|
+
resolveAt
|
|
300
|
+
};
|
|
301
|
+
}, [
|
|
302
|
+
snapshot,
|
|
303
|
+
resolveAt,
|
|
304
|
+
axes,
|
|
305
|
+
activeTheme,
|
|
306
|
+
activeAxes,
|
|
307
|
+
diagnostics,
|
|
308
|
+
cssVarPrefix,
|
|
309
|
+
indicators,
|
|
310
|
+
listing,
|
|
311
|
+
derivedVarianceByPath,
|
|
312
|
+
tokenGraph
|
|
313
|
+
]);
|
|
314
|
+
const fallback = useVirtualModuleFallback(snapshot === null);
|
|
315
|
+
return providerData ?? fallback;
|
|
316
|
+
}
|
|
317
|
+
function useVirtualModuleFallback(enabled) {
|
|
318
|
+
const contextThemeName = useActiveTheme();
|
|
319
|
+
const contextAxes = useActiveAxes();
|
|
320
|
+
const source = useProjectSource();
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
if (!enabled) return;
|
|
323
|
+
ensureStylesheet(source.css);
|
|
324
|
+
}, [enabled, source.css]);
|
|
325
|
+
const activeAxes = useMemo(() => {
|
|
326
|
+
return Object.keys(contextAxes).length > 0 ? { ...contextAxes } : source.activeAxes ?? defaultTuple(source.axes);
|
|
327
|
+
}, [
|
|
328
|
+
contextAxes,
|
|
329
|
+
source.activeAxes,
|
|
330
|
+
source.axes
|
|
331
|
+
]);
|
|
332
|
+
const activeTheme = contextThemeName || tupleToName(source.axes, activeAxes);
|
|
333
|
+
const resolveAt = useMemo(() => makeResolveAt(source.tokenGraph), [source.tokenGraph]);
|
|
334
|
+
const fallbackVarianceByPath = useMemo(() => computeVarianceByPath(source.tokenGraph), [source.tokenGraph]);
|
|
335
|
+
return useMemo(() => ({
|
|
336
|
+
activeTheme,
|
|
337
|
+
activeAxes,
|
|
338
|
+
axes: source.axes,
|
|
339
|
+
resolved: resolveAt(activeAxes),
|
|
340
|
+
diagnostics: source.diagnostics,
|
|
341
|
+
cssVarPrefix: source.cssVarPrefix,
|
|
342
|
+
indicators: source.indicators,
|
|
343
|
+
listing: source.listing,
|
|
344
|
+
varianceByPath: fallbackVarianceByPath,
|
|
345
|
+
resolveAt
|
|
346
|
+
}), [
|
|
347
|
+
activeTheme,
|
|
348
|
+
activeAxes,
|
|
349
|
+
source.axes,
|
|
350
|
+
source.diagnostics,
|
|
351
|
+
source.cssVarPrefix,
|
|
352
|
+
source.indicators,
|
|
353
|
+
source.listing,
|
|
354
|
+
fallbackVarianceByPath,
|
|
355
|
+
resolveAt
|
|
356
|
+
]);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Resolve a token's CSS var reference, preferring the authoritative name
|
|
360
|
+
* emitted by `@terrazzo/plugin-css` (as recorded by
|
|
361
|
+
* `@terrazzo/plugin-token-listing` in the snapshot's `listing` field).
|
|
362
|
+
* Falls back to `cssVarRef` when the listing lacks an entry for this
|
|
363
|
+
* path — covers non-resolver projects, hand-built snapshots, and any
|
|
364
|
+
* listing-plugin miss.
|
|
365
|
+
*/
|
|
366
|
+
function resolveCssVar(path, project) {
|
|
367
|
+
const listed = project.listing[path]?.names?.["css"];
|
|
368
|
+
if (listed) return `var(${listed})`;
|
|
369
|
+
return cssVarRef(path, project.cssVarPrefix);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Resolve a color value's display string + gamut flag, preferring the
|
|
373
|
+
* listing's `previewValue` when the user's active color-format matches
|
|
374
|
+
* plugin-css's output (hex). For any other format we fall back to
|
|
375
|
+
* `formatColor` so the toolbar's inspection modes (rgb / hsl / oklch /
|
|
376
|
+
* raw) keep working — the listing has only one canonical format.
|
|
377
|
+
*
|
|
378
|
+
* Pass `path === undefined` when resolving a sub-color inside a composite
|
|
379
|
+
* (shadow / border / gradient stop): composites' `previewValue` covers
|
|
380
|
+
* the whole token's rendering, not the individual channel, so there's no
|
|
381
|
+
* listing entry to key against.
|
|
382
|
+
*/
|
|
383
|
+
function resolveColorValue(path, raw, colorFormat, project) {
|
|
384
|
+
if (path !== void 0 && colorFormat === "hex") {
|
|
385
|
+
const listed = project.listing[path]?.previewValue;
|
|
386
|
+
if (typeof listed === "string") return {
|
|
387
|
+
value: listed,
|
|
388
|
+
outOfGamut: false
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return formatColor(raw, colorFormat);
|
|
392
|
+
}
|
|
393
|
+
//#endregion
|
|
625
394
|
//#region src/BorderPreview.tsx
|
|
626
395
|
/**
|
|
627
396
|
* Pure derivation of the preview's display rows from resolved project data.
|
|
@@ -639,7 +408,8 @@ function deriveBorderRows(resolved, project, { filter, sortBy, sortDir, colorFor
|
|
|
639
408
|
return {
|
|
640
409
|
path,
|
|
641
410
|
cssVar: resolveCssVar(path, project),
|
|
642
|
-
|
|
411
|
+
token,
|
|
412
|
+
width: formatDimension(value.width),
|
|
643
413
|
style: value.style != null ? String(value.style) : "—",
|
|
644
414
|
color: formatSubColor(value.color, colorFormat)
|
|
645
415
|
};
|
|
@@ -647,10 +417,11 @@ function deriveBorderRows(resolved, project, { filter, sortBy, sortDir, colorFor
|
|
|
647
417
|
}
|
|
648
418
|
/**
|
|
649
419
|
* Pure presentation for the border preview. Renders from plain props;
|
|
650
|
-
* composes the connected `BorderSample` as a child
|
|
651
|
-
*
|
|
420
|
+
* composes the connected `BorderSample` as a child, feeding it this row's
|
|
421
|
+
* already-resolved `token`/`cssVar` per the presenter contract.
|
|
652
422
|
*/
|
|
653
|
-
function BorderPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter, caption }) {
|
|
423
|
+
function BorderPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, colorFormat, filter, caption }) {
|
|
424
|
+
const Sample = usePresenter("border");
|
|
654
425
|
const captionText = caption ?? `${rows.length} border${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
655
426
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
656
427
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -679,7 +450,12 @@ function BorderPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter
|
|
|
679
450
|
}),
|
|
680
451
|
/* @__PURE__ */ jsx("div", {
|
|
681
452
|
className: "sb-border-preview__sample-cell",
|
|
682
|
-
children: /* @__PURE__ */ jsx(
|
|
453
|
+
children: Sample && /* @__PURE__ */ jsx(Sample, {
|
|
454
|
+
path: row.path,
|
|
455
|
+
token: row.token,
|
|
456
|
+
cssVar: row.cssVar,
|
|
457
|
+
colorFormat
|
|
458
|
+
})
|
|
683
459
|
}),
|
|
684
460
|
/* @__PURE__ */ jsxs("div", {
|
|
685
461
|
className: "sb-border-preview__breakdown",
|
|
@@ -727,6 +503,7 @@ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc", colo
|
|
|
727
503
|
activeTheme,
|
|
728
504
|
cssVarPrefix,
|
|
729
505
|
activeAxes,
|
|
506
|
+
colorFormat: format,
|
|
730
507
|
filter,
|
|
731
508
|
caption
|
|
732
509
|
});
|
|
@@ -747,7 +524,7 @@ function fixedPrefixLength(filter) {
|
|
|
747
524
|
* Pure derivation of the palette's grouped swatch rows from resolved project
|
|
748
525
|
* data. Extracted so it is unit-testable without React or a store.
|
|
749
526
|
*/
|
|
750
|
-
function deriveColorPaletteGroups(resolved, listing, cssVarPrefix, { filter, groupBy, sortBy, sortDir
|
|
527
|
+
function deriveColorPaletteGroups(resolved, listing, cssVarPrefix, { filter, groupBy, sortBy, sortDir }) {
|
|
751
528
|
const projectFields = {
|
|
752
529
|
listing,
|
|
753
530
|
cssVarPrefix
|
|
@@ -767,13 +544,11 @@ function deriveColorPaletteGroups(resolved, listing, cssVarPrefix, { filter, gro
|
|
|
767
544
|
const groupKey = segments.slice(0, effectiveGroupBy).join(".");
|
|
768
545
|
const leaf = segments.slice(effectiveGroupBy).join(".") || segments.at(-1) || path;
|
|
769
546
|
const list = bucket.get(groupKey) ?? [];
|
|
770
|
-
const formatted = resolveColorValue(path, token.$value, colorFormat, projectFields);
|
|
771
547
|
list.push({
|
|
772
548
|
path,
|
|
773
|
-
leaf,
|
|
774
549
|
cssVar: resolveCssVar(path, projectFields),
|
|
775
|
-
|
|
776
|
-
|
|
550
|
+
leaf,
|
|
551
|
+
token
|
|
777
552
|
});
|
|
778
553
|
bucket.set(groupKey, list);
|
|
779
554
|
}
|
|
@@ -782,8 +557,13 @@ function deriveColorPaletteGroups(resolved, listing, cssVarPrefix, { filter, gro
|
|
|
782
557
|
swatches
|
|
783
558
|
}));
|
|
784
559
|
}
|
|
785
|
-
/**
|
|
786
|
-
|
|
560
|
+
/**
|
|
561
|
+
* Pure presentation for the color palette. Renders from plain props;
|
|
562
|
+
* composes the registry's `color` presenter per swatch, feeding it this
|
|
563
|
+
* row's already-resolved `token`/`cssVar` per the presenter contract.
|
|
564
|
+
*/
|
|
565
|
+
function ColorPaletteView({ groups, activeTheme, cssVarPrefix, activeAxes, colorFormat, filter, caption }) {
|
|
566
|
+
const Swatch = usePresenter("color");
|
|
787
567
|
const totalCount = groups.reduce((acc, { swatches }) => acc + swatches.length, 0);
|
|
788
568
|
const captionText = caption ?? `${totalCount} color${totalCount === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
789
569
|
if (totalCount === 0) return /* @__PURE__ */ jsx("div", {
|
|
@@ -805,27 +585,12 @@ function ColorPaletteView({ groups, activeTheme, cssVarPrefix, activeAxes, filte
|
|
|
805
585
|
children: group
|
|
806
586
|
}), /* @__PURE__ */ jsx("div", {
|
|
807
587
|
className: "sb-color-palette__grid",
|
|
808
|
-
children: swatches.map((swatch) => /* @__PURE__ */
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
815
|
-
className: "sb-color-palette__meta",
|
|
816
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
817
|
-
className: "sb-color-palette__leaf",
|
|
818
|
-
children: swatch.leaf
|
|
819
|
-
}), /* @__PURE__ */ jsxs("span", {
|
|
820
|
-
className: "sb-color-palette__value",
|
|
821
|
-
children: [swatch.value, swatch.outOfGamut && /* @__PURE__ */ jsxs("span", {
|
|
822
|
-
title: "Out of sRGB gamut for this format",
|
|
823
|
-
"aria-label": "out of gamut",
|
|
824
|
-
className: "sb-color-palette__gamut-warn",
|
|
825
|
-
children: [" ", "⚠"]
|
|
826
|
-
})]
|
|
827
|
-
})]
|
|
828
|
-
})]
|
|
588
|
+
children: swatches.map((swatch) => Swatch && /* @__PURE__ */ jsx(Swatch, {
|
|
589
|
+
path: swatch.path,
|
|
590
|
+
token: swatch.token,
|
|
591
|
+
cssVar: swatch.cssVar,
|
|
592
|
+
colorFormat,
|
|
593
|
+
options: { label: swatch.leaf }
|
|
829
594
|
}, swatch.path))
|
|
830
595
|
})]
|
|
831
596
|
}, group))]
|
|
@@ -840,8 +605,7 @@ function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "as
|
|
|
840
605
|
filter,
|
|
841
606
|
groupBy,
|
|
842
607
|
sortBy,
|
|
843
|
-
sortDir
|
|
844
|
-
colorFormat: format
|
|
608
|
+
sortDir
|
|
845
609
|
}), [
|
|
846
610
|
resolved,
|
|
847
611
|
listing,
|
|
@@ -849,12 +613,12 @@ function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "as
|
|
|
849
613
|
filter,
|
|
850
614
|
groupBy,
|
|
851
615
|
sortBy,
|
|
852
|
-
sortDir
|
|
853
|
-
format
|
|
616
|
+
sortDir
|
|
854
617
|
]),
|
|
855
618
|
activeTheme,
|
|
856
619
|
cssVarPrefix,
|
|
857
620
|
activeAxes,
|
|
621
|
+
colorFormat: format,
|
|
858
622
|
filter,
|
|
859
623
|
caption
|
|
860
624
|
});
|
|
@@ -1177,11 +941,11 @@ function CopyButton$1({ value, label, variant = "icon", className }) {
|
|
|
1177
941
|
/**
|
|
1178
942
|
* Block UI state that survives a docs-mode remount.
|
|
1179
943
|
*
|
|
1180
|
-
* In MDX docs mode Storybook re-renders the docs container on every
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
944
|
+
* In MDX docs mode Storybook re-renders the docs container on every axis
|
|
945
|
+
* flip, which unmounts and remounts the embedded blocks: destroying any
|
|
946
|
+
* plain `useState` (expand/collapse, selection, search). This is the same
|
|
947
|
+
* problem the ambient project source (`#/host.ts`) solves for token/axis
|
|
948
|
+
* data: lift the value out of React into module state so it persists
|
|
1185
949
|
* across the remount, and re-seed component state from it on mount.
|
|
1186
950
|
*
|
|
1187
951
|
* `usePersistedState` is a drop-in `useState` whose value is mirrored to a
|
|
@@ -1801,260 +1565,43 @@ function DiagnosticsView({ caption, diagnostics, cssVarPrefix, activeAxes }) {
|
|
|
1801
1565
|
role: "list",
|
|
1802
1566
|
className: "sb-diagnostics__list",
|
|
1803
1567
|
children: diagnostics.map((d, i) => /* @__PURE__ */ jsxs("li", {
|
|
1804
|
-
className: "sb-diagnostics__row",
|
|
1805
|
-
"aria-label": `${severityLabel[d.severity]}: ${d.message}`,
|
|
1806
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1807
|
-
className: cx("sb-diagnostics__label", { [`sb-diagnostics__label--${d.severity}`]: d.severity !== "info" }),
|
|
1808
|
-
"aria-hidden": true,
|
|
1809
|
-
children: severityLabel[d.severity]
|
|
1810
|
-
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: d.message }), (d.group || d.filename) && /* @__PURE__ */ jsx("div", {
|
|
1811
|
-
className: "sb-diagnostics__meta",
|
|
1812
|
-
children: [
|
|
1813
|
-
d.group,
|
|
1814
|
-
d.filename,
|
|
1815
|
-
d.line ? `:${d.line}` : ""
|
|
1816
|
-
].filter(Boolean).join(" · ")
|
|
1817
|
-
})] })]
|
|
1818
|
-
}, diagnosticKey(d, i)))
|
|
1819
|
-
})]
|
|
1820
|
-
})
|
|
1821
|
-
});
|
|
1822
|
-
}
|
|
1823
|
-
/**
|
|
1824
|
-
* Render the project's load diagnostics — parser errors, resolver warnings,
|
|
1825
|
-
* disabled-axes validation issues, etc. — as a collapsible list. Auto-opens
|
|
1826
|
-
* when the project carries errors or warnings; stays collapsed for clean
|
|
1827
|
-
* loads and info-only loads.
|
|
1828
|
-
*
|
|
1829
|
-
* Replaces the diagnostics section from the addon's (now-retired) Design
|
|
1830
|
-
* Tokens panel. Consumers compose it alongside TokenNavigator / TokenTable
|
|
1831
|
-
* on their own MDX pages.
|
|
1832
|
-
*/
|
|
1833
|
-
function Diagnostics({ caption } = {}) {
|
|
1834
|
-
const { activeAxes, cssVarPrefix, diagnostics } = useProject();
|
|
1835
|
-
return /* @__PURE__ */ jsx(DiagnosticsView, {
|
|
1836
|
-
caption,
|
|
1837
|
-
diagnostics,
|
|
1838
|
-
cssVarPrefix,
|
|
1839
|
-
activeAxes
|
|
1840
|
-
});
|
|
1841
|
-
}
|
|
1842
|
-
//#endregion
|
|
1843
|
-
//#region src/dimension-scale/dimension-px.ts
|
|
1844
|
-
/**
|
|
1845
|
-
* Convert a DTCG dimension `$value` (`{ value, unit }`) to pixels for the
|
|
1846
|
-
* purpose of deciding whether to cap the rendered size. `rootFontSizePx`
|
|
1847
|
-
* scales `rem` against the rendering context's actual root font-size
|
|
1848
|
-
* (default 16 for the no-DOM / SSR path); the bar paints at the same
|
|
1849
|
-
* context's `var()`, so passing the measured root keeps the cap decision
|
|
1850
|
-
* aligned with what's drawn. Returns `NaN` for anything other than `px` /
|
|
1851
|
-
* `rem` — `ex` / `ch` / `%`, and the non-DTCG `em` (the `dimension` type
|
|
1852
|
-
* permits only `px | rem`) — which the caller treats as "render at cssVar
|
|
1853
|
-
* but don't cap".
|
|
1854
|
-
*/
|
|
1855
|
-
function toPixels(raw, rootFontSizePx = 16) {
|
|
1856
|
-
if (raw == null || typeof raw !== "object") return NaN;
|
|
1857
|
-
const v = raw;
|
|
1858
|
-
if (typeof v.value !== "number" || typeof v.unit !== "string") return NaN;
|
|
1859
|
-
switch (v.unit) {
|
|
1860
|
-
case "px": return v.value;
|
|
1861
|
-
case "rem": return v.value * rootFontSizePx;
|
|
1862
|
-
default: return NaN;
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
//#endregion
|
|
1866
|
-
//#region src/internal/use-root-font-size.ts
|
|
1867
|
-
function readRootFontSize() {
|
|
1868
|
-
if (typeof document === "undefined") return 16;
|
|
1869
|
-
const fontSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
1870
|
-
return Number.isFinite(fontSize) && fontSize > 0 ? fontSize : 16;
|
|
1871
|
-
}
|
|
1872
|
-
function subscribe(onChange) {
|
|
1873
|
-
if (typeof window === "undefined") return () => {};
|
|
1874
|
-
window.addEventListener("resize", onChange);
|
|
1875
|
-
return () => window.removeEventListener("resize", onChange);
|
|
1876
|
-
}
|
|
1877
|
-
/**
|
|
1878
|
-
* Root font-size (px) of the context a block renders in, tracked across
|
|
1879
|
-
* viewport changes. `rem` dimension values resolve their `var()` against this
|
|
1880
|
-
* root, so the cap math (`toPixels`) and value sort (`sortTokens`) must scale
|
|
1881
|
-
* `rem` by it rather than a literal 16: a Storybook Docs page, or a responsive
|
|
1882
|
-
* desktop/tablet/mobile breakpoint, can apply a different root to the same
|
|
1883
|
-
* token. Falls back to 16 with no DOM.
|
|
1884
|
-
*/
|
|
1885
|
-
function useRootFontSize() {
|
|
1886
|
-
return useSyncExternalStore(subscribe, readRootFontSize, () => 16);
|
|
1887
|
-
}
|
|
1888
|
-
//#endregion
|
|
1889
|
-
//#region src/dimension-scale/DimensionSample.tsx
|
|
1890
|
-
/**
|
|
1891
|
-
* Pure derivation of a single dimension token's sample geometry from
|
|
1892
|
-
* resolved project data. Extracted so it is unit-testable without React or
|
|
1893
|
-
* a store.
|
|
1894
|
-
*/
|
|
1895
|
-
function deriveDimensionSample(path, project, rootFontSizePx) {
|
|
1896
|
-
const cssVar = resolveCssVar(path, project);
|
|
1897
|
-
const token = project.resolved[path];
|
|
1898
|
-
const pxValue = toPixels(token?.$value, rootFontSizePx);
|
|
1899
|
-
const capped = Number.isFinite(pxValue) && pxValue > 480;
|
|
1900
|
-
return {
|
|
1901
|
-
cssVar,
|
|
1902
|
-
pxValue,
|
|
1903
|
-
capped,
|
|
1904
|
-
cappedValue: capped ? `480px` : cssVar
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
1907
|
-
function withCap(visual) {
|
|
1908
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
1909
|
-
className: "sb-dimension-sample sb-dimension-sample--capped",
|
|
1910
|
-
title: `capped at 480px`,
|
|
1911
|
-
children: [visual, /* @__PURE__ */ jsx("span", {
|
|
1912
|
-
className: "sb-dimension-sample__cap",
|
|
1913
|
-
"aria-hidden": true,
|
|
1914
|
-
children: "…"
|
|
1915
|
-
})]
|
|
1916
|
-
});
|
|
1917
|
-
}
|
|
1918
|
-
/** Pure presentation for a single dimension token's bar/sample. Renders from plain props. */
|
|
1919
|
-
function DimensionSampleView({ cssVar, capped, cappedValue, visual }) {
|
|
1920
|
-
switch (visual) {
|
|
1921
|
-
case "radius": return /* @__PURE__ */ jsx("div", {
|
|
1922
|
-
className: "sb-dimension-sample__radius-sample",
|
|
1923
|
-
style: { borderRadius: cssVar },
|
|
1924
|
-
"aria-hidden": true
|
|
1925
|
-
});
|
|
1926
|
-
case "size": {
|
|
1927
|
-
const sample = /* @__PURE__ */ jsx("div", {
|
|
1928
|
-
className: "sb-dimension-sample__size-sample",
|
|
1929
|
-
style: {
|
|
1930
|
-
width: cappedValue,
|
|
1931
|
-
height: cappedValue
|
|
1932
|
-
},
|
|
1933
|
-
"aria-hidden": true
|
|
1934
|
-
});
|
|
1935
|
-
return capped ? withCap(sample) : sample;
|
|
1936
|
-
}
|
|
1937
|
-
default: {
|
|
1938
|
-
const bar = /* @__PURE__ */ jsx("div", {
|
|
1939
|
-
className: "sb-dimension-sample__bar",
|
|
1940
|
-
style: { width: cappedValue },
|
|
1941
|
-
"aria-hidden": true
|
|
1942
|
-
});
|
|
1943
|
-
return capped ? withCap(bar) : bar;
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
/** Connected block: resolves `path` against the active project and renders its dimension sample. */
|
|
1948
|
-
function DimensionSample({ path, visual = "length" }) {
|
|
1949
|
-
const { cssVar, capped, cappedValue } = deriveDimensionSample(path, useProject(), useRootFontSize());
|
|
1950
|
-
return /* @__PURE__ */ jsx(DimensionSampleView, {
|
|
1951
|
-
cssVar,
|
|
1952
|
-
capped,
|
|
1953
|
-
cappedValue,
|
|
1954
|
-
visual
|
|
1568
|
+
className: "sb-diagnostics__row",
|
|
1569
|
+
"aria-label": `${severityLabel[d.severity]}: ${d.message}`,
|
|
1570
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1571
|
+
className: cx("sb-diagnostics__label", { [`sb-diagnostics__label--${d.severity}`]: d.severity !== "info" }),
|
|
1572
|
+
"aria-hidden": true,
|
|
1573
|
+
children: severityLabel[d.severity]
|
|
1574
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: d.message }), (d.group || d.filename) && /* @__PURE__ */ jsx("div", {
|
|
1575
|
+
className: "sb-diagnostics__meta",
|
|
1576
|
+
children: [
|
|
1577
|
+
d.group,
|
|
1578
|
+
d.filename,
|
|
1579
|
+
d.line ? `:${d.line}` : ""
|
|
1580
|
+
].filter(Boolean).join(" · ")
|
|
1581
|
+
})] })]
|
|
1582
|
+
}, diagnosticKey(d, i)))
|
|
1583
|
+
})]
|
|
1584
|
+
})
|
|
1955
1585
|
});
|
|
1956
1586
|
}
|
|
1957
|
-
//#endregion
|
|
1958
|
-
//#region src/internal/format-token-value.ts
|
|
1959
1587
|
/**
|
|
1960
|
-
*
|
|
1961
|
-
*
|
|
1962
|
-
*
|
|
1963
|
-
*
|
|
1588
|
+
* Render the project's load diagnostics — parser errors, resolver warnings,
|
|
1589
|
+
* disabled-axes validation issues, etc. — as a collapsible list. Auto-opens
|
|
1590
|
+
* when the project carries errors or warnings; stays collapsed for clean
|
|
1591
|
+
* loads and info-only loads.
|
|
1592
|
+
*
|
|
1593
|
+
* Replaces the diagnostics section from the addon's (now-retired) Design
|
|
1594
|
+
* Tokens panel. Consumers compose it alongside TokenNavigator / TokenTable
|
|
1595
|
+
* on their own MDX pages.
|
|
1964
1596
|
*/
|
|
1965
|
-
function
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
}
|
|
1974
|
-
switch ($type) {
|
|
1975
|
-
case "color": return formatColor(value, colorFormat).value;
|
|
1976
|
-
case "dimension":
|
|
1977
|
-
case "duration": return formatDimension(value);
|
|
1978
|
-
case "fontFamily": return formatFontFamily$1(value);
|
|
1979
|
-
case "fontWeight":
|
|
1980
|
-
case "lineHeight":
|
|
1981
|
-
case "letterSpacing":
|
|
1982
|
-
case "opacity":
|
|
1983
|
-
case "number": return formatPrimitive$1(value);
|
|
1984
|
-
case "cubicBezier": return formatCubicBezier(value);
|
|
1985
|
-
case "strokeStyle": return formatStrokeStyle(value);
|
|
1986
|
-
case "shadow": return formatShadow(value, colorFormat);
|
|
1987
|
-
case "border": return formatBorder(value, colorFormat);
|
|
1988
|
-
default: return formatUnknown(value);
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
function formatDimension(v) {
|
|
1992
|
-
if (typeof v === "string" || typeof v === "number") return String(v);
|
|
1993
|
-
if (v && typeof v === "object") {
|
|
1994
|
-
const d = v;
|
|
1995
|
-
if (typeof d.value === "number" && typeof d.unit === "string") return `${d.value}${d.unit}`;
|
|
1996
|
-
}
|
|
1997
|
-
return formatUnknown(v);
|
|
1998
|
-
}
|
|
1999
|
-
function cleanFloatNoise(s) {
|
|
2000
|
-
return s.replace(/-?\d+\.\d{8,}/g, (m) => `${+Number(m).toFixed(3)}`);
|
|
2001
|
-
}
|
|
2002
|
-
function formatFontFamily$1(v) {
|
|
2003
|
-
if (typeof v === "string") return v;
|
|
2004
|
-
if (Array.isArray(v)) return v.map(String).join(", ");
|
|
2005
|
-
return formatUnknown(v);
|
|
2006
|
-
}
|
|
2007
|
-
function formatPrimitive$1(v) {
|
|
2008
|
-
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
|
|
2009
|
-
return formatUnknown(v);
|
|
2010
|
-
}
|
|
2011
|
-
function formatCubicBezier(v) {
|
|
2012
|
-
if (Array.isArray(v) && v.length === 4) return `cubic-bezier(${v.map((n) => typeof n === "number" ? n : 0).join(", ")})`;
|
|
2013
|
-
return formatUnknown(v);
|
|
2014
|
-
}
|
|
2015
|
-
function formatStrokeStyle(v) {
|
|
2016
|
-
if (typeof v === "string") return v;
|
|
2017
|
-
if (v && typeof v === "object") {
|
|
2018
|
-
const s = v;
|
|
2019
|
-
const parts = ["dashed"];
|
|
2020
|
-
if (Array.isArray(s.dashArray)) parts.push(s.dashArray.map((n) => formatDimension(n)).join(" "));
|
|
2021
|
-
if (typeof s.lineCap === "string") parts.push(s.lineCap);
|
|
2022
|
-
return parts.join(" · ");
|
|
2023
|
-
}
|
|
2024
|
-
return formatUnknown(v);
|
|
2025
|
-
}
|
|
2026
|
-
function formatShadow(v, colorFormat) {
|
|
2027
|
-
return (Array.isArray(v) ? v : [v]).map((layer) => {
|
|
2028
|
-
if (!layer || typeof layer !== "object") return formatUnknown(layer);
|
|
2029
|
-
const s = layer;
|
|
2030
|
-
const pieces = [
|
|
2031
|
-
formatDimension(s.offsetX),
|
|
2032
|
-
formatDimension(s.offsetY),
|
|
2033
|
-
formatDimension(s.blur),
|
|
2034
|
-
formatDimension(s.spread),
|
|
2035
|
-
formatColor(s.color, colorFormat).value
|
|
2036
|
-
].filter((p) => p !== "");
|
|
2037
|
-
if (s.inset) pieces.push("inset");
|
|
2038
|
-
return pieces.join(" ");
|
|
2039
|
-
}).join(", ");
|
|
2040
|
-
}
|
|
2041
|
-
function formatBorder(v, colorFormat) {
|
|
2042
|
-
if (!v || typeof v !== "object") return formatUnknown(v);
|
|
2043
|
-
const b = v;
|
|
2044
|
-
return [
|
|
2045
|
-
formatDimension(b.width),
|
|
2046
|
-
formatPrimitive$1(b.style),
|
|
2047
|
-
formatColor(b.color, colorFormat).value
|
|
2048
|
-
].filter((p) => p !== "").join(" ");
|
|
2049
|
-
}
|
|
2050
|
-
function formatUnknown(v) {
|
|
2051
|
-
if (v == null) return "";
|
|
2052
|
-
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
|
|
2053
|
-
try {
|
|
2054
|
-
return JSON.stringify(v).slice(0, 120);
|
|
2055
|
-
} catch {
|
|
2056
|
-
return String(v);
|
|
2057
|
-
}
|
|
1597
|
+
function Diagnostics({ caption } = {}) {
|
|
1598
|
+
const { activeAxes, cssVarPrefix, diagnostics } = useProject();
|
|
1599
|
+
return /* @__PURE__ */ jsx(DiagnosticsView, {
|
|
1600
|
+
caption,
|
|
1601
|
+
diagnostics,
|
|
1602
|
+
cssVarPrefix,
|
|
1603
|
+
activeAxes
|
|
1604
|
+
});
|
|
2058
1605
|
}
|
|
2059
1606
|
//#endregion
|
|
2060
1607
|
//#region src/DimensionScale.tsx
|
|
@@ -2073,15 +1620,17 @@ function deriveDimensionRows(resolved, project, { filter, sortBy, sortDir, rootF
|
|
|
2073
1620
|
}).map(([path, token]) => ({
|
|
2074
1621
|
path,
|
|
2075
1622
|
cssVar: resolveCssVar(path, project),
|
|
2076
|
-
|
|
1623
|
+
token,
|
|
1624
|
+
displayValue: formatTokenValue$2(token.$value, token.$type, "raw", project.listing[path])
|
|
2077
1625
|
}));
|
|
2078
1626
|
}
|
|
2079
1627
|
/**
|
|
2080
1628
|
* Pure presentation for the dimension scale. Renders from plain props;
|
|
2081
|
-
* composes the connected `DimensionSample` as a child
|
|
2082
|
-
*
|
|
1629
|
+
* composes the connected `DimensionSample` as a child, feeding it this row's
|
|
1630
|
+
* already-resolved `token`/`cssVar` per the presenter contract.
|
|
2083
1631
|
*/
|
|
2084
|
-
function DimensionScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, visual, filter, caption }) {
|
|
1632
|
+
function DimensionScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, visual, colorFormat, filter, caption }) {
|
|
1633
|
+
const Sample = usePresenter("dimension");
|
|
2085
1634
|
const captionText = caption ?? `${rows.length} dimension${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2086
1635
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2087
1636
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2110,9 +1659,12 @@ function DimensionScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, visua
|
|
|
2110
1659
|
}),
|
|
2111
1660
|
/* @__PURE__ */ jsx("div", {
|
|
2112
1661
|
className: "sb-dimension-scale__visual-cell",
|
|
2113
|
-
children: /* @__PURE__ */ jsx(
|
|
1662
|
+
children: Sample && /* @__PURE__ */ jsx(Sample, {
|
|
2114
1663
|
path: row.path,
|
|
2115
|
-
|
|
1664
|
+
token: row.token,
|
|
1665
|
+
cssVar: row.cssVar,
|
|
1666
|
+
colorFormat,
|
|
1667
|
+
options: { visual }
|
|
2116
1668
|
})
|
|
2117
1669
|
}),
|
|
2118
1670
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -2127,6 +1679,7 @@ function DimensionScale({ filter, visual = "length", caption, sortBy = "value",
|
|
|
2127
1679
|
const project = useProject();
|
|
2128
1680
|
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
2129
1681
|
const rootFontSize = useRootFontSize();
|
|
1682
|
+
const colorFormat = useColorFormat();
|
|
2130
1683
|
return /* @__PURE__ */ jsx(DimensionScaleView, {
|
|
2131
1684
|
rows: useMemo(() => deriveDimensionRows(resolved, project, {
|
|
2132
1685
|
filter,
|
|
@@ -2145,17 +1698,13 @@ function DimensionScale({ filter, visual = "length", caption, sortBy = "value",
|
|
|
2145
1698
|
cssVarPrefix,
|
|
2146
1699
|
activeAxes,
|
|
2147
1700
|
visual,
|
|
1701
|
+
colorFormat,
|
|
2148
1702
|
filter,
|
|
2149
1703
|
caption
|
|
2150
1704
|
});
|
|
2151
1705
|
}
|
|
2152
1706
|
//#endregion
|
|
2153
1707
|
//#region src/FontFamilyPreview.tsx
|
|
2154
|
-
function stackString(raw) {
|
|
2155
|
-
if (typeof raw === "string") return raw;
|
|
2156
|
-
if (Array.isArray(raw)) return raw.map(String).join(", ");
|
|
2157
|
-
return "";
|
|
2158
|
-
}
|
|
2159
1708
|
/**
|
|
2160
1709
|
* Pure derivation of the preview's display rows from resolved project data.
|
|
2161
1710
|
* Extracted so it is unit-testable without React or a store.
|
|
@@ -2170,11 +1719,16 @@ function deriveFontFamilyRows(resolved, project, { filter, sortBy, sortDir }) {
|
|
|
2170
1719
|
}).map(([path, token]) => ({
|
|
2171
1720
|
path,
|
|
2172
1721
|
cssVar: resolveCssVar(path, project),
|
|
2173
|
-
|
|
1722
|
+
token
|
|
2174
1723
|
}));
|
|
2175
1724
|
}
|
|
2176
|
-
/**
|
|
2177
|
-
|
|
1725
|
+
/**
|
|
1726
|
+
* Pure presentation for the font-family preview. Renders from plain props;
|
|
1727
|
+
* composes the connected `FontFamilySpecimen` as a child, feeding it this
|
|
1728
|
+
* row's already-resolved `token`/`cssVar` per the presenter contract.
|
|
1729
|
+
*/
|
|
1730
|
+
function FontFamilyPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, sample, colorFormat, filter, caption }) {
|
|
1731
|
+
const Specimen = usePresenter("fontFamily");
|
|
2178
1732
|
const captionText = caption ?? `${rows.length} fontFamily token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontFamily" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2179
1733
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2180
1734
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2188,35 +1742,19 @@ function FontFamilyPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, sa
|
|
|
2188
1742
|
children: [/* @__PURE__ */ jsx("div", {
|
|
2189
1743
|
className: "sb-block__caption",
|
|
2190
1744
|
children: captionText
|
|
2191
|
-
}), rows.map((row) => /* @__PURE__ */
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
className: "sb-font-family-sample__path",
|
|
2198
|
-
children: row.path
|
|
2199
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
2200
|
-
className: "sb-font-family-sample__stack",
|
|
2201
|
-
children: row.stack
|
|
2202
|
-
})]
|
|
2203
|
-
}),
|
|
2204
|
-
/* @__PURE__ */ jsx("div", {
|
|
2205
|
-
className: "sb-font-family-sample__sample",
|
|
2206
|
-
style: { fontFamily: row.cssVar },
|
|
2207
|
-
children: sample
|
|
2208
|
-
}),
|
|
2209
|
-
/* @__PURE__ */ jsx("span", {
|
|
2210
|
-
className: "sb-font-family-sample__css-var",
|
|
2211
|
-
children: row.cssVar
|
|
2212
|
-
})
|
|
2213
|
-
]
|
|
1745
|
+
}), rows.map((row) => Specimen && /* @__PURE__ */ jsx(Specimen, {
|
|
1746
|
+
path: row.path,
|
|
1747
|
+
token: row.token,
|
|
1748
|
+
cssVar: row.cssVar,
|
|
1749
|
+
colorFormat,
|
|
1750
|
+
options: { sample }
|
|
2214
1751
|
}, row.path))]
|
|
2215
1752
|
});
|
|
2216
1753
|
}
|
|
2217
1754
|
function FontFamilyPreview({ filter, sample = "The quick brown fox jumps over the lazy dog.", caption, sortBy = "path", sortDir = "asc" }) {
|
|
2218
1755
|
const project = useProject();
|
|
2219
1756
|
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
1757
|
+
const colorFormat = useColorFormat();
|
|
2220
1758
|
return /* @__PURE__ */ jsx(FontFamilyPreviewView, {
|
|
2221
1759
|
rows: useMemo(() => deriveFontFamilyRows(resolved, project, {
|
|
2222
1760
|
filter,
|
|
@@ -2233,35 +1771,13 @@ function FontFamilyPreview({ filter, sample = "The quick brown fox jumps over th
|
|
|
2233
1771
|
cssVarPrefix,
|
|
2234
1772
|
activeAxes,
|
|
2235
1773
|
sample,
|
|
1774
|
+
colorFormat,
|
|
2236
1775
|
filter,
|
|
2237
1776
|
caption
|
|
2238
1777
|
});
|
|
2239
1778
|
}
|
|
2240
1779
|
//#endregion
|
|
2241
|
-
//#region src/internal/css-var-style.ts
|
|
2242
|
-
/**
|
|
2243
|
-
* Coerce a CSS `var(--…)` reference into the numeric slot of a React
|
|
2244
|
-
* inline-style property.
|
|
2245
|
-
*
|
|
2246
|
-
* React's `CSSProperties` types unitless properties (`fontWeight`,
|
|
2247
|
-
* `lineHeight`, `zIndex`, …) as `number`. The DOM accepts a string at
|
|
2248
|
-
* runtime — the rendered stylesheet just receives whatever React passes —
|
|
2249
|
-
* so a `var(--font-weight)` reference works functionally. TypeScript still
|
|
2250
|
-
* complains. Centralising the type assertion in one named helper keeps
|
|
2251
|
-
* the gap visible (and greppable) instead of scattering casts across
|
|
2252
|
-
* block components that want CSS-var-driven typography or layout values.
|
|
2253
|
-
*/
|
|
2254
|
-
const cssVarAsNumber = (varRef) => varRef;
|
|
2255
|
-
//#endregion
|
|
2256
1780
|
//#region src/FontWeightScale.tsx
|
|
2257
|
-
function toWeight(raw) {
|
|
2258
|
-
if (typeof raw === "number") return raw;
|
|
2259
|
-
if (typeof raw === "string") {
|
|
2260
|
-
const n = Number.parseInt(raw, 10);
|
|
2261
|
-
return Number.isFinite(n) ? n : NaN;
|
|
2262
|
-
}
|
|
2263
|
-
return NaN;
|
|
2264
|
-
}
|
|
2265
1781
|
/**
|
|
2266
1782
|
* Pure derivation of the scale's display rows from resolved project data.
|
|
2267
1783
|
* Extracted so it is unit-testable without React or a store.
|
|
@@ -2276,12 +1792,16 @@ function deriveFontWeightRows(resolved, project, { filter, sortBy, sortDir }) {
|
|
|
2276
1792
|
}).map(([path, token]) => ({
|
|
2277
1793
|
path,
|
|
2278
1794
|
cssVar: resolveCssVar(path, project),
|
|
2279
|
-
|
|
2280
|
-
weight: toWeight(token.$value)
|
|
1795
|
+
token
|
|
2281
1796
|
}));
|
|
2282
1797
|
}
|
|
2283
|
-
/**
|
|
2284
|
-
|
|
1798
|
+
/**
|
|
1799
|
+
* Pure presentation for the font-weight scale. Renders from plain props;
|
|
1800
|
+
* composes the connected `FontWeightSpecimen` as a child, feeding it this
|
|
1801
|
+
* row's already-resolved `token`/`cssVar` per the presenter contract.
|
|
1802
|
+
*/
|
|
1803
|
+
function FontWeightScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, sample, colorFormat, filter, caption }) {
|
|
1804
|
+
const Specimen = usePresenter("fontWeight");
|
|
2285
1805
|
const captionText = caption ?? `${rows.length} fontWeight token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontWeight" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2286
1806
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2287
1807
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2295,35 +1815,19 @@ function FontWeightScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, samp
|
|
|
2295
1815
|
children: [/* @__PURE__ */ jsx("div", {
|
|
2296
1816
|
className: "sb-block__caption",
|
|
2297
1817
|
children: captionText
|
|
2298
|
-
}), rows.map((row) => /* @__PURE__ */
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
className: "sb-font-weight-scale__path",
|
|
2305
|
-
children: row.path
|
|
2306
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
2307
|
-
className: "sb-font-weight-scale__value",
|
|
2308
|
-
children: row.display
|
|
2309
|
-
})]
|
|
2310
|
-
}),
|
|
2311
|
-
/* @__PURE__ */ jsx("div", {
|
|
2312
|
-
className: "sb-font-weight-scale__sample",
|
|
2313
|
-
style: { fontWeight: cssVarAsNumber(row.cssVar) },
|
|
2314
|
-
children: sample
|
|
2315
|
-
}),
|
|
2316
|
-
/* @__PURE__ */ jsx("span", {
|
|
2317
|
-
className: "sb-font-weight-scale__css-var",
|
|
2318
|
-
children: row.cssVar
|
|
2319
|
-
})
|
|
2320
|
-
]
|
|
1818
|
+
}), rows.map((row) => Specimen && /* @__PURE__ */ jsx(Specimen, {
|
|
1819
|
+
path: row.path,
|
|
1820
|
+
token: row.token,
|
|
1821
|
+
cssVar: row.cssVar,
|
|
1822
|
+
colorFormat,
|
|
1823
|
+
options: { sample }
|
|
2321
1824
|
}, row.path))]
|
|
2322
1825
|
});
|
|
2323
1826
|
}
|
|
2324
1827
|
function FontWeightScale({ filter, sample = "Aa", caption, sortBy = "value", sortDir = "asc" }) {
|
|
2325
1828
|
const project = useProject();
|
|
2326
1829
|
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
1830
|
+
const colorFormat = useColorFormat();
|
|
2327
1831
|
return /* @__PURE__ */ jsx(FontWeightScaleView, {
|
|
2328
1832
|
rows: useMemo(() => deriveFontWeightRows(resolved, project, {
|
|
2329
1833
|
filter,
|
|
@@ -2340,6 +1844,7 @@ function FontWeightScale({ filter, sample = "Aa", caption, sortBy = "value", sor
|
|
|
2340
1844
|
cssVarPrefix,
|
|
2341
1845
|
activeAxes,
|
|
2342
1846
|
sample,
|
|
1847
|
+
colorFormat,
|
|
2343
1848
|
filter,
|
|
2344
1849
|
caption
|
|
2345
1850
|
});
|
|
@@ -2378,6 +1883,7 @@ function deriveGradientRows(resolved, listing, cssVarPrefix, { filter, sortBy, s
|
|
|
2378
1883
|
return {
|
|
2379
1884
|
path,
|
|
2380
1885
|
cssVar: resolveCssVar(path, projectFields),
|
|
1886
|
+
token,
|
|
2381
1887
|
stops: stops.map((stop, i) => ({
|
|
2382
1888
|
key: stopKey(path, stop, i),
|
|
2383
1889
|
cssColor: stopCssColor(stop),
|
|
@@ -2387,8 +1893,13 @@ function deriveGradientRows(resolved, listing, cssVarPrefix, { filter, sortBy, s
|
|
|
2387
1893
|
};
|
|
2388
1894
|
});
|
|
2389
1895
|
}
|
|
2390
|
-
/**
|
|
2391
|
-
|
|
1896
|
+
/**
|
|
1897
|
+
* Pure presentation for the gradient palette. Renders from plain props;
|
|
1898
|
+
* composes the registry's `gradient` presenter per row, feeding it this
|
|
1899
|
+
* row's already-resolved `token`/`cssVar` per the presenter contract.
|
|
1900
|
+
*/
|
|
1901
|
+
function GradientPaletteView({ rows, activeTheme, cssVarPrefix, activeAxes, colorFormat, filter, caption }) {
|
|
1902
|
+
const Swatch = usePresenter("gradient");
|
|
2392
1903
|
const captionText = caption ?? `${rows.length} gradient${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2393
1904
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2394
1905
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2415,10 +1926,11 @@ function GradientPaletteView({ rows, activeTheme, cssVarPrefix, activeAxes, filt
|
|
|
2415
1926
|
children: row.cssVar
|
|
2416
1927
|
})]
|
|
2417
1928
|
}),
|
|
2418
|
-
/* @__PURE__ */ jsx(
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
1929
|
+
Swatch && /* @__PURE__ */ jsx(Swatch, {
|
|
1930
|
+
path: row.path,
|
|
1931
|
+
token: row.token,
|
|
1932
|
+
cssVar: row.cssVar,
|
|
1933
|
+
colorFormat
|
|
2422
1934
|
}),
|
|
2423
1935
|
/* @__PURE__ */ jsx("div", {
|
|
2424
1936
|
className: "sb-gradient-palette__stops",
|
|
@@ -2468,167 +1980,12 @@ function GradientPalette({ filter, caption, sortBy = "path", sortDir = "asc", co
|
|
|
2468
1980
|
activeTheme,
|
|
2469
1981
|
cssVarPrefix,
|
|
2470
1982
|
activeAxes,
|
|
1983
|
+
colorFormat: format,
|
|
2471
1984
|
filter,
|
|
2472
1985
|
caption
|
|
2473
1986
|
});
|
|
2474
1987
|
}
|
|
2475
1988
|
//#endregion
|
|
2476
|
-
//#region src/internal/prefers-reduced-motion.ts
|
|
2477
|
-
function isChromatic() {
|
|
2478
|
-
if (typeof navigator === "undefined") return false;
|
|
2479
|
-
return navigator.userAgent.includes("Chromatic");
|
|
2480
|
-
}
|
|
2481
|
-
/**
|
|
2482
|
-
* Reactive `prefers-reduced-motion: reduce` detector. Returns the current
|
|
2483
|
-
* match and updates if the user toggles the OS-level preference. Also
|
|
2484
|
-
* returns `true` under Chromatic to keep animated samples deterministic
|
|
2485
|
-
* during visual regression capture.
|
|
2486
|
-
*/
|
|
2487
|
-
function usePrefersReducedMotion() {
|
|
2488
|
-
const [reduced, setReduced] = useState(false);
|
|
2489
|
-
useEffect(() => {
|
|
2490
|
-
if (typeof window === "undefined") return;
|
|
2491
|
-
if (isChromatic()) {
|
|
2492
|
-
setReduced(true);
|
|
2493
|
-
return;
|
|
2494
|
-
}
|
|
2495
|
-
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
2496
|
-
setReduced(query.matches);
|
|
2497
|
-
const onChange = (e) => setReduced(e.matches);
|
|
2498
|
-
query.addEventListener("change", onChange);
|
|
2499
|
-
return () => query.removeEventListener("change", onChange);
|
|
2500
|
-
}, []);
|
|
2501
|
-
return reduced;
|
|
2502
|
-
}
|
|
2503
|
-
//#endregion
|
|
2504
|
-
//#region src/motion-preview/MotionSample.tsx
|
|
2505
|
-
const DEFAULT_DURATION_MS = 300;
|
|
2506
|
-
const DEFAULT_EASING = "cubic-bezier(0.2, 0, 0, 1)";
|
|
2507
|
-
function extractDurationMs(raw) {
|
|
2508
|
-
if (raw == null) return NaN;
|
|
2509
|
-
if (typeof raw === "object") {
|
|
2510
|
-
const v = raw;
|
|
2511
|
-
if (typeof v.value === "number" && typeof v.unit === "string") {
|
|
2512
|
-
if (v.unit === "ms") return v.value;
|
|
2513
|
-
if (v.unit === "s") return v.value * 1e3;
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
return NaN;
|
|
2517
|
-
}
|
|
2518
|
-
function extractCubicBezier(raw) {
|
|
2519
|
-
if (Array.isArray(raw) && raw.length === 4 && raw.every((n) => typeof n === "number")) return `cubic-bezier(${raw.map((n) => Number(n).toFixed(3)).join(", ")})`;
|
|
2520
|
-
return null;
|
|
2521
|
-
}
|
|
2522
|
-
function asDuration(raw, themeTokens, fallback) {
|
|
2523
|
-
const direct = extractDurationMs(raw);
|
|
2524
|
-
if (Number.isFinite(direct)) return direct;
|
|
2525
|
-
if (typeof raw === "string") {
|
|
2526
|
-
const match = raw.match(/^\{([^}]+)\}$/);
|
|
2527
|
-
if (match && match[1]) {
|
|
2528
|
-
const referenced = themeTokens[match[1]];
|
|
2529
|
-
const resolved = extractDurationMs(referenced?.$value);
|
|
2530
|
-
if (Number.isFinite(resolved)) return resolved;
|
|
2531
|
-
}
|
|
2532
|
-
}
|
|
2533
|
-
return fallback;
|
|
2534
|
-
}
|
|
2535
|
-
function asEasing(raw, themeTokens, fallback) {
|
|
2536
|
-
const direct = extractCubicBezier(raw);
|
|
2537
|
-
if (direct) return direct;
|
|
2538
|
-
if (typeof raw === "string") {
|
|
2539
|
-
const match = raw.match(/^\{([^}]+)\}$/);
|
|
2540
|
-
if (match && match[1]) {
|
|
2541
|
-
const referenced = themeTokens[match[1]];
|
|
2542
|
-
const resolved = extractCubicBezier(referenced?.$value);
|
|
2543
|
-
if (resolved) return resolved;
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
return fallback;
|
|
2547
|
-
}
|
|
2548
|
-
function resolveMotionSpec(token, themeTokens) {
|
|
2549
|
-
if (!token) return null;
|
|
2550
|
-
const type = token.$type;
|
|
2551
|
-
if (type === "transition") {
|
|
2552
|
-
const v = token.$value ?? {};
|
|
2553
|
-
return {
|
|
2554
|
-
durationMs: asDuration(v.duration, themeTokens, DEFAULT_DURATION_MS),
|
|
2555
|
-
easing: asEasing(v.timingFunction, themeTokens, DEFAULT_EASING)
|
|
2556
|
-
};
|
|
2557
|
-
}
|
|
2558
|
-
if (type === "duration") {
|
|
2559
|
-
const durationMs = extractDurationMs(token.$value);
|
|
2560
|
-
if (!Number.isFinite(durationMs)) return null;
|
|
2561
|
-
return {
|
|
2562
|
-
durationMs,
|
|
2563
|
-
easing: DEFAULT_EASING
|
|
2564
|
-
};
|
|
2565
|
-
}
|
|
2566
|
-
if (type === "cubicBezier") {
|
|
2567
|
-
const easing = extractCubicBezier(token.$value);
|
|
2568
|
-
if (!easing) return null;
|
|
2569
|
-
return {
|
|
2570
|
-
durationMs: DEFAULT_DURATION_MS,
|
|
2571
|
-
easing
|
|
2572
|
-
};
|
|
2573
|
-
}
|
|
2574
|
-
return null;
|
|
2575
|
-
}
|
|
2576
|
-
/**
|
|
2577
|
-
* Pure derivation of a single motion token's animation spec from resolved
|
|
2578
|
-
* project data. Extracted so it is unit-testable without React or a store.
|
|
2579
|
-
*/
|
|
2580
|
-
function deriveMotionSample(path, project) {
|
|
2581
|
-
return { spec: resolveMotionSpec(project.resolved[path], project.resolved) };
|
|
2582
|
-
}
|
|
2583
|
-
/** Pure presentation + animation for a single motion token's sample. Renders from plain props. */
|
|
2584
|
-
function MotionSampleView({ spec, speed, runKey }) {
|
|
2585
|
-
const reducedMotion = usePrefersReducedMotion();
|
|
2586
|
-
const durationMs = spec?.durationMs ?? DEFAULT_DURATION_MS;
|
|
2587
|
-
const easing = spec?.easing ?? DEFAULT_EASING;
|
|
2588
|
-
const scaledDuration = Math.max(1, durationMs / speed);
|
|
2589
|
-
const [phase, setPhase] = useState(0);
|
|
2590
|
-
useEffect(() => {
|
|
2591
|
-
if (reducedMotion) return;
|
|
2592
|
-
setPhase(0);
|
|
2593
|
-
const id = requestAnimationFrame(() => setPhase(1));
|
|
2594
|
-
const loop = window.setInterval(() => {
|
|
2595
|
-
setPhase((p) => p === 0 ? 1 : 0);
|
|
2596
|
-
}, scaledDuration * 2);
|
|
2597
|
-
return () => {
|
|
2598
|
-
cancelAnimationFrame(id);
|
|
2599
|
-
window.clearInterval(loop);
|
|
2600
|
-
};
|
|
2601
|
-
}, [
|
|
2602
|
-
scaledDuration,
|
|
2603
|
-
runKey,
|
|
2604
|
-
reducedMotion
|
|
2605
|
-
]);
|
|
2606
|
-
if (reducedMotion) return /* @__PURE__ */ jsxs("div", {
|
|
2607
|
-
className: "sb-motion-sample__reduced-motion",
|
|
2608
|
-
children: [
|
|
2609
|
-
"Animation suppressed by ",
|
|
2610
|
-
/* @__PURE__ */ jsx("code", { children: "prefers-reduced-motion: reduce" }),
|
|
2611
|
-
"."
|
|
2612
|
-
]
|
|
2613
|
-
});
|
|
2614
|
-
return /* @__PURE__ */ jsx("div", {
|
|
2615
|
-
className: "sb-motion-sample__track",
|
|
2616
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
2617
|
-
className: cx("sb-motion-sample__ball", phase === 1 ? "sb-motion-sample__ball--end" : "sb-motion-sample__ball--start"),
|
|
2618
|
-
style: { transition: `left ${scaledDuration}ms ${easing}` },
|
|
2619
|
-
"aria-hidden": true
|
|
2620
|
-
})
|
|
2621
|
-
});
|
|
2622
|
-
}
|
|
2623
|
-
function MotionSample({ path, speed = 1, runKey = 0 }) {
|
|
2624
|
-
const { spec } = deriveMotionSample(path, useProject());
|
|
2625
|
-
return /* @__PURE__ */ jsx(MotionSampleView, {
|
|
2626
|
-
spec,
|
|
2627
|
-
speed,
|
|
2628
|
-
runKey
|
|
2629
|
-
});
|
|
2630
|
-
}
|
|
2631
|
-
//#endregion
|
|
2632
1989
|
//#region src/MotionPreview.tsx
|
|
2633
1990
|
const SPEEDS = [
|
|
2634
1991
|
.25,
|
|
@@ -2663,6 +2020,7 @@ function deriveMotionRows(resolved, project, { filter }) {
|
|
|
2663
2020
|
collected.push({
|
|
2664
2021
|
path,
|
|
2665
2022
|
cssVar: resolveCssVar(path, project),
|
|
2023
|
+
token,
|
|
2666
2024
|
durationMs: spec.durationMs,
|
|
2667
2025
|
easing: spec.easing,
|
|
2668
2026
|
kind
|
|
@@ -2678,10 +2036,11 @@ function deriveMotionRows(resolved, project, { filter }) {
|
|
|
2678
2036
|
* Pure presentation for the motion preview. Owns the speed/replay controls'
|
|
2679
2037
|
* local UI state and the `prefers-reduced-motion` read (a browser-environment
|
|
2680
2038
|
* concern, not project data); renders from the derived `rows` view-model.
|
|
2681
|
-
* Composes the connected `MotionSample` as a child
|
|
2682
|
-
*
|
|
2039
|
+
* Composes the connected `MotionSample` as a child, feeding it this row's
|
|
2040
|
+
* already-resolved `token`/`cssVar` per the presenter contract.
|
|
2683
2041
|
*/
|
|
2684
|
-
function MotionPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter, caption }) {
|
|
2042
|
+
function MotionPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, colorFormat, filter, caption }) {
|
|
2043
|
+
const Sample = usePresenter("transition");
|
|
2685
2044
|
const [speed, setSpeed] = useState(1);
|
|
2686
2045
|
const [run, setRun] = useState(0);
|
|
2687
2046
|
const reducedMotion = usePrefersReducedMotion();
|
|
@@ -2736,10 +2095,15 @@ function MotionPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter
|
|
|
2736
2095
|
children: formatSpec(row)
|
|
2737
2096
|
})]
|
|
2738
2097
|
}),
|
|
2739
|
-
/* @__PURE__ */ jsx(
|
|
2098
|
+
Sample && /* @__PURE__ */ jsx(Sample, {
|
|
2740
2099
|
path: row.path,
|
|
2741
|
-
|
|
2742
|
-
|
|
2100
|
+
token: row.token,
|
|
2101
|
+
cssVar: row.cssVar,
|
|
2102
|
+
colorFormat,
|
|
2103
|
+
options: {
|
|
2104
|
+
speed,
|
|
2105
|
+
runKey: run
|
|
2106
|
+
}
|
|
2743
2107
|
}),
|
|
2744
2108
|
/* @__PURE__ */ jsx("span", {
|
|
2745
2109
|
className: "sb-motion-preview__css-var",
|
|
@@ -2753,6 +2117,7 @@ function MotionPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter
|
|
|
2753
2117
|
function MotionPreview({ filter, caption }) {
|
|
2754
2118
|
const project = useProject();
|
|
2755
2119
|
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
2120
|
+
const colorFormat = useColorFormat();
|
|
2756
2121
|
return /* @__PURE__ */ jsx(MotionPreviewView, {
|
|
2757
2122
|
rows: useMemo(() => deriveMotionRows(resolved, project, { filter }), [
|
|
2758
2123
|
resolved,
|
|
@@ -2762,6 +2127,7 @@ function MotionPreview({ filter, caption }) {
|
|
|
2762
2127
|
activeTheme,
|
|
2763
2128
|
cssVarPrefix,
|
|
2764
2129
|
activeAxes,
|
|
2130
|
+
colorFormat,
|
|
2765
2131
|
filter,
|
|
2766
2132
|
caption
|
|
2767
2133
|
});
|
|
@@ -2794,12 +2160,18 @@ function deriveOpacityRows(resolved, project, { filter, type, sortBy, sortDir })
|
|
|
2794
2160
|
path,
|
|
2795
2161
|
cssVar: resolveCssVar(path, project),
|
|
2796
2162
|
opacity,
|
|
2797
|
-
displayValue: String(opacity)
|
|
2163
|
+
displayValue: String(opacity),
|
|
2164
|
+
token
|
|
2798
2165
|
};
|
|
2799
2166
|
});
|
|
2800
2167
|
}
|
|
2801
|
-
/**
|
|
2802
|
-
|
|
2168
|
+
/**
|
|
2169
|
+
* Pure presentation for the opacity scale. Renders from plain props;
|
|
2170
|
+
* composes the connected `OpacitySwatch` as a child, feeding it this row's
|
|
2171
|
+
* already-resolved `token`/`cssVar` per the presenter contract.
|
|
2172
|
+
*/
|
|
2173
|
+
function OpacityScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, sampleColorVar, colorFormat, filter, caption }) {
|
|
2174
|
+
const Swatch = usePresenter("number");
|
|
2803
2175
|
const captionText = caption ?? `${rows.length} opacity token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2804
2176
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2805
2177
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2817,13 +2189,12 @@ function OpacityScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, sampleC
|
|
|
2817
2189
|
className: "sb-opacity-scale__grid",
|
|
2818
2190
|
children: rows.map((row) => /* @__PURE__ */ jsxs("div", {
|
|
2819
2191
|
className: "sb-opacity-scale__card",
|
|
2820
|
-
children: [/* @__PURE__ */ jsx(
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
}
|
|
2826
|
-
"aria-hidden": true
|
|
2192
|
+
children: [Swatch && /* @__PURE__ */ jsx(Swatch, {
|
|
2193
|
+
path: row.path,
|
|
2194
|
+
token: row.token,
|
|
2195
|
+
cssVar: row.cssVar,
|
|
2196
|
+
colorFormat,
|
|
2197
|
+
options: { sampleColorVar }
|
|
2827
2198
|
}), /* @__PURE__ */ jsxs("div", {
|
|
2828
2199
|
className: "sb-opacity-scale__meta",
|
|
2829
2200
|
children: [
|
|
@@ -2854,6 +2225,7 @@ function OpacityScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, sampleC
|
|
|
2854
2225
|
function OpacityScale({ filter, type = "number", sampleColor = "color.accent.bg", caption, sortBy = "value", sortDir = "asc" }) {
|
|
2855
2226
|
const project = useProject();
|
|
2856
2227
|
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
2228
|
+
const colorFormat = useColorFormat();
|
|
2857
2229
|
return /* @__PURE__ */ jsx(OpacityScaleView, {
|
|
2858
2230
|
rows: useMemo(() => deriveOpacityRows(resolved, project, {
|
|
2859
2231
|
filter,
|
|
@@ -2872,59 +2244,87 @@ function OpacityScale({ filter, type = "number", sampleColor = "color.accent.bg"
|
|
|
2872
2244
|
cssVarPrefix,
|
|
2873
2245
|
activeAxes,
|
|
2874
2246
|
sampleColorVar: resolveCssVar(sampleColor, project),
|
|
2247
|
+
colorFormat,
|
|
2875
2248
|
filter,
|
|
2876
2249
|
caption
|
|
2877
2250
|
});
|
|
2878
2251
|
}
|
|
2879
2252
|
//#endregion
|
|
2880
2253
|
//#region src/provider.tsx
|
|
2254
|
+
const SetAxesContext = createContext(null);
|
|
2255
|
+
function assemble(snapshot, activeAxes) {
|
|
2256
|
+
return {
|
|
2257
|
+
axes: snapshot.axes,
|
|
2258
|
+
activeAxes,
|
|
2259
|
+
activeTheme: tupleToName(snapshot.axes, activeAxes),
|
|
2260
|
+
cssVarPrefix: snapshot.cssVarPrefix,
|
|
2261
|
+
indicators: snapshot.indicators,
|
|
2262
|
+
diagnostics: snapshot.diagnostics,
|
|
2263
|
+
css: snapshot.css,
|
|
2264
|
+
listing: snapshot.listing,
|
|
2265
|
+
tokenGraph: snapshot.tokenGraph,
|
|
2266
|
+
defaultTuple: snapshot.defaultTuple,
|
|
2267
|
+
defaultColorFormat: snapshot.defaultColorFormat
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2881
2270
|
/**
|
|
2882
|
-
* Wraps a tree of blocks with the token data they need to render
|
|
2271
|
+
* Wraps a tree of blocks with the token data they need to render, assembled
|
|
2272
|
+
* from core's wire snapshot plus an active axis tuple.
|
|
2883
2273
|
*
|
|
2884
|
-
* The Storybook addon's preview decorator mounts this automatically
|
|
2885
|
-
* story/MDX authors typically never
|
|
2886
|
-
* tests, custom React apps, non-Storybook
|
|
2887
|
-
* a {@link
|
|
2888
|
-
*
|
|
2274
|
+
* The Storybook addon's preview decorator mounts this automatically
|
|
2275
|
+
* (`axes` controlled by the toolbar), so story/MDX authors typically never
|
|
2276
|
+
* see it. Outside Storybook: unit tests, custom React apps, non-Storybook
|
|
2277
|
+
* doc sites. Consumers pass a {@link SnapshotForWire} (often imported from
|
|
2278
|
+
* a JSON file) and either drive `axes` themselves or let the provider own
|
|
2279
|
+
* the tuple via `defaultAxes` + {@link useSetAxes}.
|
|
2889
2280
|
*/
|
|
2890
|
-
function SwatchbookProvider({
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2281
|
+
function SwatchbookProvider({ snapshot, axes, defaultAxes, presenters, mountCss = true, children }) {
|
|
2282
|
+
const [internalAxes, setInternalAxes] = useState(() => defaultAxes ?? snapshot.defaultTuple);
|
|
2283
|
+
useEffect(() => {
|
|
2284
|
+
if (!mountCss) return;
|
|
2285
|
+
ensureStyleElement(SWATCHBOOK_STYLE_ELEMENT_ID, snapshot.css);
|
|
2286
|
+
}, [mountCss, snapshot.css]);
|
|
2287
|
+
const controlled = axes !== void 0;
|
|
2288
|
+
const activeAxes = controlled ? axes : internalAxes;
|
|
2289
|
+
const value = useMemo(() => assemble(snapshot, activeAxes), [snapshot, activeAxes]);
|
|
2290
|
+
const merged = useMemo(() => mergePresenters(presenters), [presenters]);
|
|
2291
|
+
const setAxes = controlled ? null : setInternalAxes;
|
|
2292
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2293
|
+
...perAxisAttrs(snapshot.cssVarPrefix, activeAxes),
|
|
2294
|
+
children: /* @__PURE__ */ jsx(SwatchbookContext.Provider, {
|
|
2295
|
+
value,
|
|
2296
|
+
children: /* @__PURE__ */ jsx(PresenterContext.Provider, {
|
|
2297
|
+
value: merged,
|
|
2298
|
+
children: /* @__PURE__ */ jsx(SetAxesContext.Provider, {
|
|
2299
|
+
value: setAxes,
|
|
2300
|
+
children
|
|
2301
|
+
})
|
|
2302
|
+
})
|
|
2303
|
+
})
|
|
2894
2304
|
});
|
|
2895
2305
|
}
|
|
2896
2306
|
/**
|
|
2307
|
+
* Flip the active tuple on an uncontrolled {@link SwatchbookProvider}
|
|
2308
|
+
* (mounted with `defaultAxes`, not `axes`). Throws when the nearest
|
|
2309
|
+
* provider is controlled (the host owns `axes`) or absent, since there's
|
|
2310
|
+
* no internal state to flip in either case.
|
|
2311
|
+
*/
|
|
2312
|
+
function useSetAxes() {
|
|
2313
|
+
const setAxes = useContext(SetAxesContext);
|
|
2314
|
+
if (!setAxes) throw new Error("[swatchbook-blocks] useSetAxes() requires an uncontrolled <SwatchbookProvider defaultAxes={…}> (not one given an `axes` prop, and not no provider at all).");
|
|
2315
|
+
return setAxes;
|
|
2316
|
+
}
|
|
2317
|
+
/**
|
|
2897
2318
|
* Read the current {@link ProjectSnapshot}. Throws if called outside a
|
|
2898
2319
|
* {@link SwatchbookProvider}; blocks that need to fall back to the
|
|
2899
2320
|
* virtual module go through the internal `useProject()` hook instead.
|
|
2900
2321
|
*/
|
|
2901
2322
|
function useSwatchbookData() {
|
|
2902
2323
|
const value = useOptionalSwatchbookData();
|
|
2903
|
-
if (!value) throw new Error("[swatchbook-blocks] useSwatchbookData() called outside <SwatchbookProvider>. Wrap your tree in <SwatchbookProvider
|
|
2324
|
+
if (!value) throw new Error("[swatchbook-blocks] useSwatchbookData() called outside <SwatchbookProvider>. Wrap your tree in <SwatchbookProvider snapshot={wire}> or render inside a Storybook story.");
|
|
2904
2325
|
return value;
|
|
2905
2326
|
}
|
|
2906
2327
|
//#endregion
|
|
2907
|
-
//#region src/shadow-preview/ShadowSample.tsx
|
|
2908
|
-
/**
|
|
2909
|
-
* Pure derivation of a single shadow token's sample data from resolved
|
|
2910
|
-
* project data. Extracted so it is unit-testable without React or a store.
|
|
2911
|
-
*/
|
|
2912
|
-
function deriveShadowSample(path, project) {
|
|
2913
|
-
return { cssVar: resolveCssVar(path, project) };
|
|
2914
|
-
}
|
|
2915
|
-
/** Pure presentation for a single shadow token's sample. Renders from plain props. */
|
|
2916
|
-
function ShadowSampleView({ cssVar }) {
|
|
2917
|
-
return /* @__PURE__ */ jsx("div", {
|
|
2918
|
-
className: "sb-shadow-sample",
|
|
2919
|
-
style: { boxShadow: cssVar },
|
|
2920
|
-
"aria-hidden": true
|
|
2921
|
-
});
|
|
2922
|
-
}
|
|
2923
|
-
function ShadowSample({ path }) {
|
|
2924
|
-
const { cssVar } = deriveShadowSample(path, useProject());
|
|
2925
|
-
return /* @__PURE__ */ jsx(ShadowSampleView, { cssVar });
|
|
2926
|
-
}
|
|
2927
|
-
//#endregion
|
|
2928
2328
|
//#region src/ShadowPreview.tsx
|
|
2929
2329
|
function layerKey(path, layer, fallback) {
|
|
2930
2330
|
return `${path}|${layer.offset}|${layer.blur}|${layer.spread}|${fallback}`;
|
|
@@ -2936,9 +2336,9 @@ function asLayers(raw) {
|
|
|
2936
2336
|
}
|
|
2937
2337
|
function formatLayer(layer, colorFormat) {
|
|
2938
2338
|
return {
|
|
2939
|
-
offset: `${formatDimension
|
|
2940
|
-
blur: formatDimension
|
|
2941
|
-
spread: formatDimension
|
|
2339
|
+
offset: `${formatDimension(layer.offsetX)} ${formatDimension(layer.offsetY)}`,
|
|
2340
|
+
blur: formatDimension(layer.blur),
|
|
2341
|
+
spread: formatDimension(layer.spread),
|
|
2942
2342
|
color: formatSubColor(layer.color, colorFormat),
|
|
2943
2343
|
inset: layer.inset ? String(layer.inset) : void 0
|
|
2944
2344
|
};
|
|
@@ -2957,15 +2357,17 @@ function deriveShadowRows(resolved, project, { filter, sortBy, sortDir, colorFor
|
|
|
2957
2357
|
}).map(([path, token]) => ({
|
|
2958
2358
|
path,
|
|
2959
2359
|
cssVar: resolveCssVar(path, project),
|
|
2360
|
+
token,
|
|
2960
2361
|
layers: asLayers(token.$value).map((layer) => formatLayer(layer, colorFormat))
|
|
2961
2362
|
}));
|
|
2962
2363
|
}
|
|
2963
2364
|
/**
|
|
2964
2365
|
* Pure presentation for the shadow preview. Renders from plain props;
|
|
2965
|
-
* composes the connected `ShadowSample` as a child
|
|
2966
|
-
*
|
|
2366
|
+
* composes the connected `ShadowSample` as a child, feeding it this row's
|
|
2367
|
+
* already-resolved `token`/`cssVar` per the presenter contract.
|
|
2967
2368
|
*/
|
|
2968
|
-
function ShadowPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter, caption }) {
|
|
2369
|
+
function ShadowPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, colorFormat, filter, caption }) {
|
|
2370
|
+
const Sample = usePresenter("shadow");
|
|
2969
2371
|
const captionText = caption ?? `${rows.length} shadow${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2970
2372
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2971
2373
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2994,7 +2396,12 @@ function ShadowPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter
|
|
|
2994
2396
|
}),
|
|
2995
2397
|
/* @__PURE__ */ jsx("div", {
|
|
2996
2398
|
className: "sb-shadow-preview__sample-cell",
|
|
2997
|
-
children: /* @__PURE__ */ jsx(
|
|
2399
|
+
children: Sample && /* @__PURE__ */ jsx(Sample, {
|
|
2400
|
+
path: row.path,
|
|
2401
|
+
token: row.token,
|
|
2402
|
+
cssVar: row.cssVar,
|
|
2403
|
+
colorFormat
|
|
2404
|
+
})
|
|
2998
2405
|
}),
|
|
2999
2406
|
/* @__PURE__ */ jsx("div", {
|
|
3000
2407
|
className: "sb-shadow-preview__breakdown",
|
|
@@ -3061,26 +2468,13 @@ function ShadowPreview({ filter, caption, sortBy = "path", sortDir = "asc", colo
|
|
|
3061
2468
|
activeTheme,
|
|
3062
2469
|
cssVarPrefix,
|
|
3063
2470
|
activeAxes,
|
|
2471
|
+
colorFormat: format,
|
|
3064
2472
|
filter,
|
|
3065
2473
|
caption
|
|
3066
2474
|
});
|
|
3067
2475
|
}
|
|
3068
2476
|
//#endregion
|
|
3069
2477
|
//#region src/StrokeStylePreview.tsx
|
|
3070
|
-
const STRING_STYLES = new Set([
|
|
3071
|
-
"solid",
|
|
3072
|
-
"dashed",
|
|
3073
|
-
"dotted",
|
|
3074
|
-
"double",
|
|
3075
|
-
"groove",
|
|
3076
|
-
"ridge",
|
|
3077
|
-
"outset",
|
|
3078
|
-
"inset"
|
|
3079
|
-
]);
|
|
3080
|
-
function extractCssStyle(value) {
|
|
3081
|
-
if (typeof value === "string" && STRING_STYLES.has(value)) return value;
|
|
3082
|
-
return null;
|
|
3083
|
-
}
|
|
3084
2478
|
/**
|
|
3085
2479
|
* Pure derivation of the preview's display rows from resolved project data.
|
|
3086
2480
|
* Extracted so it is unit-testable without React or a store.
|
|
@@ -3095,12 +2489,17 @@ function deriveStrokeStyleRows(resolved, project, { filter, sortBy, sortDir }) {
|
|
|
3095
2489
|
}).map(([path, token]) => ({
|
|
3096
2490
|
path,
|
|
3097
2491
|
cssVar: resolveCssVar(path, project),
|
|
3098
|
-
displayValue: formatTokenValue(token.$value, token.$type, "raw", project.listing[path]),
|
|
3099
|
-
|
|
2492
|
+
displayValue: formatTokenValue$2(token.$value, token.$type, "raw", project.listing[path]),
|
|
2493
|
+
token
|
|
3100
2494
|
}));
|
|
3101
2495
|
}
|
|
3102
|
-
/**
|
|
3103
|
-
|
|
2496
|
+
/**
|
|
2497
|
+
* Pure presentation for the stroke-style preview. Renders from plain props;
|
|
2498
|
+
* composes the connected `StrokeSample` as a child, feeding it this row's
|
|
2499
|
+
* already-resolved `token`/`cssVar` per the presenter contract.
|
|
2500
|
+
*/
|
|
2501
|
+
function StrokeStylePreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, colorFormat, filter, caption }) {
|
|
2502
|
+
const Sample = usePresenter("strokeStyle");
|
|
3104
2503
|
const captionText = caption ?? `${rows.length} strokeStyle token${rows.length === 1 ? "" : "s"}${filter && filter !== "strokeStyle" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
3105
2504
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
3106
2505
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -3127,13 +2526,11 @@ function StrokeStylePreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, f
|
|
|
3127
2526
|
children: row.displayValue
|
|
3128
2527
|
})]
|
|
3129
2528
|
}),
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
className: "sb-stroke-style-sample__object-fallback",
|
|
3136
|
-
children: "Object-form (dashArray + lineCap) — no pure CSS `border-style` equivalent."
|
|
2529
|
+
Sample && /* @__PURE__ */ jsx(Sample, {
|
|
2530
|
+
path: row.path,
|
|
2531
|
+
token: row.token,
|
|
2532
|
+
cssVar: row.cssVar,
|
|
2533
|
+
colorFormat
|
|
3137
2534
|
}),
|
|
3138
2535
|
/* @__PURE__ */ jsx("span", {
|
|
3139
2536
|
className: "sb-stroke-style-sample__css-var",
|
|
@@ -3146,6 +2543,7 @@ function StrokeStylePreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, f
|
|
|
3146
2543
|
function StrokeStylePreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
3147
2544
|
const project = useProject();
|
|
3148
2545
|
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
2546
|
+
const colorFormat = useColorFormat();
|
|
3149
2547
|
return /* @__PURE__ */ jsx(StrokeStylePreviewView, {
|
|
3150
2548
|
rows: useMemo(() => deriveStrokeStyleRows(resolved, project, {
|
|
3151
2549
|
filter,
|
|
@@ -3161,6 +2559,7 @@ function StrokeStylePreview({ filter, caption, sortBy = "path", sortDir = "asc"
|
|
|
3161
2559
|
activeTheme,
|
|
3162
2560
|
cssVarPrefix,
|
|
3163
2561
|
activeAxes,
|
|
2562
|
+
colorFormat,
|
|
3164
2563
|
filter,
|
|
3165
2564
|
caption
|
|
3166
2565
|
});
|
|
@@ -3458,7 +2857,7 @@ function AxisVariance({ path, colorFormat }) {
|
|
|
3458
2857
|
}
|
|
3459
2858
|
function valueFor(token, $type, format) {
|
|
3460
2859
|
if (!token) return "—";
|
|
3461
|
-
return formatTokenValue(token.$value, $type, format);
|
|
2860
|
+
return formatTokenValue$2(token.$value, $type, format);
|
|
3462
2861
|
}
|
|
3463
2862
|
//#endregion
|
|
3464
2863
|
//#region src/token-detail/CompositeBreakdown.tsx
|
|
@@ -4101,7 +3500,7 @@ function deriveTokenDetail(path, token, listing, colorFormat) {
|
|
|
4101
3500
|
if (!token) return EMPTY_DERIVED;
|
|
4102
3501
|
const isColor = token.$type === "color";
|
|
4103
3502
|
const gamut = isColor ? formatColor(token.$value, colorFormat) : null;
|
|
4104
|
-
const value = formatTokenValue(token.$value, token.$type, colorFormat, listing[path]);
|
|
3503
|
+
const value = formatTokenValue$2(token.$value, token.$type, colorFormat, listing[path]);
|
|
4105
3504
|
const outOfGamut = gamut?.outOfGamut ?? false;
|
|
4106
3505
|
const dep = token.$deprecated;
|
|
4107
3506
|
return {
|
|
@@ -4921,7 +4320,7 @@ const LeafPreview = memo(function LeafPreview({ path, token }) {
|
|
|
4921
4320
|
className: "sb-token-navigator__preview-box",
|
|
4922
4321
|
children: [/* @__PURE__ */ jsx("span", {
|
|
4923
4322
|
className: "sb-token-navigator__value",
|
|
4924
|
-
children: formatTokenValue(token.$value, type, colorFormat, project.listing[path])
|
|
4323
|
+
children: formatTokenValue$2(token.$value, type, colorFormat, project.listing[path])
|
|
4925
4324
|
}), /* @__PURE__ */ jsx("span", {
|
|
4926
4325
|
className: "sb-token-navigator__color-swatch",
|
|
4927
4326
|
style: { background: cssVar },
|
|
@@ -4933,12 +4332,15 @@ const LeafPreview = memo(function LeafPreview({ path, token }) {
|
|
|
4933
4332
|
className: "sb-token-navigator__preview-box",
|
|
4934
4333
|
children: [/* @__PURE__ */ jsx("span", {
|
|
4935
4334
|
className: "sb-token-navigator__value",
|
|
4936
|
-
children: formatTokenValue(token.$value, type, colorFormat, project.listing[path])
|
|
4335
|
+
children: formatTokenValue$2(token.$value, type, colorFormat, project.listing[path])
|
|
4937
4336
|
}), /* @__PURE__ */ jsx("span", {
|
|
4938
4337
|
className: "sb-token-navigator__preview-dimension",
|
|
4939
4338
|
children: /* @__PURE__ */ jsx(DimensionSample, {
|
|
4940
4339
|
path,
|
|
4941
|
-
|
|
4340
|
+
token,
|
|
4341
|
+
cssVar: resolveCssVar(path, project),
|
|
4342
|
+
colorFormat,
|
|
4343
|
+
options: { visual: "length" }
|
|
4942
4344
|
})
|
|
4943
4345
|
})]
|
|
4944
4346
|
});
|
|
@@ -4946,28 +4348,43 @@ const LeafPreview = memo(function LeafPreview({ path, token }) {
|
|
|
4946
4348
|
className: "sb-token-navigator__preview-box",
|
|
4947
4349
|
children: /* @__PURE__ */ jsx("span", {
|
|
4948
4350
|
className: "sb-token-navigator__preview-scaled",
|
|
4949
|
-
children: /* @__PURE__ */ jsx(ShadowSample, {
|
|
4351
|
+
children: /* @__PURE__ */ jsx(ShadowSample, {
|
|
4352
|
+
path,
|
|
4353
|
+
token,
|
|
4354
|
+
cssVar: resolveCssVar(path, project),
|
|
4355
|
+
colorFormat
|
|
4356
|
+
})
|
|
4950
4357
|
})
|
|
4951
4358
|
});
|
|
4952
4359
|
if (type === "border") return /* @__PURE__ */ jsx("span", {
|
|
4953
4360
|
className: "sb-token-navigator__preview-box",
|
|
4954
4361
|
children: /* @__PURE__ */ jsx("span", {
|
|
4955
4362
|
className: "sb-token-navigator__preview-scaled",
|
|
4956
|
-
children: /* @__PURE__ */ jsx(BorderSample, {
|
|
4363
|
+
children: /* @__PURE__ */ jsx(BorderSample, {
|
|
4364
|
+
path,
|
|
4365
|
+
token,
|
|
4366
|
+
cssVar: resolveCssVar(path, project),
|
|
4367
|
+
colorFormat
|
|
4368
|
+
})
|
|
4957
4369
|
})
|
|
4958
4370
|
});
|
|
4959
4371
|
if (type === "transition" || type === "duration" || type === "cubicBezier") return /* @__PURE__ */ jsx("span", {
|
|
4960
4372
|
className: "sb-token-navigator__preview-box",
|
|
4961
4373
|
children: /* @__PURE__ */ jsx("span", {
|
|
4962
4374
|
className: "sb-token-navigator__preview-motion",
|
|
4963
|
-
children: /* @__PURE__ */ jsx(MotionSample, {
|
|
4375
|
+
children: /* @__PURE__ */ jsx(MotionSample, {
|
|
4376
|
+
path,
|
|
4377
|
+
token,
|
|
4378
|
+
cssVar: resolveCssVar(path, project),
|
|
4379
|
+
colorFormat
|
|
4380
|
+
})
|
|
4964
4381
|
})
|
|
4965
4382
|
});
|
|
4966
4383
|
return /* @__PURE__ */ jsx("span", {
|
|
4967
4384
|
className: "sb-token-navigator__preview-box",
|
|
4968
4385
|
children: /* @__PURE__ */ jsx("span", {
|
|
4969
4386
|
className: "sb-token-navigator__value",
|
|
4970
|
-
children: formatTokenValue(token.$value, type, colorFormat, project.listing[path])
|
|
4387
|
+
children: formatTokenValue$2(token.$value, type, colorFormat, project.listing[path])
|
|
4971
4388
|
})
|
|
4972
4389
|
});
|
|
4973
4390
|
});
|
|
@@ -4999,7 +4416,7 @@ function deriveTokenRows(resolved, listing, cssVarPrefix, varianceByPath, { filt
|
|
|
4999
4416
|
return {
|
|
5000
4417
|
path,
|
|
5001
4418
|
type: token.$type ?? "",
|
|
5002
|
-
value: formatTokenValue(token.$value, token.$type, colorFormat, listing[path]),
|
|
4419
|
+
value: formatTokenValue$2(token.$value, token.$type, colorFormat, listing[path]),
|
|
5003
4420
|
outOfGamut: color?.outOfGamut ?? false,
|
|
5004
4421
|
cssVar: resolveCssVar(path, projectFields),
|
|
5005
4422
|
isColor,
|
|
@@ -5293,63 +4710,30 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
|
|
|
5293
4710
|
}
|
|
5294
4711
|
//#endregion
|
|
5295
4712
|
//#region src/TypographyScale.tsx
|
|
5296
|
-
function asDimension(raw) {
|
|
5297
|
-
if (raw == null) return void 0;
|
|
5298
|
-
if (typeof raw === "string" || typeof raw === "number") return String(raw);
|
|
5299
|
-
if (typeof raw === "object") {
|
|
5300
|
-
const v = raw;
|
|
5301
|
-
if (v.value !== void 0 && v.unit !== void 0) return `${String(v.value)}${String(v.unit)}`;
|
|
5302
|
-
}
|
|
5303
|
-
}
|
|
5304
|
-
function asFontFamily(raw) {
|
|
5305
|
-
if (typeof raw === "string") return raw;
|
|
5306
|
-
if (Array.isArray(raw)) return raw.map(String).join(", ");
|
|
5307
|
-
}
|
|
5308
|
-
function buildRow(path, composite) {
|
|
5309
|
-
const fontFamily = asFontFamily(composite.fontFamily);
|
|
5310
|
-
const fontSize = asDimension(composite.fontSize);
|
|
5311
|
-
const fontWeight = composite.fontWeight == null ? void 0 : String(composite.fontWeight);
|
|
5312
|
-
const lineHeight = composite.lineHeight == null ? void 0 : String(composite.lineHeight);
|
|
5313
|
-
const letterSpacing = asDimension(composite.letterSpacing);
|
|
5314
|
-
const sampleStyle = {};
|
|
5315
|
-
if (fontFamily) sampleStyle.fontFamily = fontFamily;
|
|
5316
|
-
if (fontSize) sampleStyle.fontSize = fontSize;
|
|
5317
|
-
if (fontWeight) sampleStyle.fontWeight = fontWeight;
|
|
5318
|
-
if (lineHeight) sampleStyle.lineHeight = lineHeight;
|
|
5319
|
-
if (letterSpacing) sampleStyle.letterSpacing = letterSpacing;
|
|
5320
|
-
return {
|
|
5321
|
-
path,
|
|
5322
|
-
sampleStyle,
|
|
5323
|
-
specs: [
|
|
5324
|
-
fontSize,
|
|
5325
|
-
fontWeight ? `w${fontWeight}` : void 0,
|
|
5326
|
-
lineHeight ? `lh ${lineHeight}` : void 0
|
|
5327
|
-
].filter(Boolean).join(" · ")
|
|
5328
|
-
};
|
|
5329
|
-
}
|
|
5330
4713
|
/**
|
|
5331
4714
|
* Pure derivation of the scale's display rows from resolved project data.
|
|
5332
4715
|
* Extracted so it is unit-testable without React or a store.
|
|
5333
4716
|
*/
|
|
5334
|
-
function deriveTypographyRows(resolved, { filter, sortBy, sortDir }) {
|
|
4717
|
+
function deriveTypographyRows(resolved, project, { filter, sortBy, sortDir }) {
|
|
5335
4718
|
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
5336
4719
|
if (token.$type !== "typography") return false;
|
|
5337
4720
|
return matchPath(path, filter);
|
|
5338
4721
|
}), {
|
|
5339
4722
|
by: sortBy,
|
|
5340
4723
|
dir: sortDir
|
|
5341
|
-
}).map(([path, token]) => {
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
specs: ""
|
|
5347
|
-
};
|
|
5348
|
-
return buildRow(path, value);
|
|
5349
|
-
});
|
|
4724
|
+
}).map(([path, token]) => ({
|
|
4725
|
+
path,
|
|
4726
|
+
cssVar: resolveCssVar(path, project),
|
|
4727
|
+
token
|
|
4728
|
+
}));
|
|
5350
4729
|
}
|
|
5351
|
-
/**
|
|
5352
|
-
|
|
4730
|
+
/**
|
|
4731
|
+
* Pure presentation for the typography scale. Renders from plain props;
|
|
4732
|
+
* composes the connected `TypeSpecimen` as a child, feeding it this row's
|
|
4733
|
+
* already-resolved `token`/`cssVar` per the presenter contract.
|
|
4734
|
+
*/
|
|
4735
|
+
function TypographyScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, sample, colorFormat, filter, caption }) {
|
|
4736
|
+
const Specimen = usePresenter("typography");
|
|
5353
4737
|
const captionText = caption ?? `${rows.length} typography token${rows.length === 1 ? "" : "s"}${filter && filter !== "typography" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
5354
4738
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
5355
4739
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -5365,31 +4749,31 @@ function TypographyScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, samp
|
|
|
5365
4749
|
children: captionText
|
|
5366
4750
|
}), rows.map((row) => /* @__PURE__ */ jsxs("div", {
|
|
5367
4751
|
className: "sb-typography-scale__row",
|
|
5368
|
-
children: [/* @__PURE__ */
|
|
5369
|
-
className: "sb-typography-
|
|
5370
|
-
children:
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
}
|
|
5377
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
5378
|
-
style: row.sampleStyle,
|
|
5379
|
-
children: sample
|
|
4752
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4753
|
+
className: "sb-typography-scale__path",
|
|
4754
|
+
children: row.path
|
|
4755
|
+
}), Specimen && /* @__PURE__ */ jsx(Specimen, {
|
|
4756
|
+
path: row.path,
|
|
4757
|
+
token: row.token,
|
|
4758
|
+
cssVar: row.cssVar,
|
|
4759
|
+
colorFormat,
|
|
4760
|
+
options: { sample }
|
|
5380
4761
|
})]
|
|
5381
4762
|
}, row.path))]
|
|
5382
4763
|
});
|
|
5383
4764
|
}
|
|
5384
4765
|
function TypographyScale({ filter, sample = "The quick brown fox jumps over the lazy dog.", caption, sortBy = "path", sortDir = "asc" }) {
|
|
5385
|
-
const
|
|
4766
|
+
const project = useProject();
|
|
4767
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
4768
|
+
const colorFormat = useColorFormat();
|
|
5386
4769
|
return /* @__PURE__ */ jsx(TypographyScaleView, {
|
|
5387
|
-
rows: useMemo(() => deriveTypographyRows(resolved, {
|
|
4770
|
+
rows: useMemo(() => deriveTypographyRows(resolved, project, {
|
|
5388
4771
|
filter,
|
|
5389
4772
|
sortBy,
|
|
5390
4773
|
sortDir
|
|
5391
4774
|
}), [
|
|
5392
4775
|
resolved,
|
|
4776
|
+
project,
|
|
5393
4777
|
filter,
|
|
5394
4778
|
sortBy,
|
|
5395
4779
|
sortDir
|
|
@@ -5398,11 +4782,12 @@ function TypographyScale({ filter, sample = "The quick brown fox jumps over the
|
|
|
5398
4782
|
cssVarPrefix,
|
|
5399
4783
|
activeAxes,
|
|
5400
4784
|
sample,
|
|
4785
|
+
colorFormat,
|
|
5401
4786
|
filter,
|
|
5402
4787
|
caption
|
|
5403
4788
|
});
|
|
5404
4789
|
}
|
|
5405
4790
|
//#endregion
|
|
5406
|
-
export { AliasChain, AliasedBy, AxesContext, AxisVariance, BorderPreview, BorderSample, COLOR_FORMATS, ColorFormatContext, ColorPalette, ColorTable, CompositeBreakdown, CompositePreview, ConsumerOutput, Diagnostics, DimensionSample, DimensionScale, FontFamilyPreview, FontWeightScale, GradientPalette, MotionPreview, MotionSample, OpacityScale, ShadowPreview, ShadowSample, StrokeStylePreview,
|
|
4791
|
+
export { AliasChain, AliasedBy, AxesContext, AxisVariance, BorderPreview, BorderSample, COLOR_FORMATS, ColorFormatContext, ColorPalette, ColorSwatch, ColorTable, CompositeBreakdown, CompositePreview, ConsumerOutput, DEFAULT_PRESENTERS, Diagnostics, DimensionSample, DimensionScale, FontFamilyPreview, FontFamilySpecimen, FontWeightScale, FontWeightSpecimen, GradientPalette, GradientSwatch, MotionPreview, MotionSample, OpacityScale, OpacitySwatch, PresenterContext, ShadowPreview, ShadowSample, StrokeSample, StrokeStylePreview, SwatchbookContext, SwatchbookProvider, ThemeContext, TokenDetail, TokenHeader, TokenNavigator, TokenTable, TokenUsageSnippet, TypeSpecimen, TypographyScale, formatColor, formatTokenValue, mergePresenters, useActiveAxes, useActiveTheme, useColorFormat, useOptionalSwatchbookData, usePresenter, useSetAxes, useSwatchbookData };
|
|
5407
4792
|
|
|
5408
4793
|
//# sourceMappingURL=index.mjs.map
|