@unpunnyfuns/swatchbook-blocks 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,3908 @@
1
+ import Color from "colorjs.io";
2
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
3
+ import { addons } from "storybook/preview-api";
4
+ import { axes, css, cssVarPrefix, defaultTheme, themes, themesResolved } from "virtual:swatchbook/tokens";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/internal/format-color.ts
7
+ const COLOR_FORMATS = [
8
+ "hex",
9
+ "rgb",
10
+ "hsl",
11
+ "oklch",
12
+ "raw"
13
+ ];
14
+ const DEFAULT_FALLBACK = "—";
15
+ /**
16
+ * Convert Terrazzo's normalized color payload into a display string in the
17
+ * requested format. Pure function — never throws; returns `{ value: '—' }`
18
+ * for unrecognized input so calling blocks don't need try/catch.
19
+ */
20
+ function formatColor(value, format, fallback = DEFAULT_FALLBACK) {
21
+ const normalized = coerce(value);
22
+ if (!normalized) return {
23
+ value: stringifyFallback(value, fallback),
24
+ outOfGamut: false
25
+ };
26
+ if (format === "raw") return {
27
+ value: compactJson(normalized),
28
+ outOfGamut: false
29
+ };
30
+ const color = toColor(normalized);
31
+ if (!color) return {
32
+ value: stringifyFallback(value, fallback),
33
+ outOfGamut: false
34
+ };
35
+ const alpha = typeof normalized.alpha === "number" ? normalized.alpha : 1;
36
+ if (format === "hex") return formatHex(color, alpha);
37
+ if (format === "rgb") return formatRgb(color, alpha);
38
+ if (format === "hsl") return formatHsl(color, alpha);
39
+ return formatOklch(color, alpha);
40
+ }
41
+ function coerce(value) {
42
+ if (!value || typeof value !== "object") return null;
43
+ const v = value;
44
+ const colorSpace = typeof v["colorSpace"] === "string" ? v["colorSpace"] : void 0;
45
+ const components = Array.isArray(v["components"]) ? v["components"] : Array.isArray(v["channels"]) ? v["channels"] : void 0;
46
+ if (!colorSpace || !components) {
47
+ if (typeof v["hex"] === "string") return {
48
+ colorSpace: "srgb",
49
+ components: hexToComponents(v["hex"])
50
+ };
51
+ return null;
52
+ }
53
+ const alpha = typeof v["alpha"] === "number" ? v["alpha"] : void 0;
54
+ const hex = typeof v["hex"] === "string" ? v["hex"] : void 0;
55
+ return {
56
+ colorSpace,
57
+ components,
58
+ ...alpha !== void 0 && { alpha },
59
+ ...hex !== void 0 && { hex }
60
+ };
61
+ }
62
+ function hexToComponents(hex) {
63
+ const h = hex.replace("#", "");
64
+ const expanded = h.length === 3 || h.length === 4 ? h.split("").map((c) => c + c).join("") : h;
65
+ return [
66
+ parseInt(expanded.slice(0, 2), 16) / 255,
67
+ parseInt(expanded.slice(2, 4), 16) / 255,
68
+ parseInt(expanded.slice(4, 6), 16) / 255
69
+ ];
70
+ }
71
+ /**
72
+ * Map Terrazzo's canonical CSS Color 4 space identifiers to the shorter
73
+ * identifiers colorjs.io registers. Only the ones that differ need an entry.
74
+ */
75
+ const COLORJS_SPACE_ALIASES = {
76
+ "display-p3": "p3",
77
+ "a98-rgb": "a98rgb",
78
+ "prophoto-rgb": "prophoto"
79
+ };
80
+ function toColor(normalized) {
81
+ const source = normalized.components ?? normalized.channels ?? [];
82
+ const coords = [
83
+ numberOrZero(source[0]),
84
+ numberOrZero(source[1]),
85
+ numberOrZero(source[2])
86
+ ];
87
+ const space = COLORJS_SPACE_ALIASES[normalized.colorSpace] ?? normalized.colorSpace;
88
+ try {
89
+ return new Color(space, coords, normalized.alpha ?? 1);
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+ function numberOrZero(n) {
95
+ return typeof n === "number" && !Number.isNaN(n) ? n : 0;
96
+ }
97
+ function coord(color, i) {
98
+ const c = color.coords[i];
99
+ return typeof c === "number" && !Number.isNaN(c) ? c : 0;
100
+ }
101
+ function formatHex(color, alpha) {
102
+ const srgb = color.to("srgb");
103
+ if (!srgb.inGamut("srgb")) return {
104
+ value: formatRgb(color, alpha).value,
105
+ outOfGamut: true
106
+ };
107
+ const r = clamp255(coord(srgb, 0));
108
+ const g = clamp255(coord(srgb, 1));
109
+ const b = clamp255(coord(srgb, 2));
110
+ const base = `#${toHexByte(r)}${toHexByte(g)}${toHexByte(b)}`;
111
+ if (alpha >= 1) return {
112
+ value: base,
113
+ outOfGamut: false
114
+ };
115
+ return {
116
+ value: `${base}${toHexByte(clamp255(alpha))}`,
117
+ outOfGamut: false
118
+ };
119
+ }
120
+ function formatRgb(color, alpha) {
121
+ const srgb = color.to("srgb");
122
+ const inGamut = srgb.inGamut("srgb");
123
+ const body = `${Math.round(clampUnit(coord(srgb, 0)) * 255)} ${Math.round(clampUnit(coord(srgb, 1)) * 255)} ${Math.round(clampUnit(coord(srgb, 2)) * 255)}`;
124
+ return {
125
+ value: alpha >= 1 ? `rgb(${body})` : `rgb(${body} / ${roundAlpha(alpha)})`,
126
+ outOfGamut: !inGamut
127
+ };
128
+ }
129
+ function formatHsl(color, alpha) {
130
+ const hsl = color.to("hsl");
131
+ const inGamut = color.to("srgb").inGamut("srgb");
132
+ const body = `${roundHue(coord(hsl, 0))} ${roundPercent(coord(hsl, 1))}% ${roundPercent(coord(hsl, 2))}%`;
133
+ return {
134
+ value: alpha >= 1 ? `hsl(${body})` : `hsl(${body} / ${roundAlpha(alpha)})`,
135
+ outOfGamut: !inGamut
136
+ };
137
+ }
138
+ function formatOklch(color, alpha) {
139
+ const oklch = color.to("oklch");
140
+ const body = `${roundTo(coord(oklch, 0), 3)} ${roundTo(coord(oklch, 1), 3)} ${roundTo(coord(oklch, 2), 2)}`;
141
+ return {
142
+ value: alpha >= 1 ? `oklch(${body})` : `oklch(${body} / ${roundAlpha(alpha)})`,
143
+ outOfGamut: false
144
+ };
145
+ }
146
+ function clamp255(n) {
147
+ return Math.max(0, Math.min(255, Math.round(n * 255)));
148
+ }
149
+ function clampUnit(n) {
150
+ return Math.max(0, Math.min(1, n));
151
+ }
152
+ function toHexByte(n) {
153
+ return n.toString(16).padStart(2, "0");
154
+ }
155
+ function roundTo(n, digits) {
156
+ const f = 10 ** digits;
157
+ return Math.round(n * f) / f;
158
+ }
159
+ function roundHue(h) {
160
+ return roundTo((h % 360 + 360) % 360, 1);
161
+ }
162
+ function roundPercent(n) {
163
+ return Math.round(n * 10) / 10;
164
+ }
165
+ function roundAlpha(a) {
166
+ return roundTo(a, 3);
167
+ }
168
+ function compactJson(value) {
169
+ const parts = [`"colorSpace":${JSON.stringify(value.colorSpace)}`];
170
+ const components = value.components ?? value.channels;
171
+ if (components) parts.push(`"components":[${components.map((c) => c === null ? "null" : c).join(", ")}]`);
172
+ if (typeof value.alpha === "number" && value.alpha !== 1) parts.push(`"alpha":${value.alpha}`);
173
+ return `{ ${parts.join(", ")} }`;
174
+ }
175
+ function stringifyFallback(value, fallback) {
176
+ if (value == null) return fallback;
177
+ if (typeof value === "string" || typeof value === "number") return String(value);
178
+ return fallback;
179
+ }
180
+ //#endregion
181
+ //#region src/contexts.ts
182
+ /**
183
+ * Context carrying the full {@link ProjectSnapshot}. `null` sentinel lets
184
+ * `useSwatchbookData()` tell "provider present" from "fall back to the
185
+ * virtual module".
186
+ */
187
+ const SwatchbookContext = createContext(null);
188
+ function useOptionalSwatchbookData() {
189
+ return useContext(SwatchbookContext);
190
+ }
191
+ /**
192
+ * Active swatchbook theme for the current story/docs render. Populated by
193
+ * the addon's preview decorator and consumed by `useToken` + any future
194
+ * consumer hooks.
195
+ *
196
+ * This runs through plain React context rather than Storybook's
197
+ * `useGlobals` so the same hook works in autodocs / MDX renders where the
198
+ * preview-hooks context isn't available.
199
+ */
200
+ const ThemeContext = createContext("");
201
+ function useActiveTheme() {
202
+ return useContext(ThemeContext);
203
+ }
204
+ /**
205
+ * Active axis tuple for the current story/docs render — `Record<axisName,
206
+ * contextName>`. Derived from the same input as {@link ThemeContext}; split
207
+ * out so consumers needing per-axis info (toolbar, panel, tuple-aware
208
+ * blocks) don't have to reparse the composed permutation ID.
209
+ */
210
+ const AxesContext = createContext({});
211
+ function useActiveAxes() {
212
+ return useContext(AxesContext);
213
+ }
214
+ /**
215
+ * Active color-display format for the current story/docs render. Populated
216
+ * by the addon's preview decorator from the `swatchbookColorFormat` global
217
+ * (per-story `globals` or toolbar dropdown) and consumed by blocks that
218
+ * render color-token values. Emitted CSS is unaffected.
219
+ *
220
+ * Runs through plain React context rather than Storybook's `useGlobals` so
221
+ * per-story seeded globals flow through on first render and the same hook
222
+ * is safe to call from MDX doc blocks (where the preview-hooks context
223
+ * isn't available).
224
+ */
225
+ const ColorFormatContext = createContext("hex");
226
+ function useColorFormat() {
227
+ return useContext(ColorFormatContext);
228
+ }
229
+ //#endregion
230
+ //#region src/internal/use-project.ts
231
+ const STYLE_ELEMENT_ID = "swatchbook-tokens";
232
+ const GLOBAL_KEY = "swatchbookTheme";
233
+ const AXES_GLOBAL_KEY = "swatchbookAxes";
234
+ function ensureStylesheet(css) {
235
+ if (typeof document === "undefined") return;
236
+ let style = document.getElementById(STYLE_ELEMENT_ID);
237
+ if (!style) {
238
+ style = document.createElement("style");
239
+ style.id = STYLE_ELEMENT_ID;
240
+ document.head.appendChild(style);
241
+ }
242
+ if (style.textContent !== css) style.textContent = css;
243
+ }
244
+ function defaultTuple(axes) {
245
+ const out = {};
246
+ for (const axis of axes) out[axis.name] = axis.default;
247
+ return out;
248
+ }
249
+ function tupleForName(themesList, name) {
250
+ return themesList.find((t) => t.name === name)?.input;
251
+ }
252
+ function tuplesEqual(a, b) {
253
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
254
+ for (const k of keys) if (a[k] !== b[k]) return false;
255
+ return true;
256
+ }
257
+ function nameForTuple(themesList, tuple) {
258
+ return themesList.find((t) => tuplesEqual(t.input, tuple))?.name;
259
+ }
260
+ function snapshotToData(snapshot) {
261
+ return {
262
+ activeTheme: snapshot.activeTheme,
263
+ activeAxes: { ...snapshot.activeAxes },
264
+ axes: snapshot.axes,
265
+ themes: snapshot.themes,
266
+ themesResolved: snapshot.themesResolved,
267
+ resolved: snapshot.themesResolved[snapshot.activeTheme] ?? {},
268
+ cssVarPrefix: snapshot.cssVarPrefix
269
+ };
270
+ }
271
+ /**
272
+ * Reads project data either from a mounted {@link SwatchbookProvider}
273
+ * (preferred — the addon's preview decorator installs one around every
274
+ * story) or — as a back-compat fallback — directly from the virtual
275
+ * module plus Storybook globals.
276
+ *
277
+ * The fallback path is what makes the hook safe to call from MDX doc
278
+ * blocks and autodocs renders where no story is active. It self-mounts
279
+ * the virtual module's per-theme CSS and tracks the active tuple via the
280
+ * `globalsUpdated` channel event; {@link useGlobals} from
281
+ * `storybook/preview-api` would throw outside a story render.
282
+ */
283
+ function useProject() {
284
+ const snapshot = useOptionalSwatchbookData();
285
+ const fallback = useVirtualModuleFallback(snapshot === null);
286
+ return snapshot !== null ? snapshotToData(snapshot) : fallback;
287
+ }
288
+ function useVirtualModuleFallback(enabled) {
289
+ const contextTheme = useActiveTheme();
290
+ const contextAxes = useActiveAxes();
291
+ const [channelTheme, setChannelTheme] = useState(null);
292
+ const [channelAxes, setChannelAxes] = useState(null);
293
+ useEffect(() => {
294
+ if (!enabled) return;
295
+ ensureStylesheet(css);
296
+ }, [enabled]);
297
+ useEffect(() => {
298
+ if (!enabled) return;
299
+ const channel = addons.getChannel();
300
+ const onGlobals = (payload) => {
301
+ const nextTheme = payload.globals?.[GLOBAL_KEY];
302
+ if (typeof nextTheme === "string") setChannelTheme(nextTheme);
303
+ const nextAxes = payload.globals?.[AXES_GLOBAL_KEY];
304
+ if (nextAxes && typeof nextAxes === "object") setChannelAxes(nextAxes);
305
+ };
306
+ channel.on("globalsUpdated", onGlobals);
307
+ channel.on("updateGlobals", onGlobals);
308
+ return () => {
309
+ channel.off("globalsUpdated", onGlobals);
310
+ channel.off("updateGlobals", onGlobals);
311
+ };
312
+ }, [enabled]);
313
+ const activeAxes = Object.keys(contextAxes).length > 0 ? { ...contextAxes } : channelAxes ?? defaultTuple(axes);
314
+ const derivedName = nameForTuple(themes, activeAxes);
315
+ const fallbackTupleName = channelTheme && tupleForName(themes, channelTheme) ? channelTheme : null;
316
+ const activeTheme = contextTheme || derivedName || fallbackTupleName || channelTheme || defaultTheme || themes[0]?.name || "";
317
+ return {
318
+ activeTheme,
319
+ activeAxes,
320
+ axes,
321
+ themes,
322
+ themesResolved,
323
+ resolved: themesResolved[activeTheme] ?? {},
324
+ cssVarPrefix
325
+ };
326
+ }
327
+ function makeCssVar(path, prefix) {
328
+ const tail = path.replaceAll(".", "-");
329
+ return prefix ? `var(--${prefix}-${tail})` : `var(--${tail})`;
330
+ }
331
+ function globMatch(path, glob) {
332
+ if (!glob) return true;
333
+ if (glob === "*" || glob === "**") return true;
334
+ if (glob.endsWith(".*")) return path.startsWith(`${glob.slice(0, -2)}.`);
335
+ if (glob.endsWith("**")) return path.startsWith(glob.slice(0, -2));
336
+ return path === glob || path.startsWith(`${glob}.`);
337
+ }
338
+ function formatValue(value) {
339
+ if (value == null) return "";
340
+ if (typeof value === "string" || typeof value === "number") return String(value);
341
+ if (typeof value === "object") {
342
+ const v = value;
343
+ if (typeof v["hex"] === "string") return v["hex"];
344
+ if ("value" in v && "unit" in v) return `${String(v["value"])}${String(v["unit"])}`;
345
+ return JSON.stringify(value).slice(0, 120);
346
+ }
347
+ return String(value);
348
+ }
349
+ //#endregion
350
+ //#region src/border-preview/BorderSample.tsx
351
+ const sampleStyle$1 = {
352
+ width: 120,
353
+ height: 56,
354
+ background: "var(--sb-color-sys-surface-raised, transparent)",
355
+ borderRadius: 6
356
+ };
357
+ function BorderSample({ path }) {
358
+ const { cssVarPrefix } = useProject();
359
+ const cssVar = makeCssVar(path, cssVarPrefix);
360
+ return /* @__PURE__ */ jsx("div", {
361
+ style: {
362
+ ...sampleStyle$1,
363
+ border: cssVar
364
+ },
365
+ "aria-hidden": true
366
+ });
367
+ }
368
+ //#endregion
369
+ //#region src/internal/styles.ts
370
+ const MONO_STACK = "ui-monospace, SFMono-Regular, Menlo, monospace";
371
+ const BORDER_DEFAULT = "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.2))";
372
+ const BORDER_FAINT = "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.15))";
373
+ const surfaceStyle = {
374
+ fontFamily: "var(--sb-typography-sys-body-font-family, system-ui)",
375
+ fontSize: "var(--sb-typography-sys-body-font-size, 14px)",
376
+ color: "var(--sb-color-sys-text-default, CanvasText)",
377
+ background: "var(--sb-color-sys-surface-default, Canvas)",
378
+ padding: 12,
379
+ borderRadius: 6
380
+ };
381
+ const captionStyle = {
382
+ padding: "4px 0 12px",
383
+ opacity: .7,
384
+ fontSize: 12
385
+ };
386
+ const emptyStyle = {
387
+ padding: "24px 12px",
388
+ textAlign: "center",
389
+ opacity: .6
390
+ };
391
+ //#endregion
392
+ //#region src/BorderPreview.tsx
393
+ const styles$14 = {
394
+ wrapper: surfaceStyle,
395
+ caption: captionStyle,
396
+ empty: emptyStyle,
397
+ row: {
398
+ display: "grid",
399
+ gridTemplateColumns: "minmax(160px, 220px) 140px 1fr",
400
+ gap: 16,
401
+ alignItems: "center",
402
+ padding: "14px 0",
403
+ borderBottom: BORDER_DEFAULT
404
+ },
405
+ meta: {
406
+ display: "flex",
407
+ flexDirection: "column",
408
+ gap: 2,
409
+ minWidth: 0
410
+ },
411
+ path: {
412
+ fontFamily: MONO_STACK,
413
+ fontSize: 12,
414
+ overflow: "hidden",
415
+ textOverflow: "ellipsis",
416
+ whiteSpace: "nowrap"
417
+ },
418
+ cssVar: {
419
+ fontFamily: MONO_STACK,
420
+ fontSize: 11,
421
+ opacity: .7
422
+ },
423
+ sampleCell: {
424
+ display: "flex",
425
+ alignItems: "center",
426
+ justifyContent: "center"
427
+ },
428
+ breakdown: {
429
+ fontFamily: MONO_STACK,
430
+ fontSize: 11,
431
+ display: "grid",
432
+ gridTemplateColumns: "auto 1fr",
433
+ columnGap: 12,
434
+ rowGap: 2
435
+ },
436
+ breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" }
437
+ };
438
+ function formatDimension$1(raw) {
439
+ if (raw == null) return "—";
440
+ if (typeof raw === "number") return String(raw);
441
+ if (typeof raw === "string") return raw;
442
+ if (typeof raw === "object") {
443
+ const v = raw;
444
+ if (typeof v.value === "number" && typeof v.unit === "string") return `${v.value}${v.unit}`;
445
+ }
446
+ return JSON.stringify(raw);
447
+ }
448
+ function formatColor$2(raw) {
449
+ if (raw == null) return "—";
450
+ if (typeof raw === "string") return raw;
451
+ if (typeof raw === "object") {
452
+ const v = raw;
453
+ if (Array.isArray(v.components) && typeof v.colorSpace === "string") {
454
+ const parts = v.components.map((c) => typeof c === "number" ? c.toFixed(3) : String(c));
455
+ const alpha = typeof v.alpha === "number" && v.alpha !== 1 ? `, ${v.alpha}` : "";
456
+ return `${v.colorSpace}(${parts.join(" ")}${alpha})`;
457
+ }
458
+ }
459
+ return JSON.stringify(raw);
460
+ }
461
+ function BorderPreview({ filter = "border", caption }) {
462
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
463
+ const rows = useMemo(() => {
464
+ const collected = [];
465
+ for (const [path, token] of Object.entries(resolved)) {
466
+ if (token.$type !== "border") continue;
467
+ if (!globMatch(path, filter)) continue;
468
+ collected.push({
469
+ path,
470
+ cssVar: makeCssVar(path, cssVarPrefix),
471
+ value: token.$value ?? {}
472
+ });
473
+ }
474
+ collected.sort((a, b) => a.path.localeCompare(b.path, void 0, { numeric: true }));
475
+ return collected;
476
+ }, [
477
+ resolved,
478
+ filter,
479
+ cssVarPrefix
480
+ ]);
481
+ const captionText = caption ?? `${rows.length} border${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
482
+ if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
483
+ "data-theme": activeTheme,
484
+ style: styles$14.wrapper,
485
+ children: /* @__PURE__ */ jsx("div", {
486
+ style: styles$14.empty,
487
+ children: "No border tokens match this filter."
488
+ })
489
+ });
490
+ return /* @__PURE__ */ jsxs("div", {
491
+ "data-theme": activeTheme,
492
+ style: styles$14.wrapper,
493
+ children: [/* @__PURE__ */ jsx("div", {
494
+ style: styles$14.caption,
495
+ children: captionText
496
+ }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
497
+ style: styles$14.row,
498
+ children: [
499
+ /* @__PURE__ */ jsxs("div", {
500
+ style: styles$14.meta,
501
+ children: [/* @__PURE__ */ jsx("span", {
502
+ style: styles$14.path,
503
+ children: row.path
504
+ }), /* @__PURE__ */ jsx("span", {
505
+ style: styles$14.cssVar,
506
+ children: row.cssVar
507
+ })]
508
+ }),
509
+ /* @__PURE__ */ jsx("div", {
510
+ style: styles$14.sampleCell,
511
+ children: /* @__PURE__ */ jsx(BorderSample, { path: row.path })
512
+ }),
513
+ /* @__PURE__ */ jsxs("div", {
514
+ style: styles$14.breakdown,
515
+ children: [
516
+ /* @__PURE__ */ jsx("span", {
517
+ style: styles$14.breakdownKey,
518
+ children: "width"
519
+ }),
520
+ /* @__PURE__ */ jsx("span", { children: formatDimension$1(row.value.width) }),
521
+ /* @__PURE__ */ jsx("span", {
522
+ style: styles$14.breakdownKey,
523
+ children: "style"
524
+ }),
525
+ /* @__PURE__ */ jsx("span", { children: row.value.style != null ? String(row.value.style) : "—" }),
526
+ /* @__PURE__ */ jsx("span", {
527
+ style: styles$14.breakdownKey,
528
+ children: "color"
529
+ }),
530
+ /* @__PURE__ */ jsx("span", { children: formatColor$2(row.value.color) })
531
+ ]
532
+ })
533
+ ]
534
+ }, row.path))]
535
+ });
536
+ }
537
+ //#endregion
538
+ //#region src/ColorPalette.tsx
539
+ const styles$13 = {
540
+ wrapper: surfaceStyle,
541
+ caption: captionStyle,
542
+ empty: emptyStyle,
543
+ group: { marginBottom: 20 },
544
+ groupHeader: {
545
+ fontFamily: MONO_STACK,
546
+ fontSize: 11,
547
+ textTransform: "uppercase",
548
+ letterSpacing: .5,
549
+ opacity: .6,
550
+ marginBottom: 8
551
+ },
552
+ grid: {
553
+ display: "grid",
554
+ gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))",
555
+ gap: 8
556
+ },
557
+ card: {
558
+ border: BORDER_DEFAULT,
559
+ borderRadius: 6,
560
+ overflow: "hidden",
561
+ display: "flex",
562
+ flexDirection: "column"
563
+ },
564
+ swatch: {
565
+ height: 56,
566
+ width: "100%",
567
+ borderBottom: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.08))"
568
+ },
569
+ meta: {
570
+ padding: "8px 10px",
571
+ display: "flex",
572
+ flexDirection: "column",
573
+ gap: 2
574
+ },
575
+ leaf: {
576
+ fontFamily: MONO_STACK,
577
+ fontSize: 12
578
+ },
579
+ value: {
580
+ fontFamily: MONO_STACK,
581
+ fontSize: 11,
582
+ opacity: .7
583
+ }
584
+ };
585
+ /**
586
+ * Count segments in the filter before the first glob (`*` / `**`).
587
+ * `color.ref.*` → 2; `color.sys.surface.*` → 3; `color` → 1; undefined → 0.
588
+ */
589
+ function fixedPrefixLength(filter) {
590
+ if (!filter) return 0;
591
+ const segments = filter.split(".");
592
+ let fixed = 0;
593
+ for (const seg of segments) {
594
+ if (seg === "*" || seg === "**") break;
595
+ fixed += 1;
596
+ }
597
+ return fixed;
598
+ }
599
+ function ColorPalette({ filter = "color", groupBy, caption }) {
600
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
601
+ const colorFormat = useColorFormat();
602
+ const groups = useMemo(() => {
603
+ const entries = Object.entries(resolved).filter(([path, token]) => {
604
+ if (token.$type !== "color") return false;
605
+ return globMatch(path, filter);
606
+ }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
607
+ const maxDepth = entries.reduce((m, [p]) => Math.max(m, p.split(".").length), 0);
608
+ /**
609
+ * Auto-derive: group one level below the filter's fixed prefix, but
610
+ * clamp so each swatch retains at least one leaf segment. A filter
611
+ * like `color.ref.blue.*` (fixed length 3) with only 4-segment tokens
612
+ * would try groupBy=4 → one-per-group; clamp to `maxDepth - 1` so the
613
+ * whole ramp lands in one group with each shade as a leaf.
614
+ */
615
+ const effectiveGroupBy = groupBy ?? Math.min(fixedPrefixLength(filter) + 1, Math.max(maxDepth - 1, 1));
616
+ const bucket = /* @__PURE__ */ new Map();
617
+ for (const [path, token] of entries) {
618
+ const segments = path.split(".");
619
+ const groupKey = segments.slice(0, effectiveGroupBy).join(".");
620
+ const leaf = segments.slice(effectiveGroupBy).join(".") || segments.at(-1) || path;
621
+ const list = bucket.get(groupKey) ?? [];
622
+ const formatted = formatColor(token.$value, colorFormat);
623
+ list.push({
624
+ path,
625
+ leaf,
626
+ cssVar: makeCssVar(path, cssVarPrefix),
627
+ value: formatted.value,
628
+ outOfGamut: formatted.outOfGamut
629
+ });
630
+ bucket.set(groupKey, list);
631
+ }
632
+ return [...bucket.entries()].toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
633
+ }, [
634
+ resolved,
635
+ filter,
636
+ groupBy,
637
+ cssVarPrefix,
638
+ colorFormat
639
+ ]);
640
+ const totalCount = groups.reduce((acc, [, swatches]) => acc + swatches.length, 0);
641
+ const captionText = caption ?? `${totalCount} color${totalCount === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
642
+ if (totalCount === 0) return /* @__PURE__ */ jsx("div", {
643
+ "data-theme": activeTheme,
644
+ style: styles$13.wrapper,
645
+ children: /* @__PURE__ */ jsx("div", {
646
+ style: styles$13.empty,
647
+ children: "No color tokens match this filter."
648
+ })
649
+ });
650
+ return /* @__PURE__ */ jsxs("div", {
651
+ "data-theme": activeTheme,
652
+ style: styles$13.wrapper,
653
+ children: [/* @__PURE__ */ jsx("div", {
654
+ style: styles$13.caption,
655
+ children: captionText
656
+ }), groups.map(([group, swatches]) => /* @__PURE__ */ jsxs("section", {
657
+ style: styles$13.group,
658
+ children: [/* @__PURE__ */ jsx("div", {
659
+ style: styles$13.groupHeader,
660
+ children: group
661
+ }), /* @__PURE__ */ jsx("div", {
662
+ style: styles$13.grid,
663
+ children: swatches.map((swatch) => /* @__PURE__ */ jsxs("div", {
664
+ style: styles$13.card,
665
+ children: [/* @__PURE__ */ jsx("div", {
666
+ style: {
667
+ ...styles$13.swatch,
668
+ background: swatch.cssVar
669
+ },
670
+ "aria-hidden": true
671
+ }), /* @__PURE__ */ jsxs("div", {
672
+ style: styles$13.meta,
673
+ children: [/* @__PURE__ */ jsx("span", {
674
+ style: styles$13.leaf,
675
+ children: swatch.leaf
676
+ }), /* @__PURE__ */ jsxs("span", {
677
+ style: styles$13.value,
678
+ children: [swatch.value, swatch.outOfGamut && /* @__PURE__ */ jsxs("span", {
679
+ title: "Out of sRGB gamut for this format",
680
+ "aria-label": "out of gamut",
681
+ style: { marginLeft: 4 },
682
+ children: [" ", "⚠"]
683
+ })]
684
+ })]
685
+ })]
686
+ }, swatch.path))
687
+ })]
688
+ }, group))]
689
+ });
690
+ }
691
+ //#endregion
692
+ //#region src/dimension-scale/DimensionBar.tsx
693
+ const MAX_RENDER_PX$1 = 480;
694
+ const styles$12 = {
695
+ bar: {
696
+ height: 14,
697
+ background: "var(--sb-color-sys-accent-bg, #3b82f6)",
698
+ borderRadius: 2,
699
+ minWidth: 1
700
+ },
701
+ radiusSample: {
702
+ width: 56,
703
+ height: 56,
704
+ background: "var(--sb-color-sys-accent-bg, #3b82f6)",
705
+ border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))"
706
+ },
707
+ sizeSample: {
708
+ background: "var(--sb-color-sys-accent-bg, #3b82f6)",
709
+ border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
710
+ minWidth: 1,
711
+ minHeight: 1
712
+ }
713
+ };
714
+ /**
715
+ * Convert a DTCG dimension `$value` (`{ value, unit }`) to pixels for the
716
+ * purpose of deciding whether to cap the rendered bar. Returns `NaN` for
717
+ * units we can't reasonably approximate (ex / ch / %), which the caller
718
+ * treats as "render at cssVar but don't cap".
719
+ */
720
+ function toPixels$1(raw) {
721
+ if (raw == null || typeof raw !== "object") return NaN;
722
+ const v = raw;
723
+ if (typeof v.value !== "number" || typeof v.unit !== "string") return NaN;
724
+ switch (v.unit) {
725
+ case "px": return v.value;
726
+ case "rem":
727
+ case "em": return v.value * 16;
728
+ default: return NaN;
729
+ }
730
+ }
731
+ function DimensionBar({ path, kind = "length" }) {
732
+ const { resolved, cssVarPrefix } = useProject();
733
+ const cssVar = makeCssVar(path, cssVarPrefix);
734
+ const token = resolved[path];
735
+ const pxValue = toPixels$1(token?.$value);
736
+ const cappedValue = Number.isFinite(pxValue) && pxValue > MAX_RENDER_PX$1 ? `${MAX_RENDER_PX$1}px` : cssVar;
737
+ switch (kind) {
738
+ case "radius": return /* @__PURE__ */ jsx("div", {
739
+ style: {
740
+ ...styles$12.radiusSample,
741
+ borderRadius: cssVar
742
+ },
743
+ "aria-hidden": true
744
+ });
745
+ case "size": return /* @__PURE__ */ jsx("div", {
746
+ style: {
747
+ ...styles$12.sizeSample,
748
+ width: cappedValue,
749
+ height: cappedValue
750
+ },
751
+ "aria-hidden": true
752
+ });
753
+ default: return /* @__PURE__ */ jsx("div", {
754
+ style: {
755
+ ...styles$12.bar,
756
+ width: cappedValue
757
+ },
758
+ "aria-hidden": true
759
+ });
760
+ }
761
+ }
762
+ //#endregion
763
+ //#region src/DimensionScale.tsx
764
+ const MAX_RENDER_PX = 480;
765
+ const styles$11 = {
766
+ wrapper: surfaceStyle,
767
+ caption: captionStyle,
768
+ empty: emptyStyle,
769
+ row: {
770
+ display: "grid",
771
+ gridTemplateColumns: "minmax(160px, 220px) 1fr auto",
772
+ gap: 16,
773
+ alignItems: "center",
774
+ padding: "10px 0",
775
+ borderBottom: BORDER_DEFAULT
776
+ },
777
+ meta: {
778
+ display: "flex",
779
+ flexDirection: "column",
780
+ gap: 2,
781
+ minWidth: 0
782
+ },
783
+ path: {
784
+ fontFamily: MONO_STACK,
785
+ fontSize: 12,
786
+ overflow: "hidden",
787
+ textOverflow: "ellipsis",
788
+ whiteSpace: "nowrap"
789
+ },
790
+ specs: {
791
+ fontFamily: MONO_STACK,
792
+ fontSize: 11,
793
+ opacity: .7
794
+ },
795
+ visualCell: {
796
+ display: "flex",
797
+ alignItems: "center",
798
+ minWidth: 0
799
+ },
800
+ cssVar: {
801
+ fontFamily: MONO_STACK,
802
+ fontSize: 11,
803
+ opacity: .7,
804
+ whiteSpace: "nowrap"
805
+ },
806
+ cap: {
807
+ fontFamily: MONO_STACK,
808
+ fontSize: 10,
809
+ opacity: .6,
810
+ marginLeft: 6
811
+ }
812
+ };
813
+ /**
814
+ * Convert a DTCG dimension `$value` (`{ value, unit }`) to pixels for the
815
+ * purpose of ordering and deciding whether to show a cap indicator.
816
+ */
817
+ function toPixels(raw) {
818
+ if (raw == null || typeof raw !== "object") return NaN;
819
+ const v = raw;
820
+ if (typeof v.value !== "number" || typeof v.unit !== "string") return NaN;
821
+ switch (v.unit) {
822
+ case "px": return v.value;
823
+ case "rem":
824
+ case "em": return v.value * 16;
825
+ default: return NaN;
826
+ }
827
+ }
828
+ function DimensionScale({ filter = "dimension", kind = "length", caption }) {
829
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
830
+ const rows = useMemo(() => {
831
+ const collected = [];
832
+ for (const [path, token] of Object.entries(resolved)) {
833
+ if (token.$type !== "dimension") continue;
834
+ if (!globMatch(path, filter)) continue;
835
+ const pxValue = toPixels(token.$value);
836
+ collected.push({
837
+ path,
838
+ cssVar: makeCssVar(path, cssVarPrefix),
839
+ displayValue: formatValue(token.$value),
840
+ pxValue,
841
+ capped: Number.isFinite(pxValue) && pxValue > MAX_RENDER_PX
842
+ });
843
+ }
844
+ collected.sort((a, b) => {
845
+ if (Number.isFinite(a.pxValue) && Number.isFinite(b.pxValue)) return a.pxValue - b.pxValue;
846
+ return a.path.localeCompare(b.path, void 0, { numeric: true });
847
+ });
848
+ return collected;
849
+ }, [
850
+ resolved,
851
+ filter,
852
+ cssVarPrefix
853
+ ]);
854
+ const captionText = caption ?? `${rows.length} dimension${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
855
+ if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
856
+ "data-theme": activeTheme,
857
+ style: styles$11.wrapper,
858
+ children: /* @__PURE__ */ jsx("div", {
859
+ style: styles$11.empty,
860
+ children: "No dimension tokens match this filter."
861
+ })
862
+ });
863
+ return /* @__PURE__ */ jsxs("div", {
864
+ "data-theme": activeTheme,
865
+ style: styles$11.wrapper,
866
+ children: [/* @__PURE__ */ jsx("div", {
867
+ style: styles$11.caption,
868
+ children: captionText
869
+ }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
870
+ style: styles$11.row,
871
+ children: [
872
+ /* @__PURE__ */ jsxs("div", {
873
+ style: styles$11.meta,
874
+ children: [/* @__PURE__ */ jsx("span", {
875
+ style: styles$11.path,
876
+ children: row.path
877
+ }), /* @__PURE__ */ jsx("span", {
878
+ style: styles$11.specs,
879
+ children: row.displayValue
880
+ })]
881
+ }),
882
+ /* @__PURE__ */ jsxs("div", {
883
+ style: styles$11.visualCell,
884
+ children: [/* @__PURE__ */ jsx(DimensionBar, {
885
+ path: row.path,
886
+ kind
887
+ }), row.capped && /* @__PURE__ */ jsxs("span", {
888
+ style: styles$11.cap,
889
+ children: [
890
+ "capped at ",
891
+ MAX_RENDER_PX,
892
+ "px"
893
+ ]
894
+ })]
895
+ }),
896
+ /* @__PURE__ */ jsx("span", {
897
+ style: styles$11.cssVar,
898
+ children: row.cssVar
899
+ })
900
+ ]
901
+ }, row.path))]
902
+ });
903
+ }
904
+ //#endregion
905
+ //#region src/FontFamilySample.tsx
906
+ const styles$10 = {
907
+ wrapper: surfaceStyle,
908
+ caption: captionStyle,
909
+ row: {
910
+ display: "grid",
911
+ gridTemplateColumns: "minmax(160px, 220px) 1fr auto",
912
+ gap: 16,
913
+ alignItems: "baseline",
914
+ padding: "14px 0",
915
+ borderBottom: BORDER_DEFAULT
916
+ },
917
+ meta: {
918
+ display: "flex",
919
+ flexDirection: "column",
920
+ gap: 2,
921
+ minWidth: 0
922
+ },
923
+ path: {
924
+ fontFamily: MONO_STACK,
925
+ fontSize: 12,
926
+ overflow: "hidden",
927
+ textOverflow: "ellipsis",
928
+ whiteSpace: "nowrap"
929
+ },
930
+ stack: {
931
+ fontFamily: MONO_STACK,
932
+ fontSize: 11,
933
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
934
+ },
935
+ sample: {
936
+ fontSize: 22,
937
+ lineHeight: 1.2
938
+ },
939
+ cssVar: {
940
+ fontFamily: MONO_STACK,
941
+ fontSize: 11,
942
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
943
+ },
944
+ empty: emptyStyle
945
+ };
946
+ function stackString(raw) {
947
+ if (typeof raw === "string") return raw;
948
+ if (Array.isArray(raw)) return raw.map(String).join(", ");
949
+ return "";
950
+ }
951
+ function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox jumps over the lazy dog.", caption }) {
952
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
953
+ const rows = useMemo(() => {
954
+ return Object.entries(resolved).filter(([path, token]) => {
955
+ if (token.$type !== "fontFamily") return false;
956
+ return globMatch(path, filter);
957
+ }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => ({
958
+ path,
959
+ cssVar: makeCssVar(path, cssVarPrefix),
960
+ stack: stackString(token.$value)
961
+ }));
962
+ }, [
963
+ resolved,
964
+ filter,
965
+ cssVarPrefix
966
+ ]);
967
+ const captionText = caption ?? `${rows.length} fontFamily token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontFamily" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
968
+ if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
969
+ "data-theme": activeTheme,
970
+ style: styles$10.wrapper,
971
+ children: /* @__PURE__ */ jsx("div", {
972
+ style: styles$10.empty,
973
+ children: "No fontFamily tokens match this filter."
974
+ })
975
+ });
976
+ return /* @__PURE__ */ jsxs("div", {
977
+ "data-theme": activeTheme,
978
+ style: styles$10.wrapper,
979
+ children: [/* @__PURE__ */ jsx("div", {
980
+ style: styles$10.caption,
981
+ children: captionText
982
+ }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
983
+ style: styles$10.row,
984
+ children: [
985
+ /* @__PURE__ */ jsxs("div", {
986
+ style: styles$10.meta,
987
+ children: [/* @__PURE__ */ jsx("span", {
988
+ style: styles$10.path,
989
+ children: row.path
990
+ }), /* @__PURE__ */ jsx("span", {
991
+ style: styles$10.stack,
992
+ children: row.stack
993
+ })]
994
+ }),
995
+ /* @__PURE__ */ jsx("div", {
996
+ style: {
997
+ ...styles$10.sample,
998
+ fontFamily: row.cssVar
999
+ },
1000
+ children: sample
1001
+ }),
1002
+ /* @__PURE__ */ jsx("span", {
1003
+ style: styles$10.cssVar,
1004
+ children: row.cssVar
1005
+ })
1006
+ ]
1007
+ }, row.path))]
1008
+ });
1009
+ }
1010
+ //#endregion
1011
+ //#region src/FontWeightScale.tsx
1012
+ const styles$9 = {
1013
+ wrapper: surfaceStyle,
1014
+ caption: captionStyle,
1015
+ empty: emptyStyle,
1016
+ row: {
1017
+ display: "grid",
1018
+ gridTemplateColumns: "minmax(160px, 220px) 1fr auto",
1019
+ gap: 16,
1020
+ alignItems: "baseline",
1021
+ padding: "12px 0",
1022
+ borderBottom: BORDER_DEFAULT
1023
+ },
1024
+ meta: {
1025
+ display: "flex",
1026
+ flexDirection: "column",
1027
+ gap: 2,
1028
+ minWidth: 0
1029
+ },
1030
+ path: {
1031
+ fontFamily: MONO_STACK,
1032
+ fontSize: 12,
1033
+ overflow: "hidden",
1034
+ textOverflow: "ellipsis",
1035
+ whiteSpace: "nowrap"
1036
+ },
1037
+ value: {
1038
+ fontFamily: MONO_STACK,
1039
+ fontSize: 11,
1040
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
1041
+ },
1042
+ sample: {
1043
+ fontSize: 28,
1044
+ lineHeight: 1
1045
+ },
1046
+ cssVar: {
1047
+ fontFamily: MONO_STACK,
1048
+ fontSize: 11,
1049
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
1050
+ }
1051
+ };
1052
+ function toWeight(raw) {
1053
+ if (typeof raw === "number") return raw;
1054
+ if (typeof raw === "string") {
1055
+ const n = Number.parseInt(raw, 10);
1056
+ return Number.isFinite(n) ? n : NaN;
1057
+ }
1058
+ return NaN;
1059
+ }
1060
+ function FontWeightScale({ filter = "fontWeight", sample = "Aa", caption }) {
1061
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
1062
+ const rows = useMemo(() => {
1063
+ const collected = [];
1064
+ for (const [path, token] of Object.entries(resolved)) {
1065
+ if (token.$type !== "fontWeight") continue;
1066
+ if (!globMatch(path, filter)) continue;
1067
+ collected.push({
1068
+ path,
1069
+ cssVar: makeCssVar(path, cssVarPrefix),
1070
+ display: token.$value == null ? "" : String(token.$value),
1071
+ weight: toWeight(token.$value)
1072
+ });
1073
+ }
1074
+ collected.sort((a, b) => {
1075
+ if (Number.isFinite(a.weight) && Number.isFinite(b.weight)) return a.weight - b.weight;
1076
+ return a.path.localeCompare(b.path, void 0, { numeric: true });
1077
+ });
1078
+ return collected;
1079
+ }, [
1080
+ resolved,
1081
+ filter,
1082
+ cssVarPrefix
1083
+ ]);
1084
+ const captionText = caption ?? `${rows.length} fontWeight token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontWeight" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1085
+ if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1086
+ "data-theme": activeTheme,
1087
+ style: styles$9.wrapper,
1088
+ children: /* @__PURE__ */ jsx("div", {
1089
+ style: styles$9.empty,
1090
+ children: "No fontWeight tokens match this filter."
1091
+ })
1092
+ });
1093
+ return /* @__PURE__ */ jsxs("div", {
1094
+ "data-theme": activeTheme,
1095
+ style: styles$9.wrapper,
1096
+ children: [/* @__PURE__ */ jsx("div", {
1097
+ style: styles$9.caption,
1098
+ children: captionText
1099
+ }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1100
+ style: styles$9.row,
1101
+ children: [
1102
+ /* @__PURE__ */ jsxs("div", {
1103
+ style: styles$9.meta,
1104
+ children: [/* @__PURE__ */ jsx("span", {
1105
+ style: styles$9.path,
1106
+ children: row.path
1107
+ }), /* @__PURE__ */ jsx("span", {
1108
+ style: styles$9.value,
1109
+ children: row.display
1110
+ })]
1111
+ }),
1112
+ /* @__PURE__ */ jsx("div", {
1113
+ style: {
1114
+ ...styles$9.sample,
1115
+ fontWeight: row.cssVar
1116
+ },
1117
+ children: sample
1118
+ }),
1119
+ /* @__PURE__ */ jsx("span", {
1120
+ style: styles$9.cssVar,
1121
+ children: row.cssVar
1122
+ })
1123
+ ]
1124
+ }, row.path))]
1125
+ });
1126
+ }
1127
+ //#endregion
1128
+ //#region src/GradientPalette.tsx
1129
+ const styles$8 = {
1130
+ wrapper: surfaceStyle,
1131
+ caption: captionStyle,
1132
+ empty: emptyStyle,
1133
+ row: {
1134
+ display: "grid",
1135
+ gridTemplateColumns: "minmax(180px, 240px) 1fr minmax(140px, 220px)",
1136
+ gap: 16,
1137
+ alignItems: "center",
1138
+ padding: "16px 0",
1139
+ borderBottom: BORDER_DEFAULT
1140
+ },
1141
+ meta: {
1142
+ display: "flex",
1143
+ flexDirection: "column",
1144
+ gap: 2,
1145
+ minWidth: 0
1146
+ },
1147
+ path: {
1148
+ fontFamily: MONO_STACK,
1149
+ fontSize: 12,
1150
+ overflow: "hidden",
1151
+ textOverflow: "ellipsis",
1152
+ whiteSpace: "nowrap"
1153
+ },
1154
+ cssVar: {
1155
+ fontFamily: MONO_STACK,
1156
+ fontSize: 11,
1157
+ opacity: .7
1158
+ },
1159
+ sample: {
1160
+ height: 56,
1161
+ borderRadius: 6,
1162
+ border: BORDER_FAINT
1163
+ },
1164
+ stops: {
1165
+ fontFamily: MONO_STACK,
1166
+ fontSize: 11,
1167
+ display: "flex",
1168
+ flexDirection: "column",
1169
+ gap: 2
1170
+ },
1171
+ stopRow: {
1172
+ display: "flex",
1173
+ alignItems: "center",
1174
+ gap: 8
1175
+ },
1176
+ stopSwatch: {
1177
+ width: 10,
1178
+ height: 10,
1179
+ borderRadius: 2,
1180
+ border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))",
1181
+ flex: "0 0 auto"
1182
+ },
1183
+ stopPosition: { opacity: .6 }
1184
+ };
1185
+ function asStops(raw) {
1186
+ if (!Array.isArray(raw)) return [];
1187
+ return raw;
1188
+ }
1189
+ const pct = (n) => `${(n * 100).toFixed(3)}%`;
1190
+ function stopCssColor(stop) {
1191
+ const color = stop.color;
1192
+ if (!color || !Array.isArray(color.components) || color.components.length < 3) return "transparent";
1193
+ const [r, g, b] = color.components;
1194
+ if (r === void 0 || g === void 0 || b === void 0) return "transparent";
1195
+ const alpha = color.alpha ?? 1;
1196
+ return alpha === 1 ? `rgb(${pct(r)} ${pct(g)} ${pct(b)})` : `rgb(${pct(r)} ${pct(g)} ${pct(b)} / ${alpha})`;
1197
+ }
1198
+ function stopKey(path, stop, fallback) {
1199
+ return `${path}|${stop.position ?? fallback}|${stopCssColor(stop)}`;
1200
+ }
1201
+ function GradientPalette({ filter = "gradient", caption }) {
1202
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
1203
+ const rows = useMemo(() => {
1204
+ const collected = [];
1205
+ for (const [path, token] of Object.entries(resolved)) {
1206
+ if (token.$type !== "gradient") continue;
1207
+ if (!globMatch(path, filter)) continue;
1208
+ collected.push({
1209
+ path,
1210
+ cssVar: makeCssVar(path, cssVarPrefix),
1211
+ stops: asStops(token.$value)
1212
+ });
1213
+ }
1214
+ collected.sort((a, b) => a.path.localeCompare(b.path, void 0, { numeric: true }));
1215
+ return collected;
1216
+ }, [
1217
+ resolved,
1218
+ filter,
1219
+ cssVarPrefix
1220
+ ]);
1221
+ const captionText = caption ?? `${rows.length} gradient${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1222
+ if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1223
+ "data-theme": activeTheme,
1224
+ style: styles$8.wrapper,
1225
+ children: /* @__PURE__ */ jsx("div", {
1226
+ style: styles$8.empty,
1227
+ children: "No gradient tokens match this filter."
1228
+ })
1229
+ });
1230
+ return /* @__PURE__ */ jsxs("div", {
1231
+ "data-theme": activeTheme,
1232
+ style: styles$8.wrapper,
1233
+ children: [/* @__PURE__ */ jsx("div", {
1234
+ style: styles$8.caption,
1235
+ children: captionText
1236
+ }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1237
+ style: styles$8.row,
1238
+ children: [
1239
+ /* @__PURE__ */ jsxs("div", {
1240
+ style: styles$8.meta,
1241
+ children: [/* @__PURE__ */ jsx("span", {
1242
+ style: styles$8.path,
1243
+ children: row.path
1244
+ }), /* @__PURE__ */ jsx("span", {
1245
+ style: styles$8.cssVar,
1246
+ children: row.cssVar
1247
+ })]
1248
+ }),
1249
+ /* @__PURE__ */ jsx("div", {
1250
+ style: {
1251
+ ...styles$8.sample,
1252
+ background: `linear-gradient(to right, ${row.cssVar})`
1253
+ },
1254
+ "aria-hidden": true
1255
+ }),
1256
+ /* @__PURE__ */ jsx("div", {
1257
+ style: styles$8.stops,
1258
+ children: row.stops.map((stop, i) => /* @__PURE__ */ jsxs("div", {
1259
+ style: styles$8.stopRow,
1260
+ children: [
1261
+ /* @__PURE__ */ jsx("span", {
1262
+ style: {
1263
+ ...styles$8.stopSwatch,
1264
+ background: stopCssColor(stop)
1265
+ },
1266
+ "aria-hidden": true
1267
+ }),
1268
+ /* @__PURE__ */ jsx("span", { children: stopCssColor(stop) }),
1269
+ /* @__PURE__ */ jsxs("span", {
1270
+ style: styles$8.stopPosition,
1271
+ children: [
1272
+ "@ ",
1273
+ ((stop.position ?? 0) * 100).toFixed(0),
1274
+ "%"
1275
+ ]
1276
+ })
1277
+ ]
1278
+ }, stopKey(row.path, stop, i)))
1279
+ })
1280
+ ]
1281
+ }, row.path))]
1282
+ });
1283
+ }
1284
+ //#endregion
1285
+ //#region src/internal/prefers-reduced-motion.ts
1286
+ /**
1287
+ * Reactive `prefers-reduced-motion: reduce` detector. Returns the current
1288
+ * match and updates if the user toggles the OS-level preference.
1289
+ */
1290
+ function usePrefersReducedMotion() {
1291
+ const [reduced, setReduced] = useState(false);
1292
+ useEffect(() => {
1293
+ if (typeof window === "undefined") return;
1294
+ const query = window.matchMedia("(prefers-reduced-motion: reduce)");
1295
+ setReduced(query.matches);
1296
+ const onChange = (e) => setReduced(e.matches);
1297
+ query.addEventListener("change", onChange);
1298
+ return () => query.removeEventListener("change", onChange);
1299
+ }, []);
1300
+ return reduced;
1301
+ }
1302
+ //#endregion
1303
+ //#region src/motion-preview/MotionSample.tsx
1304
+ const DEFAULT_DURATION_MS = 300;
1305
+ const DEFAULT_EASING = "cubic-bezier(0.2, 0, 0, 1)";
1306
+ const styles$7 = {
1307
+ track: {
1308
+ position: "relative",
1309
+ height: 36,
1310
+ background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.08))",
1311
+ borderRadius: 18,
1312
+ overflow: "hidden"
1313
+ },
1314
+ ball: {
1315
+ position: "absolute",
1316
+ top: "50%",
1317
+ width: 28,
1318
+ height: 28,
1319
+ marginTop: -14,
1320
+ borderRadius: "50%",
1321
+ background: "var(--sb-color-sys-accent-bg, #3b82f6)"
1322
+ },
1323
+ reducedMotion: {
1324
+ fontSize: 11,
1325
+ color: "var(--sb-color-sys-text-muted, CanvasText)",
1326
+ fontStyle: "italic"
1327
+ }
1328
+ };
1329
+ function extractDurationMs(raw) {
1330
+ if (raw == null) return NaN;
1331
+ if (typeof raw === "object") {
1332
+ const v = raw;
1333
+ if (typeof v.value === "number" && typeof v.unit === "string") {
1334
+ if (v.unit === "ms") return v.value;
1335
+ if (v.unit === "s") return v.value * 1e3;
1336
+ }
1337
+ }
1338
+ return NaN;
1339
+ }
1340
+ function extractCubicBezier(raw) {
1341
+ if (Array.isArray(raw) && raw.length === 4 && raw.every((n) => typeof n === "number")) return `cubic-bezier(${raw.map((n) => Number(n).toFixed(3)).join(", ")})`;
1342
+ return null;
1343
+ }
1344
+ function asDuration(raw, themeTokens, fallback) {
1345
+ const direct = extractDurationMs(raw);
1346
+ if (Number.isFinite(direct)) return direct;
1347
+ if (typeof raw === "string") {
1348
+ const match = raw.match(/^\{([^}]+)\}$/);
1349
+ if (match && match[1]) {
1350
+ const referenced = themeTokens[match[1]];
1351
+ const resolved = extractDurationMs(referenced?.$value);
1352
+ if (Number.isFinite(resolved)) return resolved;
1353
+ }
1354
+ }
1355
+ return fallback;
1356
+ }
1357
+ function asEasing(raw, themeTokens, fallback) {
1358
+ const direct = extractCubicBezier(raw);
1359
+ if (direct) return direct;
1360
+ if (typeof raw === "string") {
1361
+ const match = raw.match(/^\{([^}]+)\}$/);
1362
+ if (match && match[1]) {
1363
+ const referenced = themeTokens[match[1]];
1364
+ const resolved = extractCubicBezier(referenced?.$value);
1365
+ if (resolved) return resolved;
1366
+ }
1367
+ }
1368
+ return fallback;
1369
+ }
1370
+ function resolveMotionSpec(token, themeTokens) {
1371
+ if (!token) return null;
1372
+ const type = token.$type;
1373
+ if (type === "transition") {
1374
+ const v = token.$value ?? {};
1375
+ return {
1376
+ durationMs: asDuration(v.duration, themeTokens, DEFAULT_DURATION_MS),
1377
+ easing: asEasing(v.timingFunction, themeTokens, DEFAULT_EASING)
1378
+ };
1379
+ }
1380
+ if (type === "duration") {
1381
+ const durationMs = extractDurationMs(token.$value);
1382
+ if (!Number.isFinite(durationMs)) return null;
1383
+ return {
1384
+ durationMs,
1385
+ easing: DEFAULT_EASING
1386
+ };
1387
+ }
1388
+ if (type === "cubicBezier") {
1389
+ const easing = extractCubicBezier(token.$value);
1390
+ if (!easing) return null;
1391
+ return {
1392
+ durationMs: DEFAULT_DURATION_MS,
1393
+ easing
1394
+ };
1395
+ }
1396
+ return null;
1397
+ }
1398
+ function MotionSample({ path, speed = 1, runKey = 0 }) {
1399
+ const { resolved } = useProject();
1400
+ const reducedMotion = usePrefersReducedMotion();
1401
+ const spec = useMemo(() => resolveMotionSpec(resolved[path], resolved), [resolved, path]);
1402
+ const durationMs = spec?.durationMs ?? DEFAULT_DURATION_MS;
1403
+ const easing = spec?.easing ?? DEFAULT_EASING;
1404
+ const scaledDuration = Math.max(1, durationMs / speed);
1405
+ const [phase, setPhase] = useState(0);
1406
+ useEffect(() => {
1407
+ if (reducedMotion) return;
1408
+ setPhase(0);
1409
+ const id = requestAnimationFrame(() => setPhase(1));
1410
+ const loop = window.setInterval(() => {
1411
+ setPhase((p) => p === 0 ? 1 : 0);
1412
+ }, scaledDuration * 2);
1413
+ return () => {
1414
+ cancelAnimationFrame(id);
1415
+ window.clearInterval(loop);
1416
+ };
1417
+ }, [
1418
+ scaledDuration,
1419
+ runKey,
1420
+ reducedMotion
1421
+ ]);
1422
+ if (reducedMotion) return /* @__PURE__ */ jsx("div", {
1423
+ style: styles$7.reducedMotion,
1424
+ children: "Animation suppressed by `prefers-reduced-motion: reduce`."
1425
+ });
1426
+ return /* @__PURE__ */ jsx("div", {
1427
+ style: styles$7.track,
1428
+ children: /* @__PURE__ */ jsx("div", {
1429
+ style: {
1430
+ ...styles$7.ball,
1431
+ left: phase === 1 ? "calc(100% - 32px)" : "4px",
1432
+ transition: `left ${scaledDuration}ms ${easing}`
1433
+ },
1434
+ "aria-hidden": true
1435
+ })
1436
+ });
1437
+ }
1438
+ //#endregion
1439
+ //#region src/MotionPreview.tsx
1440
+ const SPEEDS = [
1441
+ .25,
1442
+ .5,
1443
+ 1,
1444
+ 2
1445
+ ];
1446
+ const styles$6 = {
1447
+ wrapper: surfaceStyle,
1448
+ caption: {
1449
+ padding: "4px 0 4px",
1450
+ color: "var(--sb-color-sys-text-muted, CanvasText)",
1451
+ fontSize: 12
1452
+ },
1453
+ controls: {
1454
+ display: "flex",
1455
+ alignItems: "center",
1456
+ gap: 8,
1457
+ padding: "8px 0 12px"
1458
+ },
1459
+ controlLabel: {
1460
+ fontSize: 11,
1461
+ color: "var(--sb-color-sys-text-muted, CanvasText)",
1462
+ textTransform: "uppercase",
1463
+ letterSpacing: .5
1464
+ },
1465
+ speedBtn: {
1466
+ fontFamily: MONO_STACK,
1467
+ fontSize: 11,
1468
+ padding: "4px 8px",
1469
+ background: "transparent",
1470
+ color: "inherit",
1471
+ border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
1472
+ borderRadius: 4,
1473
+ cursor: "pointer"
1474
+ },
1475
+ speedBtnActive: {
1476
+ background: "var(--sb-color-sys-accent-bg, #3b82f6)",
1477
+ color: "var(--sb-color-sys-accent-fg, #fff)",
1478
+ borderColor: "transparent"
1479
+ },
1480
+ replayBtn: {
1481
+ fontSize: 11,
1482
+ padding: "4px 10px",
1483
+ marginLeft: "auto",
1484
+ background: "transparent",
1485
+ color: "inherit",
1486
+ border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
1487
+ borderRadius: 4,
1488
+ cursor: "pointer"
1489
+ },
1490
+ row: {
1491
+ display: "grid",
1492
+ gridTemplateColumns: "minmax(180px, 240px) 1fr auto",
1493
+ gap: 16,
1494
+ alignItems: "center",
1495
+ padding: "14px 0",
1496
+ borderBottom: BORDER_DEFAULT
1497
+ },
1498
+ meta: {
1499
+ display: "flex",
1500
+ flexDirection: "column",
1501
+ gap: 2,
1502
+ minWidth: 0
1503
+ },
1504
+ path: {
1505
+ fontFamily: MONO_STACK,
1506
+ fontSize: 12,
1507
+ overflow: "hidden",
1508
+ textOverflow: "ellipsis",
1509
+ whiteSpace: "nowrap"
1510
+ },
1511
+ specs: {
1512
+ fontFamily: MONO_STACK,
1513
+ fontSize: 11,
1514
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
1515
+ },
1516
+ cssVar: {
1517
+ fontFamily: MONO_STACK,
1518
+ fontSize: 11,
1519
+ color: "var(--sb-color-sys-text-muted, CanvasText)",
1520
+ whiteSpace: "nowrap"
1521
+ },
1522
+ empty: {
1523
+ padding: "24px 12px",
1524
+ textAlign: "center",
1525
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
1526
+ }
1527
+ };
1528
+ function formatSpec(row) {
1529
+ switch (row.kind) {
1530
+ case "transition": return `transition · ${Math.round(row.durationMs)}ms · ${row.easing}`;
1531
+ case "duration": return `duration · ${Math.round(row.durationMs)}ms`;
1532
+ case "cubicBezier": return `cubicBezier · ${row.easing}`;
1533
+ }
1534
+ }
1535
+ function MotionPreview({ filter, caption }) {
1536
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
1537
+ const [speed, setSpeed] = useState(1);
1538
+ const [run, setRun] = useState(0);
1539
+ const reducedMotion = usePrefersReducedMotion();
1540
+ const rows = useMemo(() => {
1541
+ const collected = [];
1542
+ for (const [path, token] of Object.entries(resolved)) {
1543
+ if (filter && !globMatch(path, filter)) continue;
1544
+ if (!filter && ![
1545
+ "transition",
1546
+ "duration",
1547
+ "cubicBezier"
1548
+ ].includes(token.$type ?? "")) continue;
1549
+ const kind = token.$type;
1550
+ if (!kind) continue;
1551
+ const spec = resolveMotionSpec(token, resolved);
1552
+ if (!spec) continue;
1553
+ collected.push({
1554
+ path,
1555
+ cssVar: makeCssVar(path, cssVarPrefix),
1556
+ durationMs: spec.durationMs,
1557
+ easing: spec.easing,
1558
+ kind
1559
+ });
1560
+ }
1561
+ collected.sort((a, b) => {
1562
+ if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
1563
+ return a.path.localeCompare(b.path, void 0, { numeric: true });
1564
+ });
1565
+ return collected;
1566
+ }, [
1567
+ resolved,
1568
+ filter,
1569
+ cssVarPrefix
1570
+ ]);
1571
+ const captionText = caption ?? `${rows.length} motion token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1572
+ if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1573
+ "data-theme": activeTheme,
1574
+ style: styles$6.wrapper,
1575
+ children: /* @__PURE__ */ jsx("div", {
1576
+ style: styles$6.empty,
1577
+ children: "No motion tokens match this filter."
1578
+ })
1579
+ });
1580
+ return /* @__PURE__ */ jsxs("div", {
1581
+ "data-theme": activeTheme,
1582
+ style: styles$6.wrapper,
1583
+ children: [
1584
+ /* @__PURE__ */ jsx("div", {
1585
+ style: styles$6.caption,
1586
+ children: captionText
1587
+ }),
1588
+ /* @__PURE__ */ jsxs("div", {
1589
+ style: styles$6.controls,
1590
+ children: [
1591
+ /* @__PURE__ */ jsx("span", {
1592
+ style: styles$6.controlLabel,
1593
+ children: "Speed"
1594
+ }),
1595
+ SPEEDS.map((s) => /* @__PURE__ */ jsxs("button", {
1596
+ type: "button",
1597
+ style: {
1598
+ ...styles$6.speedBtn,
1599
+ ...s === speed ? styles$6.speedBtnActive : {}
1600
+ },
1601
+ onClick: () => setSpeed(s),
1602
+ children: [s, "×"]
1603
+ }, s)),
1604
+ /* @__PURE__ */ jsx("button", {
1605
+ type: "button",
1606
+ style: styles$6.replayBtn,
1607
+ onClick: () => setRun((n) => n + 1),
1608
+ disabled: reducedMotion,
1609
+ title: reducedMotion ? "Disabled by prefers-reduced-motion" : "Replay all",
1610
+ children: "↻ Replay"
1611
+ })
1612
+ ]
1613
+ }),
1614
+ rows.map((row) => /* @__PURE__ */ jsxs("div", {
1615
+ style: styles$6.row,
1616
+ children: [
1617
+ /* @__PURE__ */ jsxs("div", {
1618
+ style: styles$6.meta,
1619
+ children: [/* @__PURE__ */ jsx("span", {
1620
+ style: styles$6.path,
1621
+ children: row.path
1622
+ }), /* @__PURE__ */ jsx("span", {
1623
+ style: styles$6.specs,
1624
+ children: formatSpec(row)
1625
+ })]
1626
+ }),
1627
+ /* @__PURE__ */ jsx(MotionSample, {
1628
+ path: row.path,
1629
+ speed,
1630
+ runKey: run
1631
+ }),
1632
+ /* @__PURE__ */ jsx("span", {
1633
+ style: styles$6.cssVar,
1634
+ children: row.cssVar
1635
+ })
1636
+ ]
1637
+ }, row.path))
1638
+ ]
1639
+ });
1640
+ }
1641
+ //#endregion
1642
+ //#region src/provider.tsx
1643
+ /**
1644
+ * Wraps a tree of blocks with the token data they need to render.
1645
+ *
1646
+ * The Storybook addon's preview decorator mounts this automatically, so
1647
+ * story/MDX authors typically never see it. Outside Storybook — unit
1648
+ * tests, custom React apps, non-Storybook doc sites — consumers construct
1649
+ * a {@link ProjectSnapshot} (often imported from a JSON file) and wrap
1650
+ * their blocks in this provider.
1651
+ */
1652
+ function SwatchbookProvider({ value, children }) {
1653
+ return /* @__PURE__ */ jsx(SwatchbookContext.Provider, {
1654
+ value,
1655
+ children
1656
+ });
1657
+ }
1658
+ /**
1659
+ * Read the current {@link ProjectSnapshot}. Throws if called outside a
1660
+ * {@link SwatchbookProvider}; blocks that need to fall back to the
1661
+ * virtual module go through the internal `useProject()` hook instead.
1662
+ */
1663
+ function useSwatchbookData() {
1664
+ const value = useOptionalSwatchbookData();
1665
+ if (!value) throw new Error("[swatchbook-blocks] useSwatchbookData() called outside <SwatchbookProvider>. Wrap your tree in <SwatchbookProvider value={snapshot}> or render inside a Storybook story.");
1666
+ return value;
1667
+ }
1668
+ //#endregion
1669
+ //#region src/shadow-preview/ShadowSample.tsx
1670
+ const sampleStyle = {
1671
+ width: 120,
1672
+ height: 56,
1673
+ background: "var(--sb-color-sys-surface-raised, #fff)",
1674
+ border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.15))",
1675
+ borderRadius: 6
1676
+ };
1677
+ function ShadowSample({ path }) {
1678
+ const { cssVarPrefix } = useProject();
1679
+ const cssVar = makeCssVar(path, cssVarPrefix);
1680
+ return /* @__PURE__ */ jsx("div", {
1681
+ style: {
1682
+ ...sampleStyle,
1683
+ boxShadow: cssVar
1684
+ },
1685
+ "aria-hidden": true
1686
+ });
1687
+ }
1688
+ //#endregion
1689
+ //#region src/ShadowPreview.tsx
1690
+ const styles$5 = {
1691
+ wrapper: surfaceStyle,
1692
+ caption: captionStyle,
1693
+ empty: emptyStyle,
1694
+ row: {
1695
+ display: "grid",
1696
+ gridTemplateColumns: "minmax(160px, 220px) 140px 1fr",
1697
+ gap: 16,
1698
+ alignItems: "center",
1699
+ padding: "16px 0",
1700
+ borderBottom: BORDER_DEFAULT
1701
+ },
1702
+ meta: {
1703
+ display: "flex",
1704
+ flexDirection: "column",
1705
+ gap: 2,
1706
+ minWidth: 0
1707
+ },
1708
+ path: {
1709
+ fontFamily: MONO_STACK,
1710
+ fontSize: 12,
1711
+ overflow: "hidden",
1712
+ textOverflow: "ellipsis",
1713
+ whiteSpace: "nowrap"
1714
+ },
1715
+ cssVar: {
1716
+ fontFamily: MONO_STACK,
1717
+ fontSize: 11,
1718
+ opacity: .7
1719
+ },
1720
+ sampleCell: {
1721
+ display: "flex",
1722
+ alignItems: "center",
1723
+ justifyContent: "center",
1724
+ height: 96
1725
+ },
1726
+ breakdown: {
1727
+ fontFamily: MONO_STACK,
1728
+ fontSize: 11,
1729
+ display: "grid",
1730
+ gridTemplateColumns: "auto 1fr",
1731
+ columnGap: 12,
1732
+ rowGap: 2
1733
+ },
1734
+ breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" },
1735
+ layerHeader: {
1736
+ fontSize: 10,
1737
+ textTransform: "uppercase",
1738
+ letterSpacing: .5,
1739
+ color: "var(--sb-color-sys-text-muted, CanvasText)",
1740
+ marginTop: 6
1741
+ }
1742
+ };
1743
+ function formatDimension(raw) {
1744
+ if (raw == null) return "—";
1745
+ if (typeof raw === "number") return String(raw);
1746
+ if (typeof raw === "string") return raw;
1747
+ if (typeof raw === "object") {
1748
+ const v = raw;
1749
+ if (typeof v.value === "number" && typeof v.unit === "string") return `${v.value}${v.unit}`;
1750
+ }
1751
+ return JSON.stringify(raw);
1752
+ }
1753
+ function formatColor$1(raw) {
1754
+ if (raw == null) return "—";
1755
+ if (typeof raw === "string") return raw;
1756
+ if (typeof raw === "object") {
1757
+ const v = raw;
1758
+ if (Array.isArray(v.components) && typeof v.colorSpace === "string") {
1759
+ const parts = v.components.map((c) => typeof c === "number" ? c.toFixed(3) : String(c));
1760
+ const alpha = typeof v.alpha === "number" && v.alpha !== 1 ? `, ${v.alpha}` : "";
1761
+ return `${v.colorSpace}(${parts.join(" ")}${alpha})`;
1762
+ }
1763
+ }
1764
+ return JSON.stringify(raw);
1765
+ }
1766
+ function asLayers(raw) {
1767
+ if (Array.isArray(raw)) return raw;
1768
+ if (raw && typeof raw === "object") return [raw];
1769
+ return [];
1770
+ }
1771
+ function layerKey(path, layer, fallback) {
1772
+ return `${path}|${`${formatDimension(layer.offsetX)},${formatDimension(layer.offsetY)}`}|${formatDimension(layer.blur)}|${formatDimension(layer.spread)}|${fallback}`;
1773
+ }
1774
+ function ShadowPreview({ filter = "shadow", caption }) {
1775
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
1776
+ const rows = useMemo(() => {
1777
+ const collected = [];
1778
+ for (const [path, token] of Object.entries(resolved)) {
1779
+ if (token.$type !== "shadow") continue;
1780
+ if (!globMatch(path, filter)) continue;
1781
+ collected.push({
1782
+ path,
1783
+ cssVar: makeCssVar(path, cssVarPrefix),
1784
+ layers: asLayers(token.$value)
1785
+ });
1786
+ }
1787
+ collected.sort((a, b) => a.path.localeCompare(b.path, void 0, { numeric: true }));
1788
+ return collected;
1789
+ }, [
1790
+ resolved,
1791
+ filter,
1792
+ cssVarPrefix
1793
+ ]);
1794
+ const captionText = caption ?? `${rows.length} shadow${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1795
+ if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1796
+ "data-theme": activeTheme,
1797
+ style: styles$5.wrapper,
1798
+ children: /* @__PURE__ */ jsx("div", {
1799
+ style: styles$5.empty,
1800
+ children: "No shadow tokens match this filter."
1801
+ })
1802
+ });
1803
+ return /* @__PURE__ */ jsxs("div", {
1804
+ "data-theme": activeTheme,
1805
+ style: styles$5.wrapper,
1806
+ children: [/* @__PURE__ */ jsx("div", {
1807
+ style: styles$5.caption,
1808
+ children: captionText
1809
+ }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1810
+ style: styles$5.row,
1811
+ children: [
1812
+ /* @__PURE__ */ jsxs("div", {
1813
+ style: styles$5.meta,
1814
+ children: [/* @__PURE__ */ jsx("span", {
1815
+ style: styles$5.path,
1816
+ children: row.path
1817
+ }), /* @__PURE__ */ jsx("span", {
1818
+ style: styles$5.cssVar,
1819
+ children: row.cssVar
1820
+ })]
1821
+ }),
1822
+ /* @__PURE__ */ jsx("div", {
1823
+ style: styles$5.sampleCell,
1824
+ children: /* @__PURE__ */ jsx(ShadowSample, { path: row.path })
1825
+ }),
1826
+ /* @__PURE__ */ jsx("div", {
1827
+ style: styles$5.breakdown,
1828
+ children: row.layers.length === 1 ? renderLayer(row.layers[0]) : row.layers.map((layer, i) => /* @__PURE__ */ jsx(Layer, {
1829
+ layer,
1830
+ index: i,
1831
+ total: row.layers.length
1832
+ }, layerKey(row.path, layer, i)))
1833
+ })
1834
+ ]
1835
+ }, row.path))]
1836
+ });
1837
+ }
1838
+ function renderLayer(layer) {
1839
+ if (!layer) return [];
1840
+ const entries = [
1841
+ ["offset", `${formatDimension(layer.offsetX)} ${formatDimension(layer.offsetY)}`],
1842
+ ["blur", formatDimension(layer.blur)],
1843
+ ["spread", formatDimension(layer.spread)],
1844
+ ["color", formatColor$1(layer.color)]
1845
+ ];
1846
+ if (layer.inset) entries.push(["inset", String(layer.inset)]);
1847
+ return entries.flatMap(([k, v]) => [/* @__PURE__ */ jsx("span", {
1848
+ style: styles$5.breakdownKey,
1849
+ children: k
1850
+ }, `k-${k}`), /* @__PURE__ */ jsx("span", { children: v }, `v-${k}`)]);
1851
+ }
1852
+ function Layer({ layer, index, total }) {
1853
+ return /* @__PURE__ */ jsxs("div", {
1854
+ style: { gridColumn: "1 / -1" },
1855
+ children: [/* @__PURE__ */ jsxs("div", {
1856
+ style: styles$5.layerHeader,
1857
+ children: [
1858
+ "layer ",
1859
+ index + 1,
1860
+ " of ",
1861
+ total
1862
+ ]
1863
+ }), /* @__PURE__ */ jsx("div", {
1864
+ style: {
1865
+ ...styles$5.breakdown,
1866
+ marginTop: 2
1867
+ },
1868
+ children: renderLayer(layer)
1869
+ })]
1870
+ });
1871
+ }
1872
+ //#endregion
1873
+ //#region src/StrokeStyleSample.tsx
1874
+ const STRING_STYLES = new Set([
1875
+ "solid",
1876
+ "dashed",
1877
+ "dotted",
1878
+ "double",
1879
+ "groove",
1880
+ "ridge",
1881
+ "outset",
1882
+ "inset"
1883
+ ]);
1884
+ const styles$4 = {
1885
+ wrapper: surfaceStyle,
1886
+ caption: captionStyle,
1887
+ empty: emptyStyle,
1888
+ row: {
1889
+ display: "grid",
1890
+ gridTemplateColumns: "minmax(160px, 220px) 1fr auto",
1891
+ gap: 16,
1892
+ alignItems: "center",
1893
+ padding: "14px 0",
1894
+ borderBottom: BORDER_DEFAULT
1895
+ },
1896
+ meta: {
1897
+ display: "flex",
1898
+ flexDirection: "column",
1899
+ gap: 2,
1900
+ minWidth: 0
1901
+ },
1902
+ path: {
1903
+ fontFamily: MONO_STACK,
1904
+ fontSize: 12,
1905
+ overflow: "hidden",
1906
+ textOverflow: "ellipsis",
1907
+ whiteSpace: "nowrap"
1908
+ },
1909
+ value: {
1910
+ fontFamily: MONO_STACK,
1911
+ fontSize: 11,
1912
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
1913
+ },
1914
+ line: {
1915
+ height: 0,
1916
+ borderTopWidth: 4,
1917
+ borderTopColor: "var(--sb-color-sys-text-default, CanvasText)",
1918
+ width: "100%"
1919
+ },
1920
+ objectFallback: {
1921
+ fontFamily: MONO_STACK,
1922
+ fontSize: 11,
1923
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
1924
+ },
1925
+ cssVar: {
1926
+ fontFamily: MONO_STACK,
1927
+ fontSize: 11,
1928
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
1929
+ }
1930
+ };
1931
+ function extractCssStyle(value) {
1932
+ if (typeof value === "string" && STRING_STYLES.has(value)) return value;
1933
+ return null;
1934
+ }
1935
+ function StrokeStyleSample({ filter = "strokeStyle", caption }) {
1936
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
1937
+ const rows = useMemo(() => {
1938
+ return Object.entries(resolved).filter(([path, token]) => {
1939
+ if (token.$type !== "strokeStyle") return false;
1940
+ return globMatch(path, filter);
1941
+ }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => ({
1942
+ path,
1943
+ cssVar: makeCssVar(path, cssVarPrefix),
1944
+ displayValue: formatValue(token.$value),
1945
+ cssStyle: extractCssStyle(token.$value)
1946
+ }));
1947
+ }, [
1948
+ resolved,
1949
+ filter,
1950
+ cssVarPrefix
1951
+ ]);
1952
+ const captionText = caption ?? `${rows.length} strokeStyle token${rows.length === 1 ? "" : "s"}${filter && filter !== "strokeStyle" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1953
+ if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1954
+ "data-theme": activeTheme,
1955
+ style: styles$4.wrapper,
1956
+ children: /* @__PURE__ */ jsx("div", {
1957
+ style: styles$4.empty,
1958
+ children: "No strokeStyle tokens match this filter."
1959
+ })
1960
+ });
1961
+ return /* @__PURE__ */ jsxs("div", {
1962
+ "data-theme": activeTheme,
1963
+ style: styles$4.wrapper,
1964
+ children: [/* @__PURE__ */ jsx("div", {
1965
+ style: styles$4.caption,
1966
+ children: captionText
1967
+ }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1968
+ style: styles$4.row,
1969
+ children: [
1970
+ /* @__PURE__ */ jsxs("div", {
1971
+ style: styles$4.meta,
1972
+ children: [/* @__PURE__ */ jsx("span", {
1973
+ style: styles$4.path,
1974
+ children: row.path
1975
+ }), /* @__PURE__ */ jsx("span", {
1976
+ style: styles$4.value,
1977
+ children: row.displayValue
1978
+ })]
1979
+ }),
1980
+ row.cssStyle ? /* @__PURE__ */ jsx("div", {
1981
+ style: {
1982
+ ...styles$4.line,
1983
+ borderTopStyle: row.cssStyle
1984
+ },
1985
+ "aria-hidden": true
1986
+ }) : /* @__PURE__ */ jsx("span", {
1987
+ style: styles$4.objectFallback,
1988
+ children: "Object-form (dashArray + lineCap) — no pure CSS `border-style` equivalent."
1989
+ }),
1990
+ /* @__PURE__ */ jsx("span", {
1991
+ style: styles$4.cssVar,
1992
+ children: row.cssVar
1993
+ })
1994
+ ]
1995
+ }, row.path))]
1996
+ });
1997
+ }
1998
+ //#endregion
1999
+ //#region src/token-detail/styles.ts
2000
+ const styles$3 = {
2001
+ wrapper: {
2002
+ ...surfaceStyle,
2003
+ padding: 16,
2004
+ border: BORDER_DEFAULT
2005
+ },
2006
+ heading: {
2007
+ margin: 0,
2008
+ fontFamily: MONO_STACK,
2009
+ fontSize: 16
2010
+ },
2011
+ subline: {
2012
+ display: "flex",
2013
+ alignItems: "center",
2014
+ gap: 8,
2015
+ margin: "4px 0 12px",
2016
+ fontSize: 12,
2017
+ opacity: .8
2018
+ },
2019
+ typePill: {
2020
+ display: "inline-block",
2021
+ padding: "2px 6px",
2022
+ borderRadius: 4,
2023
+ fontSize: 10,
2024
+ letterSpacing: .5,
2025
+ textTransform: "uppercase",
2026
+ background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
2027
+ },
2028
+ description: {
2029
+ margin: "0 0 12px",
2030
+ opacity: .85
2031
+ },
2032
+ sectionHeader: {
2033
+ fontFamily: MONO_STACK,
2034
+ fontSize: 11,
2035
+ textTransform: "uppercase",
2036
+ letterSpacing: .5,
2037
+ opacity: .6,
2038
+ margin: "12px 0 6px"
2039
+ },
2040
+ chain: {
2041
+ display: "flex",
2042
+ flexWrap: "wrap",
2043
+ gap: 6,
2044
+ alignItems: "center",
2045
+ fontFamily: MONO_STACK,
2046
+ fontSize: 12
2047
+ },
2048
+ chainNode: {
2049
+ padding: "2px 6px",
2050
+ borderRadius: 4,
2051
+ border: BORDER_DEFAULT
2052
+ },
2053
+ arrow: { opacity: .5 },
2054
+ themeTable: {
2055
+ width: "100%",
2056
+ borderCollapse: "collapse",
2057
+ tableLayout: "fixed",
2058
+ fontSize: 12
2059
+ },
2060
+ themeRow: { borderBottom: BORDER_FAINT },
2061
+ themeCell: {
2062
+ padding: "6px 8px",
2063
+ verticalAlign: "middle"
2064
+ },
2065
+ swatch: {
2066
+ display: "inline-block",
2067
+ width: 14,
2068
+ height: 14,
2069
+ verticalAlign: "middle",
2070
+ marginRight: 6,
2071
+ borderRadius: 3,
2072
+ border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
2073
+ },
2074
+ snippet: {
2075
+ display: "block",
2076
+ padding: "8px 10px",
2077
+ borderRadius: 4,
2078
+ background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.1))",
2079
+ fontFamily: MONO_STACK,
2080
+ fontSize: 12,
2081
+ whiteSpace: "pre",
2082
+ overflow: "auto"
2083
+ },
2084
+ missing: {
2085
+ padding: 12,
2086
+ opacity: .7
2087
+ },
2088
+ typographySample: { padding: "8px 0" },
2089
+ shadowSample: {
2090
+ width: 140,
2091
+ height: 56,
2092
+ background: "var(--sb-color-sys-surface-raised, #fff)",
2093
+ border: BORDER_FAINT,
2094
+ borderRadius: 6
2095
+ },
2096
+ borderSample: {
2097
+ width: 140,
2098
+ height: 56,
2099
+ background: "var(--sb-color-sys-surface-raised, transparent)",
2100
+ borderRadius: 6
2101
+ },
2102
+ gradientSample: {
2103
+ width: 220,
2104
+ height: 56,
2105
+ borderRadius: 6,
2106
+ border: BORDER_FAINT
2107
+ },
2108
+ strokeStyleLine: {
2109
+ height: 0,
2110
+ borderTopWidth: 4,
2111
+ borderTopColor: "var(--sb-color-sys-text-default, CanvasText)",
2112
+ width: 220
2113
+ },
2114
+ strokeStyleSvg: {
2115
+ width: 220,
2116
+ height: 24,
2117
+ color: "var(--sb-color-sys-text-default, CanvasText)"
2118
+ },
2119
+ strokeStyleFallback: {
2120
+ fontFamily: MONO_STACK,
2121
+ fontSize: 12,
2122
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
2123
+ },
2124
+ colorSwatchRow: {
2125
+ display: "flex",
2126
+ gap: 1,
2127
+ borderRadius: 6,
2128
+ overflow: "hidden",
2129
+ border: BORDER_DEFAULT,
2130
+ width: 220,
2131
+ height: 56
2132
+ },
2133
+ colorSwatchLight: {
2134
+ flex: 1,
2135
+ boxShadow: "inset 0 0 0 8px rgba(255, 255, 255, 0.9)"
2136
+ },
2137
+ colorSwatchDark: {
2138
+ flex: 1,
2139
+ boxShadow: "inset 0 0 0 8px rgba(17, 17, 17, 0.9)"
2140
+ },
2141
+ breakdownSection: {
2142
+ fontFamily: MONO_STACK,
2143
+ fontSize: 12,
2144
+ display: "grid",
2145
+ gridTemplateColumns: "auto 1fr",
2146
+ columnGap: 12,
2147
+ rowGap: 3,
2148
+ marginTop: 6
2149
+ },
2150
+ breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" },
2151
+ breakdownLayerHeader: {
2152
+ gridColumn: "1 / -1",
2153
+ fontSize: 10,
2154
+ textTransform: "uppercase",
2155
+ letterSpacing: .5,
2156
+ color: "var(--sb-color-sys-text-muted, CanvasText)",
2157
+ marginTop: 4
2158
+ },
2159
+ fontFamilySample: {
2160
+ padding: "4px 0",
2161
+ fontSize: 22,
2162
+ lineHeight: 1.2
2163
+ },
2164
+ fontWeightSample: {
2165
+ padding: "4px 0",
2166
+ fontSize: 32,
2167
+ lineHeight: 1
2168
+ },
2169
+ dimensionTrack: {
2170
+ display: "flex",
2171
+ alignItems: "center",
2172
+ height: 32,
2173
+ maxWidth: "100%",
2174
+ overflow: "hidden"
2175
+ },
2176
+ dimensionBar: {
2177
+ height: 16,
2178
+ background: "var(--sb-color-sys-accent-bg, #3b82f6)",
2179
+ borderRadius: 3,
2180
+ maxWidth: "100%"
2181
+ },
2182
+ motionTrack: {
2183
+ position: "relative",
2184
+ height: 32,
2185
+ width: "100%",
2186
+ maxWidth: 320,
2187
+ background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.08))",
2188
+ borderRadius: 16,
2189
+ overflow: "hidden"
2190
+ },
2191
+ motionBall: {
2192
+ position: "absolute",
2193
+ top: "50%",
2194
+ width: 24,
2195
+ height: 24,
2196
+ marginTop: -12,
2197
+ borderRadius: "50%",
2198
+ background: "var(--sb-color-sys-accent-bg, #3b82f6)"
2199
+ },
2200
+ aliasedByList: {
2201
+ listStyle: "none",
2202
+ margin: 0,
2203
+ padding: 0,
2204
+ fontFamily: MONO_STACK,
2205
+ fontSize: 12
2206
+ },
2207
+ aliasedByRow: {
2208
+ padding: "2px 0",
2209
+ display: "flex",
2210
+ alignItems: "center",
2211
+ gap: 6
2212
+ },
2213
+ aliasedByTruncated: {
2214
+ fontSize: 11,
2215
+ opacity: .6,
2216
+ fontStyle: "italic",
2217
+ marginTop: 4
2218
+ },
2219
+ reducedMotion: {
2220
+ fontSize: 11,
2221
+ color: "var(--sb-color-sys-text-muted, CanvasText)",
2222
+ fontStyle: "italic"
2223
+ },
2224
+ tupleIndicator: {
2225
+ fontSize: 11,
2226
+ opacity: .7,
2227
+ margin: "0 0 6px",
2228
+ fontFamily: MONO_STACK
2229
+ },
2230
+ consumerRow: {
2231
+ display: "flex",
2232
+ alignItems: "center",
2233
+ gap: 8,
2234
+ padding: "6px 10px",
2235
+ marginBottom: 4,
2236
+ borderRadius: 4,
2237
+ background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.1))"
2238
+ },
2239
+ consumerRowLabel: {
2240
+ fontFamily: MONO_STACK,
2241
+ fontSize: 10,
2242
+ textTransform: "uppercase",
2243
+ letterSpacing: .5,
2244
+ opacity: .6,
2245
+ minWidth: 32,
2246
+ flexShrink: 0
2247
+ },
2248
+ consumerRowValue: {
2249
+ flex: 1,
2250
+ fontFamily: MONO_STACK,
2251
+ fontSize: 12,
2252
+ whiteSpace: "nowrap",
2253
+ overflow: "auto"
2254
+ },
2255
+ consumerRowCopy: {
2256
+ padding: "3px 8px",
2257
+ fontSize: 11,
2258
+ fontFamily: MONO_STACK,
2259
+ background: "var(--sb-color-sys-surface-raised, Canvas)",
2260
+ color: "var(--sb-color-sys-text-default, CanvasText)",
2261
+ border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
2262
+ borderRadius: 4,
2263
+ cursor: "pointer",
2264
+ flexShrink: 0
2265
+ }
2266
+ };
2267
+ //#endregion
2268
+ //#region src/token-detail/internal.ts
2269
+ function useTokenDetailData(path) {
2270
+ const { activeTheme, activeAxes, axes, themes, themesResolved, resolved, cssVarPrefix } = useProject();
2271
+ const typedResolved = resolved;
2272
+ return {
2273
+ token: typedResolved[path],
2274
+ cssVar: makeCssVar(path, cssVarPrefix),
2275
+ activeTheme,
2276
+ activeAxes,
2277
+ axes,
2278
+ themes,
2279
+ themesResolved,
2280
+ resolved: typedResolved,
2281
+ cssVarPrefix
2282
+ };
2283
+ }
2284
+ //#endregion
2285
+ //#region src/token-detail/AliasChain.tsx
2286
+ function AliasChain({ path }) {
2287
+ const { token } = useTokenDetailData(path);
2288
+ const chain = useMemo(() => {
2289
+ if (!token) return [];
2290
+ if (Array.isArray(token.aliasChain) && token.aliasChain.length > 0) return [path, ...token.aliasChain];
2291
+ if (typeof token.aliasOf === "string") return [path, token.aliasOf];
2292
+ return [path];
2293
+ }, [token, path]);
2294
+ if (chain.length <= 1) return null;
2295
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2296
+ style: styles$3.sectionHeader,
2297
+ children: "Alias chain"
2298
+ }), /* @__PURE__ */ jsx("div", {
2299
+ style: styles$3.chain,
2300
+ children: chain.map((step, i) => /* @__PURE__ */ jsxs("span", {
2301
+ style: styles$3.chain,
2302
+ children: [/* @__PURE__ */ jsx("span", {
2303
+ style: styles$3.chainNode,
2304
+ children: step
2305
+ }), i < chain.length - 1 && /* @__PURE__ */ jsx("span", {
2306
+ style: styles$3.arrow,
2307
+ children: "→"
2308
+ })]
2309
+ }, step))
2310
+ })] });
2311
+ }
2312
+ //#endregion
2313
+ //#region src/token-detail/AliasedBy.tsx
2314
+ const ALIASED_BY_DEPTH_CAP = 6;
2315
+ const GROUP_RANK = {
2316
+ ref: 0,
2317
+ sys: 1
2318
+ };
2319
+ function AliasedBy({ path }) {
2320
+ const { resolved } = useTokenDetailData(path);
2321
+ const tree = useMemo(() => buildAliasedByTree(path, resolved), [path, resolved]);
2322
+ const truncated = useMemo(() => treeHasTruncation(tree), [tree]);
2323
+ if (tree.length === 0) return null;
2324
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2325
+ /* @__PURE__ */ jsx("div", {
2326
+ style: styles$3.sectionHeader,
2327
+ children: "Aliased by"
2328
+ }),
2329
+ /* @__PURE__ */ jsx("ul", {
2330
+ style: styles$3.aliasedByList,
2331
+ children: tree.map((node) => /* @__PURE__ */ jsx(AliasedByRow, {
2332
+ node,
2333
+ depth: 0
2334
+ }, node.path))
2335
+ }),
2336
+ truncated && /* @__PURE__ */ jsxs("div", {
2337
+ style: styles$3.aliasedByTruncated,
2338
+ children: [
2339
+ "Further descendants truncated at depth ",
2340
+ ALIASED_BY_DEPTH_CAP,
2341
+ "."
2342
+ ]
2343
+ })
2344
+ ] });
2345
+ }
2346
+ function AliasedByRow({ node, depth }) {
2347
+ return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("div", {
2348
+ style: {
2349
+ ...styles$3.aliasedByRow,
2350
+ paddingLeft: depth * 16
2351
+ },
2352
+ children: /* @__PURE__ */ jsx("span", {
2353
+ style: styles$3.chainNode,
2354
+ children: node.path
2355
+ })
2356
+ }), node.children.length > 0 && /* @__PURE__ */ jsx("ul", {
2357
+ style: styles$3.aliasedByList,
2358
+ children: node.children.map((child) => /* @__PURE__ */ jsx(AliasedByRow, {
2359
+ node: child,
2360
+ depth: depth + 1
2361
+ }, child.path))
2362
+ })] });
2363
+ }
2364
+ function buildAliasedByTree(rootPath, resolved) {
2365
+ const direct = resolved[rootPath]?.aliasedBy;
2366
+ if (!direct || direct.length === 0) return [];
2367
+ const visited = new Set([rootPath]);
2368
+ return sortPaths(direct).map((p) => walk(p, resolved, visited, 1));
2369
+ }
2370
+ function walk(path, resolved, visited, depth) {
2371
+ if (visited.has(path)) return {
2372
+ path,
2373
+ children: []
2374
+ };
2375
+ visited.add(path);
2376
+ const parents = resolved[path]?.aliasedBy;
2377
+ if (!parents || parents.length === 0) return {
2378
+ path,
2379
+ children: []
2380
+ };
2381
+ if (depth >= ALIASED_BY_DEPTH_CAP) return {
2382
+ path,
2383
+ children: [],
2384
+ truncated: true
2385
+ };
2386
+ return {
2387
+ path,
2388
+ children: sortPaths(parents).map((p) => walk(p, resolved, visited, depth + 1))
2389
+ };
2390
+ }
2391
+ function sortPaths(paths) {
2392
+ return paths.toSorted((a, b) => {
2393
+ const ra = GROUP_RANK[a.split(".")[0] ?? ""] ?? 2;
2394
+ const rb = GROUP_RANK[b.split(".")[0] ?? ""] ?? 2;
2395
+ return ra !== rb ? ra - rb : a.localeCompare(b, void 0, { numeric: true });
2396
+ });
2397
+ }
2398
+ function treeHasTruncation(nodes) {
2399
+ for (const n of nodes) {
2400
+ if (n.truncated) return true;
2401
+ if (treeHasTruncation(n.children)) return true;
2402
+ }
2403
+ return false;
2404
+ }
2405
+ //#endregion
2406
+ //#region src/token-detail/AxisVariance.tsx
2407
+ function AxisVariance({ path }) {
2408
+ const { token, cssVar, axes, themes, themesResolved, activeAxes } = useTokenDetailData(path);
2409
+ const colorFormat = useColorFormat();
2410
+ const isColor = token?.$type === "color";
2411
+ const formatFn = (t) => valueFor(t, isColor, colorFormat);
2412
+ const variance = useMemo(() => analyzeVariance(path, axes, themes, themesResolved), [
2413
+ path,
2414
+ axes,
2415
+ themes,
2416
+ themesResolved
2417
+ ]);
2418
+ if (themes.length === 0) return /* @__PURE__ */ jsx(Fragment, {});
2419
+ if (variance.kind === "constant") {
2420
+ const anyTheme = themes[0];
2421
+ const value = anyTheme ? formatFn(themesResolved[anyTheme.name]?.[path]) : "—";
2422
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2423
+ style: styles$3.sectionHeader,
2424
+ children: "Values across axes"
2425
+ }), /* @__PURE__ */ jsx("table", {
2426
+ style: styles$3.themeTable,
2427
+ "data-testid": "token-detail-values",
2428
+ children: /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", {
2429
+ style: styles$3.themeRow,
2430
+ children: /* @__PURE__ */ jsxs("td", {
2431
+ style: styles$3.themeCell,
2432
+ "data-testid": "token-detail-constant",
2433
+ children: [
2434
+ isColor && /* @__PURE__ */ jsx("span", {
2435
+ style: {
2436
+ ...styles$3.swatch,
2437
+ background: cssVar
2438
+ },
2439
+ "aria-hidden": true
2440
+ }),
2441
+ value,
2442
+ /* @__PURE__ */ jsxs("span", {
2443
+ style: {
2444
+ opacity: .6,
2445
+ marginLeft: 8
2446
+ },
2447
+ children: [
2448
+ "same across all ",
2449
+ themes.length,
2450
+ " tuples"
2451
+ ]
2452
+ })
2453
+ ]
2454
+ })
2455
+ }) })
2456
+ })] });
2457
+ }
2458
+ if (variance.kind === "one-axis") {
2459
+ const axisName = variance.varyingAxes[0];
2460
+ if (!axisName) return /* @__PURE__ */ jsx(Fragment, {});
2461
+ const axis = axes.find((a) => a.name === axisName);
2462
+ if (!axis) return /* @__PURE__ */ jsx(Fragment, {});
2463
+ const contextValues = axis.contexts.map((ctx) => {
2464
+ const target = {
2465
+ ...activeAxes,
2466
+ [axisName]: ctx
2467
+ };
2468
+ const name = themes.find((t) => {
2469
+ const input = t.input;
2470
+ return Object.keys(input).every((k) => input[k] === target[k]);
2471
+ })?.name ?? "";
2472
+ return {
2473
+ ctx,
2474
+ themeName: name,
2475
+ value: name ? formatFn(themesResolved[name]?.[path]) : "—"
2476
+ };
2477
+ });
2478
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
2479
+ style: styles$3.sectionHeader,
2480
+ children: ["Varies with ", axisName]
2481
+ }), /* @__PURE__ */ jsx("table", {
2482
+ style: styles$3.themeTable,
2483
+ "data-testid": "token-detail-values",
2484
+ children: /* @__PURE__ */ jsx("tbody", { children: contextValues.map((row) => /* @__PURE__ */ jsxs("tr", {
2485
+ style: styles$3.themeRow,
2486
+ "data-axis": axisName,
2487
+ "data-context": row.ctx,
2488
+ children: [/* @__PURE__ */ jsx("td", {
2489
+ style: {
2490
+ ...styles$3.themeCell,
2491
+ width: "30%"
2492
+ },
2493
+ children: row.ctx
2494
+ }), /* @__PURE__ */ jsxs("td", {
2495
+ style: styles$3.themeCell,
2496
+ children: [isColor && row.themeName && /* @__PURE__ */ jsx("span", {
2497
+ style: {
2498
+ ...styles$3.swatch,
2499
+ background: cssVar
2500
+ },
2501
+ "data-theme": row.themeName,
2502
+ "aria-hidden": true
2503
+ }), row.value]
2504
+ })]
2505
+ }, row.ctx)) })
2506
+ })] });
2507
+ }
2508
+ const [rowAxis, colAxis, ...extra] = variance.varyingAxes.map((name) => axes.find((a) => a.name === name)).filter((a) => Boolean(a)).toSorted((a, b) => b.contexts.length - a.contexts.length);
2509
+ if (!rowAxis || !colAxis) return /* @__PURE__ */ jsx(Fragment, {});
2510
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2511
+ /* @__PURE__ */ jsxs("div", {
2512
+ style: styles$3.sectionHeader,
2513
+ children: ["Varies with ", variance.varyingAxes.join(" × ")]
2514
+ }),
2515
+ /* @__PURE__ */ jsxs("table", {
2516
+ style: styles$3.themeTable,
2517
+ "data-testid": "token-detail-values",
2518
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
2519
+ style: styles$3.themeRow,
2520
+ children: [/* @__PURE__ */ jsxs("th", {
2521
+ style: {
2522
+ ...styles$3.themeCell,
2523
+ textAlign: "left",
2524
+ opacity: .7
2525
+ },
2526
+ children: [
2527
+ rowAxis.name,
2528
+ " \\ ",
2529
+ colAxis.name
2530
+ ]
2531
+ }), colAxis.contexts.map((col) => /* @__PURE__ */ jsx("th", {
2532
+ style: {
2533
+ ...styles$3.themeCell,
2534
+ textAlign: "left",
2535
+ opacity: .7
2536
+ },
2537
+ children: col
2538
+ }, col))]
2539
+ }) }), /* @__PURE__ */ jsx("tbody", { children: rowAxis.contexts.map((row) => /* @__PURE__ */ jsxs("tr", {
2540
+ style: styles$3.themeRow,
2541
+ children: [/* @__PURE__ */ jsx("td", {
2542
+ style: styles$3.themeCell,
2543
+ children: row
2544
+ }), colAxis.contexts.map((col) => {
2545
+ const name = tupleName(themes, {
2546
+ ...activeAxes,
2547
+ [rowAxis.name]: row,
2548
+ [colAxis.name]: col
2549
+ });
2550
+ const value = name ? formatFn(themesResolved[name]?.[path]) : "—";
2551
+ return /* @__PURE__ */ jsxs("td", {
2552
+ style: styles$3.themeCell,
2553
+ "data-row": row,
2554
+ "data-col": col,
2555
+ children: [isColor && name && /* @__PURE__ */ jsx("span", {
2556
+ style: {
2557
+ ...styles$3.swatch,
2558
+ background: cssVar
2559
+ },
2560
+ "data-theme": name,
2561
+ "aria-hidden": true
2562
+ }), value]
2563
+ }, col);
2564
+ })]
2565
+ }, row)) })]
2566
+ }),
2567
+ extra.length > 0 && /* @__PURE__ */ jsxs("div", {
2568
+ style: {
2569
+ ...styles$3.aliasedByTruncated,
2570
+ marginTop: 6
2571
+ },
2572
+ children: [
2573
+ "Values also vary with ",
2574
+ extra.map((a) => a.name).join(", "),
2575
+ "; matrix shows the slice for the active selection."
2576
+ ]
2577
+ })
2578
+ ] });
2579
+ }
2580
+ function valueFor(token, isColor, format) {
2581
+ if (!token) return "—";
2582
+ if (isColor) return formatColor(token.$value, format).value;
2583
+ return formatValue(token.$value);
2584
+ }
2585
+ function themeValue(themesResolved, themeName, path) {
2586
+ const t = themesResolved[themeName]?.[path];
2587
+ return t ? formatValue(t.$value) : "—";
2588
+ }
2589
+ function tupleName(themes, tuple) {
2590
+ return themes.find((t) => {
2591
+ const input = t.input;
2592
+ return Object.keys(input).every((k) => input[k] === tuple[k]);
2593
+ })?.name;
2594
+ }
2595
+ function analyzeVariance(path, axes, themes, themesResolved) {
2596
+ const varyingAxes = [];
2597
+ for (const axis of axes) {
2598
+ const byOthers = /* @__PURE__ */ new Map();
2599
+ for (const theme of themes) {
2600
+ const others = axes.filter((a) => a.name !== axis.name).map((a) => `${a.name}=${theme.input[a.name] ?? ""}`).join("|");
2601
+ const ctx = theme.input[axis.name] ?? "";
2602
+ const bucket = byOthers.get(others) ?? /* @__PURE__ */ new Map();
2603
+ bucket.set(ctx, themeValue(themesResolved, theme.name, path));
2604
+ byOthers.set(others, bucket);
2605
+ }
2606
+ let varies = false;
2607
+ for (const bucket of byOthers.values()) if (new Set(bucket.values()).size > 1) {
2608
+ varies = true;
2609
+ break;
2610
+ }
2611
+ if (varies) varyingAxes.push(axis.name);
2612
+ }
2613
+ if (varyingAxes.length === 0) return {
2614
+ kind: "constant",
2615
+ varyingAxes
2616
+ };
2617
+ if (varyingAxes.length === 1) return {
2618
+ kind: "one-axis",
2619
+ varyingAxes
2620
+ };
2621
+ return {
2622
+ kind: "multi-axis",
2623
+ varyingAxes
2624
+ };
2625
+ }
2626
+ //#endregion
2627
+ //#region src/token-detail/CompositeBreakdown.tsx
2628
+ function CompositeBreakdown({ path }) {
2629
+ const { token } = useTokenDetailData(path);
2630
+ if (!token) return null;
2631
+ return /* @__PURE__ */ jsx(CompositeBreakdownContent, {
2632
+ type: token.$type,
2633
+ rawValue: token.$value
2634
+ });
2635
+ }
2636
+ function CompositeBreakdownContent({ type, rawValue }) {
2637
+ if (!rawValue || typeof rawValue !== "object") return null;
2638
+ if (type === "typography") {
2639
+ const v = rawValue;
2640
+ return renderKeyValueList([
2641
+ ["fontFamily", formatFontFamily(v["fontFamily"])],
2642
+ ["fontSize", formatDimensionValue(v["fontSize"])],
2643
+ ["fontWeight", formatPrimitive(v["fontWeight"])],
2644
+ ["lineHeight", formatPrimitive(v["lineHeight"])],
2645
+ ["letterSpacing", formatDimensionValue(v["letterSpacing"])]
2646
+ ]);
2647
+ }
2648
+ if (type === "border") {
2649
+ const v = rawValue;
2650
+ return renderKeyValueList([
2651
+ ["color", formatColorValue(v["color"])],
2652
+ ["width", formatDimensionValue(v["width"])],
2653
+ ["style", formatPrimitive(v["style"])]
2654
+ ]);
2655
+ }
2656
+ if (type === "transition") {
2657
+ const v = rawValue;
2658
+ return renderKeyValueList([
2659
+ ["duration", formatDimensionValue(v["duration"])],
2660
+ ["timingFunction", formatPrimitive(v["timingFunction"])],
2661
+ ["delay", formatDimensionValue(v["delay"])]
2662
+ ]);
2663
+ }
2664
+ if (type === "shadow") {
2665
+ const layers = Array.isArray(rawValue) ? rawValue : [rawValue];
2666
+ const multi = layers.length > 1;
2667
+ return /* @__PURE__ */ jsx("div", {
2668
+ style: styles$3.breakdownSection,
2669
+ children: layers.map((layer, i) => {
2670
+ const v = layer;
2671
+ return /* @__PURE__ */ jsxs("div", {
2672
+ style: { display: "contents" },
2673
+ children: [
2674
+ multi && /* @__PURE__ */ jsxs("div", {
2675
+ style: styles$3.breakdownLayerHeader,
2676
+ children: ["Layer ", i + 1]
2677
+ }),
2678
+ /* @__PURE__ */ jsx(KeyValueRow, {
2679
+ label: "color",
2680
+ value: formatColorValue(v["color"])
2681
+ }),
2682
+ /* @__PURE__ */ jsx(KeyValueRow, {
2683
+ label: "offsetX",
2684
+ value: formatDimensionValue(v["offsetX"])
2685
+ }),
2686
+ /* @__PURE__ */ jsx(KeyValueRow, {
2687
+ label: "offsetY",
2688
+ value: formatDimensionValue(v["offsetY"])
2689
+ }),
2690
+ /* @__PURE__ */ jsx(KeyValueRow, {
2691
+ label: "blur",
2692
+ value: formatDimensionValue(v["blur"])
2693
+ }),
2694
+ /* @__PURE__ */ jsx(KeyValueRow, {
2695
+ label: "spread",
2696
+ value: formatDimensionValue(v["spread"])
2697
+ }),
2698
+ "inset" in v && /* @__PURE__ */ jsx(KeyValueRow, {
2699
+ label: "inset",
2700
+ value: formatPrimitive(v["inset"])
2701
+ })
2702
+ ]
2703
+ }, shadowLayerKey(v, i));
2704
+ })
2705
+ });
2706
+ }
2707
+ if (type === "gradient") {
2708
+ const stops = Array.isArray(rawValue) ? rawValue : [];
2709
+ if (stops.length === 0) return null;
2710
+ return /* @__PURE__ */ jsx("div", {
2711
+ style: styles$3.breakdownSection,
2712
+ children: stops.map((stop, i) => {
2713
+ const v = stop;
2714
+ return /* @__PURE__ */ jsx(KeyValueRow, {
2715
+ label: `${((typeof v["position"] === "number" ? v["position"] : 0) * 100).toFixed(0)}%`,
2716
+ value: formatColorValue(v["color"])
2717
+ }, gradientStopKey(v, i));
2718
+ })
2719
+ });
2720
+ }
2721
+ return null;
2722
+ }
2723
+ function renderKeyValueList(rows) {
2724
+ return /* @__PURE__ */ jsx("div", {
2725
+ style: styles$3.breakdownSection,
2726
+ children: rows.filter(([, v]) => v !== null).map(([k, v]) => /* @__PURE__ */ jsx(KeyValueRow, {
2727
+ label: k,
2728
+ value: v ?? ""
2729
+ }, k))
2730
+ });
2731
+ }
2732
+ function KeyValueRow({ label, value }) {
2733
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
2734
+ style: styles$3.breakdownKey,
2735
+ children: label
2736
+ }), /* @__PURE__ */ jsx("span", { children: value ?? "—" })] });
2737
+ }
2738
+ function formatPrimitive(v) {
2739
+ if (v == null) return null;
2740
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
2741
+ return JSON.stringify(v);
2742
+ }
2743
+ function formatFontFamily(v) {
2744
+ if (v == null) return null;
2745
+ if (typeof v === "string") return v;
2746
+ if (Array.isArray(v)) return v.map(String).join(", ");
2747
+ return JSON.stringify(v);
2748
+ }
2749
+ function formatDimensionValue(v) {
2750
+ if (v == null) return null;
2751
+ if (typeof v === "string" || typeof v === "number") return String(v);
2752
+ if (typeof v === "object") {
2753
+ const d = v;
2754
+ if (typeof d.value === "number" && typeof d.unit === "string") return `${d.value}${d.unit}`;
2755
+ }
2756
+ return JSON.stringify(v);
2757
+ }
2758
+ function formatColorValue(v) {
2759
+ if (v == null) return null;
2760
+ if (typeof v === "string") return v;
2761
+ if (typeof v === "object") {
2762
+ const c = v;
2763
+ if (Array.isArray(c.components) && typeof c.colorSpace === "string") {
2764
+ const parts = c.components.map((n) => typeof n === "number" ? n.toFixed(3) : String(n));
2765
+ const alpha = typeof c.alpha === "number" && c.alpha !== 1 ? ` / ${c.alpha}` : "";
2766
+ return `${c.colorSpace}(${parts.join(" ")}${alpha})`;
2767
+ }
2768
+ }
2769
+ return JSON.stringify(v);
2770
+ }
2771
+ function shadowLayerKey(layer, fallback) {
2772
+ return `shadow|${[
2773
+ layer["color"],
2774
+ layer["offsetX"],
2775
+ layer["offsetY"],
2776
+ layer["blur"],
2777
+ layer["spread"],
2778
+ layer["inset"]
2779
+ ].map((p) => p === void 0 ? "" : JSON.stringify(p)).join("|")}|${fallback}`;
2780
+ }
2781
+ function gradientStopKey(stop, fallback) {
2782
+ return `stop|${stop["position"] ?? fallback}|${JSON.stringify(stop["color"])}`;
2783
+ }
2784
+ //#endregion
2785
+ //#region src/token-detail/CompositePreview.tsx
2786
+ const PANGRAM = "Sphinx of black quartz, judge my vow.";
2787
+ const STROKE_STYLE_STRINGS = new Set([
2788
+ "solid",
2789
+ "dashed",
2790
+ "dotted",
2791
+ "double",
2792
+ "groove",
2793
+ "ridge",
2794
+ "outset",
2795
+ "inset"
2796
+ ]);
2797
+ function CompositePreview({ path }) {
2798
+ const { token, cssVar } = useTokenDetailData(path);
2799
+ if (!token) return null;
2800
+ return /* @__PURE__ */ jsx(CompositePreviewContent, {
2801
+ type: token.$type,
2802
+ cssVar,
2803
+ rawValue: token.$value
2804
+ });
2805
+ }
2806
+ function CompositePreviewContent({ type, cssVar, rawValue }) {
2807
+ if (type === "typography") {
2808
+ const base = cssVar.replace(/^var\(/, "").replace(/\)$/, "");
2809
+ return /* @__PURE__ */ jsx("div", {
2810
+ style: {
2811
+ ...styles$3.typographySample,
2812
+ fontFamily: `var(${base}-font-family)`,
2813
+ fontSize: `var(${base}-font-size)`,
2814
+ fontWeight: `var(${base}-font-weight)`,
2815
+ lineHeight: `var(${base}-line-height)`,
2816
+ letterSpacing: `var(${base}-letter-spacing)`
2817
+ },
2818
+ children: PANGRAM
2819
+ });
2820
+ }
2821
+ if (type === "shadow") return /* @__PURE__ */ jsx("div", {
2822
+ style: {
2823
+ ...styles$3.shadowSample,
2824
+ boxShadow: cssVar
2825
+ },
2826
+ "aria-hidden": true
2827
+ });
2828
+ if (type === "border") return /* @__PURE__ */ jsx("div", {
2829
+ style: {
2830
+ ...styles$3.borderSample,
2831
+ border: cssVar
2832
+ },
2833
+ "aria-hidden": true
2834
+ });
2835
+ if (type === "transition") return /* @__PURE__ */ jsx(TransitionSample, { transition: cssVar });
2836
+ if (type === "dimension") return /* @__PURE__ */ jsx("div", {
2837
+ style: styles$3.dimensionTrack,
2838
+ children: /* @__PURE__ */ jsx("div", {
2839
+ style: {
2840
+ ...styles$3.dimensionBar,
2841
+ width: cssVar
2842
+ },
2843
+ "aria-hidden": true
2844
+ })
2845
+ });
2846
+ if (type === "duration") return /* @__PURE__ */ jsx(TransitionSample, { transition: `left ${cssVar} ease` });
2847
+ if (type === "fontFamily") return /* @__PURE__ */ jsx("div", {
2848
+ style: {
2849
+ ...styles$3.fontFamilySample,
2850
+ fontFamily: cssVar
2851
+ },
2852
+ children: PANGRAM
2853
+ });
2854
+ if (type === "fontWeight") return /* @__PURE__ */ jsx("div", {
2855
+ style: {
2856
+ ...styles$3.fontWeightSample,
2857
+ fontWeight: cssVar
2858
+ },
2859
+ children: "Aa"
2860
+ });
2861
+ if (type === "cubicBezier") return /* @__PURE__ */ jsx(TransitionSample, { transition: `left 800ms ${cssVar}` });
2862
+ if (type === "gradient") return /* @__PURE__ */ jsx("div", {
2863
+ style: {
2864
+ ...styles$3.gradientSample,
2865
+ background: `linear-gradient(to right, ${cssVar})`
2866
+ },
2867
+ "aria-hidden": true
2868
+ });
2869
+ if (type === "strokeStyle") return /* @__PURE__ */ jsx(StrokeStylePreview, { value: rawValue });
2870
+ if (type === "color") return /* @__PURE__ */ jsxs("div", {
2871
+ style: styles$3.colorSwatchRow,
2872
+ "aria-hidden": true,
2873
+ children: [/* @__PURE__ */ jsx("div", { style: {
2874
+ ...styles$3.colorSwatchLight,
2875
+ background: cssVar
2876
+ } }), /* @__PURE__ */ jsx("div", { style: {
2877
+ ...styles$3.colorSwatchDark,
2878
+ background: cssVar
2879
+ } })]
2880
+ });
2881
+ return null;
2882
+ }
2883
+ function StrokeStylePreview({ value }) {
2884
+ if (typeof value === "string" && STROKE_STYLE_STRINGS.has(value)) return /* @__PURE__ */ jsx("div", {
2885
+ style: {
2886
+ ...styles$3.strokeStyleLine,
2887
+ borderTopStyle: value
2888
+ },
2889
+ "aria-hidden": true
2890
+ });
2891
+ if (value && typeof value === "object" && "dashArray" in value) {
2892
+ const v = value;
2893
+ const lengths = asDashLengths(v.dashArray);
2894
+ if (lengths.length === 0) return /* @__PURE__ */ jsx("div", {
2895
+ style: styles$3.strokeStyleFallback,
2896
+ children: "Object-form strokeStyle with no resolvable dashArray."
2897
+ });
2898
+ const cap = typeof v.lineCap === "string" ? v.lineCap : "butt";
2899
+ return /* @__PURE__ */ jsx("svg", {
2900
+ style: styles$3.strokeStyleSvg,
2901
+ viewBox: "0 0 220 24",
2902
+ preserveAspectRatio: "none",
2903
+ "aria-hidden": true,
2904
+ children: /* @__PURE__ */ jsx("line", {
2905
+ x1: "4",
2906
+ y1: "12",
2907
+ x2: "216",
2908
+ y2: "12",
2909
+ stroke: "currentColor",
2910
+ strokeWidth: "4",
2911
+ strokeDasharray: lengths.join(" "),
2912
+ strokeLinecap: cap
2913
+ })
2914
+ });
2915
+ }
2916
+ return /* @__PURE__ */ jsx("div", {
2917
+ style: styles$3.strokeStyleFallback,
2918
+ children: "strokeStyle value could not be previewed."
2919
+ });
2920
+ }
2921
+ function asDashLengths(raw) {
2922
+ if (!Array.isArray(raw)) return [];
2923
+ const out = [];
2924
+ for (const entry of raw) {
2925
+ if (typeof entry === "number") {
2926
+ out.push(entry);
2927
+ continue;
2928
+ }
2929
+ if (entry && typeof entry === "object") {
2930
+ const e = entry;
2931
+ if (typeof e.value === "number") out.push(e.value);
2932
+ }
2933
+ }
2934
+ return out;
2935
+ }
2936
+ function TransitionSample({ transition }) {
2937
+ const reduced = usePrefersReducedMotion();
2938
+ const [phase, setPhase] = useState(0);
2939
+ useEffect(() => {
2940
+ if (reduced) return;
2941
+ const id = requestAnimationFrame(() => setPhase(1));
2942
+ const loop = window.setInterval(() => {
2943
+ setPhase((p) => p === 0 ? 1 : 0);
2944
+ }, 1200);
2945
+ return () => {
2946
+ cancelAnimationFrame(id);
2947
+ window.clearInterval(loop);
2948
+ };
2949
+ }, [reduced]);
2950
+ if (reduced) return /* @__PURE__ */ jsx("div", {
2951
+ style: styles$3.reducedMotion,
2952
+ children: "Animation suppressed by `prefers-reduced-motion: reduce`."
2953
+ });
2954
+ return /* @__PURE__ */ jsx("div", {
2955
+ style: styles$3.motionTrack,
2956
+ children: /* @__PURE__ */ jsx("div", {
2957
+ style: {
2958
+ ...styles$3.motionBall,
2959
+ left: phase === 1 ? "calc(100% - 28px)" : "4px",
2960
+ transition
2961
+ },
2962
+ "aria-hidden": true
2963
+ })
2964
+ });
2965
+ }
2966
+ //#endregion
2967
+ //#region src/token-detail/ConsumerOutput.tsx
2968
+ function ConsumerOutput({ path }) {
2969
+ const { token, cssVar, activeAxes } = useTokenDetailData(path);
2970
+ if (!token) return null;
2971
+ const tupleLabel = Object.entries(activeAxes).map(([k, v]) => `${k}=${v}`).join(", ");
2972
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2973
+ /* @__PURE__ */ jsx("div", {
2974
+ style: styles$3.sectionHeader,
2975
+ children: "Consumer output"
2976
+ }),
2977
+ tupleLabel && /* @__PURE__ */ jsxs("div", {
2978
+ style: styles$3.tupleIndicator,
2979
+ children: ["Active tuple: ", /* @__PURE__ */ jsx("strong", { children: tupleLabel })]
2980
+ }),
2981
+ /* @__PURE__ */ jsx(OutputRow, {
2982
+ label: "Path",
2983
+ value: path,
2984
+ testId: "consumer-output-path"
2985
+ }),
2986
+ /* @__PURE__ */ jsx(OutputRow, {
2987
+ label: "CSS",
2988
+ value: cssVar,
2989
+ testId: "consumer-output-css"
2990
+ })
2991
+ ] });
2992
+ }
2993
+ function OutputRow({ label, value, testId }) {
2994
+ return /* @__PURE__ */ jsxs("div", {
2995
+ style: styles$3.consumerRow,
2996
+ children: [
2997
+ /* @__PURE__ */ jsx("span", {
2998
+ style: styles$3.consumerRowLabel,
2999
+ children: label
3000
+ }),
3001
+ /* @__PURE__ */ jsx("code", {
3002
+ style: styles$3.consumerRowValue,
3003
+ "data-testid": testId,
3004
+ children: value
3005
+ }),
3006
+ /* @__PURE__ */ jsx(CopyButton, {
3007
+ text: value,
3008
+ testId: `${testId}-copy`
3009
+ })
3010
+ ]
3011
+ });
3012
+ }
3013
+ function CopyButton({ text, testId }) {
3014
+ const [copied, setCopied] = useState(false);
3015
+ return /* @__PURE__ */ jsx("button", {
3016
+ type: "button",
3017
+ style: styles$3.consumerRowCopy,
3018
+ "data-testid": testId,
3019
+ onClick: () => {
3020
+ copyToClipboard(text).then((ok) => {
3021
+ if (!ok) return;
3022
+ setCopied(true);
3023
+ window.setTimeout(() => setCopied(false), 1200);
3024
+ });
3025
+ },
3026
+ children: copied ? "Copied" : "Copy"
3027
+ });
3028
+ }
3029
+ async function copyToClipboard(text) {
3030
+ if (typeof navigator === "undefined" || !navigator.clipboard) return false;
3031
+ try {
3032
+ await navigator.clipboard.writeText(text);
3033
+ return true;
3034
+ } catch {
3035
+ return false;
3036
+ }
3037
+ }
3038
+ //#endregion
3039
+ //#region src/token-detail/TokenHeader.tsx
3040
+ function TokenHeader({ path, heading }) {
3041
+ const { token, cssVar, activeTheme } = useTokenDetailData(path);
3042
+ if (!token) return /* @__PURE__ */ jsxs("div", {
3043
+ style: styles$3.missing,
3044
+ children: [
3045
+ "Token ",
3046
+ /* @__PURE__ */ jsx("code", { children: path }),
3047
+ " not found in theme ",
3048
+ /* @__PURE__ */ jsx("strong", { children: activeTheme }),
3049
+ "."
3050
+ ]
3051
+ });
3052
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3053
+ /* @__PURE__ */ jsx("h3", {
3054
+ style: styles$3.heading,
3055
+ children: heading ?? path
3056
+ }),
3057
+ /* @__PURE__ */ jsxs("div", {
3058
+ style: styles$3.subline,
3059
+ children: [token.$type && /* @__PURE__ */ jsx("span", {
3060
+ style: styles$3.typePill,
3061
+ children: token.$type
3062
+ }), /* @__PURE__ */ jsx("span", { children: cssVar })]
3063
+ }),
3064
+ token.$description && /* @__PURE__ */ jsx("p", {
3065
+ style: styles$3.description,
3066
+ children: token.$description
3067
+ })
3068
+ ] });
3069
+ }
3070
+ //#endregion
3071
+ //#region src/token-detail/TokenUsageSnippet.tsx
3072
+ function TokenUsageSnippet({ path }) {
3073
+ const { token, cssVar } = useTokenDetailData(path);
3074
+ if (!token) return null;
3075
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
3076
+ style: styles$3.sectionHeader,
3077
+ children: "Usage"
3078
+ }), /* @__PURE__ */ jsx("code", {
3079
+ style: styles$3.snippet,
3080
+ children: `color: ${cssVar};`
3081
+ })] });
3082
+ }
3083
+ //#endregion
3084
+ //#region src/TokenDetail.tsx
3085
+ function TokenDetail({ path, heading }) {
3086
+ const { token, cssVar, activeTheme } = useTokenDetailData(path);
3087
+ const colorFormat = useColorFormat();
3088
+ if (!token) return /* @__PURE__ */ jsx("div", {
3089
+ "data-theme": activeTheme,
3090
+ style: styles$3.wrapper,
3091
+ children: /* @__PURE__ */ jsxs("div", {
3092
+ style: styles$3.missing,
3093
+ children: [
3094
+ "Token ",
3095
+ /* @__PURE__ */ jsx("code", { children: path }),
3096
+ " not found in theme ",
3097
+ /* @__PURE__ */ jsx("strong", { children: activeTheme }),
3098
+ "."
3099
+ ]
3100
+ })
3101
+ });
3102
+ const isColor = token.$type === "color";
3103
+ const formatted = isColor ? formatColor(token.$value, colorFormat) : null;
3104
+ const value = formatted ? formatted.value : formatValue(token.$value);
3105
+ const outOfGamut = formatted?.outOfGamut ?? false;
3106
+ return /* @__PURE__ */ jsxs("div", {
3107
+ "data-theme": activeTheme,
3108
+ style: styles$3.wrapper,
3109
+ children: [
3110
+ /* @__PURE__ */ jsx(TokenHeader, {
3111
+ path,
3112
+ ...heading !== void 0 && { heading }
3113
+ }),
3114
+ /* @__PURE__ */ jsxs("div", {
3115
+ style: styles$3.sectionHeader,
3116
+ children: ["Resolved value · ", activeTheme]
3117
+ }),
3118
+ /* @__PURE__ */ jsx(CompositePreview, { path }),
3119
+ /* @__PURE__ */ jsx(CompositeBreakdown, { path }),
3120
+ /* @__PURE__ */ jsxs("div", {
3121
+ style: styles$3.chain,
3122
+ children: [
3123
+ isColor && /* @__PURE__ */ jsx("span", {
3124
+ style: {
3125
+ ...styles$3.swatch,
3126
+ background: cssVar
3127
+ },
3128
+ "aria-hidden": true
3129
+ }),
3130
+ /* @__PURE__ */ jsx("span", { children: value }),
3131
+ outOfGamut && /* @__PURE__ */ jsx("span", {
3132
+ title: "Out of sRGB gamut for this format",
3133
+ "aria-label": "out of gamut",
3134
+ style: { marginLeft: 6 },
3135
+ children: "⚠"
3136
+ })
3137
+ ]
3138
+ }),
3139
+ /* @__PURE__ */ jsx(AliasChain, { path }),
3140
+ /* @__PURE__ */ jsx(AliasedBy, { path }),
3141
+ /* @__PURE__ */ jsx(TokenUsageSnippet, { path }),
3142
+ /* @__PURE__ */ jsx(ConsumerOutput, { path }),
3143
+ /* @__PURE__ */ jsx(AxisVariance, { path })
3144
+ ]
3145
+ });
3146
+ }
3147
+ //#endregion
3148
+ //#region src/TokenNavigator.tsx
3149
+ const styles$2 = {
3150
+ wrapper: surfaceStyle,
3151
+ caption: {
3152
+ padding: "4px 0 12px",
3153
+ color: "var(--sb-color-sys-text-muted, CanvasText)",
3154
+ fontSize: 12
3155
+ },
3156
+ tree: {
3157
+ listStyle: "none",
3158
+ margin: 0,
3159
+ padding: 0
3160
+ },
3161
+ nested: {
3162
+ listStyle: "none",
3163
+ margin: 0,
3164
+ paddingLeft: 18,
3165
+ borderLeft: BORDER_DEFAULT
3166
+ },
3167
+ groupRow: {
3168
+ display: "flex",
3169
+ alignItems: "center",
3170
+ gap: 6,
3171
+ padding: "4px 6px",
3172
+ borderRadius: 4,
3173
+ cursor: "pointer",
3174
+ userSelect: "none",
3175
+ fontFamily: MONO_STACK,
3176
+ fontSize: 12
3177
+ },
3178
+ leafRow: {
3179
+ display: "flex",
3180
+ alignItems: "center",
3181
+ gap: 8,
3182
+ padding: "4px 6px",
3183
+ borderRadius: 4,
3184
+ cursor: "pointer",
3185
+ fontFamily: MONO_STACK,
3186
+ fontSize: 12
3187
+ },
3188
+ caret: {
3189
+ display: "inline-block",
3190
+ width: 12,
3191
+ textAlign: "center",
3192
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
3193
+ },
3194
+ tail: {
3195
+ fontFamily: MONO_STACK,
3196
+ fontSize: 12
3197
+ },
3198
+ typePill: {
3199
+ display: "inline-block",
3200
+ padding: "1px 6px",
3201
+ borderRadius: 4,
3202
+ fontSize: 10,
3203
+ letterSpacing: .5,
3204
+ textTransform: "uppercase",
3205
+ background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
3206
+ },
3207
+ value: {
3208
+ fontSize: 11,
3209
+ color: "var(--sb-color-sys-text-muted, CanvasText)",
3210
+ marginLeft: "auto",
3211
+ wordBreak: "break-all",
3212
+ maxWidth: "40%",
3213
+ textAlign: "right"
3214
+ },
3215
+ count: {
3216
+ marginLeft: "auto",
3217
+ fontSize: 11,
3218
+ color: "var(--sb-color-sys-text-default, CanvasText)"
3219
+ },
3220
+ colorSwatch: {
3221
+ display: "inline-block",
3222
+ width: 14,
3223
+ height: 14,
3224
+ borderRadius: 3,
3225
+ border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
3226
+ },
3227
+ previewBox: {
3228
+ display: "inline-flex",
3229
+ alignItems: "center",
3230
+ justifyContent: "flex-end",
3231
+ marginLeft: "auto"
3232
+ },
3233
+ empty: {
3234
+ padding: "24px 12px",
3235
+ textAlign: "center",
3236
+ color: "var(--sb-color-sys-text-muted, CanvasText)"
3237
+ },
3238
+ backdrop: {
3239
+ position: "fixed",
3240
+ inset: 0,
3241
+ background: "rgba(0,0,0,0.4)",
3242
+ zIndex: 1e4,
3243
+ display: "flex",
3244
+ alignItems: "stretch",
3245
+ justifyContent: "flex-end"
3246
+ },
3247
+ panel: {
3248
+ width: "min(560px, 100%)",
3249
+ height: "100%",
3250
+ overflowY: "auto",
3251
+ background: "var(--sb-color-sys-surface-default, Canvas)",
3252
+ color: "var(--sb-color-sys-text-default, CanvasText)",
3253
+ boxShadow: "-8px 0 24px rgba(0,0,0,0.2)",
3254
+ padding: 16,
3255
+ position: "relative"
3256
+ },
3257
+ closeButton: {
3258
+ position: "absolute",
3259
+ top: 8,
3260
+ right: 8,
3261
+ width: 32,
3262
+ height: 32,
3263
+ borderRadius: 4,
3264
+ border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
3265
+ background: "transparent",
3266
+ color: "inherit",
3267
+ cursor: "pointer",
3268
+ fontSize: 16,
3269
+ lineHeight: 1
3270
+ }
3271
+ };
3272
+ function buildTree(resolved, root) {
3273
+ const rootPrefix = root && root.length > 0 ? `${root}.` : "";
3274
+ const rootSegments = root ? root.split(".") : [];
3275
+ const entries = Object.entries(resolved).filter(([path]) => {
3276
+ if (!root) return true;
3277
+ return path === root || path.startsWith(rootPrefix);
3278
+ });
3279
+ const rootNode = {
3280
+ kind: "group",
3281
+ segment: "",
3282
+ path: "",
3283
+ children: []
3284
+ };
3285
+ for (const [path, token] of entries) {
3286
+ const remainder = root ? path === root ? "" : path.slice(rootPrefix.length) : path;
3287
+ const segments = remainder.length > 0 ? remainder.split(".") : [];
3288
+ let node = rootNode;
3289
+ for (let i = 0; i < segments.length - 1; i += 1) {
3290
+ const seg = segments[i];
3291
+ const prefix = [...rootSegments, ...segments.slice(0, i + 1)].join(".");
3292
+ let child = node.children.find((c) => c.kind === "group" && c.segment === seg);
3293
+ if (!child) {
3294
+ child = {
3295
+ kind: "group",
3296
+ segment: seg,
3297
+ path: prefix,
3298
+ children: []
3299
+ };
3300
+ node.children.push(child);
3301
+ }
3302
+ node = child;
3303
+ }
3304
+ const leafSegment = segments[segments.length - 1];
3305
+ if (leafSegment === void 0) node.children.push({
3306
+ kind: "leaf",
3307
+ segment: root ? rootSegments[rootSegments.length - 1] ?? path : path,
3308
+ path,
3309
+ token
3310
+ });
3311
+ else node.children.push({
3312
+ kind: "leaf",
3313
+ segment: leafSegment,
3314
+ path,
3315
+ token
3316
+ });
3317
+ }
3318
+ sortTree(rootNode);
3319
+ return rootNode.children;
3320
+ }
3321
+ function sortTree(node) {
3322
+ node.children.sort((a, b) => {
3323
+ if (a.kind !== b.kind) return a.kind === "group" ? -1 : 1;
3324
+ return a.segment.localeCompare(b.segment, void 0, { numeric: true });
3325
+ });
3326
+ for (const c of node.children) if (c.kind === "group") sortTree(c);
3327
+ }
3328
+ function collectInitialExpanded(nodes, remainingDepth, out) {
3329
+ if (remainingDepth <= 0) return;
3330
+ for (const node of nodes) {
3331
+ if (node.kind !== "group") continue;
3332
+ out.add(node.path);
3333
+ collectInitialExpanded(node.children, remainingDepth - 1, out);
3334
+ }
3335
+ }
3336
+ function countLeaves(node) {
3337
+ if (node.kind === "leaf") return 1;
3338
+ let n = 0;
3339
+ for (const c of node.children) n += countLeaves(c);
3340
+ return n;
3341
+ }
3342
+ function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
3343
+ const { resolved, activeTheme } = useProject();
3344
+ const tree = useMemo(() => buildTree(resolved, root), [resolved, root]);
3345
+ const initialExpanded = useMemo(() => {
3346
+ const out = /* @__PURE__ */ new Set();
3347
+ collectInitialExpanded(tree, initiallyExpanded, out);
3348
+ return out;
3349
+ }, [tree, initiallyExpanded]);
3350
+ const [expanded, setExpanded] = useState(initialExpanded);
3351
+ useEffect(() => {
3352
+ setExpanded(initialExpanded);
3353
+ }, [initialExpanded]);
3354
+ const [selectedPath, setSelectedPath] = useState(null);
3355
+ const toggle = useCallback((path) => {
3356
+ setExpanded((prev) => {
3357
+ const next = new Set(prev);
3358
+ if (next.has(path)) next.delete(path);
3359
+ else next.add(path);
3360
+ return next;
3361
+ });
3362
+ }, []);
3363
+ const handleLeafClick = useCallback((path) => {
3364
+ if (onSelect) onSelect(path);
3365
+ else setSelectedPath(path);
3366
+ }, [onSelect]);
3367
+ if (tree.length === 0) return /* @__PURE__ */ jsx("div", {
3368
+ "data-theme": activeTheme,
3369
+ style: styles$2.wrapper,
3370
+ children: /* @__PURE__ */ jsx("div", {
3371
+ style: styles$2.empty,
3372
+ children: root ? `No tokens under "${root}".` : "No tokens in the active theme."
3373
+ })
3374
+ });
3375
+ return /* @__PURE__ */ jsxs("div", {
3376
+ "data-theme": activeTheme,
3377
+ style: styles$2.wrapper,
3378
+ children: [
3379
+ /* @__PURE__ */ jsxs("div", {
3380
+ style: styles$2.caption,
3381
+ children: [
3382
+ root ? `Tokens under ${root}` : "Token graph",
3383
+ " · ",
3384
+ activeTheme
3385
+ ]
3386
+ }),
3387
+ /* @__PURE__ */ jsx("ul", {
3388
+ style: styles$2.tree,
3389
+ role: "tree",
3390
+ children: tree.map((node) => /* @__PURE__ */ jsx(TreeNodeRow, {
3391
+ node,
3392
+ expanded,
3393
+ onToggle: toggle,
3394
+ onLeafClick: handleLeafClick
3395
+ }, node.path || node.segment))
3396
+ }),
3397
+ selectedPath !== null && /* @__PURE__ */ jsx(DetailOverlay, {
3398
+ path: selectedPath,
3399
+ onClose: () => setSelectedPath(null)
3400
+ })
3401
+ ]
3402
+ });
3403
+ }
3404
+ function TreeNodeRow({ node, expanded, onToggle, onLeafClick }) {
3405
+ if (node.kind === "leaf") return /* @__PURE__ */ jsx(LeafRow, {
3406
+ node,
3407
+ onLeafClick
3408
+ });
3409
+ const isOpen = expanded.has(node.path);
3410
+ const onKey = (e) => {
3411
+ if (e.key === "Enter" || e.key === " ") {
3412
+ e.preventDefault();
3413
+ onToggle(node.path);
3414
+ }
3415
+ };
3416
+ return /* @__PURE__ */ jsxs("li", {
3417
+ role: "treeitem",
3418
+ "aria-expanded": isOpen,
3419
+ children: [/* @__PURE__ */ jsxs("div", {
3420
+ role: "button",
3421
+ tabIndex: 0,
3422
+ style: styles$2.groupRow,
3423
+ onClick: () => onToggle(node.path),
3424
+ onKeyDown: onKey,
3425
+ "data-path": node.path,
3426
+ "data-testid": "token-navigator-group",
3427
+ children: [
3428
+ /* @__PURE__ */ jsx("span", {
3429
+ style: styles$2.caret,
3430
+ "aria-hidden": true,
3431
+ children: isOpen ? "▾" : "▸"
3432
+ }),
3433
+ /* @__PURE__ */ jsx("span", { children: node.segment }),
3434
+ /* @__PURE__ */ jsx("span", {
3435
+ style: styles$2.count,
3436
+ children: countLeaves(node)
3437
+ })
3438
+ ]
3439
+ }), isOpen && /* @__PURE__ */ jsx("ul", {
3440
+ style: styles$2.nested,
3441
+ role: "group",
3442
+ children: node.children.map((c) => /* @__PURE__ */ jsx(TreeNodeRow, {
3443
+ node: c,
3444
+ expanded,
3445
+ onToggle,
3446
+ onLeafClick
3447
+ }, c.path || c.segment))
3448
+ })]
3449
+ });
3450
+ }
3451
+ function LeafRow({ node, onLeafClick }) {
3452
+ const onKey = (e) => {
3453
+ if (e.key === "Enter" || e.key === " ") {
3454
+ e.preventDefault();
3455
+ onLeafClick(node.path);
3456
+ }
3457
+ };
3458
+ const type = node.token.$type ?? "";
3459
+ return /* @__PURE__ */ jsx("li", {
3460
+ role: "treeitem",
3461
+ children: /* @__PURE__ */ jsxs("div", {
3462
+ role: "button",
3463
+ tabIndex: 0,
3464
+ style: styles$2.leafRow,
3465
+ onClick: () => onLeafClick(node.path),
3466
+ onKeyDown: onKey,
3467
+ "data-path": node.path,
3468
+ "data-testid": "token-navigator-leaf",
3469
+ children: [
3470
+ /* @__PURE__ */ jsx("span", {
3471
+ style: styles$2.caret,
3472
+ "aria-hidden": true,
3473
+ children: "•"
3474
+ }),
3475
+ /* @__PURE__ */ jsx("span", {
3476
+ style: styles$2.tail,
3477
+ children: node.segment
3478
+ }),
3479
+ type && /* @__PURE__ */ jsx("span", {
3480
+ style: styles$2.typePill,
3481
+ children: type
3482
+ }),
3483
+ /* @__PURE__ */ jsx(LeafPreview, {
3484
+ path: node.path,
3485
+ token: node.token
3486
+ })
3487
+ ]
3488
+ })
3489
+ });
3490
+ }
3491
+ function LeafPreview({ path, token }) {
3492
+ const { cssVarPrefix } = useProject();
3493
+ const colorFormat = useColorFormat();
3494
+ const type = token.$type;
3495
+ if (type === "color") {
3496
+ const cssVar = makeCssVar(path, cssVarPrefix);
3497
+ const formatted = formatColor(token.$value, colorFormat);
3498
+ return /* @__PURE__ */ jsxs("span", {
3499
+ style: styles$2.previewBox,
3500
+ children: [/* @__PURE__ */ jsx("span", {
3501
+ style: styles$2.value,
3502
+ children: formatted?.value ?? formatValue(token.$value)
3503
+ }), /* @__PURE__ */ jsx("span", {
3504
+ style: {
3505
+ ...styles$2.colorSwatch,
3506
+ background: cssVar,
3507
+ marginLeft: 8
3508
+ },
3509
+ "aria-hidden": true
3510
+ })]
3511
+ });
3512
+ }
3513
+ if (type === "dimension") return /* @__PURE__ */ jsxs("span", {
3514
+ style: styles$2.previewBox,
3515
+ children: [/* @__PURE__ */ jsx("span", {
3516
+ style: styles$2.value,
3517
+ children: formatValue(token.$value)
3518
+ }), /* @__PURE__ */ jsx("span", {
3519
+ style: {
3520
+ marginLeft: 8,
3521
+ display: "inline-block",
3522
+ minWidth: 40,
3523
+ maxWidth: 120
3524
+ },
3525
+ children: /* @__PURE__ */ jsx(DimensionBar, {
3526
+ path,
3527
+ kind: "length"
3528
+ })
3529
+ })]
3530
+ });
3531
+ if (type === "shadow") return /* @__PURE__ */ jsx("span", {
3532
+ style: styles$2.previewBox,
3533
+ children: /* @__PURE__ */ jsx("span", {
3534
+ style: {
3535
+ marginLeft: 8,
3536
+ display: "inline-block",
3537
+ transform: "scale(0.5)",
3538
+ transformOrigin: "right center"
3539
+ },
3540
+ children: /* @__PURE__ */ jsx(ShadowSample, { path })
3541
+ })
3542
+ });
3543
+ if (type === "border") return /* @__PURE__ */ jsx("span", {
3544
+ style: styles$2.previewBox,
3545
+ children: /* @__PURE__ */ jsx("span", {
3546
+ style: {
3547
+ marginLeft: 8,
3548
+ display: "inline-block",
3549
+ transform: "scale(0.5)",
3550
+ transformOrigin: "right center"
3551
+ },
3552
+ children: /* @__PURE__ */ jsx(BorderSample, { path })
3553
+ })
3554
+ });
3555
+ if (type === "transition" || type === "duration" || type === "cubicBezier") return /* @__PURE__ */ jsx("span", {
3556
+ style: styles$2.previewBox,
3557
+ children: /* @__PURE__ */ jsx("span", {
3558
+ style: {
3559
+ marginLeft: 8,
3560
+ display: "inline-block",
3561
+ width: 80
3562
+ },
3563
+ children: /* @__PURE__ */ jsx(MotionSample, { path })
3564
+ })
3565
+ });
3566
+ return /* @__PURE__ */ jsx("span", {
3567
+ style: styles$2.previewBox,
3568
+ children: /* @__PURE__ */ jsx("span", {
3569
+ style: styles$2.value,
3570
+ children: formatValue(token.$value)
3571
+ })
3572
+ });
3573
+ }
3574
+ function DetailOverlay({ path, onClose }) {
3575
+ useEffect(() => {
3576
+ const onKey = (e) => {
3577
+ if (e.key === "Escape") onClose();
3578
+ };
3579
+ window.addEventListener("keydown", onKey);
3580
+ return () => window.removeEventListener("keydown", onKey);
3581
+ }, [onClose]);
3582
+ return /* @__PURE__ */ jsx("div", {
3583
+ style: styles$2.backdrop,
3584
+ onClick: onClose,
3585
+ role: "presentation",
3586
+ "data-testid": "token-navigator-overlay",
3587
+ children: /* @__PURE__ */ jsxs("div", {
3588
+ style: styles$2.panel,
3589
+ onClick: (e) => e.stopPropagation(),
3590
+ role: "dialog",
3591
+ "aria-modal": "true",
3592
+ "aria-label": `Token detail for ${path}`,
3593
+ children: [/* @__PURE__ */ jsx("button", {
3594
+ type: "button",
3595
+ style: styles$2.closeButton,
3596
+ onClick: onClose,
3597
+ "aria-label": "Close",
3598
+ "data-testid": "token-navigator-close",
3599
+ children: "×"
3600
+ }), /* @__PURE__ */ jsx(TokenDetail, { path })]
3601
+ })
3602
+ });
3603
+ }
3604
+ //#endregion
3605
+ //#region src/TokenTable.tsx
3606
+ const styles$1 = {
3607
+ wrapper: surfaceStyle,
3608
+ empty: emptyStyle,
3609
+ caption: {
3610
+ captionSide: "top",
3611
+ textAlign: "left",
3612
+ padding: "8px 0",
3613
+ opacity: .7,
3614
+ fontSize: 12
3615
+ },
3616
+ table: {
3617
+ width: "100%",
3618
+ borderCollapse: "collapse",
3619
+ tableLayout: "fixed"
3620
+ },
3621
+ th: {
3622
+ textAlign: "left",
3623
+ padding: "8px 12px",
3624
+ fontSize: 11,
3625
+ textTransform: "uppercase",
3626
+ letterSpacing: .5,
3627
+ opacity: .6,
3628
+ borderBottom: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))"
3629
+ },
3630
+ td: {
3631
+ padding: "8px 12px",
3632
+ borderBottom: BORDER_FAINT,
3633
+ verticalAlign: "top"
3634
+ },
3635
+ path: {
3636
+ fontFamily: MONO_STACK,
3637
+ fontSize: 12
3638
+ },
3639
+ typePill: {
3640
+ display: "inline-block",
3641
+ padding: "2px 6px",
3642
+ borderRadius: 4,
3643
+ fontSize: 10,
3644
+ letterSpacing: .5,
3645
+ textTransform: "uppercase",
3646
+ background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
3647
+ },
3648
+ value: {
3649
+ fontFamily: MONO_STACK,
3650
+ fontSize: 12,
3651
+ opacity: .85,
3652
+ wordBreak: "break-all"
3653
+ },
3654
+ swatch: {
3655
+ display: "inline-block",
3656
+ width: 14,
3657
+ height: 14,
3658
+ verticalAlign: "middle",
3659
+ marginRight: 6,
3660
+ borderRadius: 3,
3661
+ border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
3662
+ }
3663
+ };
3664
+ function TokenTable({ filter, type, showVar = true, caption }) {
3665
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
3666
+ const colorFormat = useColorFormat();
3667
+ const rows = useMemo(() => {
3668
+ return Object.entries(resolved).filter(([path, token]) => {
3669
+ if (!globMatch(path, filter)) return false;
3670
+ if (type && token.$type !== type) return false;
3671
+ return true;
3672
+ }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => {
3673
+ const isColor = token.$type === "color";
3674
+ const color = isColor ? formatColor(token.$value, colorFormat) : null;
3675
+ return {
3676
+ path,
3677
+ type: token.$type ?? "",
3678
+ value: color ? color.value : formatValue(token.$value),
3679
+ outOfGamut: color?.outOfGamut ?? false,
3680
+ description: token.$description ?? "",
3681
+ cssVar: makeCssVar(path, cssVarPrefix),
3682
+ isColor
3683
+ };
3684
+ });
3685
+ }, [
3686
+ resolved,
3687
+ filter,
3688
+ type,
3689
+ cssVarPrefix,
3690
+ colorFormat
3691
+ ]);
3692
+ const captionText = caption ?? `${rows.length} token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""}${type ? ` · $type=${type}` : ""} · ${activeTheme}`;
3693
+ if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
3694
+ "data-theme": activeTheme,
3695
+ style: styles$1.wrapper,
3696
+ children: /* @__PURE__ */ jsx("div", {
3697
+ style: styles$1.empty,
3698
+ children: "No tokens match this filter."
3699
+ })
3700
+ });
3701
+ return /* @__PURE__ */ jsx("div", {
3702
+ "data-theme": activeTheme,
3703
+ style: styles$1.wrapper,
3704
+ children: /* @__PURE__ */ jsxs("table", {
3705
+ style: styles$1.table,
3706
+ children: [
3707
+ /* @__PURE__ */ jsx("caption", {
3708
+ style: styles$1.caption,
3709
+ children: captionText
3710
+ }),
3711
+ /* @__PURE__ */ jsxs("colgroup", { children: [
3712
+ /* @__PURE__ */ jsx("col", { style: { width: "30%" } }),
3713
+ /* @__PURE__ */ jsx("col", { style: { width: "8%" } }),
3714
+ /* @__PURE__ */ jsx("col", { style: { width: showVar ? "28%" : "40%" } }),
3715
+ showVar && /* @__PURE__ */ jsx("col", { style: { width: "24%" } }),
3716
+ /* @__PURE__ */ jsx("col", {})
3717
+ ] }),
3718
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
3719
+ /* @__PURE__ */ jsx("th", {
3720
+ style: styles$1.th,
3721
+ children: "Path"
3722
+ }),
3723
+ /* @__PURE__ */ jsx("th", {
3724
+ style: styles$1.th,
3725
+ children: "Type"
3726
+ }),
3727
+ /* @__PURE__ */ jsx("th", {
3728
+ style: styles$1.th,
3729
+ children: "Value"
3730
+ }),
3731
+ showVar && /* @__PURE__ */ jsx("th", {
3732
+ style: styles$1.th,
3733
+ children: "CSS var"
3734
+ }),
3735
+ /* @__PURE__ */ jsx("th", {
3736
+ style: styles$1.th,
3737
+ children: "Description"
3738
+ })
3739
+ ] }) }),
3740
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", { children: [
3741
+ /* @__PURE__ */ jsx("td", {
3742
+ style: {
3743
+ ...styles$1.td,
3744
+ ...styles$1.path
3745
+ },
3746
+ children: row.path
3747
+ }),
3748
+ /* @__PURE__ */ jsx("td", {
3749
+ style: styles$1.td,
3750
+ children: row.type && /* @__PURE__ */ jsx("span", {
3751
+ style: styles$1.typePill,
3752
+ children: row.type
3753
+ })
3754
+ }),
3755
+ /* @__PURE__ */ jsxs("td", {
3756
+ style: {
3757
+ ...styles$1.td,
3758
+ ...styles$1.value
3759
+ },
3760
+ children: [
3761
+ row.isColor && /* @__PURE__ */ jsx("span", {
3762
+ style: {
3763
+ ...styles$1.swatch,
3764
+ background: row.cssVar
3765
+ },
3766
+ "aria-hidden": true
3767
+ }),
3768
+ /* @__PURE__ */ jsx("span", { children: row.value }),
3769
+ row.outOfGamut && /* @__PURE__ */ jsx("span", {
3770
+ title: "Out of sRGB gamut for this format",
3771
+ "aria-label": "out of gamut",
3772
+ style: { marginLeft: 6 },
3773
+ children: "⚠"
3774
+ })
3775
+ ]
3776
+ }),
3777
+ showVar && /* @__PURE__ */ jsx("td", {
3778
+ style: {
3779
+ ...styles$1.td,
3780
+ ...styles$1.value
3781
+ },
3782
+ children: row.cssVar
3783
+ }),
3784
+ /* @__PURE__ */ jsx("td", {
3785
+ style: styles$1.td,
3786
+ children: row.description
3787
+ })
3788
+ ] }, row.path)) })
3789
+ ]
3790
+ })
3791
+ });
3792
+ }
3793
+ //#endregion
3794
+ //#region src/TypographyScale.tsx
3795
+ const styles = {
3796
+ wrapper: surfaceStyle,
3797
+ caption: captionStyle,
3798
+ empty: emptyStyle,
3799
+ row: {
3800
+ display: "grid",
3801
+ gridTemplateColumns: "minmax(160px, 220px) 1fr",
3802
+ gap: 16,
3803
+ alignItems: "baseline",
3804
+ padding: "14px 0",
3805
+ borderBottom: BORDER_DEFAULT
3806
+ },
3807
+ meta: {
3808
+ display: "flex",
3809
+ flexDirection: "column",
3810
+ gap: 2
3811
+ },
3812
+ path: {
3813
+ fontFamily: MONO_STACK,
3814
+ fontSize: 12
3815
+ },
3816
+ specs: {
3817
+ fontFamily: MONO_STACK,
3818
+ fontSize: 11,
3819
+ opacity: .7
3820
+ }
3821
+ };
3822
+ function asDimension(raw) {
3823
+ if (raw == null) return void 0;
3824
+ if (typeof raw === "string" || typeof raw === "number") return String(raw);
3825
+ if (typeof raw === "object") {
3826
+ const v = raw;
3827
+ if ("value" in v && "unit" in v) return `${String(v["value"])}${String(v["unit"])}`;
3828
+ }
3829
+ }
3830
+ function asFontFamily(raw) {
3831
+ if (typeof raw === "string") return raw;
3832
+ if (Array.isArray(raw)) return raw.map(String).join(", ");
3833
+ }
3834
+ function buildRow(path, composite) {
3835
+ const fontFamily = asFontFamily(composite["fontFamily"]);
3836
+ const fontSize = asDimension(composite["fontSize"]);
3837
+ const fontWeight = composite["fontWeight"] == null ? void 0 : String(composite["fontWeight"]);
3838
+ const lineHeight = composite["lineHeight"] == null ? void 0 : String(composite["lineHeight"]);
3839
+ const letterSpacing = asDimension(composite["letterSpacing"]);
3840
+ const sampleStyle = {};
3841
+ if (fontFamily) sampleStyle.fontFamily = fontFamily;
3842
+ if (fontSize) sampleStyle.fontSize = fontSize;
3843
+ if (fontWeight) sampleStyle.fontWeight = fontWeight;
3844
+ if (lineHeight) sampleStyle.lineHeight = lineHeight;
3845
+ if (letterSpacing) sampleStyle.letterSpacing = letterSpacing;
3846
+ return {
3847
+ path,
3848
+ sampleStyle,
3849
+ specs: [
3850
+ fontSize,
3851
+ fontWeight ? `w${fontWeight}` : void 0,
3852
+ lineHeight ? `lh ${lineHeight}` : void 0
3853
+ ].filter(Boolean).join(" · ")
3854
+ };
3855
+ }
3856
+ function TypographyScale({ filter = "typography", sample = "The quick brown fox jumps over the lazy dog.", caption }) {
3857
+ const { resolved, activeTheme } = useProject();
3858
+ const rows = useMemo(() => {
3859
+ return Object.entries(resolved).filter(([path, token]) => {
3860
+ if (token.$type !== "typography") return false;
3861
+ return globMatch(path, filter);
3862
+ }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => {
3863
+ const value = token.$value;
3864
+ if (!value || typeof value !== "object") return {
3865
+ path,
3866
+ sampleStyle: {},
3867
+ specs: ""
3868
+ };
3869
+ return buildRow(path, value);
3870
+ });
3871
+ }, [resolved, filter]);
3872
+ const captionText = caption ?? `${rows.length} typography token${rows.length === 1 ? "" : "s"}${filter && filter !== "typography" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
3873
+ if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
3874
+ "data-theme": activeTheme,
3875
+ style: styles.wrapper,
3876
+ children: /* @__PURE__ */ jsx("div", {
3877
+ style: styles.empty,
3878
+ children: "No typography tokens match this filter."
3879
+ })
3880
+ });
3881
+ return /* @__PURE__ */ jsxs("div", {
3882
+ "data-theme": activeTheme,
3883
+ style: styles.wrapper,
3884
+ children: [/* @__PURE__ */ jsx("div", {
3885
+ style: styles.caption,
3886
+ children: captionText
3887
+ }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
3888
+ style: styles.row,
3889
+ children: [/* @__PURE__ */ jsxs("div", {
3890
+ style: styles.meta,
3891
+ children: [/* @__PURE__ */ jsx("span", {
3892
+ style: styles.path,
3893
+ children: row.path
3894
+ }), row.specs && /* @__PURE__ */ jsx("span", {
3895
+ style: styles.specs,
3896
+ children: row.specs
3897
+ })]
3898
+ }), /* @__PURE__ */ jsx("div", {
3899
+ style: row.sampleStyle,
3900
+ children: sample
3901
+ })]
3902
+ }, row.path))]
3903
+ });
3904
+ }
3905
+ //#endregion
3906
+ export { AliasChain, AliasedBy, AxesContext, AxisVariance, BorderPreview, BorderSample, COLOR_FORMATS, ColorFormatContext, ColorPalette, CompositeBreakdown, CompositePreview, ConsumerOutput, DimensionBar, DimensionScale, FontFamilySample, FontWeightScale, GradientPalette, MotionPreview, MotionSample, ShadowPreview, ShadowSample, StrokeStyleSample, SwatchbookContext, SwatchbookProvider, ThemeContext, TokenDetail, TokenHeader, TokenNavigator, TokenTable, TokenUsageSnippet, TypographyScale, formatColor, useActiveAxes, useActiveTheme, useColorFormat, useOptionalSwatchbookData, useSwatchbookData };
3907
+
3908
+ //# sourceMappingURL=index.mjs.map