@unpunnyfuns/swatchbook-blocks 0.2.1 → 0.3.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 CHANGED
@@ -1,8 +1,8 @@
1
1
  import Color from "colorjs.io";
2
2
  import { createContext, useCallback, useContext, useEffect, useMemo, useState, useSyncExternalStore } from "react";
3
- import { addons } from "storybook/preview-api";
4
- import { axes, css, cssVarPrefix, defaultTheme, themes, themesResolved } from "virtual:swatchbook/tokens";
5
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { addons } from "storybook/preview-api";
5
+ import { axes, css, cssVarPrefix, defaultTheme, diagnostics, themes, themesResolved } from "virtual:swatchbook/tokens";
6
6
  //#region src/format-color.ts
7
7
  const COLOR_FORMATS = [
8
8
  "hex",
@@ -178,6 +178,65 @@ function stringifyFallback(value, fallback) {
178
178
  return fallback;
179
179
  }
180
180
  //#endregion
181
+ //#region src/internal/styles.tsx
182
+ /**
183
+ * Chrome-style primitives shared across every block. The `var(--sb-*)`
184
+ * references resolve to the active `cssVarPrefix` via the wrapper's
185
+ * `chromeAliases()` spread, so blocks stay theme-aware without each one
186
+ * reimplementing the fallback chain.
187
+ *
188
+ * Add a new constant here when a second block needs it — not when the
189
+ * first one does. Keep ad-hoc inline values out of blocks so restyling
190
+ * the chrome is a one-file change.
191
+ */
192
+ const MONO_STACK = "ui-monospace, SFMono-Regular, Menlo, monospace";
193
+ const TEXT_DEFAULT = "var(--sb-color-sys-text-default, CanvasText)";
194
+ const TEXT_MUTED = "var(--sb-color-sys-text-muted, CanvasText)";
195
+ const SURFACE_DEFAULT = "var(--sb-color-sys-surface-default, Canvas)";
196
+ const SURFACE_RAISED = "var(--sb-color-sys-surface-raised, Canvas)";
197
+ const SURFACE_MUTED = "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))";
198
+ const BORDER_DEFAULT = `1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.2))`;
199
+ const BORDER_FAINT = `1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.15))`;
200
+ const BORDER_STRONG = `1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))`;
201
+ const surfaceStyle = {
202
+ fontFamily: "var(--sb-typography-sys-body-font-family, system-ui)",
203
+ fontSize: "var(--sb-typography-sys-body-font-size, 14px)",
204
+ color: TEXT_DEFAULT,
205
+ background: SURFACE_DEFAULT,
206
+ padding: 12,
207
+ borderRadius: 6
208
+ };
209
+ const captionStyle = {
210
+ padding: "4px 0 12px",
211
+ color: TEXT_MUTED,
212
+ fontSize: 12
213
+ };
214
+ const typePillStyle = {
215
+ display: "inline-block",
216
+ padding: "2px 6px",
217
+ borderRadius: 4,
218
+ fontSize: 10,
219
+ letterSpacing: .5,
220
+ textTransform: "uppercase",
221
+ background: SURFACE_MUTED
222
+ };
223
+ const emptyStyle = {
224
+ padding: "24px 12px",
225
+ textAlign: "center",
226
+ color: TEXT_MUTED
227
+ };
228
+ /**
229
+ * Inner content for a block's "nothing to render" state. Call sites wrap
230
+ * it in their own block wrapper (which already carries `themeAttrs` +
231
+ * `chromeAliases`), so the message itself just needs the muted type.
232
+ */
233
+ function EmptyState({ children }) {
234
+ return /* @__PURE__ */ jsx("div", {
235
+ style: emptyStyle,
236
+ children
237
+ });
238
+ }
239
+ //#endregion
181
240
  //#region src/internal/data-attr.ts
182
241
  /**
183
242
  * Produce a prefixed `data-*` attribute name when `prefix` is set, bare
@@ -189,12 +248,23 @@ function dataAttr(prefix, key) {
189
248
  return prefix ? `data-${prefix}-${key}` : `data-${key}`;
190
249
  }
191
250
  /**
192
- * Spread helper for the common `<div data-<prefix>-theme="…">` block
193
- * wrapper. Returns an object keyed on the prefixed attribute name so the
194
- * call site stays readable: `<div {...themeAttrs(prefix, theme)} />`.
251
+ * Marker attribute set on every block wrapper. Used as the scope selector
252
+ * for the defensive element-reset stylesheet in `block-reset.ts` so
253
+ * Storybook MDX docs CSS (`.sbdocs table`, `.sbdocs ul`, …) can't bleed
254
+ * into our chrome.
255
+ */
256
+ const BLOCK_ATTR = "data-swatchbook-block";
257
+ /**
258
+ * Spread helper for the common `<div data-<prefix>-theme="…" data-swatchbook-block>`
259
+ * block wrapper. Returns an object keyed on the prefixed theme attribute
260
+ * plus the scoping marker so the call site stays readable:
261
+ * `<div {...themeAttrs(prefix, theme)} />`.
195
262
  */
196
263
  function themeAttrs(prefix, themeName) {
197
- return { [dataAttr(prefix, "theme")]: themeName };
264
+ return {
265
+ [dataAttr(prefix, "theme")]: themeName,
266
+ [BLOCK_ATTR]: ""
267
+ };
198
268
  }
199
269
  /**
200
270
  * Vars block chrome reads by literal `var(--sb-*)` regardless of what the
@@ -361,6 +431,62 @@ function useColorFormat() {
361
431
  return contextValue ?? channelGlobals.format ?? "hex";
362
432
  }
363
433
  //#endregion
434
+ //#region src/internal/block-reset.ts
435
+ /**
436
+ * Defensive element reset scoped to every block wrapper.
437
+ *
438
+ * Storybook 10's MDX docs applies house-style CSS to semantic elements
439
+ * (`.sbdocs table`, `.sbdocs th/td`, `.sbdocs ul/li`, `.sbdocs details/summary`)
440
+ * that bleeds into any block rendered inside a docs page. Our blocks set
441
+ * inline styles on those elements for the properties they care about, but
442
+ * anything they don't touch (background, border, margin, etc.) inherits
443
+ * the docs look. That's why the Dashboard tree/table looked different
444
+ * inside MDX vs a regular story.
445
+ *
446
+ * The fix mounts one stylesheet once per browsing session that scopes
447
+ * `all: revert` on the semantic elements used by blocks, under the
448
+ * `[data-swatchbook-block]` attribute carried by every block's outer
449
+ * wrapper (via `themeAttrs`). `all: revert` restores browser defaults —
450
+ * the block's inline styles still win on top of that, and MDX house
451
+ * styles stop leaking in.
452
+ *
453
+ * The reset excludes the wrapper itself (direct `[data-swatchbook-block]`
454
+ * selector) because the wrapper's `surfaceStyle` is what we want to keep.
455
+ */
456
+ const STYLE_ID = "swatchbook-block-reset";
457
+ const RESET_CSS = `[data-swatchbook-block] table,
458
+ [data-swatchbook-block] thead,
459
+ [data-swatchbook-block] tbody,
460
+ [data-swatchbook-block] tr,
461
+ [data-swatchbook-block] th,
462
+ [data-swatchbook-block] td,
463
+ [data-swatchbook-block] caption,
464
+ [data-swatchbook-block] ul,
465
+ [data-swatchbook-block] ol,
466
+ [data-swatchbook-block] li,
467
+ [data-swatchbook-block] details,
468
+ [data-swatchbook-block] summary,
469
+ [data-swatchbook-block] code,
470
+ [data-swatchbook-block] pre,
471
+ [data-swatchbook-block] p,
472
+ [data-swatchbook-block] h1,
473
+ [data-swatchbook-block] h2,
474
+ [data-swatchbook-block] h3,
475
+ [data-swatchbook-block] h4,
476
+ [data-swatchbook-block] h5,
477
+ [data-swatchbook-block] h6 {
478
+ all: revert-layer;
479
+ }
480
+ `;
481
+ function ensureBlockResetStylesheet() {
482
+ if (typeof document === "undefined") return;
483
+ if (document.getElementById(STYLE_ID)) return;
484
+ const style = document.createElement("style");
485
+ style.id = STYLE_ID;
486
+ style.textContent = RESET_CSS;
487
+ document.head.appendChild(style);
488
+ }
489
+ //#endregion
364
490
  //#region src/internal/use-project.ts
365
491
  const STYLE_ELEMENT_ID = "swatchbook-tokens";
366
492
  function ensureStylesheet(css) {
@@ -397,6 +523,7 @@ function snapshotToData(snapshot) {
397
523
  themes: snapshot.themes,
398
524
  themesResolved: snapshot.themesResolved,
399
525
  resolved: snapshot.themesResolved[snapshot.activeTheme] ?? {},
526
+ diagnostics: snapshot.diagnostics,
400
527
  cssVarPrefix: snapshot.cssVarPrefix
401
528
  };
402
529
  }
@@ -415,6 +542,9 @@ function snapshotToData(snapshot) {
415
542
  function useProject() {
416
543
  const snapshot = useOptionalSwatchbookData();
417
544
  const fallback = useVirtualModuleFallback(snapshot === null);
545
+ useEffect(() => {
546
+ ensureBlockResetStylesheet();
547
+ }, []);
418
548
  return snapshot !== null ? snapshotToData(snapshot) : fallback;
419
549
  }
420
550
  function useVirtualModuleFallback(enabled) {
@@ -437,6 +567,7 @@ function useVirtualModuleFallback(enabled) {
437
567
  themes,
438
568
  themesResolved,
439
569
  resolved: themesResolved[activeTheme] ?? {},
570
+ diagnostics,
440
571
  cssVarPrefix
441
572
  };
442
573
  }
@@ -444,6 +575,24 @@ function makeCssVar(path, prefix) {
444
575
  const tail = path.replaceAll(".", "-");
445
576
  return prefix ? `var(--${prefix}-${tail})` : `var(--${tail})`;
446
577
  }
578
+ /**
579
+ * Match a dot-separated DTCG token path against a block `filter` prop.
580
+ *
581
+ * **Supported shapes** (the narrow subset we need — DTCG paths don't have
582
+ * directories, brace expansion, or regex, so we skip a full glob engine):
583
+ *
584
+ * | Pattern | Matches |
585
+ * | ------------------ | --------------------------------------------------- |
586
+ * | `undefined` / `''` | everything |
587
+ * | `*` or `**` | everything |
588
+ * | `color` | exact path `color`, or any descendant `color.*` |
589
+ * | `color.sys.*` | any path whose fixed prefix is `color.sys.` |
590
+ * | `color**` | any path starting with `color` |
591
+ *
592
+ * Not supported (all pass through as literal segment matchers): brace
593
+ * expansion (`{a,b}`), mid-path globs (`color.*.bg`), negation (`!foo`),
594
+ * character classes (`[sys]`). If you hit those, pre-filter by hand.
595
+ */
447
596
  function globMatch(path, glob) {
448
597
  if (!glob) return true;
449
598
  if (glob === "*" || glob === "**") return true;
@@ -451,23 +600,12 @@ function globMatch(path, glob) {
451
600
  if (glob.endsWith("**")) return path.startsWith(glob.slice(0, -2));
452
601
  return path === glob || path.startsWith(`${glob}.`);
453
602
  }
454
- function formatValue(value) {
455
- if (value == null) return "";
456
- if (typeof value === "string" || typeof value === "number") return String(value);
457
- if (typeof value === "object") {
458
- const v = value;
459
- if (typeof v["hex"] === "string") return v["hex"];
460
- if ("value" in v && "unit" in v) return `${String(v["value"])}${String(v["unit"])}`;
461
- return JSON.stringify(value).slice(0, 120);
462
- }
463
- return String(value);
464
- }
465
603
  //#endregion
466
604
  //#region src/border-preview/BorderSample.tsx
467
605
  const sampleStyle$1 = {
468
606
  width: 120,
469
607
  height: 56,
470
- background: "var(--sb-color-sys-surface-raised, transparent)",
608
+ background: SURFACE_RAISED,
471
609
  borderRadius: 6
472
610
  };
473
611
  function BorderSample({ path }) {
@@ -483,31 +621,112 @@ function BorderSample({ path }) {
483
621
  });
484
622
  }
485
623
  //#endregion
486
- //#region src/internal/styles.ts
487
- const MONO_STACK = "ui-monospace, SFMono-Regular, Menlo, monospace";
488
- const BORDER_DEFAULT = "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.2))";
489
- const BORDER_FAINT = "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.15))";
490
- const surfaceStyle = {
491
- fontFamily: "var(--sb-typography-sys-body-font-family, system-ui)",
492
- fontSize: "var(--sb-typography-sys-body-font-size, 14px)",
493
- color: "var(--sb-color-sys-text-default, CanvasText)",
494
- background: "var(--sb-color-sys-surface-default, Canvas)",
495
- padding: 12,
496
- borderRadius: 6
497
- };
498
- const captionStyle = {
499
- padding: "4px 0 12px",
500
- opacity: .7,
501
- fontSize: 12
502
- };
503
- const emptyStyle = {
504
- padding: "24px 12px",
505
- textAlign: "center",
506
- opacity: .6
507
- };
624
+ //#region src/internal/sort-tokens.ts
625
+ /**
626
+ * Stable sort for a filtered `[path, token][]` list.
627
+ *
628
+ * `sortBy: 'path'` — lexicographic on the dot-path (locale-aware, numeric).
629
+ * `sortBy: 'value'` — per-`$type` ordering:
630
+ * - `dimension` / `duration` → numeric pixels / ms (via `toMagnitude`).
631
+ * - `fontWeight` / `opacity` / `number` / `lineHeight` → numeric.
632
+ * - `color` → perceptual by oklch L → C → H.
633
+ * - `fontFamily` / `strokeStyle` (string form) → lexicographic.
634
+ * - Composites (`typography`, `shadow`, `border`, `gradient`, `transition`) →
635
+ * fall back to path-alpha. No useful single-axis order.
636
+ * `sortBy: 'none'` — preserve input order (still respects `sortDir: 'desc'`
637
+ * as a reverse).
638
+ */
639
+ function sortTokens(entries, options = {}) {
640
+ const by = options.by ?? "path";
641
+ const dir = options.dir ?? "asc";
642
+ const sign = dir === "desc" ? -1 : 1;
643
+ if (by === "none") return dir === "desc" ? [...entries].toReversed() : [...entries];
644
+ if (by === "path") return [...entries].toSorted(([a], [b]) => sign * a.localeCompare(b, void 0, { numeric: true }));
645
+ return [...entries].toSorted(([aPath, aTok], [bPath, bTok]) => {
646
+ const cmp = compareValue(aTok, bTok);
647
+ if (cmp !== 0) return sign * cmp;
648
+ return sign * aPath.localeCompare(bPath, void 0, { numeric: true });
649
+ });
650
+ }
651
+ function compareValue(a, b) {
652
+ const type = a.$type;
653
+ if (type !== b.$type) return String(type ?? "").localeCompare(String(b.$type ?? ""));
654
+ if (!type) return 0;
655
+ if (type === "dimension" || type === "duration" || type === "fontWeight" || type === "opacity" || type === "number" || type === "lineHeight") {
656
+ const av = toMagnitude(a.$value);
657
+ const bv = toMagnitude(b.$value);
658
+ if (Number.isFinite(av) && Number.isFinite(bv)) return av - bv;
659
+ if (Number.isFinite(av)) return -1;
660
+ if (Number.isFinite(bv)) return 1;
661
+ return 0;
662
+ }
663
+ if (type === "color") {
664
+ const ak = colorKey(a.$value);
665
+ const bk = colorKey(b.$value);
666
+ if (!ak && !bk) return 0;
667
+ if (!ak) return 1;
668
+ if (!bk) return -1;
669
+ if (ak.l !== bk.l) return ak.l - bk.l;
670
+ if (ak.c !== bk.c) return ak.c - bk.c;
671
+ return ak.h - bk.h;
672
+ }
673
+ if (type === "fontFamily" || type === "strokeStyle") {
674
+ const as = toDisplayable(a.$value);
675
+ const bs = toDisplayable(b.$value);
676
+ return as.localeCompare(bs, void 0, { numeric: true });
677
+ }
678
+ return 0;
679
+ }
680
+ function toMagnitude(v) {
681
+ if (typeof v === "number") return v;
682
+ if (v && typeof v === "object") {
683
+ const d = v;
684
+ if (typeof d.value !== "number") return NaN;
685
+ if (typeof d.unit !== "string") return d.value;
686
+ switch (d.unit) {
687
+ case "px":
688
+ case "ms": return d.value;
689
+ case "s": return d.value * 1e3;
690
+ case "rem":
691
+ case "em": return d.value * 16;
692
+ default: return d.value;
693
+ }
694
+ }
695
+ return NaN;
696
+ }
697
+ function colorKey(v) {
698
+ if (!v || typeof v !== "object") return null;
699
+ try {
700
+ const c = v;
701
+ let source;
702
+ if (typeof c.hex === "string") source = c.hex;
703
+ else if (typeof c.colorSpace === "string") {
704
+ const channels = Array.isArray(c.components) ? c.components : Array.isArray(c.channels) ? c.channels : void 0;
705
+ if (!channels) return null;
706
+ source = {
707
+ space: c.colorSpace,
708
+ coords: channels
709
+ };
710
+ } else return null;
711
+ const [l, chroma, h] = new Color(source).to("oklch").coords;
712
+ return {
713
+ l: Number.isFinite(l) ? l : 0,
714
+ c: Number.isFinite(chroma) ? chroma : 0,
715
+ h: Number.isFinite(h) ? h : 0
716
+ };
717
+ } catch {
718
+ return null;
719
+ }
720
+ }
721
+ function toDisplayable(v) {
722
+ if (typeof v === "string") return v;
723
+ if (Array.isArray(v)) return v.map(String).join(", ");
724
+ if (v && typeof v === "object") return JSON.stringify(v);
725
+ return String(v ?? "");
726
+ }
508
727
  //#endregion
509
728
  //#region src/BorderPreview.tsx
510
- const styles$14 = {
729
+ const styles$16 = {
511
730
  wrapper: surfaceStyle,
512
731
  caption: captionStyle,
513
732
  empty: emptyStyle,
@@ -550,9 +769,9 @@ const styles$14 = {
550
769
  columnGap: 12,
551
770
  rowGap: 2
552
771
  },
553
- breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" }
772
+ breakdownKey: { color: TEXT_MUTED }
554
773
  };
555
- function formatDimension$1(raw) {
774
+ function formatDimension$2(raw) {
556
775
  if (raw == null) return "—";
557
776
  if (typeof raw === "number") return String(raw);
558
777
  if (typeof raw === "string") return raw;
@@ -575,35 +794,36 @@ function formatColor$2(raw) {
575
794
  }
576
795
  return JSON.stringify(raw);
577
796
  }
578
- function BorderPreview({ filter = "border", caption }) {
797
+ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
579
798
  const { resolved, activeTheme, cssVarPrefix } = useProject();
580
799
  const rows = useMemo(() => {
581
- const collected = [];
582
- for (const [path, token] of Object.entries(resolved)) {
583
- if (token.$type !== "border") continue;
584
- if (!globMatch(path, filter)) continue;
585
- collected.push({
586
- path,
587
- cssVar: makeCssVar(path, cssVarPrefix),
588
- value: token.$value ?? {}
589
- });
590
- }
591
- collected.sort((a, b) => a.path.localeCompare(b.path, void 0, { numeric: true }));
592
- return collected;
800
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
801
+ if (token.$type !== "border") return false;
802
+ return globMatch(path, filter);
803
+ }), {
804
+ by: sortBy,
805
+ dir: sortDir
806
+ }).map(([path, token]) => ({
807
+ path,
808
+ cssVar: makeCssVar(path, cssVarPrefix),
809
+ value: token.$value ?? {}
810
+ }));
593
811
  }, [
594
812
  resolved,
595
813
  filter,
596
- cssVarPrefix
814
+ cssVarPrefix,
815
+ sortBy,
816
+ sortDir
597
817
  ]);
598
818
  const captionText = caption ?? `${rows.length} border${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
599
819
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
600
820
  ...themeAttrs(cssVarPrefix, activeTheme),
601
821
  style: {
602
822
  ...chromeAliases(cssVarPrefix),
603
- ...styles$14.wrapper
823
+ ...styles$16.wrapper
604
824
  },
605
825
  children: /* @__PURE__ */ jsx("div", {
606
- style: styles$14.empty,
826
+ style: styles$16.empty,
607
827
  children: "No border tokens match this filter."
608
828
  })
609
829
  });
@@ -611,43 +831,43 @@ function BorderPreview({ filter = "border", caption }) {
611
831
  ...themeAttrs(cssVarPrefix, activeTheme),
612
832
  style: {
613
833
  ...chromeAliases(cssVarPrefix),
614
- ...styles$14.wrapper
834
+ ...styles$16.wrapper
615
835
  },
616
836
  children: [/* @__PURE__ */ jsx("div", {
617
- style: styles$14.caption,
837
+ style: styles$16.caption,
618
838
  children: captionText
619
839
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
620
- style: styles$14.row,
840
+ style: styles$16.row,
621
841
  children: [
622
842
  /* @__PURE__ */ jsxs("div", {
623
- style: styles$14.meta,
843
+ style: styles$16.meta,
624
844
  children: [/* @__PURE__ */ jsx("span", {
625
- style: styles$14.path,
845
+ style: styles$16.path,
626
846
  children: row.path
627
847
  }), /* @__PURE__ */ jsx("span", {
628
- style: styles$14.cssVar,
848
+ style: styles$16.cssVar,
629
849
  children: row.cssVar
630
850
  })]
631
851
  }),
632
852
  /* @__PURE__ */ jsx("div", {
633
- style: styles$14.sampleCell,
853
+ style: styles$16.sampleCell,
634
854
  children: /* @__PURE__ */ jsx(BorderSample, { path: row.path })
635
855
  }),
636
856
  /* @__PURE__ */ jsxs("div", {
637
- style: styles$14.breakdown,
857
+ style: styles$16.breakdown,
638
858
  children: [
639
859
  /* @__PURE__ */ jsx("span", {
640
- style: styles$14.breakdownKey,
860
+ style: styles$16.breakdownKey,
641
861
  children: "width"
642
862
  }),
643
- /* @__PURE__ */ jsx("span", { children: formatDimension$1(row.value.width) }),
863
+ /* @__PURE__ */ jsx("span", { children: formatDimension$2(row.value.width) }),
644
864
  /* @__PURE__ */ jsx("span", {
645
- style: styles$14.breakdownKey,
865
+ style: styles$16.breakdownKey,
646
866
  children: "style"
647
867
  }),
648
868
  /* @__PURE__ */ jsx("span", { children: row.value.style != null ? String(row.value.style) : "—" }),
649
869
  /* @__PURE__ */ jsx("span", {
650
- style: styles$14.breakdownKey,
870
+ style: styles$16.breakdownKey,
651
871
  children: "color"
652
872
  }),
653
873
  /* @__PURE__ */ jsx("span", { children: formatColor$2(row.value.color) })
@@ -659,7 +879,7 @@ function BorderPreview({ filter = "border", caption }) {
659
879
  }
660
880
  //#endregion
661
881
  //#region src/ColorPalette.tsx
662
- const styles$13 = {
882
+ const styles$15 = {
663
883
  wrapper: surfaceStyle,
664
884
  caption: captionStyle,
665
885
  empty: emptyStyle,
@@ -687,7 +907,7 @@ const styles$13 = {
687
907
  swatch: {
688
908
  height: 56,
689
909
  width: "100%",
690
- borderBottom: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.08))"
910
+ borderBottom: BORDER_FAINT
691
911
  },
692
912
  meta: {
693
913
  padding: "8px 10px",
@@ -719,14 +939,17 @@ function fixedPrefixLength(filter) {
719
939
  }
720
940
  return fixed;
721
941
  }
722
- function ColorPalette({ filter = "color", groupBy, caption }) {
942
+ function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "asc" }) {
723
943
  const { resolved, activeTheme, cssVarPrefix } = useProject();
724
944
  const colorFormat = useColorFormat();
725
945
  const groups = useMemo(() => {
726
- const entries = Object.entries(resolved).filter(([path, token]) => {
946
+ const entries = sortTokens(Object.entries(resolved).filter(([path, token]) => {
727
947
  if (token.$type !== "color") return false;
728
948
  return globMatch(path, filter);
729
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
949
+ }), {
950
+ by: sortBy,
951
+ dir: sortDir
952
+ });
730
953
  const maxDepth = entries.reduce((m, [p]) => Math.max(m, p.split(".").length), 0);
731
954
  /**
732
955
  * Auto-derive: group one level below the filter's fixed prefix, but
@@ -758,7 +981,9 @@ function ColorPalette({ filter = "color", groupBy, caption }) {
758
981
  filter,
759
982
  groupBy,
760
983
  cssVarPrefix,
761
- colorFormat
984
+ colorFormat,
985
+ sortBy,
986
+ sortDir
762
987
  ]);
763
988
  const totalCount = groups.reduce((acc, [, swatches]) => acc + swatches.length, 0);
764
989
  const captionText = caption ?? `${totalCount} color${totalCount === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
@@ -766,41 +991,44 @@ function ColorPalette({ filter = "color", groupBy, caption }) {
766
991
  ...themeAttrs(cssVarPrefix, activeTheme),
767
992
  style: {
768
993
  ...chromeAliases(cssVarPrefix),
769
- ...styles$13.wrapper
994
+ ...styles$15.wrapper
770
995
  },
771
996
  children: /* @__PURE__ */ jsx("div", {
772
- style: styles$13.empty,
997
+ style: styles$15.empty,
773
998
  children: "No color tokens match this filter."
774
999
  })
775
1000
  });
776
1001
  return /* @__PURE__ */ jsxs("div", {
777
1002
  ...themeAttrs(cssVarPrefix, activeTheme),
778
- style: styles$13.wrapper,
1003
+ style: {
1004
+ ...chromeAliases(cssVarPrefix),
1005
+ ...styles$15.wrapper
1006
+ },
779
1007
  children: [/* @__PURE__ */ jsx("div", {
780
- style: styles$13.caption,
1008
+ style: styles$15.caption,
781
1009
  children: captionText
782
1010
  }), groups.map(([group, swatches]) => /* @__PURE__ */ jsxs("section", {
783
- style: styles$13.group,
1011
+ style: styles$15.group,
784
1012
  children: [/* @__PURE__ */ jsx("div", {
785
- style: styles$13.groupHeader,
1013
+ style: styles$15.groupHeader,
786
1014
  children: group
787
1015
  }), /* @__PURE__ */ jsx("div", {
788
- style: styles$13.grid,
1016
+ style: styles$15.grid,
789
1017
  children: swatches.map((swatch) => /* @__PURE__ */ jsxs("div", {
790
- style: styles$13.card,
1018
+ style: styles$15.card,
791
1019
  children: [/* @__PURE__ */ jsx("div", {
792
1020
  style: {
793
- ...styles$13.swatch,
1021
+ ...styles$15.swatch,
794
1022
  background: swatch.cssVar
795
1023
  },
796
1024
  "aria-hidden": true
797
1025
  }), /* @__PURE__ */ jsxs("div", {
798
- style: styles$13.meta,
1026
+ style: styles$15.meta,
799
1027
  children: [/* @__PURE__ */ jsx("span", {
800
- style: styles$13.leaf,
1028
+ style: styles$15.leaf,
801
1029
  children: swatch.leaf
802
1030
  }), /* @__PURE__ */ jsxs("span", {
803
- style: styles$13.value,
1031
+ style: styles$15.value,
804
1032
  children: [swatch.value, swatch.outOfGamut && /* @__PURE__ */ jsxs("span", {
805
1033
  title: "Out of sRGB gamut for this format",
806
1034
  "aria-label": "out of gamut",
@@ -815,9 +1043,135 @@ function ColorPalette({ filter = "color", groupBy, caption }) {
815
1043
  });
816
1044
  }
817
1045
  //#endregion
1046
+ //#region src/Diagnostics.tsx
1047
+ const severityColor = {
1048
+ error: "#d64545",
1049
+ warn: "#b08900",
1050
+ info: "inherit"
1051
+ };
1052
+ const severityLabel = {
1053
+ error: "ERROR",
1054
+ warn: "WARN",
1055
+ info: "INFO"
1056
+ };
1057
+ const styles$14 = {
1058
+ wrapper: surfaceStyle,
1059
+ summary: {
1060
+ display: "flex",
1061
+ alignItems: "center",
1062
+ gap: 8,
1063
+ padding: "4px 0",
1064
+ fontSize: 13,
1065
+ cursor: "pointer",
1066
+ listStyle: "none",
1067
+ fontWeight: 600
1068
+ },
1069
+ list: {
1070
+ listStyle: "none",
1071
+ margin: "8px 0 0",
1072
+ padding: 0,
1073
+ display: "flex",
1074
+ flexDirection: "column"
1075
+ },
1076
+ row: {
1077
+ display: "grid",
1078
+ gridTemplateColumns: "60px 1fr",
1079
+ gap: 12,
1080
+ padding: "8px 4px",
1081
+ borderTop: BORDER_FAINT,
1082
+ fontSize: 12
1083
+ },
1084
+ label: {
1085
+ fontWeight: 600,
1086
+ fontSize: 10,
1087
+ letterSpacing: .5
1088
+ },
1089
+ meta: {
1090
+ color: TEXT_MUTED,
1091
+ fontSize: 11,
1092
+ marginTop: 4,
1093
+ opacity: .7
1094
+ }
1095
+ };
1096
+ function summaryText(diagnostics) {
1097
+ if (diagnostics.length === 0) return "✔ OK · no diagnostics";
1098
+ const counts = {
1099
+ error: 0,
1100
+ warn: 0,
1101
+ info: 0
1102
+ };
1103
+ for (const d of diagnostics) counts[d.severity] += 1;
1104
+ const parts = [];
1105
+ if (counts.error > 0) parts.push(`✖ ${counts.error} error${counts.error === 1 ? "" : "s"}`);
1106
+ if (counts.warn > 0) parts.push(`⚠ ${counts.warn} warning${counts.warn === 1 ? "" : "s"}`);
1107
+ if (counts.info > 0) parts.push(`${counts.info} info`);
1108
+ return parts.join(" · ");
1109
+ }
1110
+ function diagnosticKey(d, i) {
1111
+ return `${d.severity}:${d.group}:${d.filename ?? ""}:${d.line ?? ""}:${d.message}:${i}`;
1112
+ }
1113
+ function summaryColor(diagnostics) {
1114
+ if (diagnostics.length === 0) return "#30a46c";
1115
+ if (diagnostics.some((d) => d.severity === "error")) return severityColor.error;
1116
+ if (diagnostics.some((d) => d.severity === "warn")) return severityColor.warn;
1117
+ return "inherit";
1118
+ }
1119
+ /**
1120
+ * Render the project's load diagnostics — parser errors, resolver warnings,
1121
+ * disabled-axes validation issues, etc. — as a collapsible list. Auto-opens
1122
+ * when the project carries errors or warnings; stays collapsed for clean
1123
+ * loads and info-only loads.
1124
+ *
1125
+ * Replaces the diagnostics section from the addon's (now-retired) Design
1126
+ * Tokens panel. Consumers compose it alongside TokenNavigator / TokenTable
1127
+ * on their own MDX pages.
1128
+ */
1129
+ function Diagnostics({ caption } = {}) {
1130
+ const { activeTheme, cssVarPrefix, diagnostics } = useProject();
1131
+ const hasErrorsOrWarnings = diagnostics.some((d) => d.severity === "error" || d.severity === "warn");
1132
+ const headingText = caption ?? `Diagnostics · ${summaryText(diagnostics)}`;
1133
+ return /* @__PURE__ */ jsx("div", {
1134
+ ...themeAttrs(cssVarPrefix, activeTheme),
1135
+ style: {
1136
+ ...chromeAliases(cssVarPrefix),
1137
+ ...styles$14.wrapper
1138
+ },
1139
+ "data-testid": "diagnostics",
1140
+ children: /* @__PURE__ */ jsxs("details", {
1141
+ open: hasErrorsOrWarnings,
1142
+ children: [/* @__PURE__ */ jsx("summary", {
1143
+ style: {
1144
+ ...styles$14.summary,
1145
+ color: summaryColor(diagnostics)
1146
+ },
1147
+ children: headingText
1148
+ }), diagnostics.length > 0 && /* @__PURE__ */ jsx("ul", {
1149
+ style: styles$14.list,
1150
+ children: diagnostics.map((d, i) => /* @__PURE__ */ jsxs("li", {
1151
+ style: styles$14.row,
1152
+ children: [/* @__PURE__ */ jsx("span", {
1153
+ style: {
1154
+ ...styles$14.label,
1155
+ color: severityColor[d.severity]
1156
+ },
1157
+ children: severityLabel[d.severity]
1158
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: d.message }), (d.group || d.filename) && /* @__PURE__ */ jsx("div", {
1159
+ style: styles$14.meta,
1160
+ children: [
1161
+ d.group,
1162
+ d.filename,
1163
+ d.line ? `:${d.line}` : ""
1164
+ ].filter(Boolean).join(" · ")
1165
+ })] })]
1166
+ }, diagnosticKey(d, i)))
1167
+ })]
1168
+ })
1169
+ });
1170
+ }
1171
+ //#endregion
818
1172
  //#region src/dimension-scale/DimensionBar.tsx
819
1173
  const MAX_RENDER_PX$1 = 480;
820
- const styles$12 = {
1174
+ const styles$13 = {
821
1175
  bar: {
822
1176
  height: 14,
823
1177
  background: "var(--sb-color-sys-accent-bg, #3b82f6)",
@@ -828,11 +1182,11 @@ const styles$12 = {
828
1182
  width: 56,
829
1183
  height: 56,
830
1184
  background: "var(--sb-color-sys-accent-bg, #3b82f6)",
831
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))"
1185
+ border: BORDER_STRONG
832
1186
  },
833
1187
  sizeSample: {
834
1188
  background: "var(--sb-color-sys-accent-bg, #3b82f6)",
835
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
1189
+ border: BORDER_STRONG,
836
1190
  minWidth: 1,
837
1191
  minHeight: 1
838
1192
  }
@@ -865,7 +1219,7 @@ function DimensionBar({ path, kind = "length" }) {
865
1219
  case "radius": return /* @__PURE__ */ jsx("div", {
866
1220
  style: {
867
1221
  ...aliases,
868
- ...styles$12.radiusSample,
1222
+ ...styles$13.radiusSample,
869
1223
  borderRadius: cssVar
870
1224
  },
871
1225
  "aria-hidden": true
@@ -873,7 +1227,7 @@ function DimensionBar({ path, kind = "length" }) {
873
1227
  case "size": return /* @__PURE__ */ jsx("div", {
874
1228
  style: {
875
1229
  ...aliases,
876
- ...styles$12.sizeSample,
1230
+ ...styles$13.sizeSample,
877
1231
  width: cappedValue,
878
1232
  height: cappedValue
879
1233
  },
@@ -882,7 +1236,7 @@ function DimensionBar({ path, kind = "length" }) {
882
1236
  default: return /* @__PURE__ */ jsx("div", {
883
1237
  style: {
884
1238
  ...aliases,
885
- ...styles$12.bar,
1239
+ ...styles$13.bar,
886
1240
  width: cappedValue
887
1241
  },
888
1242
  "aria-hidden": true
@@ -890,9 +1244,151 @@ function DimensionBar({ path, kind = "length" }) {
890
1244
  }
891
1245
  }
892
1246
  //#endregion
1247
+ //#region src/internal/format-token-value.ts
1248
+ /**
1249
+ * Produce a single-line display string for any DTCG token `$value`,
1250
+ * respecting the active color format for color-typed tokens and the
1251
+ * color sub-values of composite types (border, shadow, gradient).
1252
+ *
1253
+ * Replaces the old `formatValue` one-shot that hex-short-circuited
1254
+ * colors and fell through to raw JSON for known composites.
1255
+ *
1256
+ * Shape by type:
1257
+ * - `color` → `formatColor(value, colorFormat)` — e.g. `#3b82f6`, `oklch(...)`, `raw` JSON.
1258
+ * - `dimension|duration` → `value + unit` — e.g. `16px`, `200ms`.
1259
+ * - `fontFamily` → string or array joined with `, `.
1260
+ * - `fontWeight` → primitive.
1261
+ * - `cubicBezier` → `cubic-bezier(a, b, c, d)`.
1262
+ * - `strokeStyle` → primitive string, or `dashed · dashArray · lineCap` when it's the object shape.
1263
+ * - `shadow` → one or more `offsetX offsetY blur spread color` layers joined with `, `.
1264
+ * - `border` → `width style color`.
1265
+ * - `transition` → `duration easing [delay]`.
1266
+ * - `typography` → `family / size / line-height / weight`.
1267
+ * - `gradient` → `stops joined with →` — compact representation, not a CSS gradient string (those live in GradientPalette's preview).
1268
+ *
1269
+ * Unknown object shapes fall through to truncated JSON.
1270
+ */
1271
+ function formatTokenValue(value, $type, colorFormat) {
1272
+ if (value == null) return "";
1273
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
1274
+ switch ($type) {
1275
+ case "color": return formatColor(value, colorFormat).value;
1276
+ case "dimension":
1277
+ case "duration": return formatDimension$1(value);
1278
+ case "fontFamily": return formatFontFamily$1(value);
1279
+ case "fontWeight":
1280
+ case "lineHeight":
1281
+ case "letterSpacing":
1282
+ case "opacity":
1283
+ case "number": return formatPrimitive$1(value);
1284
+ case "cubicBezier": return formatCubicBezier(value);
1285
+ case "strokeStyle": return formatStrokeStyle(value);
1286
+ case "shadow": return formatShadow(value, colorFormat);
1287
+ case "border": return formatBorder(value, colorFormat);
1288
+ case "transition": return formatTransition(value);
1289
+ case "typography": return formatTypography(value);
1290
+ case "gradient": return formatGradient(value, colorFormat);
1291
+ default: return formatUnknown(value);
1292
+ }
1293
+ }
1294
+ function formatDimension$1(v) {
1295
+ if (typeof v === "string" || typeof v === "number") return String(v);
1296
+ if (v && typeof v === "object") {
1297
+ const d = v;
1298
+ if (typeof d.value === "number" && typeof d.unit === "string") return `${d.value}${d.unit}`;
1299
+ }
1300
+ return formatUnknown(v);
1301
+ }
1302
+ function formatFontFamily$1(v) {
1303
+ if (typeof v === "string") return v;
1304
+ if (Array.isArray(v)) return v.map(String).join(", ");
1305
+ return formatUnknown(v);
1306
+ }
1307
+ function formatPrimitive$1(v) {
1308
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
1309
+ return formatUnknown(v);
1310
+ }
1311
+ function formatCubicBezier(v) {
1312
+ if (Array.isArray(v) && v.length === 4) return `cubic-bezier(${v.map((n) => typeof n === "number" ? n : 0).join(", ")})`;
1313
+ return formatUnknown(v);
1314
+ }
1315
+ function formatStrokeStyle(v) {
1316
+ if (typeof v === "string") return v;
1317
+ if (v && typeof v === "object") {
1318
+ const s = v;
1319
+ const parts = ["dashed"];
1320
+ if (Array.isArray(s.dashArray)) parts.push(s.dashArray.map((n) => formatDimension$1(n)).join(" "));
1321
+ if (typeof s.lineCap === "string") parts.push(s.lineCap);
1322
+ return parts.join(" · ");
1323
+ }
1324
+ return formatUnknown(v);
1325
+ }
1326
+ function formatShadow(v, colorFormat) {
1327
+ return (Array.isArray(v) ? v : [v]).map((layer) => {
1328
+ if (!layer || typeof layer !== "object") return formatUnknown(layer);
1329
+ const s = layer;
1330
+ const pieces = [
1331
+ formatDimension$1(s["offsetX"]),
1332
+ formatDimension$1(s["offsetY"]),
1333
+ formatDimension$1(s["blur"]),
1334
+ formatDimension$1(s["spread"]),
1335
+ formatColor(s["color"], colorFormat).value
1336
+ ].filter((p) => p !== "");
1337
+ if (s["inset"]) pieces.push("inset");
1338
+ return pieces.join(" ");
1339
+ }).join(", ");
1340
+ }
1341
+ function formatBorder(v, colorFormat) {
1342
+ if (!v || typeof v !== "object") return formatUnknown(v);
1343
+ const b = v;
1344
+ return [
1345
+ formatDimension$1(b["width"]),
1346
+ formatPrimitive$1(b["style"]),
1347
+ formatColor(b["color"], colorFormat).value
1348
+ ].filter((p) => p !== "").join(" ");
1349
+ }
1350
+ function formatTransition(v) {
1351
+ if (!v || typeof v !== "object") return formatUnknown(v);
1352
+ const t = v;
1353
+ const duration = formatDimension$1(t["duration"]);
1354
+ const easing = formatPrimitive$1(t["timingFunction"]);
1355
+ const delay = formatDimension$1(t["delay"]);
1356
+ const parts = [duration, easing];
1357
+ if (!/^0\D/.test(delay) && delay !== "") parts.push(delay);
1358
+ return parts.filter((p) => p !== "").join(" ");
1359
+ }
1360
+ function formatTypography(v) {
1361
+ if (!v || typeof v !== "object") return formatUnknown(v);
1362
+ const t = v;
1363
+ return [
1364
+ formatFontFamily$1(t["fontFamily"]),
1365
+ formatDimension$1(t["fontSize"]),
1366
+ formatPrimitive$1(t["lineHeight"]),
1367
+ formatPrimitive$1(t["fontWeight"])
1368
+ ].filter((p) => p !== "").join(" / ");
1369
+ }
1370
+ function formatGradient(v, colorFormat) {
1371
+ if (!Array.isArray(v) || v.length === 0) return formatUnknown(v);
1372
+ return v.map((stop) => {
1373
+ if (!stop || typeof stop !== "object") return formatUnknown(stop);
1374
+ const s = stop;
1375
+ const position = typeof s["position"] === "number" ? `${Math.round(s["position"] * 100)}%` : "?";
1376
+ return `${formatColor(s["color"], colorFormat).value} ${position}`;
1377
+ }).join(" → ");
1378
+ }
1379
+ function formatUnknown(v) {
1380
+ if (v == null) return "";
1381
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
1382
+ try {
1383
+ return JSON.stringify(v).slice(0, 120);
1384
+ } catch {
1385
+ return String(v);
1386
+ }
1387
+ }
1388
+ //#endregion
893
1389
  //#region src/DimensionScale.tsx
894
1390
  const MAX_RENDER_PX = 480;
895
- const styles$11 = {
1391
+ const styles$12 = {
896
1392
  wrapper: surfaceStyle,
897
1393
  caption: captionStyle,
898
1394
  empty: emptyStyle,
@@ -955,41 +1451,41 @@ function toPixels(raw) {
955
1451
  default: return NaN;
956
1452
  }
957
1453
  }
958
- function DimensionScale({ filter = "dimension", kind = "length", caption }) {
1454
+ function DimensionScale({ filter, kind = "length", caption, sortBy = "value", sortDir = "asc" }) {
959
1455
  const { resolved, activeTheme, cssVarPrefix } = useProject();
960
1456
  const rows = useMemo(() => {
961
- const collected = [];
962
- for (const [path, token] of Object.entries(resolved)) {
963
- if (token.$type !== "dimension") continue;
964
- if (!globMatch(path, filter)) continue;
1457
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
1458
+ if (token.$type !== "dimension") return false;
1459
+ return globMatch(path, filter);
1460
+ }), {
1461
+ by: sortBy,
1462
+ dir: sortDir
1463
+ }).map(([path, token]) => {
965
1464
  const pxValue = toPixels(token.$value);
966
- collected.push({
1465
+ return {
967
1466
  path,
968
1467
  cssVar: makeCssVar(path, cssVarPrefix),
969
- displayValue: formatValue(token.$value),
1468
+ displayValue: formatTokenValue(token.$value, token.$type, "raw"),
970
1469
  pxValue,
971
1470
  capped: Number.isFinite(pxValue) && pxValue > MAX_RENDER_PX
972
- });
973
- }
974
- collected.sort((a, b) => {
975
- if (Number.isFinite(a.pxValue) && Number.isFinite(b.pxValue)) return a.pxValue - b.pxValue;
976
- return a.path.localeCompare(b.path, void 0, { numeric: true });
1471
+ };
977
1472
  });
978
- return collected;
979
1473
  }, [
980
1474
  resolved,
981
1475
  filter,
982
- cssVarPrefix
1476
+ cssVarPrefix,
1477
+ sortBy,
1478
+ sortDir
983
1479
  ]);
984
1480
  const captionText = caption ?? `${rows.length} dimension${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
985
1481
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
986
1482
  ...themeAttrs(cssVarPrefix, activeTheme),
987
1483
  style: {
988
1484
  ...chromeAliases(cssVarPrefix),
989
- ...styles$11.wrapper
1485
+ ...styles$12.wrapper
990
1486
  },
991
1487
  children: /* @__PURE__ */ jsx("div", {
992
- style: styles$11.empty,
1488
+ style: styles$12.empty,
993
1489
  children: "No dimension tokens match this filter."
994
1490
  })
995
1491
  });
@@ -997,31 +1493,31 @@ function DimensionScale({ filter = "dimension", kind = "length", caption }) {
997
1493
  ...themeAttrs(cssVarPrefix, activeTheme),
998
1494
  style: {
999
1495
  ...chromeAliases(cssVarPrefix),
1000
- ...styles$11.wrapper
1496
+ ...styles$12.wrapper
1001
1497
  },
1002
1498
  children: [/* @__PURE__ */ jsx("div", {
1003
- style: styles$11.caption,
1499
+ style: styles$12.caption,
1004
1500
  children: captionText
1005
1501
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1006
- style: styles$11.row,
1502
+ style: styles$12.row,
1007
1503
  children: [
1008
1504
  /* @__PURE__ */ jsxs("div", {
1009
- style: styles$11.meta,
1505
+ style: styles$12.meta,
1010
1506
  children: [/* @__PURE__ */ jsx("span", {
1011
- style: styles$11.path,
1507
+ style: styles$12.path,
1012
1508
  children: row.path
1013
1509
  }), /* @__PURE__ */ jsx("span", {
1014
- style: styles$11.specs,
1510
+ style: styles$12.specs,
1015
1511
  children: row.displayValue
1016
1512
  })]
1017
1513
  }),
1018
1514
  /* @__PURE__ */ jsxs("div", {
1019
- style: styles$11.visualCell,
1515
+ style: styles$12.visualCell,
1020
1516
  children: [/* @__PURE__ */ jsx(DimensionBar, {
1021
1517
  path: row.path,
1022
1518
  kind
1023
1519
  }), row.capped && /* @__PURE__ */ jsxs("span", {
1024
- style: styles$11.cap,
1520
+ style: styles$12.cap,
1025
1521
  children: [
1026
1522
  "capped at ",
1027
1523
  MAX_RENDER_PX,
@@ -1030,7 +1526,7 @@ function DimensionScale({ filter = "dimension", kind = "length", caption }) {
1030
1526
  })]
1031
1527
  }),
1032
1528
  /* @__PURE__ */ jsx("span", {
1033
- style: styles$11.cssVar,
1529
+ style: styles$12.cssVar,
1034
1530
  children: row.cssVar
1035
1531
  })
1036
1532
  ]
@@ -1039,7 +1535,7 @@ function DimensionScale({ filter = "dimension", kind = "length", caption }) {
1039
1535
  }
1040
1536
  //#endregion
1041
1537
  //#region src/FontFamilySample.tsx
1042
- const styles$10 = {
1538
+ const styles$11 = {
1043
1539
  wrapper: surfaceStyle,
1044
1540
  caption: captionStyle,
1045
1541
  row: {
@@ -1066,7 +1562,7 @@ const styles$10 = {
1066
1562
  stack: {
1067
1563
  fontFamily: MONO_STACK,
1068
1564
  fontSize: 11,
1069
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1565
+ color: TEXT_MUTED
1070
1566
  },
1071
1567
  sample: {
1072
1568
  fontSize: 22,
@@ -1075,7 +1571,7 @@ const styles$10 = {
1075
1571
  cssVar: {
1076
1572
  fontFamily: MONO_STACK,
1077
1573
  fontSize: 11,
1078
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1574
+ color: TEXT_MUTED
1079
1575
  },
1080
1576
  empty: emptyStyle
1081
1577
  };
@@ -1084,13 +1580,16 @@ function stackString(raw) {
1084
1580
  if (Array.isArray(raw)) return raw.map(String).join(", ");
1085
1581
  return "";
1086
1582
  }
1087
- function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox jumps over the lazy dog.", caption }) {
1583
+ function FontFamilySample({ filter, sample = "The quick brown fox jumps over the lazy dog.", caption, sortBy = "path", sortDir = "asc" }) {
1088
1584
  const { resolved, activeTheme, cssVarPrefix } = useProject();
1089
1585
  const rows = useMemo(() => {
1090
- return Object.entries(resolved).filter(([path, token]) => {
1586
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
1091
1587
  if (token.$type !== "fontFamily") return false;
1092
1588
  return globMatch(path, filter);
1093
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => ({
1589
+ }), {
1590
+ by: sortBy,
1591
+ dir: sortDir
1592
+ }).map(([path, token]) => ({
1094
1593
  path,
1095
1594
  cssVar: makeCssVar(path, cssVarPrefix),
1096
1595
  stack: stackString(token.$value)
@@ -1098,17 +1597,19 @@ function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox
1098
1597
  }, [
1099
1598
  resolved,
1100
1599
  filter,
1101
- cssVarPrefix
1600
+ cssVarPrefix,
1601
+ sortBy,
1602
+ sortDir
1102
1603
  ]);
1103
1604
  const captionText = caption ?? `${rows.length} fontFamily token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontFamily" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1104
1605
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1105
1606
  ...themeAttrs(cssVarPrefix, activeTheme),
1106
1607
  style: {
1107
1608
  ...chromeAliases(cssVarPrefix),
1108
- ...styles$10.wrapper
1609
+ ...styles$11.wrapper
1109
1610
  },
1110
1611
  children: /* @__PURE__ */ jsx("div", {
1111
- style: styles$10.empty,
1612
+ style: styles$11.empty,
1112
1613
  children: "No fontFamily tokens match this filter."
1113
1614
  })
1114
1615
  });
@@ -1116,33 +1617,33 @@ function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox
1116
1617
  ...themeAttrs(cssVarPrefix, activeTheme),
1117
1618
  style: {
1118
1619
  ...chromeAliases(cssVarPrefix),
1119
- ...styles$10.wrapper
1620
+ ...styles$11.wrapper
1120
1621
  },
1121
1622
  children: [/* @__PURE__ */ jsx("div", {
1122
- style: styles$10.caption,
1623
+ style: styles$11.caption,
1123
1624
  children: captionText
1124
1625
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1125
- style: styles$10.row,
1626
+ style: styles$11.row,
1126
1627
  children: [
1127
1628
  /* @__PURE__ */ jsxs("div", {
1128
- style: styles$10.meta,
1629
+ style: styles$11.meta,
1129
1630
  children: [/* @__PURE__ */ jsx("span", {
1130
- style: styles$10.path,
1631
+ style: styles$11.path,
1131
1632
  children: row.path
1132
1633
  }), /* @__PURE__ */ jsx("span", {
1133
- style: styles$10.stack,
1634
+ style: styles$11.stack,
1134
1635
  children: row.stack
1135
1636
  })]
1136
1637
  }),
1137
1638
  /* @__PURE__ */ jsx("div", {
1138
1639
  style: {
1139
- ...styles$10.sample,
1640
+ ...styles$11.sample,
1140
1641
  fontFamily: row.cssVar
1141
1642
  },
1142
1643
  children: sample
1143
1644
  }),
1144
1645
  /* @__PURE__ */ jsx("span", {
1145
- style: styles$10.cssVar,
1646
+ style: styles$11.cssVar,
1146
1647
  children: row.cssVar
1147
1648
  })
1148
1649
  ]
@@ -1151,7 +1652,7 @@ function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox
1151
1652
  }
1152
1653
  //#endregion
1153
1654
  //#region src/FontWeightScale.tsx
1154
- const styles$9 = {
1655
+ const styles$10 = {
1155
1656
  wrapper: surfaceStyle,
1156
1657
  caption: captionStyle,
1157
1658
  empty: emptyStyle,
@@ -1179,7 +1680,7 @@ const styles$9 = {
1179
1680
  value: {
1180
1681
  fontFamily: MONO_STACK,
1181
1682
  fontSize: 11,
1182
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1683
+ color: TEXT_MUTED
1183
1684
  },
1184
1685
  sample: {
1185
1686
  fontSize: 28,
@@ -1188,7 +1689,7 @@ const styles$9 = {
1188
1689
  cssVar: {
1189
1690
  fontFamily: MONO_STACK,
1190
1691
  fontSize: 11,
1191
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1692
+ color: TEXT_MUTED
1192
1693
  }
1193
1694
  };
1194
1695
  function toWeight(raw) {
@@ -1199,39 +1700,37 @@ function toWeight(raw) {
1199
1700
  }
1200
1701
  return NaN;
1201
1702
  }
1202
- function FontWeightScale({ filter = "fontWeight", sample = "Aa", caption }) {
1703
+ function FontWeightScale({ filter, sample = "Aa", caption, sortBy = "value", sortDir = "asc" }) {
1203
1704
  const { resolved, activeTheme, cssVarPrefix } = useProject();
1204
1705
  const rows = useMemo(() => {
1205
- const collected = [];
1206
- for (const [path, token] of Object.entries(resolved)) {
1207
- if (token.$type !== "fontWeight") continue;
1208
- if (!globMatch(path, filter)) continue;
1209
- collected.push({
1210
- path,
1211
- cssVar: makeCssVar(path, cssVarPrefix),
1212
- display: token.$value == null ? "" : String(token.$value),
1213
- weight: toWeight(token.$value)
1214
- });
1215
- }
1216
- collected.sort((a, b) => {
1217
- if (Number.isFinite(a.weight) && Number.isFinite(b.weight)) return a.weight - b.weight;
1218
- return a.path.localeCompare(b.path, void 0, { numeric: true });
1219
- });
1220
- return collected;
1706
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
1707
+ if (token.$type !== "fontWeight") return false;
1708
+ return globMatch(path, filter);
1709
+ }), {
1710
+ by: sortBy,
1711
+ dir: sortDir
1712
+ }).map(([path, token]) => ({
1713
+ path,
1714
+ cssVar: makeCssVar(path, cssVarPrefix),
1715
+ display: token.$value == null ? "" : String(token.$value),
1716
+ weight: toWeight(token.$value)
1717
+ }));
1221
1718
  }, [
1222
1719
  resolved,
1223
1720
  filter,
1224
- cssVarPrefix
1721
+ cssVarPrefix,
1722
+ sortBy,
1723
+ sortDir
1225
1724
  ]);
1226
1725
  const captionText = caption ?? `${rows.length} fontWeight token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontWeight" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1227
1726
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1228
1727
  ...themeAttrs(cssVarPrefix, activeTheme),
1229
1728
  style: {
1230
1729
  ...chromeAliases(cssVarPrefix),
1231
- ...styles$9.wrapper
1730
+ ...styles$10.wrapper
1232
1731
  },
1233
1732
  children: /* @__PURE__ */ jsx("div", {
1234
- style: styles$9.empty,
1733
+ style: styles$10.empty,
1235
1734
  children: "No fontWeight tokens match this filter."
1236
1735
  })
1237
1736
  });
@@ -1239,33 +1738,33 @@ function FontWeightScale({ filter = "fontWeight", sample = "Aa", caption }) {
1239
1738
  ...themeAttrs(cssVarPrefix, activeTheme),
1240
1739
  style: {
1241
1740
  ...chromeAliases(cssVarPrefix),
1242
- ...styles$9.wrapper
1741
+ ...styles$10.wrapper
1243
1742
  },
1244
1743
  children: [/* @__PURE__ */ jsx("div", {
1245
- style: styles$9.caption,
1744
+ style: styles$10.caption,
1246
1745
  children: captionText
1247
1746
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1248
- style: styles$9.row,
1747
+ style: styles$10.row,
1249
1748
  children: [
1250
1749
  /* @__PURE__ */ jsxs("div", {
1251
- style: styles$9.meta,
1750
+ style: styles$10.meta,
1252
1751
  children: [/* @__PURE__ */ jsx("span", {
1253
- style: styles$9.path,
1752
+ style: styles$10.path,
1254
1753
  children: row.path
1255
1754
  }), /* @__PURE__ */ jsx("span", {
1256
- style: styles$9.value,
1755
+ style: styles$10.value,
1257
1756
  children: row.display
1258
1757
  })]
1259
1758
  }),
1260
1759
  /* @__PURE__ */ jsx("div", {
1261
1760
  style: {
1262
- ...styles$9.sample,
1761
+ ...styles$10.sample,
1263
1762
  fontWeight: row.cssVar
1264
1763
  },
1265
1764
  children: sample
1266
1765
  }),
1267
1766
  /* @__PURE__ */ jsx("span", {
1268
- style: styles$9.cssVar,
1767
+ style: styles$10.cssVar,
1269
1768
  children: row.cssVar
1270
1769
  })
1271
1770
  ]
@@ -1274,7 +1773,7 @@ function FontWeightScale({ filter = "fontWeight", sample = "Aa", caption }) {
1274
1773
  }
1275
1774
  //#endregion
1276
1775
  //#region src/GradientPalette.tsx
1277
- const styles$8 = {
1776
+ const styles$9 = {
1278
1777
  wrapper: surfaceStyle,
1279
1778
  caption: captionStyle,
1280
1779
  empty: emptyStyle,
@@ -1325,7 +1824,7 @@ const styles$8 = {
1325
1824
  width: 10,
1326
1825
  height: 10,
1327
1826
  borderRadius: 2,
1328
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))",
1827
+ border: BORDER_DEFAULT,
1329
1828
  flex: "0 0 auto"
1330
1829
  },
1331
1830
  stopPosition: { opacity: .6 }
@@ -1346,35 +1845,36 @@ function stopCssColor(stop) {
1346
1845
  function stopKey(path, stop, fallback) {
1347
1846
  return `${path}|${stop.position ?? fallback}|${stopCssColor(stop)}`;
1348
1847
  }
1349
- function GradientPalette({ filter = "gradient", caption }) {
1848
+ function GradientPalette({ filter, caption, sortBy = "path", sortDir = "asc" }) {
1350
1849
  const { resolved, activeTheme, cssVarPrefix } = useProject();
1351
1850
  const rows = useMemo(() => {
1352
- const collected = [];
1353
- for (const [path, token] of Object.entries(resolved)) {
1354
- if (token.$type !== "gradient") continue;
1355
- if (!globMatch(path, filter)) continue;
1356
- collected.push({
1357
- path,
1358
- cssVar: makeCssVar(path, cssVarPrefix),
1359
- stops: asStops(token.$value)
1360
- });
1361
- }
1362
- collected.sort((a, b) => a.path.localeCompare(b.path, void 0, { numeric: true }));
1363
- return collected;
1851
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
1852
+ if (token.$type !== "gradient") return false;
1853
+ return globMatch(path, filter);
1854
+ }), {
1855
+ by: sortBy,
1856
+ dir: sortDir
1857
+ }).map(([path, token]) => ({
1858
+ path,
1859
+ cssVar: makeCssVar(path, cssVarPrefix),
1860
+ stops: asStops(token.$value)
1861
+ }));
1364
1862
  }, [
1365
1863
  resolved,
1366
1864
  filter,
1367
- cssVarPrefix
1865
+ cssVarPrefix,
1866
+ sortBy,
1867
+ sortDir
1368
1868
  ]);
1369
1869
  const captionText = caption ?? `${rows.length} gradient${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1370
1870
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1371
1871
  ...themeAttrs(cssVarPrefix, activeTheme),
1372
1872
  style: {
1373
1873
  ...chromeAliases(cssVarPrefix),
1374
- ...styles$8.wrapper
1874
+ ...styles$9.wrapper
1375
1875
  },
1376
1876
  children: /* @__PURE__ */ jsx("div", {
1377
- style: styles$8.empty,
1877
+ style: styles$9.empty,
1378
1878
  children: "No gradient tokens match this filter."
1379
1879
  })
1380
1880
  });
@@ -1382,46 +1882,46 @@ function GradientPalette({ filter = "gradient", caption }) {
1382
1882
  ...themeAttrs(cssVarPrefix, activeTheme),
1383
1883
  style: {
1384
1884
  ...chromeAliases(cssVarPrefix),
1385
- ...styles$8.wrapper
1885
+ ...styles$9.wrapper
1386
1886
  },
1387
1887
  children: [/* @__PURE__ */ jsx("div", {
1388
- style: styles$8.caption,
1888
+ style: styles$9.caption,
1389
1889
  children: captionText
1390
1890
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1391
- style: styles$8.row,
1891
+ style: styles$9.row,
1392
1892
  children: [
1393
1893
  /* @__PURE__ */ jsxs("div", {
1394
- style: styles$8.meta,
1894
+ style: styles$9.meta,
1395
1895
  children: [/* @__PURE__ */ jsx("span", {
1396
- style: styles$8.path,
1896
+ style: styles$9.path,
1397
1897
  children: row.path
1398
1898
  }), /* @__PURE__ */ jsx("span", {
1399
- style: styles$8.cssVar,
1899
+ style: styles$9.cssVar,
1400
1900
  children: row.cssVar
1401
1901
  })]
1402
1902
  }),
1403
1903
  /* @__PURE__ */ jsx("div", {
1404
1904
  style: {
1405
- ...styles$8.sample,
1905
+ ...styles$9.sample,
1406
1906
  background: `linear-gradient(to right, ${row.cssVar})`
1407
1907
  },
1408
1908
  "aria-hidden": true
1409
1909
  }),
1410
1910
  /* @__PURE__ */ jsx("div", {
1411
- style: styles$8.stops,
1911
+ style: styles$9.stops,
1412
1912
  children: row.stops.map((stop, i) => /* @__PURE__ */ jsxs("div", {
1413
- style: styles$8.stopRow,
1913
+ style: styles$9.stopRow,
1414
1914
  children: [
1415
1915
  /* @__PURE__ */ jsx("span", {
1416
1916
  style: {
1417
- ...styles$8.stopSwatch,
1917
+ ...styles$9.stopSwatch,
1418
1918
  background: stopCssColor(stop)
1419
1919
  },
1420
1920
  "aria-hidden": true
1421
1921
  }),
1422
1922
  /* @__PURE__ */ jsx("span", { children: stopCssColor(stop) }),
1423
1923
  /* @__PURE__ */ jsxs("span", {
1424
- style: styles$8.stopPosition,
1924
+ style: styles$9.stopPosition,
1425
1925
  children: [
1426
1926
  "@ ",
1427
1927
  ((stop.position ?? 0) * 100).toFixed(0),
@@ -1457,11 +1957,11 @@ function usePrefersReducedMotion() {
1457
1957
  //#region src/motion-preview/MotionSample.tsx
1458
1958
  const DEFAULT_DURATION_MS = 300;
1459
1959
  const DEFAULT_EASING = "cubic-bezier(0.2, 0, 0, 1)";
1460
- const styles$7 = {
1960
+ const styles$8 = {
1461
1961
  track: {
1462
1962
  position: "relative",
1463
1963
  height: 36,
1464
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.08))",
1964
+ background: SURFACE_MUTED,
1465
1965
  borderRadius: 18,
1466
1966
  overflow: "hidden"
1467
1967
  },
@@ -1476,7 +1976,7 @@ const styles$7 = {
1476
1976
  },
1477
1977
  reducedMotion: {
1478
1978
  fontSize: 11,
1479
- color: "var(--sb-color-sys-text-muted, CanvasText)",
1979
+ color: TEXT_MUTED,
1480
1980
  fontStyle: "italic"
1481
1981
  }
1482
1982
  };
@@ -1576,18 +2076,18 @@ function MotionSample({ path, speed = 1, runKey = 0 }) {
1576
2076
  if (reducedMotion) return /* @__PURE__ */ jsx("div", {
1577
2077
  style: {
1578
2078
  ...chromeAliases(cssVarPrefix),
1579
- ...styles$7.reducedMotion
2079
+ ...styles$8.reducedMotion
1580
2080
  },
1581
2081
  children: "Animation suppressed by `prefers-reduced-motion: reduce`."
1582
2082
  });
1583
2083
  return /* @__PURE__ */ jsx("div", {
1584
2084
  style: {
1585
2085
  ...chromeAliases(cssVarPrefix),
1586
- ...styles$7.track
2086
+ ...styles$8.track
1587
2087
  },
1588
2088
  children: /* @__PURE__ */ jsx("div", {
1589
2089
  style: {
1590
- ...styles$7.ball,
2090
+ ...styles$8.ball,
1591
2091
  left: phase === 1 ? "calc(100% - 32px)" : "4px",
1592
2092
  transition: `left ${scaledDuration}ms ${easing}`
1593
2093
  },
@@ -1603,11 +2103,11 @@ const SPEEDS = [
1603
2103
  1,
1604
2104
  2
1605
2105
  ];
1606
- const styles$6 = {
2106
+ const styles$7 = {
1607
2107
  wrapper: surfaceStyle,
1608
2108
  caption: {
1609
2109
  padding: "4px 0 4px",
1610
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2110
+ color: TEXT_MUTED,
1611
2111
  fontSize: 12
1612
2112
  },
1613
2113
  controls: {
@@ -1618,7 +2118,7 @@ const styles$6 = {
1618
2118
  },
1619
2119
  controlLabel: {
1620
2120
  fontSize: 11,
1621
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2121
+ color: TEXT_MUTED,
1622
2122
  textTransform: "uppercase",
1623
2123
  letterSpacing: .5
1624
2124
  },
@@ -1628,7 +2128,7 @@ const styles$6 = {
1628
2128
  padding: "4px 8px",
1629
2129
  background: "transparent",
1630
2130
  color: "inherit",
1631
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
2131
+ border: BORDER_STRONG,
1632
2132
  borderRadius: 4,
1633
2133
  cursor: "pointer"
1634
2134
  },
@@ -1643,7 +2143,7 @@ const styles$6 = {
1643
2143
  marginLeft: "auto",
1644
2144
  background: "transparent",
1645
2145
  color: "inherit",
1646
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
2146
+ border: BORDER_STRONG,
1647
2147
  borderRadius: 4,
1648
2148
  cursor: "pointer"
1649
2149
  },
@@ -1671,18 +2171,18 @@ const styles$6 = {
1671
2171
  specs: {
1672
2172
  fontFamily: MONO_STACK,
1673
2173
  fontSize: 11,
1674
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2174
+ color: TEXT_MUTED
1675
2175
  },
1676
2176
  cssVar: {
1677
2177
  fontFamily: MONO_STACK,
1678
2178
  fontSize: 11,
1679
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2179
+ color: TEXT_MUTED,
1680
2180
  whiteSpace: "nowrap"
1681
2181
  },
1682
2182
  empty: {
1683
2183
  padding: "24px 12px",
1684
2184
  textAlign: "center",
1685
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2185
+ color: TEXT_MUTED
1686
2186
  }
1687
2187
  };
1688
2188
  function formatSpec(row) {
@@ -1733,10 +2233,10 @@ function MotionPreview({ filter, caption }) {
1733
2233
  ...themeAttrs(cssVarPrefix, activeTheme),
1734
2234
  style: {
1735
2235
  ...chromeAliases(cssVarPrefix),
1736
- ...styles$6.wrapper
2236
+ ...styles$7.wrapper
1737
2237
  },
1738
2238
  children: /* @__PURE__ */ jsx("div", {
1739
- style: styles$6.empty,
2239
+ style: styles$7.empty,
1740
2240
  children: "No motion tokens match this filter."
1741
2241
  })
1742
2242
  });
@@ -1744,32 +2244,32 @@ function MotionPreview({ filter, caption }) {
1744
2244
  ...themeAttrs(cssVarPrefix, activeTheme),
1745
2245
  style: {
1746
2246
  ...chromeAliases(cssVarPrefix),
1747
- ...styles$6.wrapper
2247
+ ...styles$7.wrapper
1748
2248
  },
1749
2249
  children: [
1750
2250
  /* @__PURE__ */ jsx("div", {
1751
- style: styles$6.caption,
2251
+ style: styles$7.caption,
1752
2252
  children: captionText
1753
2253
  }),
1754
2254
  /* @__PURE__ */ jsxs("div", {
1755
- style: styles$6.controls,
2255
+ style: styles$7.controls,
1756
2256
  children: [
1757
2257
  /* @__PURE__ */ jsx("span", {
1758
- style: styles$6.controlLabel,
2258
+ style: styles$7.controlLabel,
1759
2259
  children: "Speed"
1760
2260
  }),
1761
2261
  SPEEDS.map((s) => /* @__PURE__ */ jsxs("button", {
1762
2262
  type: "button",
1763
2263
  style: {
1764
- ...styles$6.speedBtn,
1765
- ...s === speed ? styles$6.speedBtnActive : {}
2264
+ ...styles$7.speedBtn,
2265
+ ...s === speed ? styles$7.speedBtnActive : {}
1766
2266
  },
1767
2267
  onClick: () => setSpeed(s),
1768
2268
  children: [s, "×"]
1769
2269
  }, s)),
1770
2270
  /* @__PURE__ */ jsx("button", {
1771
2271
  type: "button",
1772
- style: styles$6.replayBtn,
2272
+ style: styles$7.replayBtn,
1773
2273
  onClick: () => setRun((n) => n + 1),
1774
2274
  disabled: reducedMotion,
1775
2275
  title: reducedMotion ? "Disabled by prefers-reduced-motion" : "Replay all",
@@ -1778,15 +2278,15 @@ function MotionPreview({ filter, caption }) {
1778
2278
  ]
1779
2279
  }),
1780
2280
  rows.map((row) => /* @__PURE__ */ jsxs("div", {
1781
- style: styles$6.row,
2281
+ style: styles$7.row,
1782
2282
  children: [
1783
2283
  /* @__PURE__ */ jsxs("div", {
1784
- style: styles$6.meta,
2284
+ style: styles$7.meta,
1785
2285
  children: [/* @__PURE__ */ jsx("span", {
1786
- style: styles$6.path,
2286
+ style: styles$7.path,
1787
2287
  children: row.path
1788
2288
  }), /* @__PURE__ */ jsx("span", {
1789
- style: styles$6.specs,
2289
+ style: styles$7.specs,
1790
2290
  children: formatSpec(row)
1791
2291
  })]
1792
2292
  }),
@@ -1796,7 +2296,7 @@ function MotionPreview({ filter, caption }) {
1796
2296
  runKey: run
1797
2297
  }),
1798
2298
  /* @__PURE__ */ jsx("span", {
1799
- style: styles$6.cssVar,
2299
+ style: styles$7.cssVar,
1800
2300
  children: row.cssVar
1801
2301
  })
1802
2302
  ]
@@ -1836,8 +2336,8 @@ function useSwatchbookData() {
1836
2336
  const sampleStyle = {
1837
2337
  width: 120,
1838
2338
  height: 56,
1839
- background: "var(--sb-color-sys-surface-raised, #fff)",
1840
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.15))",
2339
+ background: SURFACE_RAISED,
2340
+ border: BORDER_FAINT,
1841
2341
  borderRadius: 6
1842
2342
  };
1843
2343
  function ShadowSample({ path }) {
@@ -1854,7 +2354,7 @@ function ShadowSample({ path }) {
1854
2354
  }
1855
2355
  //#endregion
1856
2356
  //#region src/ShadowPreview.tsx
1857
- const styles$5 = {
2357
+ const styles$6 = {
1858
2358
  wrapper: surfaceStyle,
1859
2359
  caption: captionStyle,
1860
2360
  empty: emptyStyle,
@@ -1898,12 +2398,12 @@ const styles$5 = {
1898
2398
  columnGap: 12,
1899
2399
  rowGap: 2
1900
2400
  },
1901
- breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" },
2401
+ breakdownKey: { color: TEXT_MUTED },
1902
2402
  layerHeader: {
1903
2403
  fontSize: 10,
1904
2404
  textTransform: "uppercase",
1905
2405
  letterSpacing: .5,
1906
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2406
+ color: TEXT_MUTED,
1907
2407
  marginTop: 6
1908
2408
  }
1909
2409
  };
@@ -1935,38 +2435,39 @@ function asLayers(raw) {
1935
2435
  if (raw && typeof raw === "object") return [raw];
1936
2436
  return [];
1937
2437
  }
1938
- function layerKey(path, layer, fallback) {
1939
- return `${path}|${`${formatDimension(layer.offsetX)},${formatDimension(layer.offsetY)}`}|${formatDimension(layer.blur)}|${formatDimension(layer.spread)}|${fallback}`;
1940
- }
1941
- function ShadowPreview({ filter = "shadow", caption }) {
1942
- const { resolved, activeTheme, cssVarPrefix } = useProject();
1943
- const rows = useMemo(() => {
1944
- const collected = [];
1945
- for (const [path, token] of Object.entries(resolved)) {
1946
- if (token.$type !== "shadow") continue;
1947
- if (!globMatch(path, filter)) continue;
1948
- collected.push({
1949
- path,
1950
- cssVar: makeCssVar(path, cssVarPrefix),
1951
- layers: asLayers(token.$value)
1952
- });
1953
- }
1954
- collected.sort((a, b) => a.path.localeCompare(b.path, void 0, { numeric: true }));
1955
- return collected;
2438
+ function layerKey(path, layer, fallback) {
2439
+ return `${path}|${`${formatDimension(layer.offsetX)},${formatDimension(layer.offsetY)}`}|${formatDimension(layer.blur)}|${formatDimension(layer.spread)}|${fallback}`;
2440
+ }
2441
+ function ShadowPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
2442
+ const { resolved, activeTheme, cssVarPrefix } = useProject();
2443
+ const rows = useMemo(() => {
2444
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
2445
+ if (token.$type !== "shadow") return false;
2446
+ return globMatch(path, filter);
2447
+ }), {
2448
+ by: sortBy,
2449
+ dir: sortDir
2450
+ }).map(([path, token]) => ({
2451
+ path,
2452
+ cssVar: makeCssVar(path, cssVarPrefix),
2453
+ layers: asLayers(token.$value)
2454
+ }));
1956
2455
  }, [
1957
2456
  resolved,
1958
2457
  filter,
1959
- cssVarPrefix
2458
+ cssVarPrefix,
2459
+ sortBy,
2460
+ sortDir
1960
2461
  ]);
1961
2462
  const captionText = caption ?? `${rows.length} shadow${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1962
2463
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1963
2464
  ...themeAttrs(cssVarPrefix, activeTheme),
1964
2465
  style: {
1965
2466
  ...chromeAliases(cssVarPrefix),
1966
- ...styles$5.wrapper
2467
+ ...styles$6.wrapper
1967
2468
  },
1968
2469
  children: /* @__PURE__ */ jsx("div", {
1969
- style: styles$5.empty,
2470
+ style: styles$6.empty,
1970
2471
  children: "No shadow tokens match this filter."
1971
2472
  })
1972
2473
  });
@@ -1974,30 +2475,30 @@ function ShadowPreview({ filter = "shadow", caption }) {
1974
2475
  ...themeAttrs(cssVarPrefix, activeTheme),
1975
2476
  style: {
1976
2477
  ...chromeAliases(cssVarPrefix),
1977
- ...styles$5.wrapper
2478
+ ...styles$6.wrapper
1978
2479
  },
1979
2480
  children: [/* @__PURE__ */ jsx("div", {
1980
- style: styles$5.caption,
2481
+ style: styles$6.caption,
1981
2482
  children: captionText
1982
2483
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1983
- style: styles$5.row,
2484
+ style: styles$6.row,
1984
2485
  children: [
1985
2486
  /* @__PURE__ */ jsxs("div", {
1986
- style: styles$5.meta,
2487
+ style: styles$6.meta,
1987
2488
  children: [/* @__PURE__ */ jsx("span", {
1988
- style: styles$5.path,
2489
+ style: styles$6.path,
1989
2490
  children: row.path
1990
2491
  }), /* @__PURE__ */ jsx("span", {
1991
- style: styles$5.cssVar,
2492
+ style: styles$6.cssVar,
1992
2493
  children: row.cssVar
1993
2494
  })]
1994
2495
  }),
1995
2496
  /* @__PURE__ */ jsx("div", {
1996
- style: styles$5.sampleCell,
2497
+ style: styles$6.sampleCell,
1997
2498
  children: /* @__PURE__ */ jsx(ShadowSample, { path: row.path })
1998
2499
  }),
1999
2500
  /* @__PURE__ */ jsx("div", {
2000
- style: styles$5.breakdown,
2501
+ style: styles$6.breakdown,
2001
2502
  children: row.layers.length === 1 ? renderLayer(row.layers[0]) : row.layers.map((layer, i) => /* @__PURE__ */ jsx(Layer, {
2002
2503
  layer,
2003
2504
  index: i,
@@ -2018,7 +2519,7 @@ function renderLayer(layer) {
2018
2519
  ];
2019
2520
  if (layer.inset) entries.push(["inset", String(layer.inset)]);
2020
2521
  return entries.flatMap(([k, v]) => [/* @__PURE__ */ jsx("span", {
2021
- style: styles$5.breakdownKey,
2522
+ style: styles$6.breakdownKey,
2022
2523
  children: k
2023
2524
  }, `k-${k}`), /* @__PURE__ */ jsx("span", { children: v }, `v-${k}`)]);
2024
2525
  }
@@ -2026,7 +2527,7 @@ function Layer({ layer, index, total }) {
2026
2527
  return /* @__PURE__ */ jsxs("div", {
2027
2528
  style: { gridColumn: "1 / -1" },
2028
2529
  children: [/* @__PURE__ */ jsxs("div", {
2029
- style: styles$5.layerHeader,
2530
+ style: styles$6.layerHeader,
2030
2531
  children: [
2031
2532
  "layer ",
2032
2533
  index + 1,
@@ -2035,7 +2536,7 @@ function Layer({ layer, index, total }) {
2035
2536
  ]
2036
2537
  }), /* @__PURE__ */ jsx("div", {
2037
2538
  style: {
2038
- ...styles$5.breakdown,
2539
+ ...styles$6.breakdown,
2039
2540
  marginTop: 2
2040
2541
  },
2041
2542
  children: renderLayer(layer)
@@ -2054,7 +2555,7 @@ const STRING_STYLES = new Set([
2054
2555
  "outset",
2055
2556
  "inset"
2056
2557
  ]);
2057
- const styles$4 = {
2558
+ const styles$5 = {
2058
2559
  wrapper: surfaceStyle,
2059
2560
  caption: captionStyle,
2060
2561
  empty: emptyStyle,
@@ -2082,55 +2583,60 @@ const styles$4 = {
2082
2583
  value: {
2083
2584
  fontFamily: MONO_STACK,
2084
2585
  fontSize: 11,
2085
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2586
+ color: TEXT_MUTED
2086
2587
  },
2087
2588
  line: {
2088
2589
  height: 0,
2089
2590
  borderTopWidth: 4,
2090
- borderTopColor: "var(--sb-color-sys-text-default, CanvasText)",
2591
+ borderTopColor: TEXT_DEFAULT,
2091
2592
  width: "100%"
2092
2593
  },
2093
2594
  objectFallback: {
2094
2595
  fontFamily: MONO_STACK,
2095
2596
  fontSize: 11,
2096
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2597
+ color: TEXT_MUTED
2097
2598
  },
2098
2599
  cssVar: {
2099
2600
  fontFamily: MONO_STACK,
2100
2601
  fontSize: 11,
2101
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2602
+ color: TEXT_MUTED
2102
2603
  }
2103
2604
  };
2104
2605
  function extractCssStyle(value) {
2105
2606
  if (typeof value === "string" && STRING_STYLES.has(value)) return value;
2106
2607
  return null;
2107
2608
  }
2108
- function StrokeStyleSample({ filter = "strokeStyle", caption }) {
2609
+ function StrokeStyleSample({ filter, caption, sortBy = "path", sortDir = "asc" }) {
2109
2610
  const { resolved, activeTheme, cssVarPrefix } = useProject();
2110
2611
  const rows = useMemo(() => {
2111
- return Object.entries(resolved).filter(([path, token]) => {
2612
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
2112
2613
  if (token.$type !== "strokeStyle") return false;
2113
2614
  return globMatch(path, filter);
2114
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => ({
2615
+ }), {
2616
+ by: sortBy,
2617
+ dir: sortDir
2618
+ }).map(([path, token]) => ({
2115
2619
  path,
2116
2620
  cssVar: makeCssVar(path, cssVarPrefix),
2117
- displayValue: formatValue(token.$value),
2621
+ displayValue: formatTokenValue(token.$value, token.$type, "raw"),
2118
2622
  cssStyle: extractCssStyle(token.$value)
2119
2623
  }));
2120
2624
  }, [
2121
2625
  resolved,
2122
2626
  filter,
2123
- cssVarPrefix
2627
+ cssVarPrefix,
2628
+ sortBy,
2629
+ sortDir
2124
2630
  ]);
2125
2631
  const captionText = caption ?? `${rows.length} strokeStyle token${rows.length === 1 ? "" : "s"}${filter && filter !== "strokeStyle" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
2126
2632
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
2127
2633
  ...themeAttrs(cssVarPrefix, activeTheme),
2128
2634
  style: {
2129
2635
  ...chromeAliases(cssVarPrefix),
2130
- ...styles$4.wrapper
2636
+ ...styles$5.wrapper
2131
2637
  },
2132
2638
  children: /* @__PURE__ */ jsx("div", {
2133
- style: styles$4.empty,
2639
+ style: styles$5.empty,
2134
2640
  children: "No strokeStyle tokens match this filter."
2135
2641
  })
2136
2642
  });
@@ -2138,36 +2644,36 @@ function StrokeStyleSample({ filter = "strokeStyle", caption }) {
2138
2644
  ...themeAttrs(cssVarPrefix, activeTheme),
2139
2645
  style: {
2140
2646
  ...chromeAliases(cssVarPrefix),
2141
- ...styles$4.wrapper
2647
+ ...styles$5.wrapper
2142
2648
  },
2143
2649
  children: [/* @__PURE__ */ jsx("div", {
2144
- style: styles$4.caption,
2650
+ style: styles$5.caption,
2145
2651
  children: captionText
2146
2652
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
2147
- style: styles$4.row,
2653
+ style: styles$5.row,
2148
2654
  children: [
2149
2655
  /* @__PURE__ */ jsxs("div", {
2150
- style: styles$4.meta,
2656
+ style: styles$5.meta,
2151
2657
  children: [/* @__PURE__ */ jsx("span", {
2152
- style: styles$4.path,
2658
+ style: styles$5.path,
2153
2659
  children: row.path
2154
2660
  }), /* @__PURE__ */ jsx("span", {
2155
- style: styles$4.value,
2661
+ style: styles$5.value,
2156
2662
  children: row.displayValue
2157
2663
  })]
2158
2664
  }),
2159
2665
  row.cssStyle ? /* @__PURE__ */ jsx("div", {
2160
2666
  style: {
2161
- ...styles$4.line,
2667
+ ...styles$5.line,
2162
2668
  borderTopStyle: row.cssStyle
2163
2669
  },
2164
2670
  "aria-hidden": true
2165
2671
  }) : /* @__PURE__ */ jsx("span", {
2166
- style: styles$4.objectFallback,
2672
+ style: styles$5.objectFallback,
2167
2673
  children: "Object-form (dashArray + lineCap) — no pure CSS `border-style` equivalent."
2168
2674
  }),
2169
2675
  /* @__PURE__ */ jsx("span", {
2170
- style: styles$4.cssVar,
2676
+ style: styles$5.cssVar,
2171
2677
  children: row.cssVar
2172
2678
  })
2173
2679
  ]
@@ -2176,7 +2682,7 @@ function StrokeStyleSample({ filter = "strokeStyle", caption }) {
2176
2682
  }
2177
2683
  //#endregion
2178
2684
  //#region src/token-detail/styles.ts
2179
- const styles$3 = {
2685
+ const styles$4 = {
2180
2686
  wrapper: {
2181
2687
  ...surfaceStyle,
2182
2688
  padding: 16,
@@ -2202,7 +2708,7 @@ const styles$3 = {
2202
2708
  fontSize: 10,
2203
2709
  letterSpacing: .5,
2204
2710
  textTransform: "uppercase",
2205
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
2711
+ background: SURFACE_MUTED
2206
2712
  },
2207
2713
  description: {
2208
2714
  margin: "0 0 12px",
@@ -2248,13 +2754,13 @@ const styles$3 = {
2248
2754
  verticalAlign: "middle",
2249
2755
  marginRight: 6,
2250
2756
  borderRadius: 3,
2251
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
2757
+ border: BORDER_DEFAULT
2252
2758
  },
2253
2759
  snippet: {
2254
2760
  display: "block",
2255
2761
  padding: "8px 10px",
2256
2762
  borderRadius: 4,
2257
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.1))",
2763
+ background: SURFACE_MUTED,
2258
2764
  fontFamily: MONO_STACK,
2259
2765
  fontSize: 12,
2260
2766
  whiteSpace: "pre",
@@ -2268,14 +2774,14 @@ const styles$3 = {
2268
2774
  shadowSample: {
2269
2775
  width: 140,
2270
2776
  height: 56,
2271
- background: "var(--sb-color-sys-surface-raised, #fff)",
2777
+ background: SURFACE_RAISED,
2272
2778
  border: BORDER_FAINT,
2273
2779
  borderRadius: 6
2274
2780
  },
2275
2781
  borderSample: {
2276
2782
  width: 140,
2277
2783
  height: 56,
2278
- background: "var(--sb-color-sys-surface-raised, transparent)",
2784
+ background: SURFACE_RAISED,
2279
2785
  borderRadius: 6
2280
2786
  },
2281
2787
  gradientSample: {
@@ -2287,18 +2793,18 @@ const styles$3 = {
2287
2793
  strokeStyleLine: {
2288
2794
  height: 0,
2289
2795
  borderTopWidth: 4,
2290
- borderTopColor: "var(--sb-color-sys-text-default, CanvasText)",
2796
+ borderTopColor: TEXT_DEFAULT,
2291
2797
  width: 220
2292
2798
  },
2293
2799
  strokeStyleSvg: {
2294
2800
  width: 220,
2295
2801
  height: 24,
2296
- color: "var(--sb-color-sys-text-default, CanvasText)"
2802
+ color: TEXT_DEFAULT
2297
2803
  },
2298
2804
  strokeStyleFallback: {
2299
2805
  fontFamily: MONO_STACK,
2300
2806
  fontSize: 12,
2301
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2807
+ color: TEXT_MUTED
2302
2808
  },
2303
2809
  colorSwatchRow: {
2304
2810
  display: "flex",
@@ -2326,13 +2832,13 @@ const styles$3 = {
2326
2832
  rowGap: 3,
2327
2833
  marginTop: 6
2328
2834
  },
2329
- breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" },
2835
+ breakdownKey: { color: TEXT_MUTED },
2330
2836
  breakdownLayerHeader: {
2331
2837
  gridColumn: "1 / -1",
2332
2838
  fontSize: 10,
2333
2839
  textTransform: "uppercase",
2334
2840
  letterSpacing: .5,
2335
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2841
+ color: TEXT_MUTED,
2336
2842
  marginTop: 4
2337
2843
  },
2338
2844
  fontFamilySample: {
@@ -2363,7 +2869,7 @@ const styles$3 = {
2363
2869
  height: 32,
2364
2870
  width: "100%",
2365
2871
  maxWidth: 320,
2366
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.08))",
2872
+ background: SURFACE_MUTED,
2367
2873
  borderRadius: 16,
2368
2874
  overflow: "hidden"
2369
2875
  },
@@ -2397,7 +2903,7 @@ const styles$3 = {
2397
2903
  },
2398
2904
  reducedMotion: {
2399
2905
  fontSize: 11,
2400
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2906
+ color: TEXT_MUTED,
2401
2907
  fontStyle: "italic"
2402
2908
  },
2403
2909
  tupleIndicator: {
@@ -2413,7 +2919,7 @@ const styles$3 = {
2413
2919
  padding: "6px 10px",
2414
2920
  marginBottom: 4,
2415
2921
  borderRadius: 4,
2416
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.1))"
2922
+ background: SURFACE_MUTED
2417
2923
  },
2418
2924
  consumerRowLabel: {
2419
2925
  fontFamily: MONO_STACK,
@@ -2435,9 +2941,9 @@ const styles$3 = {
2435
2941
  padding: "3px 8px",
2436
2942
  fontSize: 11,
2437
2943
  fontFamily: MONO_STACK,
2438
- background: "var(--sb-color-sys-surface-raised, Canvas)",
2439
- color: "var(--sb-color-sys-text-default, CanvasText)",
2440
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
2944
+ background: SURFACE_RAISED,
2945
+ color: TEXT_DEFAULT,
2946
+ border: BORDER_STRONG,
2441
2947
  borderRadius: 4,
2442
2948
  cursor: "pointer",
2443
2949
  flexShrink: 0
@@ -2472,17 +2978,17 @@ function AliasChain({ path }) {
2472
2978
  }, [token, path]);
2473
2979
  if (chain.length <= 1) return null;
2474
2980
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2475
- style: styles$3.sectionHeader,
2981
+ style: styles$4.sectionHeader,
2476
2982
  children: "Alias chain"
2477
2983
  }), /* @__PURE__ */ jsx("div", {
2478
- style: styles$3.chain,
2984
+ style: styles$4.chain,
2479
2985
  children: chain.map((step, i) => /* @__PURE__ */ jsxs("span", {
2480
- style: styles$3.chain,
2986
+ style: styles$4.chain,
2481
2987
  children: [/* @__PURE__ */ jsx("span", {
2482
- style: styles$3.chainNode,
2988
+ style: styles$4.chainNode,
2483
2989
  children: step
2484
2990
  }), i < chain.length - 1 && /* @__PURE__ */ jsx("span", {
2485
- style: styles$3.arrow,
2991
+ style: styles$4.arrow,
2486
2992
  children: "→"
2487
2993
  })]
2488
2994
  }, step))
@@ -2502,18 +3008,18 @@ function AliasedBy({ path }) {
2502
3008
  if (tree.length === 0) return null;
2503
3009
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2504
3010
  /* @__PURE__ */ jsx("div", {
2505
- style: styles$3.sectionHeader,
3011
+ style: styles$4.sectionHeader,
2506
3012
  children: "Aliased by"
2507
3013
  }),
2508
3014
  /* @__PURE__ */ jsx("ul", {
2509
- style: styles$3.aliasedByList,
3015
+ style: styles$4.aliasedByList,
2510
3016
  children: tree.map((node) => /* @__PURE__ */ jsx(AliasedByRow, {
2511
3017
  node,
2512
3018
  depth: 0
2513
3019
  }, node.path))
2514
3020
  }),
2515
3021
  truncated && /* @__PURE__ */ jsxs("div", {
2516
- style: styles$3.aliasedByTruncated,
3022
+ style: styles$4.aliasedByTruncated,
2517
3023
  children: [
2518
3024
  "Further descendants truncated at depth ",
2519
3025
  ALIASED_BY_DEPTH_CAP,
@@ -2525,15 +3031,15 @@ function AliasedBy({ path }) {
2525
3031
  function AliasedByRow({ node, depth }) {
2526
3032
  return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("div", {
2527
3033
  style: {
2528
- ...styles$3.aliasedByRow,
3034
+ ...styles$4.aliasedByRow,
2529
3035
  paddingLeft: depth * 16
2530
3036
  },
2531
3037
  children: /* @__PURE__ */ jsx("span", {
2532
- style: styles$3.chainNode,
3038
+ style: styles$4.chainNode,
2533
3039
  children: node.path
2534
3040
  })
2535
3041
  }), node.children.length > 0 && /* @__PURE__ */ jsx("ul", {
2536
- style: styles$3.aliasedByList,
3042
+ style: styles$4.aliasedByList,
2537
3043
  children: node.children.map((child) => /* @__PURE__ */ jsx(AliasedByRow, {
2538
3044
  node: child,
2539
3045
  depth: depth + 1
@@ -2586,8 +3092,9 @@ function treeHasTruncation(nodes) {
2586
3092
  function AxisVariance({ path }) {
2587
3093
  const { token, cssVar, axes, themes, themesResolved, activeAxes, cssVarPrefix } = useTokenDetailData(path);
2588
3094
  const colorFormat = useColorFormat();
2589
- const isColor = token?.$type === "color";
2590
- const formatFn = (t) => valueFor(t, isColor, colorFormat);
3095
+ const tokenType = token?.$type;
3096
+ const isColor = tokenType === "color";
3097
+ const formatFn = (t) => valueFor(t, tokenType, colorFormat);
2591
3098
  const variance = useMemo(() => analyzeVariance(path, axes, themes, themesResolved), [
2592
3099
  path,
2593
3100
  axes,
@@ -2599,20 +3106,20 @@ function AxisVariance({ path }) {
2599
3106
  const anyTheme = themes[0];
2600
3107
  const value = anyTheme ? formatFn(themesResolved[anyTheme.name]?.[path]) : "—";
2601
3108
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2602
- style: styles$3.sectionHeader,
3109
+ style: styles$4.sectionHeader,
2603
3110
  children: "Values across axes"
2604
3111
  }), /* @__PURE__ */ jsx("table", {
2605
- style: styles$3.themeTable,
3112
+ style: styles$4.themeTable,
2606
3113
  "data-testid": "token-detail-values",
2607
3114
  children: /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", {
2608
- style: styles$3.themeRow,
3115
+ style: styles$4.themeRow,
2609
3116
  children: /* @__PURE__ */ jsxs("td", {
2610
- style: styles$3.themeCell,
3117
+ style: styles$4.themeCell,
2611
3118
  "data-testid": "token-detail-constant",
2612
3119
  children: [
2613
3120
  isColor && /* @__PURE__ */ jsx("span", {
2614
3121
  style: {
2615
- ...styles$3.swatch,
3122
+ ...styles$4.swatch,
2616
3123
  background: cssVar
2617
3124
  },
2618
3125
  "aria-hidden": true
@@ -2655,26 +3162,26 @@ function AxisVariance({ path }) {
2655
3162
  };
2656
3163
  });
2657
3164
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
2658
- style: styles$3.sectionHeader,
3165
+ style: styles$4.sectionHeader,
2659
3166
  children: ["Varies with ", axisName]
2660
3167
  }), /* @__PURE__ */ jsx("table", {
2661
- style: styles$3.themeTable,
3168
+ style: styles$4.themeTable,
2662
3169
  "data-testid": "token-detail-values",
2663
3170
  children: /* @__PURE__ */ jsx("tbody", { children: contextValues.map((row) => /* @__PURE__ */ jsxs("tr", {
2664
- style: styles$3.themeRow,
3171
+ style: styles$4.themeRow,
2665
3172
  "data-axis": axisName,
2666
3173
  "data-context": row.ctx,
2667
3174
  children: [/* @__PURE__ */ jsx("td", {
2668
3175
  style: {
2669
- ...styles$3.themeCell,
3176
+ ...styles$4.themeCell,
2670
3177
  width: "30%"
2671
3178
  },
2672
3179
  children: row.ctx
2673
3180
  }), /* @__PURE__ */ jsxs("td", {
2674
- style: styles$3.themeCell,
3181
+ style: styles$4.themeCell,
2675
3182
  children: [isColor && row.themeName && /* @__PURE__ */ jsx("span", {
2676
3183
  style: {
2677
- ...styles$3.swatch,
3184
+ ...styles$4.swatch,
2678
3185
  background: cssVar
2679
3186
  },
2680
3187
  [dataAttr(cssVarPrefix, "theme")]: row.themeName,
@@ -2688,17 +3195,17 @@ function AxisVariance({ path }) {
2688
3195
  if (!rowAxis || !colAxis) return /* @__PURE__ */ jsx(Fragment, {});
2689
3196
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2690
3197
  /* @__PURE__ */ jsxs("div", {
2691
- style: styles$3.sectionHeader,
3198
+ style: styles$4.sectionHeader,
2692
3199
  children: ["Varies with ", variance.varyingAxes.join(" × ")]
2693
3200
  }),
2694
3201
  /* @__PURE__ */ jsxs("table", {
2695
- style: styles$3.themeTable,
3202
+ style: styles$4.themeTable,
2696
3203
  "data-testid": "token-detail-values",
2697
3204
  children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
2698
- style: styles$3.themeRow,
3205
+ style: styles$4.themeRow,
2699
3206
  children: [/* @__PURE__ */ jsxs("th", {
2700
3207
  style: {
2701
- ...styles$3.themeCell,
3208
+ ...styles$4.themeCell,
2702
3209
  textAlign: "left",
2703
3210
  opacity: .7
2704
3211
  },
@@ -2709,16 +3216,16 @@ function AxisVariance({ path }) {
2709
3216
  ]
2710
3217
  }), colAxis.contexts.map((col) => /* @__PURE__ */ jsx("th", {
2711
3218
  style: {
2712
- ...styles$3.themeCell,
3219
+ ...styles$4.themeCell,
2713
3220
  textAlign: "left",
2714
3221
  opacity: .7
2715
3222
  },
2716
3223
  children: col
2717
3224
  }, col))]
2718
3225
  }) }), /* @__PURE__ */ jsx("tbody", { children: rowAxis.contexts.map((row) => /* @__PURE__ */ jsxs("tr", {
2719
- style: styles$3.themeRow,
3226
+ style: styles$4.themeRow,
2720
3227
  children: [/* @__PURE__ */ jsx("td", {
2721
- style: styles$3.themeCell,
3228
+ style: styles$4.themeCell,
2722
3229
  children: row
2723
3230
  }), colAxis.contexts.map((col) => {
2724
3231
  const name = tupleName(themes, {
@@ -2728,12 +3235,12 @@ function AxisVariance({ path }) {
2728
3235
  });
2729
3236
  const value = name ? formatFn(themesResolved[name]?.[path]) : "—";
2730
3237
  return /* @__PURE__ */ jsxs("td", {
2731
- style: styles$3.themeCell,
3238
+ style: styles$4.themeCell,
2732
3239
  "data-row": row,
2733
3240
  "data-col": col,
2734
3241
  children: [isColor && name && /* @__PURE__ */ jsx("span", {
2735
3242
  style: {
2736
- ...styles$3.swatch,
3243
+ ...styles$4.swatch,
2737
3244
  background: cssVar
2738
3245
  },
2739
3246
  [dataAttr(cssVarPrefix, "theme")]: name,
@@ -2745,7 +3252,7 @@ function AxisVariance({ path }) {
2745
3252
  }),
2746
3253
  extra.length > 0 && /* @__PURE__ */ jsxs("div", {
2747
3254
  style: {
2748
- ...styles$3.aliasedByTruncated,
3255
+ ...styles$4.aliasedByTruncated,
2749
3256
  marginTop: 6
2750
3257
  },
2751
3258
  children: [
@@ -2756,14 +3263,20 @@ function AxisVariance({ path }) {
2756
3263
  })
2757
3264
  ] });
2758
3265
  }
2759
- function valueFor(token, isColor, format) {
3266
+ function valueFor(token, $type, format) {
2760
3267
  if (!token) return "—";
2761
- if (isColor) return formatColor(token.$value, format).value;
2762
- return formatValue(token.$value);
3268
+ return formatTokenValue(token.$value, $type, format);
2763
3269
  }
2764
- function themeValue(themesResolved, themeName, path) {
3270
+ /**
3271
+ * Stable key for variance detection — compares structural equality across
3272
+ * themes, not a display string. We pin `raw` so color representation
3273
+ * changes (the toolbar's format dropdown) don't artificially make axes
3274
+ * look like they vary.
3275
+ */
3276
+ function varianceKey(themesResolved, themeName, path) {
2765
3277
  const t = themesResolved[themeName]?.[path];
2766
- return t ? formatValue(t.$value) : "";
3278
+ if (!t) return "";
3279
+ return JSON.stringify(t.$value);
2767
3280
  }
2768
3281
  function tupleName(themes, tuple) {
2769
3282
  return themes.find((t) => {
@@ -2779,7 +3292,7 @@ function analyzeVariance(path, axes, themes, themesResolved) {
2779
3292
  const others = axes.filter((a) => a.name !== axis.name).map((a) => `${a.name}=${theme.input[a.name] ?? ""}`).join("|");
2780
3293
  const ctx = theme.input[axis.name] ?? "";
2781
3294
  const bucket = byOthers.get(others) ?? /* @__PURE__ */ new Map();
2782
- bucket.set(ctx, themeValue(themesResolved, theme.name, path));
3295
+ bucket.set(ctx, varianceKey(themesResolved, theme.name, path));
2783
3296
  byOthers.set(others, bucket);
2784
3297
  }
2785
3298
  let varies = false;
@@ -2806,13 +3319,15 @@ function analyzeVariance(path, axes, themes, themesResolved) {
2806
3319
  //#region src/token-detail/CompositeBreakdown.tsx
2807
3320
  function CompositeBreakdown({ path }) {
2808
3321
  const { token } = useTokenDetailData(path);
3322
+ const colorFormat = useColorFormat();
2809
3323
  if (!token) return null;
2810
3324
  return /* @__PURE__ */ jsx(CompositeBreakdownContent, {
2811
3325
  type: token.$type,
2812
- rawValue: token.$value
3326
+ rawValue: token.$value,
3327
+ colorFormat
2813
3328
  });
2814
3329
  }
2815
- function CompositeBreakdownContent({ type, rawValue }) {
3330
+ function CompositeBreakdownContent({ type, rawValue, colorFormat }) {
2816
3331
  if (!rawValue || typeof rawValue !== "object") return null;
2817
3332
  if (type === "typography") {
2818
3333
  const v = rawValue;
@@ -2827,7 +3342,7 @@ function CompositeBreakdownContent({ type, rawValue }) {
2827
3342
  if (type === "border") {
2828
3343
  const v = rawValue;
2829
3344
  return renderKeyValueList([
2830
- ["color", formatColorValue(v["color"])],
3345
+ ["color", formatColorSubValue(v["color"], colorFormat)],
2831
3346
  ["width", formatDimensionValue(v["width"])],
2832
3347
  ["style", formatPrimitive(v["style"])]
2833
3348
  ]);
@@ -2844,19 +3359,19 @@ function CompositeBreakdownContent({ type, rawValue }) {
2844
3359
  const layers = Array.isArray(rawValue) ? rawValue : [rawValue];
2845
3360
  const multi = layers.length > 1;
2846
3361
  return /* @__PURE__ */ jsx("div", {
2847
- style: styles$3.breakdownSection,
3362
+ style: styles$4.breakdownSection,
2848
3363
  children: layers.map((layer, i) => {
2849
3364
  const v = layer;
2850
3365
  return /* @__PURE__ */ jsxs("div", {
2851
3366
  style: { display: "contents" },
2852
3367
  children: [
2853
3368
  multi && /* @__PURE__ */ jsxs("div", {
2854
- style: styles$3.breakdownLayerHeader,
3369
+ style: styles$4.breakdownLayerHeader,
2855
3370
  children: ["Layer ", i + 1]
2856
3371
  }),
2857
3372
  /* @__PURE__ */ jsx(KeyValueRow, {
2858
3373
  label: "color",
2859
- value: formatColorValue(v["color"])
3374
+ value: formatColorSubValue(v["color"], colorFormat)
2860
3375
  }),
2861
3376
  /* @__PURE__ */ jsx(KeyValueRow, {
2862
3377
  label: "offsetX",
@@ -2887,12 +3402,12 @@ function CompositeBreakdownContent({ type, rawValue }) {
2887
3402
  const stops = Array.isArray(rawValue) ? rawValue : [];
2888
3403
  if (stops.length === 0) return null;
2889
3404
  return /* @__PURE__ */ jsx("div", {
2890
- style: styles$3.breakdownSection,
3405
+ style: styles$4.breakdownSection,
2891
3406
  children: stops.map((stop, i) => {
2892
3407
  const v = stop;
2893
3408
  return /* @__PURE__ */ jsx(KeyValueRow, {
2894
3409
  label: `${((typeof v["position"] === "number" ? v["position"] : 0) * 100).toFixed(0)}%`,
2895
- value: formatColorValue(v["color"])
3410
+ value: formatColorSubValue(v["color"], colorFormat)
2896
3411
  }, gradientStopKey(v, i));
2897
3412
  })
2898
3413
  });
@@ -2901,7 +3416,7 @@ function CompositeBreakdownContent({ type, rawValue }) {
2901
3416
  }
2902
3417
  function renderKeyValueList(rows) {
2903
3418
  return /* @__PURE__ */ jsx("div", {
2904
- style: styles$3.breakdownSection,
3419
+ style: styles$4.breakdownSection,
2905
3420
  children: rows.filter(([, v]) => v !== null).map(([k, v]) => /* @__PURE__ */ jsx(KeyValueRow, {
2906
3421
  label: k,
2907
3422
  value: v ?? ""
@@ -2910,7 +3425,7 @@ function renderKeyValueList(rows) {
2910
3425
  }
2911
3426
  function KeyValueRow({ label, value }) {
2912
3427
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
2913
- style: styles$3.breakdownKey,
3428
+ style: styles$4.breakdownKey,
2914
3429
  children: label
2915
3430
  }), /* @__PURE__ */ jsx("span", { children: value ?? "—" })] });
2916
3431
  }
@@ -2934,18 +3449,15 @@ function formatDimensionValue(v) {
2934
3449
  }
2935
3450
  return JSON.stringify(v);
2936
3451
  }
2937
- function formatColorValue(v) {
3452
+ /**
3453
+ * Route sub-value colors through `formatColor` so they honor the active
3454
+ * color-format dropdown, just like the standalone `<ColorPalette />` and
3455
+ * `<TokenDetail />` top-line do. Returns `null` for a missing field so
3456
+ * the key/value row drops out entirely.
3457
+ */
3458
+ function formatColorSubValue(v, format) {
2938
3459
  if (v == null) return null;
2939
- if (typeof v === "string") return v;
2940
- if (typeof v === "object") {
2941
- const c = v;
2942
- if (Array.isArray(c.components) && typeof c.colorSpace === "string") {
2943
- const parts = c.components.map((n) => typeof n === "number" ? n.toFixed(3) : String(n));
2944
- const alpha = typeof c.alpha === "number" && c.alpha !== 1 ? ` / ${c.alpha}` : "";
2945
- return `${c.colorSpace}(${parts.join(" ")}${alpha})`;
2946
- }
2947
- }
2948
- return JSON.stringify(v);
3460
+ return formatColor(v, format).value;
2949
3461
  }
2950
3462
  function shadowLayerKey(layer, fallback) {
2951
3463
  return `shadow|${[
@@ -2987,7 +3499,7 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
2987
3499
  const base = cssVar.replace(/^var\(/, "").replace(/\)$/, "");
2988
3500
  return /* @__PURE__ */ jsx("div", {
2989
3501
  style: {
2990
- ...styles$3.typographySample,
3502
+ ...styles$4.typographySample,
2991
3503
  fontFamily: `var(${base}-font-family)`,
2992
3504
  fontSize: `var(${base}-font-size)`,
2993
3505
  fontWeight: `var(${base}-font-weight)`,
@@ -2999,24 +3511,24 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
2999
3511
  }
3000
3512
  if (type === "shadow") return /* @__PURE__ */ jsx("div", {
3001
3513
  style: {
3002
- ...styles$3.shadowSample,
3514
+ ...styles$4.shadowSample,
3003
3515
  boxShadow: cssVar
3004
3516
  },
3005
3517
  "aria-hidden": true
3006
3518
  });
3007
3519
  if (type === "border") return /* @__PURE__ */ jsx("div", {
3008
3520
  style: {
3009
- ...styles$3.borderSample,
3521
+ ...styles$4.borderSample,
3010
3522
  border: cssVar
3011
3523
  },
3012
3524
  "aria-hidden": true
3013
3525
  });
3014
3526
  if (type === "transition") return /* @__PURE__ */ jsx(TransitionSample, { transition: cssVar });
3015
3527
  if (type === "dimension") return /* @__PURE__ */ jsx("div", {
3016
- style: styles$3.dimensionTrack,
3528
+ style: styles$4.dimensionTrack,
3017
3529
  children: /* @__PURE__ */ jsx("div", {
3018
3530
  style: {
3019
- ...styles$3.dimensionBar,
3531
+ ...styles$4.dimensionBar,
3020
3532
  width: cssVar
3021
3533
  },
3022
3534
  "aria-hidden": true
@@ -3025,14 +3537,14 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
3025
3537
  if (type === "duration") return /* @__PURE__ */ jsx(TransitionSample, { transition: `left ${cssVar} ease` });
3026
3538
  if (type === "fontFamily") return /* @__PURE__ */ jsx("div", {
3027
3539
  style: {
3028
- ...styles$3.fontFamilySample,
3540
+ ...styles$4.fontFamilySample,
3029
3541
  fontFamily: cssVar
3030
3542
  },
3031
3543
  children: PANGRAM
3032
3544
  });
3033
3545
  if (type === "fontWeight") return /* @__PURE__ */ jsx("div", {
3034
3546
  style: {
3035
- ...styles$3.fontWeightSample,
3547
+ ...styles$4.fontWeightSample,
3036
3548
  fontWeight: cssVar
3037
3549
  },
3038
3550
  children: "Aa"
@@ -3040,20 +3552,20 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
3040
3552
  if (type === "cubicBezier") return /* @__PURE__ */ jsx(TransitionSample, { transition: `left 800ms ${cssVar}` });
3041
3553
  if (type === "gradient") return /* @__PURE__ */ jsx("div", {
3042
3554
  style: {
3043
- ...styles$3.gradientSample,
3555
+ ...styles$4.gradientSample,
3044
3556
  background: `linear-gradient(to right, ${cssVar})`
3045
3557
  },
3046
3558
  "aria-hidden": true
3047
3559
  });
3048
3560
  if (type === "strokeStyle") return /* @__PURE__ */ jsx(StrokeStylePreview, { value: rawValue });
3049
3561
  if (type === "color") return /* @__PURE__ */ jsxs("div", {
3050
- style: styles$3.colorSwatchRow,
3562
+ style: styles$4.colorSwatchRow,
3051
3563
  "aria-hidden": true,
3052
3564
  children: [/* @__PURE__ */ jsx("div", { style: {
3053
- ...styles$3.colorSwatchLight,
3565
+ ...styles$4.colorSwatchLight,
3054
3566
  background: cssVar
3055
3567
  } }), /* @__PURE__ */ jsx("div", { style: {
3056
- ...styles$3.colorSwatchDark,
3568
+ ...styles$4.colorSwatchDark,
3057
3569
  background: cssVar
3058
3570
  } })]
3059
3571
  });
@@ -3062,7 +3574,7 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
3062
3574
  function StrokeStylePreview({ value }) {
3063
3575
  if (typeof value === "string" && STROKE_STYLE_STRINGS.has(value)) return /* @__PURE__ */ jsx("div", {
3064
3576
  style: {
3065
- ...styles$3.strokeStyleLine,
3577
+ ...styles$4.strokeStyleLine,
3066
3578
  borderTopStyle: value
3067
3579
  },
3068
3580
  "aria-hidden": true
@@ -3071,12 +3583,12 @@ function StrokeStylePreview({ value }) {
3071
3583
  const v = value;
3072
3584
  const lengths = asDashLengths(v.dashArray);
3073
3585
  if (lengths.length === 0) return /* @__PURE__ */ jsx("div", {
3074
- style: styles$3.strokeStyleFallback,
3586
+ style: styles$4.strokeStyleFallback,
3075
3587
  children: "Object-form strokeStyle with no resolvable dashArray."
3076
3588
  });
3077
3589
  const cap = typeof v.lineCap === "string" ? v.lineCap : "butt";
3078
3590
  return /* @__PURE__ */ jsx("svg", {
3079
- style: styles$3.strokeStyleSvg,
3591
+ style: styles$4.strokeStyleSvg,
3080
3592
  viewBox: "0 0 220 24",
3081
3593
  preserveAspectRatio: "none",
3082
3594
  "aria-hidden": true,
@@ -3093,7 +3605,7 @@ function StrokeStylePreview({ value }) {
3093
3605
  });
3094
3606
  }
3095
3607
  return /* @__PURE__ */ jsx("div", {
3096
- style: styles$3.strokeStyleFallback,
3608
+ style: styles$4.strokeStyleFallback,
3097
3609
  children: "strokeStyle value could not be previewed."
3098
3610
  });
3099
3611
  }
@@ -3127,14 +3639,14 @@ function TransitionSample({ transition }) {
3127
3639
  };
3128
3640
  }, [reduced]);
3129
3641
  if (reduced) return /* @__PURE__ */ jsx("div", {
3130
- style: styles$3.reducedMotion,
3642
+ style: styles$4.reducedMotion,
3131
3643
  children: "Animation suppressed by `prefers-reduced-motion: reduce`."
3132
3644
  });
3133
3645
  return /* @__PURE__ */ jsx("div", {
3134
- style: styles$3.motionTrack,
3646
+ style: styles$4.motionTrack,
3135
3647
  children: /* @__PURE__ */ jsx("div", {
3136
3648
  style: {
3137
- ...styles$3.motionBall,
3649
+ ...styles$4.motionBall,
3138
3650
  left: phase === 1 ? "calc(100% - 28px)" : "4px",
3139
3651
  transition
3140
3652
  },
@@ -3150,11 +3662,11 @@ function ConsumerOutput({ path }) {
3150
3662
  const tupleLabel = Object.entries(activeAxes).map(([k, v]) => `${k}=${v}`).join(", ");
3151
3663
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3152
3664
  /* @__PURE__ */ jsx("div", {
3153
- style: styles$3.sectionHeader,
3665
+ style: styles$4.sectionHeader,
3154
3666
  children: "Consumer output"
3155
3667
  }),
3156
3668
  tupleLabel && /* @__PURE__ */ jsxs("div", {
3157
- style: styles$3.tupleIndicator,
3669
+ style: styles$4.tupleIndicator,
3158
3670
  children: ["Active tuple: ", /* @__PURE__ */ jsx("strong", { children: tupleLabel })]
3159
3671
  }),
3160
3672
  /* @__PURE__ */ jsx(OutputRow, {
@@ -3171,14 +3683,14 @@ function ConsumerOutput({ path }) {
3171
3683
  }
3172
3684
  function OutputRow({ label, value, testId }) {
3173
3685
  return /* @__PURE__ */ jsxs("div", {
3174
- style: styles$3.consumerRow,
3686
+ style: styles$4.consumerRow,
3175
3687
  children: [
3176
3688
  /* @__PURE__ */ jsx("span", {
3177
- style: styles$3.consumerRowLabel,
3689
+ style: styles$4.consumerRowLabel,
3178
3690
  children: label
3179
3691
  }),
3180
3692
  /* @__PURE__ */ jsx("code", {
3181
- style: styles$3.consumerRowValue,
3693
+ style: styles$4.consumerRowValue,
3182
3694
  "data-testid": testId,
3183
3695
  children: value
3184
3696
  }),
@@ -3193,7 +3705,7 @@ function CopyButton({ text, testId }) {
3193
3705
  const [copied, setCopied] = useState(false);
3194
3706
  return /* @__PURE__ */ jsx("button", {
3195
3707
  type: "button",
3196
- style: styles$3.consumerRowCopy,
3708
+ style: styles$4.consumerRowCopy,
3197
3709
  "data-testid": testId,
3198
3710
  onClick: () => {
3199
3711
  copyToClipboard(text).then((ok) => {
@@ -3219,7 +3731,7 @@ async function copyToClipboard(text) {
3219
3731
  function TokenHeader({ path, heading }) {
3220
3732
  const { token, cssVar, activeTheme } = useTokenDetailData(path);
3221
3733
  if (!token) return /* @__PURE__ */ jsxs("div", {
3222
- style: styles$3.missing,
3734
+ style: styles$4.missing,
3223
3735
  children: [
3224
3736
  "Token ",
3225
3737
  /* @__PURE__ */ jsx("code", { children: path }),
@@ -3230,18 +3742,18 @@ function TokenHeader({ path, heading }) {
3230
3742
  });
3231
3743
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3232
3744
  /* @__PURE__ */ jsx("h3", {
3233
- style: styles$3.heading,
3745
+ style: styles$4.heading,
3234
3746
  children: heading ?? path
3235
3747
  }),
3236
3748
  /* @__PURE__ */ jsxs("div", {
3237
- style: styles$3.subline,
3749
+ style: styles$4.subline,
3238
3750
  children: [token.$type && /* @__PURE__ */ jsx("span", {
3239
- style: styles$3.typePill,
3751
+ style: styles$4.typePill,
3240
3752
  children: token.$type
3241
3753
  }), /* @__PURE__ */ jsx("span", { children: cssVar })]
3242
3754
  }),
3243
3755
  token.$description && /* @__PURE__ */ jsx("p", {
3244
- style: styles$3.description,
3756
+ style: styles$4.description,
3245
3757
  children: token.$description
3246
3758
  })
3247
3759
  ] });
@@ -3252,10 +3764,10 @@ function TokenUsageSnippet({ path }) {
3252
3764
  const { token, cssVar } = useTokenDetailData(path);
3253
3765
  if (!token) return null;
3254
3766
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
3255
- style: styles$3.sectionHeader,
3767
+ style: styles$4.sectionHeader,
3256
3768
  children: "Usage"
3257
3769
  }), /* @__PURE__ */ jsx("code", {
3258
- style: styles$3.snippet,
3770
+ style: styles$4.snippet,
3259
3771
  children: `color: ${cssVar};`
3260
3772
  })] });
3261
3773
  }
@@ -3268,10 +3780,10 @@ function TokenDetail({ path, heading }) {
3268
3780
  ...themeAttrs(cssVarPrefix, activeTheme),
3269
3781
  style: {
3270
3782
  ...chromeAliases(cssVarPrefix),
3271
- ...styles$3.wrapper
3783
+ ...styles$4.wrapper
3272
3784
  },
3273
3785
  children: /* @__PURE__ */ jsxs("div", {
3274
- style: styles$3.missing,
3786
+ style: styles$4.missing,
3275
3787
  children: [
3276
3788
  "Token ",
3277
3789
  /* @__PURE__ */ jsx("code", { children: path }),
@@ -3282,14 +3794,14 @@ function TokenDetail({ path, heading }) {
3282
3794
  })
3283
3795
  });
3284
3796
  const isColor = token.$type === "color";
3285
- const formatted = isColor ? formatColor(token.$value, colorFormat) : null;
3286
- const value = formatted ? formatted.value : formatValue(token.$value);
3287
- const outOfGamut = formatted?.outOfGamut ?? false;
3797
+ const gamut = isColor ? formatColor(token.$value, colorFormat) : null;
3798
+ const value = formatTokenValue(token.$value, token.$type, colorFormat);
3799
+ const outOfGamut = gamut?.outOfGamut ?? false;
3288
3800
  return /* @__PURE__ */ jsxs("div", {
3289
3801
  ...themeAttrs(cssVarPrefix, activeTheme),
3290
3802
  style: {
3291
3803
  ...chromeAliases(cssVarPrefix),
3292
- ...styles$3.wrapper
3804
+ ...styles$4.wrapper
3293
3805
  },
3294
3806
  children: [
3295
3807
  /* @__PURE__ */ jsx(TokenHeader, {
@@ -3297,17 +3809,17 @@ function TokenDetail({ path, heading }) {
3297
3809
  ...heading !== void 0 && { heading }
3298
3810
  }),
3299
3811
  /* @__PURE__ */ jsxs("div", {
3300
- style: styles$3.sectionHeader,
3812
+ style: styles$4.sectionHeader,
3301
3813
  children: ["Resolved value · ", activeTheme]
3302
3814
  }),
3303
3815
  /* @__PURE__ */ jsx(CompositePreview, { path }),
3304
3816
  /* @__PURE__ */ jsx(CompositeBreakdown, { path }),
3305
3817
  /* @__PURE__ */ jsxs("div", {
3306
- style: styles$3.chain,
3818
+ style: styles$4.chain,
3307
3819
  children: [
3308
3820
  isColor && /* @__PURE__ */ jsx("span", {
3309
3821
  style: {
3310
- ...styles$3.swatch,
3822
+ ...styles$4.swatch,
3311
3823
  background: cssVar
3312
3824
  },
3313
3825
  "aria-hidden": true
@@ -3330,12 +3842,87 @@ function TokenDetail({ path, heading }) {
3330
3842
  });
3331
3843
  }
3332
3844
  //#endregion
3845
+ //#region src/internal/DetailOverlay.tsx
3846
+ /**
3847
+ * Slide-over that wraps `<TokenDetail>`. Shared between `<TokenNavigator />`
3848
+ * and `<TokenTable />` so both land on the same opener and the same styling.
3849
+ *
3850
+ * Dismisses on backdrop click, Escape, and the close button. `role="dialog"`
3851
+ * + `aria-modal="true"` hints to AT that focus is trapped here; actual focus
3852
+ * management is tracked in the open-issue #253.
3853
+ */
3854
+ const styles$3 = {
3855
+ backdrop: {
3856
+ position: "fixed",
3857
+ inset: 0,
3858
+ background: "rgba(0,0,0,0.4)",
3859
+ zIndex: 1e4,
3860
+ display: "flex",
3861
+ alignItems: "stretch",
3862
+ justifyContent: "flex-end"
3863
+ },
3864
+ panel: {
3865
+ width: "min(560px, 100%)",
3866
+ height: "100%",
3867
+ overflowY: "auto",
3868
+ background: SURFACE_DEFAULT,
3869
+ color: TEXT_DEFAULT,
3870
+ boxShadow: "-8px 0 24px rgba(0,0,0,0.2)",
3871
+ padding: 16,
3872
+ position: "relative"
3873
+ },
3874
+ closeButton: {
3875
+ position: "absolute",
3876
+ top: 8,
3877
+ right: 8,
3878
+ width: 32,
3879
+ height: 32,
3880
+ borderRadius: 4,
3881
+ border: BORDER_STRONG,
3882
+ background: "transparent",
3883
+ color: "inherit",
3884
+ cursor: "pointer",
3885
+ fontSize: 16,
3886
+ lineHeight: 1
3887
+ }
3888
+ };
3889
+ function DetailOverlay({ path, onClose, testId = "swatchbook-overlay" }) {
3890
+ useEffect(() => {
3891
+ const onKey = (e) => {
3892
+ if (e.key === "Escape") onClose();
3893
+ };
3894
+ window.addEventListener("keydown", onKey);
3895
+ return () => window.removeEventListener("keydown", onKey);
3896
+ }, [onClose]);
3897
+ return /* @__PURE__ */ jsx("div", {
3898
+ style: styles$3.backdrop,
3899
+ onClick: onClose,
3900
+ role: "presentation",
3901
+ "data-testid": testId,
3902
+ children: /* @__PURE__ */ jsxs("div", {
3903
+ style: styles$3.panel,
3904
+ onClick: (e) => e.stopPropagation(),
3905
+ role: "dialog",
3906
+ "aria-modal": "true",
3907
+ "aria-label": `Token detail for ${path}`,
3908
+ children: [/* @__PURE__ */ jsx("button", {
3909
+ type: "button",
3910
+ style: styles$3.closeButton,
3911
+ onClick: onClose,
3912
+ "aria-label": "Close",
3913
+ "data-testid": `${testId}-close`,
3914
+ children: "×"
3915
+ }), /* @__PURE__ */ jsx(TokenDetail, { path })]
3916
+ })
3917
+ });
3918
+ }
3919
+ //#endregion
3333
3920
  //#region src/TokenNavigator.tsx
3334
3921
  const styles$2 = {
3335
3922
  wrapper: surfaceStyle,
3336
3923
  caption: {
3337
3924
  padding: "4px 0 12px",
3338
- color: "var(--sb-color-sys-text-muted, CanvasText)",
3925
+ color: TEXT_MUTED,
3339
3926
  fontSize: 12
3340
3927
  },
3341
3928
  tree: {
@@ -3374,84 +3961,44 @@ const styles$2 = {
3374
3961
  display: "inline-block",
3375
3962
  width: 12,
3376
3963
  textAlign: "center",
3377
- color: "var(--sb-color-sys-text-muted, CanvasText)"
3964
+ color: TEXT_MUTED
3378
3965
  },
3379
3966
  tail: {
3380
3967
  fontFamily: MONO_STACK,
3381
3968
  fontSize: 12
3382
3969
  },
3383
3970
  typePill: {
3384
- display: "inline-block",
3971
+ ...typePillStyle,
3385
3972
  padding: "1px 6px",
3386
- borderRadius: 4,
3387
- fontSize: 10,
3388
- letterSpacing: .5,
3389
- textTransform: "uppercase",
3390
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
3973
+ fontSize: 10
3391
3974
  },
3392
3975
  value: {
3393
3976
  fontSize: 11,
3394
- color: "var(--sb-color-sys-text-muted, CanvasText)",
3977
+ color: TEXT_MUTED,
3395
3978
  marginLeft: "auto",
3396
- wordBreak: "break-all",
3397
- maxWidth: "40%",
3398
- textAlign: "right"
3979
+ minWidth: 0,
3980
+ textAlign: "right",
3981
+ overflow: "hidden",
3982
+ textOverflow: "ellipsis",
3983
+ whiteSpace: "nowrap"
3399
3984
  },
3400
3985
  count: {
3401
3986
  marginLeft: "auto",
3402
3987
  fontSize: 11,
3403
- color: "var(--sb-color-sys-text-default, CanvasText)"
3988
+ color: TEXT_DEFAULT
3404
3989
  },
3405
3990
  colorSwatch: {
3406
3991
  display: "inline-block",
3407
3992
  width: 14,
3408
3993
  height: 14,
3409
3994
  borderRadius: 3,
3410
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
3995
+ border: BORDER_DEFAULT
3411
3996
  },
3412
3997
  previewBox: {
3413
3998
  display: "inline-flex",
3414
3999
  alignItems: "center",
3415
4000
  justifyContent: "flex-end",
3416
4001
  marginLeft: "auto"
3417
- },
3418
- empty: {
3419
- padding: "24px 12px",
3420
- textAlign: "center",
3421
- color: "var(--sb-color-sys-text-muted, CanvasText)"
3422
- },
3423
- backdrop: {
3424
- position: "fixed",
3425
- inset: 0,
3426
- background: "rgba(0,0,0,0.4)",
3427
- zIndex: 1e4,
3428
- display: "flex",
3429
- alignItems: "stretch",
3430
- justifyContent: "flex-end"
3431
- },
3432
- panel: {
3433
- width: "min(560px, 100%)",
3434
- height: "100%",
3435
- overflowY: "auto",
3436
- background: "var(--sb-color-sys-surface-default, Canvas)",
3437
- color: "var(--sb-color-sys-text-default, CanvasText)",
3438
- boxShadow: "-8px 0 24px rgba(0,0,0,0.2)",
3439
- padding: 16,
3440
- position: "relative"
3441
- },
3442
- closeButton: {
3443
- position: "absolute",
3444
- top: 8,
3445
- right: 8,
3446
- width: 32,
3447
- height: 32,
3448
- borderRadius: 4,
3449
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
3450
- background: "transparent",
3451
- color: "inherit",
3452
- cursor: "pointer",
3453
- fontSize: 16,
3454
- lineHeight: 1
3455
4002
  }
3456
4003
  };
3457
4004
  function buildTree(resolved, root) {
@@ -3555,10 +4102,7 @@ function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
3555
4102
  ...chromeAliases(cssVarPrefix),
3556
4103
  ...styles$2.wrapper
3557
4104
  },
3558
- children: /* @__PURE__ */ jsx("div", {
3559
- style: styles$2.empty,
3560
- children: root ? `No tokens under "${root}".` : "No tokens in the active theme."
3561
- })
4105
+ children: /* @__PURE__ */ jsx(EmptyState, { children: root ? `No tokens under "${root}".` : "No tokens in the active theme." })
3562
4106
  });
3563
4107
  return /* @__PURE__ */ jsxs("div", {
3564
4108
  ...themeAttrs(cssVarPrefix, activeTheme),
@@ -3587,7 +4131,8 @@ function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
3587
4131
  }),
3588
4132
  selectedPath !== null && /* @__PURE__ */ jsx(DetailOverlay, {
3589
4133
  path: selectedPath,
3590
- onClose: () => setSelectedPath(null)
4134
+ onClose: () => setSelectedPath(null),
4135
+ testId: "token-navigator-overlay"
3591
4136
  })
3592
4137
  ]
3593
4138
  });
@@ -3685,12 +4230,11 @@ function LeafPreview({ path, token }) {
3685
4230
  const type = token.$type;
3686
4231
  if (type === "color") {
3687
4232
  const cssVar = makeCssVar(path, cssVarPrefix);
3688
- const formatted = formatColor(token.$value, colorFormat);
3689
4233
  return /* @__PURE__ */ jsxs("span", {
3690
4234
  style: styles$2.previewBox,
3691
4235
  children: [/* @__PURE__ */ jsx("span", {
3692
4236
  style: styles$2.value,
3693
- children: formatted?.value ?? formatValue(token.$value)
4237
+ children: formatTokenValue(token.$value, type, colorFormat)
3694
4238
  }), /* @__PURE__ */ jsx("span", {
3695
4239
  style: {
3696
4240
  ...styles$2.colorSwatch,
@@ -3705,7 +4249,7 @@ function LeafPreview({ path, token }) {
3705
4249
  style: styles$2.previewBox,
3706
4250
  children: [/* @__PURE__ */ jsx("span", {
3707
4251
  style: styles$2.value,
3708
- children: formatValue(token.$value)
4252
+ children: formatTokenValue(token.$value, type, colorFormat)
3709
4253
  }), /* @__PURE__ */ jsx("span", {
3710
4254
  style: {
3711
4255
  marginLeft: 8,
@@ -3758,37 +4302,7 @@ function LeafPreview({ path, token }) {
3758
4302
  style: styles$2.previewBox,
3759
4303
  children: /* @__PURE__ */ jsx("span", {
3760
4304
  style: styles$2.value,
3761
- children: formatValue(token.$value)
3762
- })
3763
- });
3764
- }
3765
- function DetailOverlay({ path, onClose }) {
3766
- useEffect(() => {
3767
- const onKey = (e) => {
3768
- if (e.key === "Escape") onClose();
3769
- };
3770
- window.addEventListener("keydown", onKey);
3771
- return () => window.removeEventListener("keydown", onKey);
3772
- }, [onClose]);
3773
- return /* @__PURE__ */ jsx("div", {
3774
- style: styles$2.backdrop,
3775
- onClick: onClose,
3776
- role: "presentation",
3777
- "data-testid": "token-navigator-overlay",
3778
- children: /* @__PURE__ */ jsxs("div", {
3779
- style: styles$2.panel,
3780
- onClick: (e) => e.stopPropagation(),
3781
- role: "dialog",
3782
- "aria-modal": "true",
3783
- "aria-label": `Token detail for ${path}`,
3784
- children: [/* @__PURE__ */ jsx("button", {
3785
- type: "button",
3786
- style: styles$2.closeButton,
3787
- onClick: onClose,
3788
- "aria-label": "Close",
3789
- "data-testid": "token-navigator-close",
3790
- children: "×"
3791
- }), /* @__PURE__ */ jsx(TokenDetail, { path })]
4305
+ children: formatTokenValue(token.$value, type, colorFormat)
3792
4306
  })
3793
4307
  });
3794
4308
  }
@@ -3801,13 +4315,12 @@ const styles$1 = {
3801
4315
  captionSide: "top",
3802
4316
  textAlign: "left",
3803
4317
  padding: "8px 0",
3804
- opacity: .7,
4318
+ color: TEXT_MUTED,
3805
4319
  fontSize: 12
3806
4320
  },
3807
4321
  table: {
3808
4322
  width: "100%",
3809
- borderCollapse: "collapse",
3810
- tableLayout: "fixed"
4323
+ borderCollapse: "collapse"
3811
4324
  },
3812
4325
  th: {
3813
4326
  textAlign: "left",
@@ -3815,9 +4328,12 @@ const styles$1 = {
3815
4328
  fontSize: 11,
3816
4329
  textTransform: "uppercase",
3817
4330
  letterSpacing: .5,
3818
- opacity: .6,
3819
- borderBottom: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))"
4331
+ color: TEXT_MUTED,
4332
+ borderBottom: BORDER_STRONG
3820
4333
  },
4334
+ thPath: { minWidth: 180 },
4335
+ thValue: { minWidth: 160 },
4336
+ row: { cursor: "pointer" },
3821
4337
  td: {
3822
4338
  padding: "8px 12px",
3823
4339
  borderBottom: BORDER_FAINT,
@@ -3827,48 +4343,61 @@ const styles$1 = {
3827
4343
  fontFamily: MONO_STACK,
3828
4344
  fontSize: 12
3829
4345
  },
4346
+ valueCell: {
4347
+ display: "flex",
4348
+ alignItems: "center",
4349
+ gap: 8,
4350
+ minWidth: 0,
4351
+ fontFamily: MONO_STACK,
4352
+ fontSize: 12
4353
+ },
3830
4354
  typePill: {
3831
4355
  display: "inline-block",
3832
- padding: "2px 6px",
4356
+ padding: "1px 6px",
3833
4357
  borderRadius: 4,
3834
4358
  fontSize: 10,
3835
4359
  letterSpacing: .5,
3836
4360
  textTransform: "uppercase",
3837
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
3838
- },
3839
- value: {
4361
+ background: SURFACE_MUTED,
4362
+ color: TEXT_MUTED,
3840
4363
  fontFamily: MONO_STACK,
3841
- fontSize: 12,
3842
- opacity: .85,
3843
- wordBreak: "break-all"
4364
+ flexShrink: 0
3844
4365
  },
3845
4366
  swatch: {
3846
4367
  display: "inline-block",
3847
- width: 14,
3848
- height: 14,
3849
- verticalAlign: "middle",
3850
- marginRight: 6,
4368
+ width: 16,
4369
+ height: 16,
3851
4370
  borderRadius: 3,
3852
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
4371
+ border: BORDER_STRONG,
4372
+ flexShrink: 0
4373
+ },
4374
+ valueText: {
4375
+ minWidth: 0,
4376
+ overflow: "hidden",
4377
+ textOverflow: "ellipsis",
4378
+ whiteSpace: "nowrap"
3853
4379
  }
3854
4380
  };
3855
- function TokenTable({ filter, type, showVar = true, caption }) {
4381
+ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", onSelect }) {
3856
4382
  const { resolved, activeTheme, cssVarPrefix } = useProject();
3857
4383
  const colorFormat = useColorFormat();
4384
+ const [selectedPath, setSelectedPath] = useState(null);
3858
4385
  const rows = useMemo(() => {
3859
- return Object.entries(resolved).filter(([path, token]) => {
4386
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
3860
4387
  if (!globMatch(path, filter)) return false;
3861
4388
  if (type && token.$type !== type) return false;
3862
4389
  return true;
3863
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => {
4390
+ }), {
4391
+ by: sortBy,
4392
+ dir: sortDir
4393
+ }).map(([path, token]) => {
3864
4394
  const isColor = token.$type === "color";
3865
4395
  const color = isColor ? formatColor(token.$value, colorFormat) : null;
3866
4396
  return {
3867
4397
  path,
3868
4398
  type: token.$type ?? "",
3869
- value: color ? color.value : formatValue(token.$value),
4399
+ value: formatTokenValue(token.$value, token.$type, colorFormat),
3870
4400
  outOfGamut: color?.outOfGamut ?? false,
3871
- description: token.$description ?? "",
3872
4401
  cssVar: makeCssVar(path, cssVarPrefix),
3873
4402
  isColor
3874
4403
  };
@@ -3878,8 +4407,14 @@ function TokenTable({ filter, type, showVar = true, caption }) {
3878
4407
  filter,
3879
4408
  type,
3880
4409
  cssVarPrefix,
3881
- colorFormat
4410
+ colorFormat,
4411
+ sortBy,
4412
+ sortDir
3882
4413
  ]);
4414
+ const handleRowClick = useCallback((path) => {
4415
+ if (onSelect) onSelect(path);
4416
+ else setSelectedPath(path);
4417
+ }, [onSelect]);
3883
4418
  const captionText = caption ?? `${rows.length} token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""}${type ? ` · $type=${type}` : ""} · ${activeTheme}`;
3884
4419
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
3885
4420
  ...themeAttrs(cssVarPrefix, activeTheme),
@@ -3892,99 +4427,89 @@ function TokenTable({ filter, type, showVar = true, caption }) {
3892
4427
  children: "No tokens match this filter."
3893
4428
  })
3894
4429
  });
3895
- return /* @__PURE__ */ jsx("div", {
4430
+ return /* @__PURE__ */ jsxs("div", {
3896
4431
  ...themeAttrs(cssVarPrefix, activeTheme),
3897
4432
  style: {
3898
4433
  ...chromeAliases(cssVarPrefix),
3899
4434
  ...styles$1.wrapper
3900
4435
  },
3901
- children: /* @__PURE__ */ jsxs("table", {
4436
+ children: [/* @__PURE__ */ jsxs("table", {
3902
4437
  style: styles$1.table,
3903
4438
  children: [
3904
4439
  /* @__PURE__ */ jsx("caption", {
3905
4440
  style: styles$1.caption,
3906
4441
  children: captionText
3907
4442
  }),
3908
- /* @__PURE__ */ jsxs("colgroup", { children: [
3909
- /* @__PURE__ */ jsx("col", { style: { width: "30%" } }),
3910
- /* @__PURE__ */ jsx("col", { style: { width: "8%" } }),
3911
- /* @__PURE__ */ jsx("col", { style: { width: showVar ? "28%" : "40%" } }),
3912
- showVar && /* @__PURE__ */ jsx("col", { style: { width: "24%" } }),
3913
- /* @__PURE__ */ jsx("col", {})
3914
- ] }),
3915
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
3916
- /* @__PURE__ */ jsx("th", {
3917
- style: styles$1.th,
3918
- children: "Path"
3919
- }),
3920
- /* @__PURE__ */ jsx("th", {
3921
- style: styles$1.th,
3922
- children: "Type"
3923
- }),
3924
- /* @__PURE__ */ jsx("th", {
3925
- style: styles$1.th,
3926
- children: "Value"
3927
- }),
3928
- showVar && /* @__PURE__ */ jsx("th", {
3929
- style: styles$1.th,
3930
- children: "CSS var"
3931
- }),
3932
- /* @__PURE__ */ jsx("th", {
3933
- style: styles$1.th,
3934
- children: "Description"
3935
- })
3936
- ] }) }),
3937
- /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", { children: [
3938
- /* @__PURE__ */ jsx("td", {
4443
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
4444
+ style: {
4445
+ ...styles$1.th,
4446
+ ...styles$1.thPath
4447
+ },
4448
+ children: "Path"
4449
+ }), /* @__PURE__ */ jsx("th", {
4450
+ style: {
4451
+ ...styles$1.th,
4452
+ ...styles$1.thValue
4453
+ },
4454
+ children: "Value"
4455
+ })] }) }),
4456
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", {
4457
+ style: styles$1.row,
4458
+ onClick: () => handleRowClick(row.path),
4459
+ onKeyDown: (e) => {
4460
+ if (e.key === "Enter" || e.key === " ") {
4461
+ e.preventDefault();
4462
+ handleRowClick(row.path);
4463
+ }
4464
+ },
4465
+ tabIndex: 0,
4466
+ "aria-label": `Inspect ${row.path}`,
4467
+ "data-testid": "token-table-row",
4468
+ "data-path": row.path,
4469
+ children: [/* @__PURE__ */ jsx("td", {
3939
4470
  style: {
3940
4471
  ...styles$1.td,
3941
4472
  ...styles$1.path
3942
4473
  },
3943
4474
  children: row.path
3944
- }),
3945
- /* @__PURE__ */ jsx("td", {
4475
+ }), /* @__PURE__ */ jsx("td", {
3946
4476
  style: styles$1.td,
3947
- children: row.type && /* @__PURE__ */ jsx("span", {
3948
- style: styles$1.typePill,
3949
- children: row.type
4477
+ children: /* @__PURE__ */ jsxs("span", {
4478
+ style: styles$1.valueCell,
4479
+ children: [
4480
+ row.type && /* @__PURE__ */ jsx("span", {
4481
+ style: styles$1.typePill,
4482
+ children: row.type
4483
+ }),
4484
+ row.isColor && /* @__PURE__ */ jsx("span", {
4485
+ style: {
4486
+ ...styles$1.swatch,
4487
+ background: row.cssVar
4488
+ },
4489
+ "aria-hidden": true
4490
+ }),
4491
+ /* @__PURE__ */ jsx("span", {
4492
+ style: styles$1.valueText,
4493
+ title: row.value,
4494
+ "data-testid": "token-table-value",
4495
+ children: row.value
4496
+ }),
4497
+ row.outOfGamut && /* @__PURE__ */ jsx("span", {
4498
+ title: "Out of sRGB gamut for this format",
4499
+ "aria-label": "out of gamut",
4500
+ style: { flexShrink: 0 },
4501
+ children: "⚠"
4502
+ })
4503
+ ]
3950
4504
  })
3951
- }),
3952
- /* @__PURE__ */ jsxs("td", {
3953
- style: {
3954
- ...styles$1.td,
3955
- ...styles$1.value
3956
- },
3957
- children: [
3958
- row.isColor && /* @__PURE__ */ jsx("span", {
3959
- style: {
3960
- ...styles$1.swatch,
3961
- background: row.cssVar
3962
- },
3963
- "aria-hidden": true
3964
- }),
3965
- /* @__PURE__ */ jsx("span", { children: row.value }),
3966
- row.outOfGamut && /* @__PURE__ */ jsx("span", {
3967
- title: "Out of sRGB gamut for this format",
3968
- "aria-label": "out of gamut",
3969
- style: { marginLeft: 6 },
3970
- children: "⚠"
3971
- })
3972
- ]
3973
- }),
3974
- showVar && /* @__PURE__ */ jsx("td", {
3975
- style: {
3976
- ...styles$1.td,
3977
- ...styles$1.value
3978
- },
3979
- children: row.cssVar
3980
- }),
3981
- /* @__PURE__ */ jsx("td", {
3982
- style: styles$1.td,
3983
- children: row.description
3984
- })
3985
- ] }, row.path)) })
4505
+ })]
4506
+ }, row.path)) })
3986
4507
  ]
3987
- })
4508
+ }), selectedPath !== null && /* @__PURE__ */ jsx(DetailOverlay, {
4509
+ path: selectedPath,
4510
+ onClose: () => setSelectedPath(null),
4511
+ testId: "token-table-overlay"
4512
+ })]
3988
4513
  });
3989
4514
  }
3990
4515
  //#endregion
@@ -4050,13 +4575,16 @@ function buildRow(path, composite) {
4050
4575
  ].filter(Boolean).join(" · ")
4051
4576
  };
4052
4577
  }
4053
- function TypographyScale({ filter = "typography", sample = "The quick brown fox jumps over the lazy dog.", caption }) {
4578
+ function TypographyScale({ filter, sample = "The quick brown fox jumps over the lazy dog.", caption, sortBy = "path", sortDir = "asc" }) {
4054
4579
  const { resolved, activeTheme, cssVarPrefix } = useProject();
4055
4580
  const rows = useMemo(() => {
4056
- return Object.entries(resolved).filter(([path, token]) => {
4581
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
4057
4582
  if (token.$type !== "typography") return false;
4058
4583
  return globMatch(path, filter);
4059
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => {
4584
+ }), {
4585
+ by: sortBy,
4586
+ dir: sortDir
4587
+ }).map(([path, token]) => {
4060
4588
  const value = token.$value;
4061
4589
  if (!value || typeof value !== "object") return {
4062
4590
  path,
@@ -4065,7 +4593,12 @@ function TypographyScale({ filter = "typography", sample = "The quick brown fox
4065
4593
  };
4066
4594
  return buildRow(path, value);
4067
4595
  });
4068
- }, [resolved, filter]);
4596
+ }, [
4597
+ resolved,
4598
+ filter,
4599
+ sortBy,
4600
+ sortDir
4601
+ ]);
4069
4602
  const captionText = caption ?? `${rows.length} typography token${rows.length === 1 ? "" : "s"}${filter && filter !== "typography" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
4070
4603
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
4071
4604
  ...themeAttrs(cssVarPrefix, activeTheme),
@@ -4106,6 +4639,6 @@ function TypographyScale({ filter = "typography", sample = "The quick brown fox
4106
4639
  });
4107
4640
  }
4108
4641
  //#endregion
4109
- 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 };
4642
+ export { AliasChain, AliasedBy, AxesContext, AxisVariance, BorderPreview, BorderSample, COLOR_FORMATS, ColorFormatContext, ColorPalette, CompositeBreakdown, CompositePreview, ConsumerOutput, Diagnostics, 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 };
4110
4643
 
4111
4644
  //# sourceMappingURL=index.mjs.map