@unpunnyfuns/swatchbook-blocks 0.2.2 → 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
  }
@@ -469,23 +600,12 @@ function globMatch(path, glob) {
469
600
  if (glob.endsWith("**")) return path.startsWith(glob.slice(0, -2));
470
601
  return path === glob || path.startsWith(`${glob}.`);
471
602
  }
472
- function formatValue(value) {
473
- if (value == null) return "";
474
- if (typeof value === "string" || typeof value === "number") return String(value);
475
- if (typeof value === "object") {
476
- const v = value;
477
- if (typeof v["hex"] === "string") return v["hex"];
478
- if ("value" in v && "unit" in v) return `${String(v["value"])}${String(v["unit"])}`;
479
- return JSON.stringify(value).slice(0, 120);
480
- }
481
- return String(value);
482
- }
483
603
  //#endregion
484
604
  //#region src/border-preview/BorderSample.tsx
485
605
  const sampleStyle$1 = {
486
606
  width: 120,
487
607
  height: 56,
488
- background: "var(--sb-color-sys-surface-raised, transparent)",
608
+ background: SURFACE_RAISED,
489
609
  borderRadius: 6
490
610
  };
491
611
  function BorderSample({ path }) {
@@ -501,31 +621,112 @@ function BorderSample({ path }) {
501
621
  });
502
622
  }
503
623
  //#endregion
504
- //#region src/internal/styles.ts
505
- const MONO_STACK = "ui-monospace, SFMono-Regular, Menlo, monospace";
506
- const BORDER_DEFAULT = "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.2))";
507
- const BORDER_FAINT = "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.15))";
508
- const surfaceStyle = {
509
- fontFamily: "var(--sb-typography-sys-body-font-family, system-ui)",
510
- fontSize: "var(--sb-typography-sys-body-font-size, 14px)",
511
- color: "var(--sb-color-sys-text-default, CanvasText)",
512
- background: "var(--sb-color-sys-surface-default, Canvas)",
513
- padding: 12,
514
- borderRadius: 6
515
- };
516
- const captionStyle = {
517
- padding: "4px 0 12px",
518
- opacity: .7,
519
- fontSize: 12
520
- };
521
- const emptyStyle = {
522
- padding: "24px 12px",
523
- textAlign: "center",
524
- opacity: .6
525
- };
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
+ }
526
727
  //#endregion
527
728
  //#region src/BorderPreview.tsx
528
- const styles$14 = {
729
+ const styles$16 = {
529
730
  wrapper: surfaceStyle,
530
731
  caption: captionStyle,
531
732
  empty: emptyStyle,
@@ -568,9 +769,9 @@ const styles$14 = {
568
769
  columnGap: 12,
569
770
  rowGap: 2
570
771
  },
571
- breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" }
772
+ breakdownKey: { color: TEXT_MUTED }
572
773
  };
573
- function formatDimension$1(raw) {
774
+ function formatDimension$2(raw) {
574
775
  if (raw == null) return "—";
575
776
  if (typeof raw === "number") return String(raw);
576
777
  if (typeof raw === "string") return raw;
@@ -593,35 +794,36 @@ function formatColor$2(raw) {
593
794
  }
594
795
  return JSON.stringify(raw);
595
796
  }
596
- function BorderPreview({ filter = "border", caption }) {
797
+ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
597
798
  const { resolved, activeTheme, cssVarPrefix } = useProject();
598
799
  const rows = useMemo(() => {
599
- const collected = [];
600
- for (const [path, token] of Object.entries(resolved)) {
601
- if (token.$type !== "border") continue;
602
- if (!globMatch(path, filter)) continue;
603
- collected.push({
604
- path,
605
- cssVar: makeCssVar(path, cssVarPrefix),
606
- value: token.$value ?? {}
607
- });
608
- }
609
- collected.sort((a, b) => a.path.localeCompare(b.path, void 0, { numeric: true }));
610
- 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
+ }));
611
811
  }, [
612
812
  resolved,
613
813
  filter,
614
- cssVarPrefix
814
+ cssVarPrefix,
815
+ sortBy,
816
+ sortDir
615
817
  ]);
616
818
  const captionText = caption ?? `${rows.length} border${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
617
819
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
618
820
  ...themeAttrs(cssVarPrefix, activeTheme),
619
821
  style: {
620
822
  ...chromeAliases(cssVarPrefix),
621
- ...styles$14.wrapper
823
+ ...styles$16.wrapper
622
824
  },
623
825
  children: /* @__PURE__ */ jsx("div", {
624
- style: styles$14.empty,
826
+ style: styles$16.empty,
625
827
  children: "No border tokens match this filter."
626
828
  })
627
829
  });
@@ -629,43 +831,43 @@ function BorderPreview({ filter = "border", caption }) {
629
831
  ...themeAttrs(cssVarPrefix, activeTheme),
630
832
  style: {
631
833
  ...chromeAliases(cssVarPrefix),
632
- ...styles$14.wrapper
834
+ ...styles$16.wrapper
633
835
  },
634
836
  children: [/* @__PURE__ */ jsx("div", {
635
- style: styles$14.caption,
837
+ style: styles$16.caption,
636
838
  children: captionText
637
839
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
638
- style: styles$14.row,
840
+ style: styles$16.row,
639
841
  children: [
640
842
  /* @__PURE__ */ jsxs("div", {
641
- style: styles$14.meta,
843
+ style: styles$16.meta,
642
844
  children: [/* @__PURE__ */ jsx("span", {
643
- style: styles$14.path,
845
+ style: styles$16.path,
644
846
  children: row.path
645
847
  }), /* @__PURE__ */ jsx("span", {
646
- style: styles$14.cssVar,
848
+ style: styles$16.cssVar,
647
849
  children: row.cssVar
648
850
  })]
649
851
  }),
650
852
  /* @__PURE__ */ jsx("div", {
651
- style: styles$14.sampleCell,
853
+ style: styles$16.sampleCell,
652
854
  children: /* @__PURE__ */ jsx(BorderSample, { path: row.path })
653
855
  }),
654
856
  /* @__PURE__ */ jsxs("div", {
655
- style: styles$14.breakdown,
857
+ style: styles$16.breakdown,
656
858
  children: [
657
859
  /* @__PURE__ */ jsx("span", {
658
- style: styles$14.breakdownKey,
860
+ style: styles$16.breakdownKey,
659
861
  children: "width"
660
862
  }),
661
- /* @__PURE__ */ jsx("span", { children: formatDimension$1(row.value.width) }),
863
+ /* @__PURE__ */ jsx("span", { children: formatDimension$2(row.value.width) }),
662
864
  /* @__PURE__ */ jsx("span", {
663
- style: styles$14.breakdownKey,
865
+ style: styles$16.breakdownKey,
664
866
  children: "style"
665
867
  }),
666
868
  /* @__PURE__ */ jsx("span", { children: row.value.style != null ? String(row.value.style) : "—" }),
667
869
  /* @__PURE__ */ jsx("span", {
668
- style: styles$14.breakdownKey,
870
+ style: styles$16.breakdownKey,
669
871
  children: "color"
670
872
  }),
671
873
  /* @__PURE__ */ jsx("span", { children: formatColor$2(row.value.color) })
@@ -677,7 +879,7 @@ function BorderPreview({ filter = "border", caption }) {
677
879
  }
678
880
  //#endregion
679
881
  //#region src/ColorPalette.tsx
680
- const styles$13 = {
882
+ const styles$15 = {
681
883
  wrapper: surfaceStyle,
682
884
  caption: captionStyle,
683
885
  empty: emptyStyle,
@@ -705,7 +907,7 @@ const styles$13 = {
705
907
  swatch: {
706
908
  height: 56,
707
909
  width: "100%",
708
- borderBottom: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.08))"
910
+ borderBottom: BORDER_FAINT
709
911
  },
710
912
  meta: {
711
913
  padding: "8px 10px",
@@ -726,8 +928,6 @@ const styles$13 = {
726
928
  /**
727
929
  * Count segments in the filter before the first glob (`*` / `**`).
728
930
  * `color.ref.*` → 2; `color.sys.surface.*` → 3; `color` → 1; undefined → 0.
729
- *
730
- * @internal Exported for tests; not part of the public API.
731
931
  */
732
932
  function fixedPrefixLength(filter) {
733
933
  if (!filter) return 0;
@@ -739,14 +939,17 @@ function fixedPrefixLength(filter) {
739
939
  }
740
940
  return fixed;
741
941
  }
742
- function ColorPalette({ filter = "color", groupBy, caption }) {
942
+ function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "asc" }) {
743
943
  const { resolved, activeTheme, cssVarPrefix } = useProject();
744
944
  const colorFormat = useColorFormat();
745
945
  const groups = useMemo(() => {
746
- const entries = Object.entries(resolved).filter(([path, token]) => {
946
+ const entries = sortTokens(Object.entries(resolved).filter(([path, token]) => {
747
947
  if (token.$type !== "color") return false;
748
948
  return globMatch(path, filter);
749
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
949
+ }), {
950
+ by: sortBy,
951
+ dir: sortDir
952
+ });
750
953
  const maxDepth = entries.reduce((m, [p]) => Math.max(m, p.split(".").length), 0);
751
954
  /**
752
955
  * Auto-derive: group one level below the filter's fixed prefix, but
@@ -778,7 +981,9 @@ function ColorPalette({ filter = "color", groupBy, caption }) {
778
981
  filter,
779
982
  groupBy,
780
983
  cssVarPrefix,
781
- colorFormat
984
+ colorFormat,
985
+ sortBy,
986
+ sortDir
782
987
  ]);
783
988
  const totalCount = groups.reduce((acc, [, swatches]) => acc + swatches.length, 0);
784
989
  const captionText = caption ?? `${totalCount} color${totalCount === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
@@ -786,10 +991,10 @@ function ColorPalette({ filter = "color", groupBy, caption }) {
786
991
  ...themeAttrs(cssVarPrefix, activeTheme),
787
992
  style: {
788
993
  ...chromeAliases(cssVarPrefix),
789
- ...styles$13.wrapper
994
+ ...styles$15.wrapper
790
995
  },
791
996
  children: /* @__PURE__ */ jsx("div", {
792
- style: styles$13.empty,
997
+ style: styles$15.empty,
793
998
  children: "No color tokens match this filter."
794
999
  })
795
1000
  });
@@ -797,33 +1002,33 @@ function ColorPalette({ filter = "color", groupBy, caption }) {
797
1002
  ...themeAttrs(cssVarPrefix, activeTheme),
798
1003
  style: {
799
1004
  ...chromeAliases(cssVarPrefix),
800
- ...styles$13.wrapper
1005
+ ...styles$15.wrapper
801
1006
  },
802
1007
  children: [/* @__PURE__ */ jsx("div", {
803
- style: styles$13.caption,
1008
+ style: styles$15.caption,
804
1009
  children: captionText
805
1010
  }), groups.map(([group, swatches]) => /* @__PURE__ */ jsxs("section", {
806
- style: styles$13.group,
1011
+ style: styles$15.group,
807
1012
  children: [/* @__PURE__ */ jsx("div", {
808
- style: styles$13.groupHeader,
1013
+ style: styles$15.groupHeader,
809
1014
  children: group
810
1015
  }), /* @__PURE__ */ jsx("div", {
811
- style: styles$13.grid,
1016
+ style: styles$15.grid,
812
1017
  children: swatches.map((swatch) => /* @__PURE__ */ jsxs("div", {
813
- style: styles$13.card,
1018
+ style: styles$15.card,
814
1019
  children: [/* @__PURE__ */ jsx("div", {
815
1020
  style: {
816
- ...styles$13.swatch,
1021
+ ...styles$15.swatch,
817
1022
  background: swatch.cssVar
818
1023
  },
819
1024
  "aria-hidden": true
820
1025
  }), /* @__PURE__ */ jsxs("div", {
821
- style: styles$13.meta,
1026
+ style: styles$15.meta,
822
1027
  children: [/* @__PURE__ */ jsx("span", {
823
- style: styles$13.leaf,
1028
+ style: styles$15.leaf,
824
1029
  children: swatch.leaf
825
1030
  }), /* @__PURE__ */ jsxs("span", {
826
- style: styles$13.value,
1031
+ style: styles$15.value,
827
1032
  children: [swatch.value, swatch.outOfGamut && /* @__PURE__ */ jsxs("span", {
828
1033
  title: "Out of sRGB gamut for this format",
829
1034
  "aria-label": "out of gamut",
@@ -838,9 +1043,135 @@ function ColorPalette({ filter = "color", groupBy, caption }) {
838
1043
  });
839
1044
  }
840
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
841
1172
  //#region src/dimension-scale/DimensionBar.tsx
842
1173
  const MAX_RENDER_PX$1 = 480;
843
- const styles$12 = {
1174
+ const styles$13 = {
844
1175
  bar: {
845
1176
  height: 14,
846
1177
  background: "var(--sb-color-sys-accent-bg, #3b82f6)",
@@ -851,11 +1182,11 @@ const styles$12 = {
851
1182
  width: 56,
852
1183
  height: 56,
853
1184
  background: "var(--sb-color-sys-accent-bg, #3b82f6)",
854
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))"
1185
+ border: BORDER_STRONG
855
1186
  },
856
1187
  sizeSample: {
857
1188
  background: "var(--sb-color-sys-accent-bg, #3b82f6)",
858
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
1189
+ border: BORDER_STRONG,
859
1190
  minWidth: 1,
860
1191
  minHeight: 1
861
1192
  }
@@ -888,7 +1219,7 @@ function DimensionBar({ path, kind = "length" }) {
888
1219
  case "radius": return /* @__PURE__ */ jsx("div", {
889
1220
  style: {
890
1221
  ...aliases,
891
- ...styles$12.radiusSample,
1222
+ ...styles$13.radiusSample,
892
1223
  borderRadius: cssVar
893
1224
  },
894
1225
  "aria-hidden": true
@@ -896,7 +1227,7 @@ function DimensionBar({ path, kind = "length" }) {
896
1227
  case "size": return /* @__PURE__ */ jsx("div", {
897
1228
  style: {
898
1229
  ...aliases,
899
- ...styles$12.sizeSample,
1230
+ ...styles$13.sizeSample,
900
1231
  width: cappedValue,
901
1232
  height: cappedValue
902
1233
  },
@@ -905,7 +1236,7 @@ function DimensionBar({ path, kind = "length" }) {
905
1236
  default: return /* @__PURE__ */ jsx("div", {
906
1237
  style: {
907
1238
  ...aliases,
908
- ...styles$12.bar,
1239
+ ...styles$13.bar,
909
1240
  width: cappedValue
910
1241
  },
911
1242
  "aria-hidden": true
@@ -913,9 +1244,151 @@ function DimensionBar({ path, kind = "length" }) {
913
1244
  }
914
1245
  }
915
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
916
1389
  //#region src/DimensionScale.tsx
917
1390
  const MAX_RENDER_PX = 480;
918
- const styles$11 = {
1391
+ const styles$12 = {
919
1392
  wrapper: surfaceStyle,
920
1393
  caption: captionStyle,
921
1394
  empty: emptyStyle,
@@ -978,41 +1451,41 @@ function toPixels(raw) {
978
1451
  default: return NaN;
979
1452
  }
980
1453
  }
981
- function DimensionScale({ filter = "dimension", kind = "length", caption }) {
1454
+ function DimensionScale({ filter, kind = "length", caption, sortBy = "value", sortDir = "asc" }) {
982
1455
  const { resolved, activeTheme, cssVarPrefix } = useProject();
983
1456
  const rows = useMemo(() => {
984
- const collected = [];
985
- for (const [path, token] of Object.entries(resolved)) {
986
- if (token.$type !== "dimension") continue;
987
- 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]) => {
988
1464
  const pxValue = toPixels(token.$value);
989
- collected.push({
1465
+ return {
990
1466
  path,
991
1467
  cssVar: makeCssVar(path, cssVarPrefix),
992
- displayValue: formatValue(token.$value),
1468
+ displayValue: formatTokenValue(token.$value, token.$type, "raw"),
993
1469
  pxValue,
994
1470
  capped: Number.isFinite(pxValue) && pxValue > MAX_RENDER_PX
995
- });
996
- }
997
- collected.sort((a, b) => {
998
- if (Number.isFinite(a.pxValue) && Number.isFinite(b.pxValue)) return a.pxValue - b.pxValue;
999
- return a.path.localeCompare(b.path, void 0, { numeric: true });
1471
+ };
1000
1472
  });
1001
- return collected;
1002
1473
  }, [
1003
1474
  resolved,
1004
1475
  filter,
1005
- cssVarPrefix
1476
+ cssVarPrefix,
1477
+ sortBy,
1478
+ sortDir
1006
1479
  ]);
1007
1480
  const captionText = caption ?? `${rows.length} dimension${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1008
1481
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1009
1482
  ...themeAttrs(cssVarPrefix, activeTheme),
1010
1483
  style: {
1011
1484
  ...chromeAliases(cssVarPrefix),
1012
- ...styles$11.wrapper
1485
+ ...styles$12.wrapper
1013
1486
  },
1014
1487
  children: /* @__PURE__ */ jsx("div", {
1015
- style: styles$11.empty,
1488
+ style: styles$12.empty,
1016
1489
  children: "No dimension tokens match this filter."
1017
1490
  })
1018
1491
  });
@@ -1020,31 +1493,31 @@ function DimensionScale({ filter = "dimension", kind = "length", caption }) {
1020
1493
  ...themeAttrs(cssVarPrefix, activeTheme),
1021
1494
  style: {
1022
1495
  ...chromeAliases(cssVarPrefix),
1023
- ...styles$11.wrapper
1496
+ ...styles$12.wrapper
1024
1497
  },
1025
1498
  children: [/* @__PURE__ */ jsx("div", {
1026
- style: styles$11.caption,
1499
+ style: styles$12.caption,
1027
1500
  children: captionText
1028
1501
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1029
- style: styles$11.row,
1502
+ style: styles$12.row,
1030
1503
  children: [
1031
1504
  /* @__PURE__ */ jsxs("div", {
1032
- style: styles$11.meta,
1505
+ style: styles$12.meta,
1033
1506
  children: [/* @__PURE__ */ jsx("span", {
1034
- style: styles$11.path,
1507
+ style: styles$12.path,
1035
1508
  children: row.path
1036
1509
  }), /* @__PURE__ */ jsx("span", {
1037
- style: styles$11.specs,
1510
+ style: styles$12.specs,
1038
1511
  children: row.displayValue
1039
1512
  })]
1040
1513
  }),
1041
1514
  /* @__PURE__ */ jsxs("div", {
1042
- style: styles$11.visualCell,
1515
+ style: styles$12.visualCell,
1043
1516
  children: [/* @__PURE__ */ jsx(DimensionBar, {
1044
1517
  path: row.path,
1045
1518
  kind
1046
1519
  }), row.capped && /* @__PURE__ */ jsxs("span", {
1047
- style: styles$11.cap,
1520
+ style: styles$12.cap,
1048
1521
  children: [
1049
1522
  "capped at ",
1050
1523
  MAX_RENDER_PX,
@@ -1053,7 +1526,7 @@ function DimensionScale({ filter = "dimension", kind = "length", caption }) {
1053
1526
  })]
1054
1527
  }),
1055
1528
  /* @__PURE__ */ jsx("span", {
1056
- style: styles$11.cssVar,
1529
+ style: styles$12.cssVar,
1057
1530
  children: row.cssVar
1058
1531
  })
1059
1532
  ]
@@ -1062,7 +1535,7 @@ function DimensionScale({ filter = "dimension", kind = "length", caption }) {
1062
1535
  }
1063
1536
  //#endregion
1064
1537
  //#region src/FontFamilySample.tsx
1065
- const styles$10 = {
1538
+ const styles$11 = {
1066
1539
  wrapper: surfaceStyle,
1067
1540
  caption: captionStyle,
1068
1541
  row: {
@@ -1089,7 +1562,7 @@ const styles$10 = {
1089
1562
  stack: {
1090
1563
  fontFamily: MONO_STACK,
1091
1564
  fontSize: 11,
1092
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1565
+ color: TEXT_MUTED
1093
1566
  },
1094
1567
  sample: {
1095
1568
  fontSize: 22,
@@ -1098,7 +1571,7 @@ const styles$10 = {
1098
1571
  cssVar: {
1099
1572
  fontFamily: MONO_STACK,
1100
1573
  fontSize: 11,
1101
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1574
+ color: TEXT_MUTED
1102
1575
  },
1103
1576
  empty: emptyStyle
1104
1577
  };
@@ -1107,13 +1580,16 @@ function stackString(raw) {
1107
1580
  if (Array.isArray(raw)) return raw.map(String).join(", ");
1108
1581
  return "";
1109
1582
  }
1110
- 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" }) {
1111
1584
  const { resolved, activeTheme, cssVarPrefix } = useProject();
1112
1585
  const rows = useMemo(() => {
1113
- return Object.entries(resolved).filter(([path, token]) => {
1586
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
1114
1587
  if (token.$type !== "fontFamily") return false;
1115
1588
  return globMatch(path, filter);
1116
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => ({
1589
+ }), {
1590
+ by: sortBy,
1591
+ dir: sortDir
1592
+ }).map(([path, token]) => ({
1117
1593
  path,
1118
1594
  cssVar: makeCssVar(path, cssVarPrefix),
1119
1595
  stack: stackString(token.$value)
@@ -1121,17 +1597,19 @@ function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox
1121
1597
  }, [
1122
1598
  resolved,
1123
1599
  filter,
1124
- cssVarPrefix
1600
+ cssVarPrefix,
1601
+ sortBy,
1602
+ sortDir
1125
1603
  ]);
1126
1604
  const captionText = caption ?? `${rows.length} fontFamily token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontFamily" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1127
1605
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1128
1606
  ...themeAttrs(cssVarPrefix, activeTheme),
1129
1607
  style: {
1130
1608
  ...chromeAliases(cssVarPrefix),
1131
- ...styles$10.wrapper
1609
+ ...styles$11.wrapper
1132
1610
  },
1133
1611
  children: /* @__PURE__ */ jsx("div", {
1134
- style: styles$10.empty,
1612
+ style: styles$11.empty,
1135
1613
  children: "No fontFamily tokens match this filter."
1136
1614
  })
1137
1615
  });
@@ -1139,33 +1617,33 @@ function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox
1139
1617
  ...themeAttrs(cssVarPrefix, activeTheme),
1140
1618
  style: {
1141
1619
  ...chromeAliases(cssVarPrefix),
1142
- ...styles$10.wrapper
1620
+ ...styles$11.wrapper
1143
1621
  },
1144
1622
  children: [/* @__PURE__ */ jsx("div", {
1145
- style: styles$10.caption,
1623
+ style: styles$11.caption,
1146
1624
  children: captionText
1147
1625
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1148
- style: styles$10.row,
1626
+ style: styles$11.row,
1149
1627
  children: [
1150
1628
  /* @__PURE__ */ jsxs("div", {
1151
- style: styles$10.meta,
1629
+ style: styles$11.meta,
1152
1630
  children: [/* @__PURE__ */ jsx("span", {
1153
- style: styles$10.path,
1631
+ style: styles$11.path,
1154
1632
  children: row.path
1155
1633
  }), /* @__PURE__ */ jsx("span", {
1156
- style: styles$10.stack,
1634
+ style: styles$11.stack,
1157
1635
  children: row.stack
1158
1636
  })]
1159
1637
  }),
1160
1638
  /* @__PURE__ */ jsx("div", {
1161
1639
  style: {
1162
- ...styles$10.sample,
1640
+ ...styles$11.sample,
1163
1641
  fontFamily: row.cssVar
1164
1642
  },
1165
1643
  children: sample
1166
1644
  }),
1167
1645
  /* @__PURE__ */ jsx("span", {
1168
- style: styles$10.cssVar,
1646
+ style: styles$11.cssVar,
1169
1647
  children: row.cssVar
1170
1648
  })
1171
1649
  ]
@@ -1174,7 +1652,7 @@ function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox
1174
1652
  }
1175
1653
  //#endregion
1176
1654
  //#region src/FontWeightScale.tsx
1177
- const styles$9 = {
1655
+ const styles$10 = {
1178
1656
  wrapper: surfaceStyle,
1179
1657
  caption: captionStyle,
1180
1658
  empty: emptyStyle,
@@ -1202,7 +1680,7 @@ const styles$9 = {
1202
1680
  value: {
1203
1681
  fontFamily: MONO_STACK,
1204
1682
  fontSize: 11,
1205
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1683
+ color: TEXT_MUTED
1206
1684
  },
1207
1685
  sample: {
1208
1686
  fontSize: 28,
@@ -1211,7 +1689,7 @@ const styles$9 = {
1211
1689
  cssVar: {
1212
1690
  fontFamily: MONO_STACK,
1213
1691
  fontSize: 11,
1214
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1692
+ color: TEXT_MUTED
1215
1693
  }
1216
1694
  };
1217
1695
  function toWeight(raw) {
@@ -1222,39 +1700,37 @@ function toWeight(raw) {
1222
1700
  }
1223
1701
  return NaN;
1224
1702
  }
1225
- function FontWeightScale({ filter = "fontWeight", sample = "Aa", caption }) {
1703
+ function FontWeightScale({ filter, sample = "Aa", caption, sortBy = "value", sortDir = "asc" }) {
1226
1704
  const { resolved, activeTheme, cssVarPrefix } = useProject();
1227
1705
  const rows = useMemo(() => {
1228
- const collected = [];
1229
- for (const [path, token] of Object.entries(resolved)) {
1230
- if (token.$type !== "fontWeight") continue;
1231
- if (!globMatch(path, filter)) continue;
1232
- collected.push({
1233
- path,
1234
- cssVar: makeCssVar(path, cssVarPrefix),
1235
- display: token.$value == null ? "" : String(token.$value),
1236
- weight: toWeight(token.$value)
1237
- });
1238
- }
1239
- collected.sort((a, b) => {
1240
- if (Number.isFinite(a.weight) && Number.isFinite(b.weight)) return a.weight - b.weight;
1241
- return a.path.localeCompare(b.path, void 0, { numeric: true });
1242
- });
1243
- 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
+ }));
1244
1718
  }, [
1245
1719
  resolved,
1246
1720
  filter,
1247
- cssVarPrefix
1721
+ cssVarPrefix,
1722
+ sortBy,
1723
+ sortDir
1248
1724
  ]);
1249
1725
  const captionText = caption ?? `${rows.length} fontWeight token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontWeight" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1250
1726
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1251
1727
  ...themeAttrs(cssVarPrefix, activeTheme),
1252
1728
  style: {
1253
1729
  ...chromeAliases(cssVarPrefix),
1254
- ...styles$9.wrapper
1730
+ ...styles$10.wrapper
1255
1731
  },
1256
1732
  children: /* @__PURE__ */ jsx("div", {
1257
- style: styles$9.empty,
1733
+ style: styles$10.empty,
1258
1734
  children: "No fontWeight tokens match this filter."
1259
1735
  })
1260
1736
  });
@@ -1262,33 +1738,33 @@ function FontWeightScale({ filter = "fontWeight", sample = "Aa", caption }) {
1262
1738
  ...themeAttrs(cssVarPrefix, activeTheme),
1263
1739
  style: {
1264
1740
  ...chromeAliases(cssVarPrefix),
1265
- ...styles$9.wrapper
1741
+ ...styles$10.wrapper
1266
1742
  },
1267
1743
  children: [/* @__PURE__ */ jsx("div", {
1268
- style: styles$9.caption,
1744
+ style: styles$10.caption,
1269
1745
  children: captionText
1270
1746
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1271
- style: styles$9.row,
1747
+ style: styles$10.row,
1272
1748
  children: [
1273
1749
  /* @__PURE__ */ jsxs("div", {
1274
- style: styles$9.meta,
1750
+ style: styles$10.meta,
1275
1751
  children: [/* @__PURE__ */ jsx("span", {
1276
- style: styles$9.path,
1752
+ style: styles$10.path,
1277
1753
  children: row.path
1278
1754
  }), /* @__PURE__ */ jsx("span", {
1279
- style: styles$9.value,
1755
+ style: styles$10.value,
1280
1756
  children: row.display
1281
1757
  })]
1282
1758
  }),
1283
1759
  /* @__PURE__ */ jsx("div", {
1284
1760
  style: {
1285
- ...styles$9.sample,
1761
+ ...styles$10.sample,
1286
1762
  fontWeight: row.cssVar
1287
1763
  },
1288
1764
  children: sample
1289
1765
  }),
1290
1766
  /* @__PURE__ */ jsx("span", {
1291
- style: styles$9.cssVar,
1767
+ style: styles$10.cssVar,
1292
1768
  children: row.cssVar
1293
1769
  })
1294
1770
  ]
@@ -1297,7 +1773,7 @@ function FontWeightScale({ filter = "fontWeight", sample = "Aa", caption }) {
1297
1773
  }
1298
1774
  //#endregion
1299
1775
  //#region src/GradientPalette.tsx
1300
- const styles$8 = {
1776
+ const styles$9 = {
1301
1777
  wrapper: surfaceStyle,
1302
1778
  caption: captionStyle,
1303
1779
  empty: emptyStyle,
@@ -1348,7 +1824,7 @@ const styles$8 = {
1348
1824
  width: 10,
1349
1825
  height: 10,
1350
1826
  borderRadius: 2,
1351
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))",
1827
+ border: BORDER_DEFAULT,
1352
1828
  flex: "0 0 auto"
1353
1829
  },
1354
1830
  stopPosition: { opacity: .6 }
@@ -1369,35 +1845,36 @@ function stopCssColor(stop) {
1369
1845
  function stopKey(path, stop, fallback) {
1370
1846
  return `${path}|${stop.position ?? fallback}|${stopCssColor(stop)}`;
1371
1847
  }
1372
- function GradientPalette({ filter = "gradient", caption }) {
1848
+ function GradientPalette({ filter, caption, sortBy = "path", sortDir = "asc" }) {
1373
1849
  const { resolved, activeTheme, cssVarPrefix } = useProject();
1374
1850
  const rows = useMemo(() => {
1375
- const collected = [];
1376
- for (const [path, token] of Object.entries(resolved)) {
1377
- if (token.$type !== "gradient") continue;
1378
- if (!globMatch(path, filter)) continue;
1379
- collected.push({
1380
- path,
1381
- cssVar: makeCssVar(path, cssVarPrefix),
1382
- stops: asStops(token.$value)
1383
- });
1384
- }
1385
- collected.sort((a, b) => a.path.localeCompare(b.path, void 0, { numeric: true }));
1386
- 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
+ }));
1387
1862
  }, [
1388
1863
  resolved,
1389
1864
  filter,
1390
- cssVarPrefix
1865
+ cssVarPrefix,
1866
+ sortBy,
1867
+ sortDir
1391
1868
  ]);
1392
1869
  const captionText = caption ?? `${rows.length} gradient${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1393
1870
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1394
1871
  ...themeAttrs(cssVarPrefix, activeTheme),
1395
1872
  style: {
1396
1873
  ...chromeAliases(cssVarPrefix),
1397
- ...styles$8.wrapper
1874
+ ...styles$9.wrapper
1398
1875
  },
1399
1876
  children: /* @__PURE__ */ jsx("div", {
1400
- style: styles$8.empty,
1877
+ style: styles$9.empty,
1401
1878
  children: "No gradient tokens match this filter."
1402
1879
  })
1403
1880
  });
@@ -1405,46 +1882,46 @@ function GradientPalette({ filter = "gradient", caption }) {
1405
1882
  ...themeAttrs(cssVarPrefix, activeTheme),
1406
1883
  style: {
1407
1884
  ...chromeAliases(cssVarPrefix),
1408
- ...styles$8.wrapper
1885
+ ...styles$9.wrapper
1409
1886
  },
1410
1887
  children: [/* @__PURE__ */ jsx("div", {
1411
- style: styles$8.caption,
1888
+ style: styles$9.caption,
1412
1889
  children: captionText
1413
1890
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1414
- style: styles$8.row,
1891
+ style: styles$9.row,
1415
1892
  children: [
1416
1893
  /* @__PURE__ */ jsxs("div", {
1417
- style: styles$8.meta,
1894
+ style: styles$9.meta,
1418
1895
  children: [/* @__PURE__ */ jsx("span", {
1419
- style: styles$8.path,
1896
+ style: styles$9.path,
1420
1897
  children: row.path
1421
1898
  }), /* @__PURE__ */ jsx("span", {
1422
- style: styles$8.cssVar,
1899
+ style: styles$9.cssVar,
1423
1900
  children: row.cssVar
1424
1901
  })]
1425
1902
  }),
1426
1903
  /* @__PURE__ */ jsx("div", {
1427
1904
  style: {
1428
- ...styles$8.sample,
1905
+ ...styles$9.sample,
1429
1906
  background: `linear-gradient(to right, ${row.cssVar})`
1430
1907
  },
1431
1908
  "aria-hidden": true
1432
1909
  }),
1433
1910
  /* @__PURE__ */ jsx("div", {
1434
- style: styles$8.stops,
1911
+ style: styles$9.stops,
1435
1912
  children: row.stops.map((stop, i) => /* @__PURE__ */ jsxs("div", {
1436
- style: styles$8.stopRow,
1913
+ style: styles$9.stopRow,
1437
1914
  children: [
1438
1915
  /* @__PURE__ */ jsx("span", {
1439
1916
  style: {
1440
- ...styles$8.stopSwatch,
1917
+ ...styles$9.stopSwatch,
1441
1918
  background: stopCssColor(stop)
1442
1919
  },
1443
1920
  "aria-hidden": true
1444
1921
  }),
1445
1922
  /* @__PURE__ */ jsx("span", { children: stopCssColor(stop) }),
1446
1923
  /* @__PURE__ */ jsxs("span", {
1447
- style: styles$8.stopPosition,
1924
+ style: styles$9.stopPosition,
1448
1925
  children: [
1449
1926
  "@ ",
1450
1927
  ((stop.position ?? 0) * 100).toFixed(0),
@@ -1480,11 +1957,11 @@ function usePrefersReducedMotion() {
1480
1957
  //#region src/motion-preview/MotionSample.tsx
1481
1958
  const DEFAULT_DURATION_MS = 300;
1482
1959
  const DEFAULT_EASING = "cubic-bezier(0.2, 0, 0, 1)";
1483
- const styles$7 = {
1960
+ const styles$8 = {
1484
1961
  track: {
1485
1962
  position: "relative",
1486
1963
  height: 36,
1487
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.08))",
1964
+ background: SURFACE_MUTED,
1488
1965
  borderRadius: 18,
1489
1966
  overflow: "hidden"
1490
1967
  },
@@ -1499,7 +1976,7 @@ const styles$7 = {
1499
1976
  },
1500
1977
  reducedMotion: {
1501
1978
  fontSize: 11,
1502
- color: "var(--sb-color-sys-text-muted, CanvasText)",
1979
+ color: TEXT_MUTED,
1503
1980
  fontStyle: "italic"
1504
1981
  }
1505
1982
  };
@@ -1599,18 +2076,18 @@ function MotionSample({ path, speed = 1, runKey = 0 }) {
1599
2076
  if (reducedMotion) return /* @__PURE__ */ jsx("div", {
1600
2077
  style: {
1601
2078
  ...chromeAliases(cssVarPrefix),
1602
- ...styles$7.reducedMotion
2079
+ ...styles$8.reducedMotion
1603
2080
  },
1604
2081
  children: "Animation suppressed by `prefers-reduced-motion: reduce`."
1605
2082
  });
1606
2083
  return /* @__PURE__ */ jsx("div", {
1607
2084
  style: {
1608
2085
  ...chromeAliases(cssVarPrefix),
1609
- ...styles$7.track
2086
+ ...styles$8.track
1610
2087
  },
1611
2088
  children: /* @__PURE__ */ jsx("div", {
1612
2089
  style: {
1613
- ...styles$7.ball,
2090
+ ...styles$8.ball,
1614
2091
  left: phase === 1 ? "calc(100% - 32px)" : "4px",
1615
2092
  transition: `left ${scaledDuration}ms ${easing}`
1616
2093
  },
@@ -1626,11 +2103,11 @@ const SPEEDS = [
1626
2103
  1,
1627
2104
  2
1628
2105
  ];
1629
- const styles$6 = {
2106
+ const styles$7 = {
1630
2107
  wrapper: surfaceStyle,
1631
2108
  caption: {
1632
2109
  padding: "4px 0 4px",
1633
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2110
+ color: TEXT_MUTED,
1634
2111
  fontSize: 12
1635
2112
  },
1636
2113
  controls: {
@@ -1641,7 +2118,7 @@ const styles$6 = {
1641
2118
  },
1642
2119
  controlLabel: {
1643
2120
  fontSize: 11,
1644
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2121
+ color: TEXT_MUTED,
1645
2122
  textTransform: "uppercase",
1646
2123
  letterSpacing: .5
1647
2124
  },
@@ -1651,7 +2128,7 @@ const styles$6 = {
1651
2128
  padding: "4px 8px",
1652
2129
  background: "transparent",
1653
2130
  color: "inherit",
1654
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
2131
+ border: BORDER_STRONG,
1655
2132
  borderRadius: 4,
1656
2133
  cursor: "pointer"
1657
2134
  },
@@ -1666,7 +2143,7 @@ const styles$6 = {
1666
2143
  marginLeft: "auto",
1667
2144
  background: "transparent",
1668
2145
  color: "inherit",
1669
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
2146
+ border: BORDER_STRONG,
1670
2147
  borderRadius: 4,
1671
2148
  cursor: "pointer"
1672
2149
  },
@@ -1694,18 +2171,18 @@ const styles$6 = {
1694
2171
  specs: {
1695
2172
  fontFamily: MONO_STACK,
1696
2173
  fontSize: 11,
1697
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2174
+ color: TEXT_MUTED
1698
2175
  },
1699
2176
  cssVar: {
1700
2177
  fontFamily: MONO_STACK,
1701
2178
  fontSize: 11,
1702
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2179
+ color: TEXT_MUTED,
1703
2180
  whiteSpace: "nowrap"
1704
2181
  },
1705
2182
  empty: {
1706
2183
  padding: "24px 12px",
1707
2184
  textAlign: "center",
1708
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2185
+ color: TEXT_MUTED
1709
2186
  }
1710
2187
  };
1711
2188
  function formatSpec(row) {
@@ -1756,10 +2233,10 @@ function MotionPreview({ filter, caption }) {
1756
2233
  ...themeAttrs(cssVarPrefix, activeTheme),
1757
2234
  style: {
1758
2235
  ...chromeAliases(cssVarPrefix),
1759
- ...styles$6.wrapper
2236
+ ...styles$7.wrapper
1760
2237
  },
1761
2238
  children: /* @__PURE__ */ jsx("div", {
1762
- style: styles$6.empty,
2239
+ style: styles$7.empty,
1763
2240
  children: "No motion tokens match this filter."
1764
2241
  })
1765
2242
  });
@@ -1767,32 +2244,32 @@ function MotionPreview({ filter, caption }) {
1767
2244
  ...themeAttrs(cssVarPrefix, activeTheme),
1768
2245
  style: {
1769
2246
  ...chromeAliases(cssVarPrefix),
1770
- ...styles$6.wrapper
2247
+ ...styles$7.wrapper
1771
2248
  },
1772
2249
  children: [
1773
2250
  /* @__PURE__ */ jsx("div", {
1774
- style: styles$6.caption,
2251
+ style: styles$7.caption,
1775
2252
  children: captionText
1776
2253
  }),
1777
2254
  /* @__PURE__ */ jsxs("div", {
1778
- style: styles$6.controls,
2255
+ style: styles$7.controls,
1779
2256
  children: [
1780
2257
  /* @__PURE__ */ jsx("span", {
1781
- style: styles$6.controlLabel,
2258
+ style: styles$7.controlLabel,
1782
2259
  children: "Speed"
1783
2260
  }),
1784
2261
  SPEEDS.map((s) => /* @__PURE__ */ jsxs("button", {
1785
2262
  type: "button",
1786
2263
  style: {
1787
- ...styles$6.speedBtn,
1788
- ...s === speed ? styles$6.speedBtnActive : {}
2264
+ ...styles$7.speedBtn,
2265
+ ...s === speed ? styles$7.speedBtnActive : {}
1789
2266
  },
1790
2267
  onClick: () => setSpeed(s),
1791
2268
  children: [s, "×"]
1792
2269
  }, s)),
1793
2270
  /* @__PURE__ */ jsx("button", {
1794
2271
  type: "button",
1795
- style: styles$6.replayBtn,
2272
+ style: styles$7.replayBtn,
1796
2273
  onClick: () => setRun((n) => n + 1),
1797
2274
  disabled: reducedMotion,
1798
2275
  title: reducedMotion ? "Disabled by prefers-reduced-motion" : "Replay all",
@@ -1801,15 +2278,15 @@ function MotionPreview({ filter, caption }) {
1801
2278
  ]
1802
2279
  }),
1803
2280
  rows.map((row) => /* @__PURE__ */ jsxs("div", {
1804
- style: styles$6.row,
2281
+ style: styles$7.row,
1805
2282
  children: [
1806
2283
  /* @__PURE__ */ jsxs("div", {
1807
- style: styles$6.meta,
2284
+ style: styles$7.meta,
1808
2285
  children: [/* @__PURE__ */ jsx("span", {
1809
- style: styles$6.path,
2286
+ style: styles$7.path,
1810
2287
  children: row.path
1811
2288
  }), /* @__PURE__ */ jsx("span", {
1812
- style: styles$6.specs,
2289
+ style: styles$7.specs,
1813
2290
  children: formatSpec(row)
1814
2291
  })]
1815
2292
  }),
@@ -1819,7 +2296,7 @@ function MotionPreview({ filter, caption }) {
1819
2296
  runKey: run
1820
2297
  }),
1821
2298
  /* @__PURE__ */ jsx("span", {
1822
- style: styles$6.cssVar,
2299
+ style: styles$7.cssVar,
1823
2300
  children: row.cssVar
1824
2301
  })
1825
2302
  ]
@@ -1859,8 +2336,8 @@ function useSwatchbookData() {
1859
2336
  const sampleStyle = {
1860
2337
  width: 120,
1861
2338
  height: 56,
1862
- background: "var(--sb-color-sys-surface-raised, #fff)",
1863
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.15))",
2339
+ background: SURFACE_RAISED,
2340
+ border: BORDER_FAINT,
1864
2341
  borderRadius: 6
1865
2342
  };
1866
2343
  function ShadowSample({ path }) {
@@ -1877,7 +2354,7 @@ function ShadowSample({ path }) {
1877
2354
  }
1878
2355
  //#endregion
1879
2356
  //#region src/ShadowPreview.tsx
1880
- const styles$5 = {
2357
+ const styles$6 = {
1881
2358
  wrapper: surfaceStyle,
1882
2359
  caption: captionStyle,
1883
2360
  empty: emptyStyle,
@@ -1921,12 +2398,12 @@ const styles$5 = {
1921
2398
  columnGap: 12,
1922
2399
  rowGap: 2
1923
2400
  },
1924
- breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" },
2401
+ breakdownKey: { color: TEXT_MUTED },
1925
2402
  layerHeader: {
1926
2403
  fontSize: 10,
1927
2404
  textTransform: "uppercase",
1928
2405
  letterSpacing: .5,
1929
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2406
+ color: TEXT_MUTED,
1930
2407
  marginTop: 6
1931
2408
  }
1932
2409
  };
@@ -1958,38 +2435,39 @@ function asLayers(raw) {
1958
2435
  if (raw && typeof raw === "object") return [raw];
1959
2436
  return [];
1960
2437
  }
1961
- function layerKey(path, layer, fallback) {
1962
- return `${path}|${`${formatDimension(layer.offsetX)},${formatDimension(layer.offsetY)}`}|${formatDimension(layer.blur)}|${formatDimension(layer.spread)}|${fallback}`;
1963
- }
1964
- function ShadowPreview({ filter = "shadow", caption }) {
1965
- const { resolved, activeTheme, cssVarPrefix } = useProject();
1966
- const rows = useMemo(() => {
1967
- const collected = [];
1968
- for (const [path, token] of Object.entries(resolved)) {
1969
- if (token.$type !== "shadow") continue;
1970
- if (!globMatch(path, filter)) continue;
1971
- collected.push({
1972
- path,
1973
- cssVar: makeCssVar(path, cssVarPrefix),
1974
- layers: asLayers(token.$value)
1975
- });
1976
- }
1977
- collected.sort((a, b) => a.path.localeCompare(b.path, void 0, { numeric: true }));
1978
- 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
+ }));
1979
2455
  }, [
1980
2456
  resolved,
1981
2457
  filter,
1982
- cssVarPrefix
2458
+ cssVarPrefix,
2459
+ sortBy,
2460
+ sortDir
1983
2461
  ]);
1984
2462
  const captionText = caption ?? `${rows.length} shadow${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1985
2463
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1986
2464
  ...themeAttrs(cssVarPrefix, activeTheme),
1987
2465
  style: {
1988
2466
  ...chromeAliases(cssVarPrefix),
1989
- ...styles$5.wrapper
2467
+ ...styles$6.wrapper
1990
2468
  },
1991
2469
  children: /* @__PURE__ */ jsx("div", {
1992
- style: styles$5.empty,
2470
+ style: styles$6.empty,
1993
2471
  children: "No shadow tokens match this filter."
1994
2472
  })
1995
2473
  });
@@ -1997,30 +2475,30 @@ function ShadowPreview({ filter = "shadow", caption }) {
1997
2475
  ...themeAttrs(cssVarPrefix, activeTheme),
1998
2476
  style: {
1999
2477
  ...chromeAliases(cssVarPrefix),
2000
- ...styles$5.wrapper
2478
+ ...styles$6.wrapper
2001
2479
  },
2002
2480
  children: [/* @__PURE__ */ jsx("div", {
2003
- style: styles$5.caption,
2481
+ style: styles$6.caption,
2004
2482
  children: captionText
2005
2483
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
2006
- style: styles$5.row,
2484
+ style: styles$6.row,
2007
2485
  children: [
2008
2486
  /* @__PURE__ */ jsxs("div", {
2009
- style: styles$5.meta,
2487
+ style: styles$6.meta,
2010
2488
  children: [/* @__PURE__ */ jsx("span", {
2011
- style: styles$5.path,
2489
+ style: styles$6.path,
2012
2490
  children: row.path
2013
2491
  }), /* @__PURE__ */ jsx("span", {
2014
- style: styles$5.cssVar,
2492
+ style: styles$6.cssVar,
2015
2493
  children: row.cssVar
2016
2494
  })]
2017
2495
  }),
2018
2496
  /* @__PURE__ */ jsx("div", {
2019
- style: styles$5.sampleCell,
2497
+ style: styles$6.sampleCell,
2020
2498
  children: /* @__PURE__ */ jsx(ShadowSample, { path: row.path })
2021
2499
  }),
2022
2500
  /* @__PURE__ */ jsx("div", {
2023
- style: styles$5.breakdown,
2501
+ style: styles$6.breakdown,
2024
2502
  children: row.layers.length === 1 ? renderLayer(row.layers[0]) : row.layers.map((layer, i) => /* @__PURE__ */ jsx(Layer, {
2025
2503
  layer,
2026
2504
  index: i,
@@ -2041,7 +2519,7 @@ function renderLayer(layer) {
2041
2519
  ];
2042
2520
  if (layer.inset) entries.push(["inset", String(layer.inset)]);
2043
2521
  return entries.flatMap(([k, v]) => [/* @__PURE__ */ jsx("span", {
2044
- style: styles$5.breakdownKey,
2522
+ style: styles$6.breakdownKey,
2045
2523
  children: k
2046
2524
  }, `k-${k}`), /* @__PURE__ */ jsx("span", { children: v }, `v-${k}`)]);
2047
2525
  }
@@ -2049,7 +2527,7 @@ function Layer({ layer, index, total }) {
2049
2527
  return /* @__PURE__ */ jsxs("div", {
2050
2528
  style: { gridColumn: "1 / -1" },
2051
2529
  children: [/* @__PURE__ */ jsxs("div", {
2052
- style: styles$5.layerHeader,
2530
+ style: styles$6.layerHeader,
2053
2531
  children: [
2054
2532
  "layer ",
2055
2533
  index + 1,
@@ -2058,7 +2536,7 @@ function Layer({ layer, index, total }) {
2058
2536
  ]
2059
2537
  }), /* @__PURE__ */ jsx("div", {
2060
2538
  style: {
2061
- ...styles$5.breakdown,
2539
+ ...styles$6.breakdown,
2062
2540
  marginTop: 2
2063
2541
  },
2064
2542
  children: renderLayer(layer)
@@ -2077,7 +2555,7 @@ const STRING_STYLES = new Set([
2077
2555
  "outset",
2078
2556
  "inset"
2079
2557
  ]);
2080
- const styles$4 = {
2558
+ const styles$5 = {
2081
2559
  wrapper: surfaceStyle,
2082
2560
  caption: captionStyle,
2083
2561
  empty: emptyStyle,
@@ -2105,55 +2583,60 @@ const styles$4 = {
2105
2583
  value: {
2106
2584
  fontFamily: MONO_STACK,
2107
2585
  fontSize: 11,
2108
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2586
+ color: TEXT_MUTED
2109
2587
  },
2110
2588
  line: {
2111
2589
  height: 0,
2112
2590
  borderTopWidth: 4,
2113
- borderTopColor: "var(--sb-color-sys-text-default, CanvasText)",
2591
+ borderTopColor: TEXT_DEFAULT,
2114
2592
  width: "100%"
2115
2593
  },
2116
2594
  objectFallback: {
2117
2595
  fontFamily: MONO_STACK,
2118
2596
  fontSize: 11,
2119
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2597
+ color: TEXT_MUTED
2120
2598
  },
2121
2599
  cssVar: {
2122
2600
  fontFamily: MONO_STACK,
2123
2601
  fontSize: 11,
2124
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2602
+ color: TEXT_MUTED
2125
2603
  }
2126
2604
  };
2127
2605
  function extractCssStyle(value) {
2128
2606
  if (typeof value === "string" && STRING_STYLES.has(value)) return value;
2129
2607
  return null;
2130
2608
  }
2131
- function StrokeStyleSample({ filter = "strokeStyle", caption }) {
2609
+ function StrokeStyleSample({ filter, caption, sortBy = "path", sortDir = "asc" }) {
2132
2610
  const { resolved, activeTheme, cssVarPrefix } = useProject();
2133
2611
  const rows = useMemo(() => {
2134
- return Object.entries(resolved).filter(([path, token]) => {
2612
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
2135
2613
  if (token.$type !== "strokeStyle") return false;
2136
2614
  return globMatch(path, filter);
2137
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => ({
2615
+ }), {
2616
+ by: sortBy,
2617
+ dir: sortDir
2618
+ }).map(([path, token]) => ({
2138
2619
  path,
2139
2620
  cssVar: makeCssVar(path, cssVarPrefix),
2140
- displayValue: formatValue(token.$value),
2621
+ displayValue: formatTokenValue(token.$value, token.$type, "raw"),
2141
2622
  cssStyle: extractCssStyle(token.$value)
2142
2623
  }));
2143
2624
  }, [
2144
2625
  resolved,
2145
2626
  filter,
2146
- cssVarPrefix
2627
+ cssVarPrefix,
2628
+ sortBy,
2629
+ sortDir
2147
2630
  ]);
2148
2631
  const captionText = caption ?? `${rows.length} strokeStyle token${rows.length === 1 ? "" : "s"}${filter && filter !== "strokeStyle" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
2149
2632
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
2150
2633
  ...themeAttrs(cssVarPrefix, activeTheme),
2151
2634
  style: {
2152
2635
  ...chromeAliases(cssVarPrefix),
2153
- ...styles$4.wrapper
2636
+ ...styles$5.wrapper
2154
2637
  },
2155
2638
  children: /* @__PURE__ */ jsx("div", {
2156
- style: styles$4.empty,
2639
+ style: styles$5.empty,
2157
2640
  children: "No strokeStyle tokens match this filter."
2158
2641
  })
2159
2642
  });
@@ -2161,36 +2644,36 @@ function StrokeStyleSample({ filter = "strokeStyle", caption }) {
2161
2644
  ...themeAttrs(cssVarPrefix, activeTheme),
2162
2645
  style: {
2163
2646
  ...chromeAliases(cssVarPrefix),
2164
- ...styles$4.wrapper
2647
+ ...styles$5.wrapper
2165
2648
  },
2166
2649
  children: [/* @__PURE__ */ jsx("div", {
2167
- style: styles$4.caption,
2650
+ style: styles$5.caption,
2168
2651
  children: captionText
2169
2652
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
2170
- style: styles$4.row,
2653
+ style: styles$5.row,
2171
2654
  children: [
2172
2655
  /* @__PURE__ */ jsxs("div", {
2173
- style: styles$4.meta,
2656
+ style: styles$5.meta,
2174
2657
  children: [/* @__PURE__ */ jsx("span", {
2175
- style: styles$4.path,
2658
+ style: styles$5.path,
2176
2659
  children: row.path
2177
2660
  }), /* @__PURE__ */ jsx("span", {
2178
- style: styles$4.value,
2661
+ style: styles$5.value,
2179
2662
  children: row.displayValue
2180
2663
  })]
2181
2664
  }),
2182
2665
  row.cssStyle ? /* @__PURE__ */ jsx("div", {
2183
2666
  style: {
2184
- ...styles$4.line,
2667
+ ...styles$5.line,
2185
2668
  borderTopStyle: row.cssStyle
2186
2669
  },
2187
2670
  "aria-hidden": true
2188
2671
  }) : /* @__PURE__ */ jsx("span", {
2189
- style: styles$4.objectFallback,
2672
+ style: styles$5.objectFallback,
2190
2673
  children: "Object-form (dashArray + lineCap) — no pure CSS `border-style` equivalent."
2191
2674
  }),
2192
2675
  /* @__PURE__ */ jsx("span", {
2193
- style: styles$4.cssVar,
2676
+ style: styles$5.cssVar,
2194
2677
  children: row.cssVar
2195
2678
  })
2196
2679
  ]
@@ -2199,7 +2682,7 @@ function StrokeStyleSample({ filter = "strokeStyle", caption }) {
2199
2682
  }
2200
2683
  //#endregion
2201
2684
  //#region src/token-detail/styles.ts
2202
- const styles$3 = {
2685
+ const styles$4 = {
2203
2686
  wrapper: {
2204
2687
  ...surfaceStyle,
2205
2688
  padding: 16,
@@ -2225,7 +2708,7 @@ const styles$3 = {
2225
2708
  fontSize: 10,
2226
2709
  letterSpacing: .5,
2227
2710
  textTransform: "uppercase",
2228
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
2711
+ background: SURFACE_MUTED
2229
2712
  },
2230
2713
  description: {
2231
2714
  margin: "0 0 12px",
@@ -2271,13 +2754,13 @@ const styles$3 = {
2271
2754
  verticalAlign: "middle",
2272
2755
  marginRight: 6,
2273
2756
  borderRadius: 3,
2274
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
2757
+ border: BORDER_DEFAULT
2275
2758
  },
2276
2759
  snippet: {
2277
2760
  display: "block",
2278
2761
  padding: "8px 10px",
2279
2762
  borderRadius: 4,
2280
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.1))",
2763
+ background: SURFACE_MUTED,
2281
2764
  fontFamily: MONO_STACK,
2282
2765
  fontSize: 12,
2283
2766
  whiteSpace: "pre",
@@ -2291,14 +2774,14 @@ const styles$3 = {
2291
2774
  shadowSample: {
2292
2775
  width: 140,
2293
2776
  height: 56,
2294
- background: "var(--sb-color-sys-surface-raised, #fff)",
2777
+ background: SURFACE_RAISED,
2295
2778
  border: BORDER_FAINT,
2296
2779
  borderRadius: 6
2297
2780
  },
2298
2781
  borderSample: {
2299
2782
  width: 140,
2300
2783
  height: 56,
2301
- background: "var(--sb-color-sys-surface-raised, transparent)",
2784
+ background: SURFACE_RAISED,
2302
2785
  borderRadius: 6
2303
2786
  },
2304
2787
  gradientSample: {
@@ -2310,18 +2793,18 @@ const styles$3 = {
2310
2793
  strokeStyleLine: {
2311
2794
  height: 0,
2312
2795
  borderTopWidth: 4,
2313
- borderTopColor: "var(--sb-color-sys-text-default, CanvasText)",
2796
+ borderTopColor: TEXT_DEFAULT,
2314
2797
  width: 220
2315
2798
  },
2316
2799
  strokeStyleSvg: {
2317
2800
  width: 220,
2318
2801
  height: 24,
2319
- color: "var(--sb-color-sys-text-default, CanvasText)"
2802
+ color: TEXT_DEFAULT
2320
2803
  },
2321
2804
  strokeStyleFallback: {
2322
2805
  fontFamily: MONO_STACK,
2323
2806
  fontSize: 12,
2324
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2807
+ color: TEXT_MUTED
2325
2808
  },
2326
2809
  colorSwatchRow: {
2327
2810
  display: "flex",
@@ -2349,13 +2832,13 @@ const styles$3 = {
2349
2832
  rowGap: 3,
2350
2833
  marginTop: 6
2351
2834
  },
2352
- breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" },
2835
+ breakdownKey: { color: TEXT_MUTED },
2353
2836
  breakdownLayerHeader: {
2354
2837
  gridColumn: "1 / -1",
2355
2838
  fontSize: 10,
2356
2839
  textTransform: "uppercase",
2357
2840
  letterSpacing: .5,
2358
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2841
+ color: TEXT_MUTED,
2359
2842
  marginTop: 4
2360
2843
  },
2361
2844
  fontFamilySample: {
@@ -2386,7 +2869,7 @@ const styles$3 = {
2386
2869
  height: 32,
2387
2870
  width: "100%",
2388
2871
  maxWidth: 320,
2389
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.08))",
2872
+ background: SURFACE_MUTED,
2390
2873
  borderRadius: 16,
2391
2874
  overflow: "hidden"
2392
2875
  },
@@ -2420,7 +2903,7 @@ const styles$3 = {
2420
2903
  },
2421
2904
  reducedMotion: {
2422
2905
  fontSize: 11,
2423
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2906
+ color: TEXT_MUTED,
2424
2907
  fontStyle: "italic"
2425
2908
  },
2426
2909
  tupleIndicator: {
@@ -2436,7 +2919,7 @@ const styles$3 = {
2436
2919
  padding: "6px 10px",
2437
2920
  marginBottom: 4,
2438
2921
  borderRadius: 4,
2439
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.1))"
2922
+ background: SURFACE_MUTED
2440
2923
  },
2441
2924
  consumerRowLabel: {
2442
2925
  fontFamily: MONO_STACK,
@@ -2458,9 +2941,9 @@ const styles$3 = {
2458
2941
  padding: "3px 8px",
2459
2942
  fontSize: 11,
2460
2943
  fontFamily: MONO_STACK,
2461
- background: "var(--sb-color-sys-surface-raised, Canvas)",
2462
- color: "var(--sb-color-sys-text-default, CanvasText)",
2463
- 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,
2464
2947
  borderRadius: 4,
2465
2948
  cursor: "pointer",
2466
2949
  flexShrink: 0
@@ -2495,17 +2978,17 @@ function AliasChain({ path }) {
2495
2978
  }, [token, path]);
2496
2979
  if (chain.length <= 1) return null;
2497
2980
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2498
- style: styles$3.sectionHeader,
2981
+ style: styles$4.sectionHeader,
2499
2982
  children: "Alias chain"
2500
2983
  }), /* @__PURE__ */ jsx("div", {
2501
- style: styles$3.chain,
2984
+ style: styles$4.chain,
2502
2985
  children: chain.map((step, i) => /* @__PURE__ */ jsxs("span", {
2503
- style: styles$3.chain,
2986
+ style: styles$4.chain,
2504
2987
  children: [/* @__PURE__ */ jsx("span", {
2505
- style: styles$3.chainNode,
2988
+ style: styles$4.chainNode,
2506
2989
  children: step
2507
2990
  }), i < chain.length - 1 && /* @__PURE__ */ jsx("span", {
2508
- style: styles$3.arrow,
2991
+ style: styles$4.arrow,
2509
2992
  children: "→"
2510
2993
  })]
2511
2994
  }, step))
@@ -2525,18 +3008,18 @@ function AliasedBy({ path }) {
2525
3008
  if (tree.length === 0) return null;
2526
3009
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2527
3010
  /* @__PURE__ */ jsx("div", {
2528
- style: styles$3.sectionHeader,
3011
+ style: styles$4.sectionHeader,
2529
3012
  children: "Aliased by"
2530
3013
  }),
2531
3014
  /* @__PURE__ */ jsx("ul", {
2532
- style: styles$3.aliasedByList,
3015
+ style: styles$4.aliasedByList,
2533
3016
  children: tree.map((node) => /* @__PURE__ */ jsx(AliasedByRow, {
2534
3017
  node,
2535
3018
  depth: 0
2536
3019
  }, node.path))
2537
3020
  }),
2538
3021
  truncated && /* @__PURE__ */ jsxs("div", {
2539
- style: styles$3.aliasedByTruncated,
3022
+ style: styles$4.aliasedByTruncated,
2540
3023
  children: [
2541
3024
  "Further descendants truncated at depth ",
2542
3025
  ALIASED_BY_DEPTH_CAP,
@@ -2548,15 +3031,15 @@ function AliasedBy({ path }) {
2548
3031
  function AliasedByRow({ node, depth }) {
2549
3032
  return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("div", {
2550
3033
  style: {
2551
- ...styles$3.aliasedByRow,
3034
+ ...styles$4.aliasedByRow,
2552
3035
  paddingLeft: depth * 16
2553
3036
  },
2554
3037
  children: /* @__PURE__ */ jsx("span", {
2555
- style: styles$3.chainNode,
3038
+ style: styles$4.chainNode,
2556
3039
  children: node.path
2557
3040
  })
2558
3041
  }), node.children.length > 0 && /* @__PURE__ */ jsx("ul", {
2559
- style: styles$3.aliasedByList,
3042
+ style: styles$4.aliasedByList,
2560
3043
  children: node.children.map((child) => /* @__PURE__ */ jsx(AliasedByRow, {
2561
3044
  node: child,
2562
3045
  depth: depth + 1
@@ -2609,8 +3092,9 @@ function treeHasTruncation(nodes) {
2609
3092
  function AxisVariance({ path }) {
2610
3093
  const { token, cssVar, axes, themes, themesResolved, activeAxes, cssVarPrefix } = useTokenDetailData(path);
2611
3094
  const colorFormat = useColorFormat();
2612
- const isColor = token?.$type === "color";
2613
- 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);
2614
3098
  const variance = useMemo(() => analyzeVariance(path, axes, themes, themesResolved), [
2615
3099
  path,
2616
3100
  axes,
@@ -2622,20 +3106,20 @@ function AxisVariance({ path }) {
2622
3106
  const anyTheme = themes[0];
2623
3107
  const value = anyTheme ? formatFn(themesResolved[anyTheme.name]?.[path]) : "—";
2624
3108
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2625
- style: styles$3.sectionHeader,
3109
+ style: styles$4.sectionHeader,
2626
3110
  children: "Values across axes"
2627
3111
  }), /* @__PURE__ */ jsx("table", {
2628
- style: styles$3.themeTable,
3112
+ style: styles$4.themeTable,
2629
3113
  "data-testid": "token-detail-values",
2630
3114
  children: /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", {
2631
- style: styles$3.themeRow,
3115
+ style: styles$4.themeRow,
2632
3116
  children: /* @__PURE__ */ jsxs("td", {
2633
- style: styles$3.themeCell,
3117
+ style: styles$4.themeCell,
2634
3118
  "data-testid": "token-detail-constant",
2635
3119
  children: [
2636
3120
  isColor && /* @__PURE__ */ jsx("span", {
2637
3121
  style: {
2638
- ...styles$3.swatch,
3122
+ ...styles$4.swatch,
2639
3123
  background: cssVar
2640
3124
  },
2641
3125
  "aria-hidden": true
@@ -2678,26 +3162,26 @@ function AxisVariance({ path }) {
2678
3162
  };
2679
3163
  });
2680
3164
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
2681
- style: styles$3.sectionHeader,
3165
+ style: styles$4.sectionHeader,
2682
3166
  children: ["Varies with ", axisName]
2683
3167
  }), /* @__PURE__ */ jsx("table", {
2684
- style: styles$3.themeTable,
3168
+ style: styles$4.themeTable,
2685
3169
  "data-testid": "token-detail-values",
2686
3170
  children: /* @__PURE__ */ jsx("tbody", { children: contextValues.map((row) => /* @__PURE__ */ jsxs("tr", {
2687
- style: styles$3.themeRow,
3171
+ style: styles$4.themeRow,
2688
3172
  "data-axis": axisName,
2689
3173
  "data-context": row.ctx,
2690
3174
  children: [/* @__PURE__ */ jsx("td", {
2691
3175
  style: {
2692
- ...styles$3.themeCell,
3176
+ ...styles$4.themeCell,
2693
3177
  width: "30%"
2694
3178
  },
2695
3179
  children: row.ctx
2696
3180
  }), /* @__PURE__ */ jsxs("td", {
2697
- style: styles$3.themeCell,
3181
+ style: styles$4.themeCell,
2698
3182
  children: [isColor && row.themeName && /* @__PURE__ */ jsx("span", {
2699
3183
  style: {
2700
- ...styles$3.swatch,
3184
+ ...styles$4.swatch,
2701
3185
  background: cssVar
2702
3186
  },
2703
3187
  [dataAttr(cssVarPrefix, "theme")]: row.themeName,
@@ -2711,17 +3195,17 @@ function AxisVariance({ path }) {
2711
3195
  if (!rowAxis || !colAxis) return /* @__PURE__ */ jsx(Fragment, {});
2712
3196
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2713
3197
  /* @__PURE__ */ jsxs("div", {
2714
- style: styles$3.sectionHeader,
3198
+ style: styles$4.sectionHeader,
2715
3199
  children: ["Varies with ", variance.varyingAxes.join(" × ")]
2716
3200
  }),
2717
3201
  /* @__PURE__ */ jsxs("table", {
2718
- style: styles$3.themeTable,
3202
+ style: styles$4.themeTable,
2719
3203
  "data-testid": "token-detail-values",
2720
3204
  children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
2721
- style: styles$3.themeRow,
3205
+ style: styles$4.themeRow,
2722
3206
  children: [/* @__PURE__ */ jsxs("th", {
2723
3207
  style: {
2724
- ...styles$3.themeCell,
3208
+ ...styles$4.themeCell,
2725
3209
  textAlign: "left",
2726
3210
  opacity: .7
2727
3211
  },
@@ -2732,16 +3216,16 @@ function AxisVariance({ path }) {
2732
3216
  ]
2733
3217
  }), colAxis.contexts.map((col) => /* @__PURE__ */ jsx("th", {
2734
3218
  style: {
2735
- ...styles$3.themeCell,
3219
+ ...styles$4.themeCell,
2736
3220
  textAlign: "left",
2737
3221
  opacity: .7
2738
3222
  },
2739
3223
  children: col
2740
3224
  }, col))]
2741
3225
  }) }), /* @__PURE__ */ jsx("tbody", { children: rowAxis.contexts.map((row) => /* @__PURE__ */ jsxs("tr", {
2742
- style: styles$3.themeRow,
3226
+ style: styles$4.themeRow,
2743
3227
  children: [/* @__PURE__ */ jsx("td", {
2744
- style: styles$3.themeCell,
3228
+ style: styles$4.themeCell,
2745
3229
  children: row
2746
3230
  }), colAxis.contexts.map((col) => {
2747
3231
  const name = tupleName(themes, {
@@ -2751,12 +3235,12 @@ function AxisVariance({ path }) {
2751
3235
  });
2752
3236
  const value = name ? formatFn(themesResolved[name]?.[path]) : "—";
2753
3237
  return /* @__PURE__ */ jsxs("td", {
2754
- style: styles$3.themeCell,
3238
+ style: styles$4.themeCell,
2755
3239
  "data-row": row,
2756
3240
  "data-col": col,
2757
3241
  children: [isColor && name && /* @__PURE__ */ jsx("span", {
2758
3242
  style: {
2759
- ...styles$3.swatch,
3243
+ ...styles$4.swatch,
2760
3244
  background: cssVar
2761
3245
  },
2762
3246
  [dataAttr(cssVarPrefix, "theme")]: name,
@@ -2768,7 +3252,7 @@ function AxisVariance({ path }) {
2768
3252
  }),
2769
3253
  extra.length > 0 && /* @__PURE__ */ jsxs("div", {
2770
3254
  style: {
2771
- ...styles$3.aliasedByTruncated,
3255
+ ...styles$4.aliasedByTruncated,
2772
3256
  marginTop: 6
2773
3257
  },
2774
3258
  children: [
@@ -2779,14 +3263,20 @@ function AxisVariance({ path }) {
2779
3263
  })
2780
3264
  ] });
2781
3265
  }
2782
- function valueFor(token, isColor, format) {
3266
+ function valueFor(token, $type, format) {
2783
3267
  if (!token) return "—";
2784
- if (isColor) return formatColor(token.$value, format).value;
2785
- return formatValue(token.$value);
3268
+ return formatTokenValue(token.$value, $type, format);
2786
3269
  }
2787
- 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) {
2788
3277
  const t = themesResolved[themeName]?.[path];
2789
- return t ? formatValue(t.$value) : "";
3278
+ if (!t) return "";
3279
+ return JSON.stringify(t.$value);
2790
3280
  }
2791
3281
  function tupleName(themes, tuple) {
2792
3282
  return themes.find((t) => {
@@ -2802,7 +3292,7 @@ function analyzeVariance(path, axes, themes, themesResolved) {
2802
3292
  const others = axes.filter((a) => a.name !== axis.name).map((a) => `${a.name}=${theme.input[a.name] ?? ""}`).join("|");
2803
3293
  const ctx = theme.input[axis.name] ?? "";
2804
3294
  const bucket = byOthers.get(others) ?? /* @__PURE__ */ new Map();
2805
- bucket.set(ctx, themeValue(themesResolved, theme.name, path));
3295
+ bucket.set(ctx, varianceKey(themesResolved, theme.name, path));
2806
3296
  byOthers.set(others, bucket);
2807
3297
  }
2808
3298
  let varies = false;
@@ -2829,13 +3319,15 @@ function analyzeVariance(path, axes, themes, themesResolved) {
2829
3319
  //#region src/token-detail/CompositeBreakdown.tsx
2830
3320
  function CompositeBreakdown({ path }) {
2831
3321
  const { token } = useTokenDetailData(path);
3322
+ const colorFormat = useColorFormat();
2832
3323
  if (!token) return null;
2833
3324
  return /* @__PURE__ */ jsx(CompositeBreakdownContent, {
2834
3325
  type: token.$type,
2835
- rawValue: token.$value
3326
+ rawValue: token.$value,
3327
+ colorFormat
2836
3328
  });
2837
3329
  }
2838
- function CompositeBreakdownContent({ type, rawValue }) {
3330
+ function CompositeBreakdownContent({ type, rawValue, colorFormat }) {
2839
3331
  if (!rawValue || typeof rawValue !== "object") return null;
2840
3332
  if (type === "typography") {
2841
3333
  const v = rawValue;
@@ -2850,7 +3342,7 @@ function CompositeBreakdownContent({ type, rawValue }) {
2850
3342
  if (type === "border") {
2851
3343
  const v = rawValue;
2852
3344
  return renderKeyValueList([
2853
- ["color", formatColorValue(v["color"])],
3345
+ ["color", formatColorSubValue(v["color"], colorFormat)],
2854
3346
  ["width", formatDimensionValue(v["width"])],
2855
3347
  ["style", formatPrimitive(v["style"])]
2856
3348
  ]);
@@ -2867,19 +3359,19 @@ function CompositeBreakdownContent({ type, rawValue }) {
2867
3359
  const layers = Array.isArray(rawValue) ? rawValue : [rawValue];
2868
3360
  const multi = layers.length > 1;
2869
3361
  return /* @__PURE__ */ jsx("div", {
2870
- style: styles$3.breakdownSection,
3362
+ style: styles$4.breakdownSection,
2871
3363
  children: layers.map((layer, i) => {
2872
3364
  const v = layer;
2873
3365
  return /* @__PURE__ */ jsxs("div", {
2874
3366
  style: { display: "contents" },
2875
3367
  children: [
2876
3368
  multi && /* @__PURE__ */ jsxs("div", {
2877
- style: styles$3.breakdownLayerHeader,
3369
+ style: styles$4.breakdownLayerHeader,
2878
3370
  children: ["Layer ", i + 1]
2879
3371
  }),
2880
3372
  /* @__PURE__ */ jsx(KeyValueRow, {
2881
3373
  label: "color",
2882
- value: formatColorValue(v["color"])
3374
+ value: formatColorSubValue(v["color"], colorFormat)
2883
3375
  }),
2884
3376
  /* @__PURE__ */ jsx(KeyValueRow, {
2885
3377
  label: "offsetX",
@@ -2910,12 +3402,12 @@ function CompositeBreakdownContent({ type, rawValue }) {
2910
3402
  const stops = Array.isArray(rawValue) ? rawValue : [];
2911
3403
  if (stops.length === 0) return null;
2912
3404
  return /* @__PURE__ */ jsx("div", {
2913
- style: styles$3.breakdownSection,
3405
+ style: styles$4.breakdownSection,
2914
3406
  children: stops.map((stop, i) => {
2915
3407
  const v = stop;
2916
3408
  return /* @__PURE__ */ jsx(KeyValueRow, {
2917
3409
  label: `${((typeof v["position"] === "number" ? v["position"] : 0) * 100).toFixed(0)}%`,
2918
- value: formatColorValue(v["color"])
3410
+ value: formatColorSubValue(v["color"], colorFormat)
2919
3411
  }, gradientStopKey(v, i));
2920
3412
  })
2921
3413
  });
@@ -2924,7 +3416,7 @@ function CompositeBreakdownContent({ type, rawValue }) {
2924
3416
  }
2925
3417
  function renderKeyValueList(rows) {
2926
3418
  return /* @__PURE__ */ jsx("div", {
2927
- style: styles$3.breakdownSection,
3419
+ style: styles$4.breakdownSection,
2928
3420
  children: rows.filter(([, v]) => v !== null).map(([k, v]) => /* @__PURE__ */ jsx(KeyValueRow, {
2929
3421
  label: k,
2930
3422
  value: v ?? ""
@@ -2933,7 +3425,7 @@ function renderKeyValueList(rows) {
2933
3425
  }
2934
3426
  function KeyValueRow({ label, value }) {
2935
3427
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
2936
- style: styles$3.breakdownKey,
3428
+ style: styles$4.breakdownKey,
2937
3429
  children: label
2938
3430
  }), /* @__PURE__ */ jsx("span", { children: value ?? "—" })] });
2939
3431
  }
@@ -2957,18 +3449,15 @@ function formatDimensionValue(v) {
2957
3449
  }
2958
3450
  return JSON.stringify(v);
2959
3451
  }
2960
- 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) {
2961
3459
  if (v == null) return null;
2962
- if (typeof v === "string") return v;
2963
- if (typeof v === "object") {
2964
- const c = v;
2965
- if (Array.isArray(c.components) && typeof c.colorSpace === "string") {
2966
- const parts = c.components.map((n) => typeof n === "number" ? n.toFixed(3) : String(n));
2967
- const alpha = typeof c.alpha === "number" && c.alpha !== 1 ? ` / ${c.alpha}` : "";
2968
- return `${c.colorSpace}(${parts.join(" ")}${alpha})`;
2969
- }
2970
- }
2971
- return JSON.stringify(v);
3460
+ return formatColor(v, format).value;
2972
3461
  }
2973
3462
  function shadowLayerKey(layer, fallback) {
2974
3463
  return `shadow|${[
@@ -3010,7 +3499,7 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
3010
3499
  const base = cssVar.replace(/^var\(/, "").replace(/\)$/, "");
3011
3500
  return /* @__PURE__ */ jsx("div", {
3012
3501
  style: {
3013
- ...styles$3.typographySample,
3502
+ ...styles$4.typographySample,
3014
3503
  fontFamily: `var(${base}-font-family)`,
3015
3504
  fontSize: `var(${base}-font-size)`,
3016
3505
  fontWeight: `var(${base}-font-weight)`,
@@ -3022,24 +3511,24 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
3022
3511
  }
3023
3512
  if (type === "shadow") return /* @__PURE__ */ jsx("div", {
3024
3513
  style: {
3025
- ...styles$3.shadowSample,
3514
+ ...styles$4.shadowSample,
3026
3515
  boxShadow: cssVar
3027
3516
  },
3028
3517
  "aria-hidden": true
3029
3518
  });
3030
3519
  if (type === "border") return /* @__PURE__ */ jsx("div", {
3031
3520
  style: {
3032
- ...styles$3.borderSample,
3521
+ ...styles$4.borderSample,
3033
3522
  border: cssVar
3034
3523
  },
3035
3524
  "aria-hidden": true
3036
3525
  });
3037
3526
  if (type === "transition") return /* @__PURE__ */ jsx(TransitionSample, { transition: cssVar });
3038
3527
  if (type === "dimension") return /* @__PURE__ */ jsx("div", {
3039
- style: styles$3.dimensionTrack,
3528
+ style: styles$4.dimensionTrack,
3040
3529
  children: /* @__PURE__ */ jsx("div", {
3041
3530
  style: {
3042
- ...styles$3.dimensionBar,
3531
+ ...styles$4.dimensionBar,
3043
3532
  width: cssVar
3044
3533
  },
3045
3534
  "aria-hidden": true
@@ -3048,14 +3537,14 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
3048
3537
  if (type === "duration") return /* @__PURE__ */ jsx(TransitionSample, { transition: `left ${cssVar} ease` });
3049
3538
  if (type === "fontFamily") return /* @__PURE__ */ jsx("div", {
3050
3539
  style: {
3051
- ...styles$3.fontFamilySample,
3540
+ ...styles$4.fontFamilySample,
3052
3541
  fontFamily: cssVar
3053
3542
  },
3054
3543
  children: PANGRAM
3055
3544
  });
3056
3545
  if (type === "fontWeight") return /* @__PURE__ */ jsx("div", {
3057
3546
  style: {
3058
- ...styles$3.fontWeightSample,
3547
+ ...styles$4.fontWeightSample,
3059
3548
  fontWeight: cssVar
3060
3549
  },
3061
3550
  children: "Aa"
@@ -3063,20 +3552,20 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
3063
3552
  if (type === "cubicBezier") return /* @__PURE__ */ jsx(TransitionSample, { transition: `left 800ms ${cssVar}` });
3064
3553
  if (type === "gradient") return /* @__PURE__ */ jsx("div", {
3065
3554
  style: {
3066
- ...styles$3.gradientSample,
3555
+ ...styles$4.gradientSample,
3067
3556
  background: `linear-gradient(to right, ${cssVar})`
3068
3557
  },
3069
3558
  "aria-hidden": true
3070
3559
  });
3071
3560
  if (type === "strokeStyle") return /* @__PURE__ */ jsx(StrokeStylePreview, { value: rawValue });
3072
3561
  if (type === "color") return /* @__PURE__ */ jsxs("div", {
3073
- style: styles$3.colorSwatchRow,
3562
+ style: styles$4.colorSwatchRow,
3074
3563
  "aria-hidden": true,
3075
3564
  children: [/* @__PURE__ */ jsx("div", { style: {
3076
- ...styles$3.colorSwatchLight,
3565
+ ...styles$4.colorSwatchLight,
3077
3566
  background: cssVar
3078
3567
  } }), /* @__PURE__ */ jsx("div", { style: {
3079
- ...styles$3.colorSwatchDark,
3568
+ ...styles$4.colorSwatchDark,
3080
3569
  background: cssVar
3081
3570
  } })]
3082
3571
  });
@@ -3085,7 +3574,7 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
3085
3574
  function StrokeStylePreview({ value }) {
3086
3575
  if (typeof value === "string" && STROKE_STYLE_STRINGS.has(value)) return /* @__PURE__ */ jsx("div", {
3087
3576
  style: {
3088
- ...styles$3.strokeStyleLine,
3577
+ ...styles$4.strokeStyleLine,
3089
3578
  borderTopStyle: value
3090
3579
  },
3091
3580
  "aria-hidden": true
@@ -3094,12 +3583,12 @@ function StrokeStylePreview({ value }) {
3094
3583
  const v = value;
3095
3584
  const lengths = asDashLengths(v.dashArray);
3096
3585
  if (lengths.length === 0) return /* @__PURE__ */ jsx("div", {
3097
- style: styles$3.strokeStyleFallback,
3586
+ style: styles$4.strokeStyleFallback,
3098
3587
  children: "Object-form strokeStyle with no resolvable dashArray."
3099
3588
  });
3100
3589
  const cap = typeof v.lineCap === "string" ? v.lineCap : "butt";
3101
3590
  return /* @__PURE__ */ jsx("svg", {
3102
- style: styles$3.strokeStyleSvg,
3591
+ style: styles$4.strokeStyleSvg,
3103
3592
  viewBox: "0 0 220 24",
3104
3593
  preserveAspectRatio: "none",
3105
3594
  "aria-hidden": true,
@@ -3116,7 +3605,7 @@ function StrokeStylePreview({ value }) {
3116
3605
  });
3117
3606
  }
3118
3607
  return /* @__PURE__ */ jsx("div", {
3119
- style: styles$3.strokeStyleFallback,
3608
+ style: styles$4.strokeStyleFallback,
3120
3609
  children: "strokeStyle value could not be previewed."
3121
3610
  });
3122
3611
  }
@@ -3150,14 +3639,14 @@ function TransitionSample({ transition }) {
3150
3639
  };
3151
3640
  }, [reduced]);
3152
3641
  if (reduced) return /* @__PURE__ */ jsx("div", {
3153
- style: styles$3.reducedMotion,
3642
+ style: styles$4.reducedMotion,
3154
3643
  children: "Animation suppressed by `prefers-reduced-motion: reduce`."
3155
3644
  });
3156
3645
  return /* @__PURE__ */ jsx("div", {
3157
- style: styles$3.motionTrack,
3646
+ style: styles$4.motionTrack,
3158
3647
  children: /* @__PURE__ */ jsx("div", {
3159
3648
  style: {
3160
- ...styles$3.motionBall,
3649
+ ...styles$4.motionBall,
3161
3650
  left: phase === 1 ? "calc(100% - 28px)" : "4px",
3162
3651
  transition
3163
3652
  },
@@ -3173,11 +3662,11 @@ function ConsumerOutput({ path }) {
3173
3662
  const tupleLabel = Object.entries(activeAxes).map(([k, v]) => `${k}=${v}`).join(", ");
3174
3663
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3175
3664
  /* @__PURE__ */ jsx("div", {
3176
- style: styles$3.sectionHeader,
3665
+ style: styles$4.sectionHeader,
3177
3666
  children: "Consumer output"
3178
3667
  }),
3179
3668
  tupleLabel && /* @__PURE__ */ jsxs("div", {
3180
- style: styles$3.tupleIndicator,
3669
+ style: styles$4.tupleIndicator,
3181
3670
  children: ["Active tuple: ", /* @__PURE__ */ jsx("strong", { children: tupleLabel })]
3182
3671
  }),
3183
3672
  /* @__PURE__ */ jsx(OutputRow, {
@@ -3194,14 +3683,14 @@ function ConsumerOutput({ path }) {
3194
3683
  }
3195
3684
  function OutputRow({ label, value, testId }) {
3196
3685
  return /* @__PURE__ */ jsxs("div", {
3197
- style: styles$3.consumerRow,
3686
+ style: styles$4.consumerRow,
3198
3687
  children: [
3199
3688
  /* @__PURE__ */ jsx("span", {
3200
- style: styles$3.consumerRowLabel,
3689
+ style: styles$4.consumerRowLabel,
3201
3690
  children: label
3202
3691
  }),
3203
3692
  /* @__PURE__ */ jsx("code", {
3204
- style: styles$3.consumerRowValue,
3693
+ style: styles$4.consumerRowValue,
3205
3694
  "data-testid": testId,
3206
3695
  children: value
3207
3696
  }),
@@ -3216,7 +3705,7 @@ function CopyButton({ text, testId }) {
3216
3705
  const [copied, setCopied] = useState(false);
3217
3706
  return /* @__PURE__ */ jsx("button", {
3218
3707
  type: "button",
3219
- style: styles$3.consumerRowCopy,
3708
+ style: styles$4.consumerRowCopy,
3220
3709
  "data-testid": testId,
3221
3710
  onClick: () => {
3222
3711
  copyToClipboard(text).then((ok) => {
@@ -3242,7 +3731,7 @@ async function copyToClipboard(text) {
3242
3731
  function TokenHeader({ path, heading }) {
3243
3732
  const { token, cssVar, activeTheme } = useTokenDetailData(path);
3244
3733
  if (!token) return /* @__PURE__ */ jsxs("div", {
3245
- style: styles$3.missing,
3734
+ style: styles$4.missing,
3246
3735
  children: [
3247
3736
  "Token ",
3248
3737
  /* @__PURE__ */ jsx("code", { children: path }),
@@ -3253,18 +3742,18 @@ function TokenHeader({ path, heading }) {
3253
3742
  });
3254
3743
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3255
3744
  /* @__PURE__ */ jsx("h3", {
3256
- style: styles$3.heading,
3745
+ style: styles$4.heading,
3257
3746
  children: heading ?? path
3258
3747
  }),
3259
3748
  /* @__PURE__ */ jsxs("div", {
3260
- style: styles$3.subline,
3749
+ style: styles$4.subline,
3261
3750
  children: [token.$type && /* @__PURE__ */ jsx("span", {
3262
- style: styles$3.typePill,
3751
+ style: styles$4.typePill,
3263
3752
  children: token.$type
3264
3753
  }), /* @__PURE__ */ jsx("span", { children: cssVar })]
3265
3754
  }),
3266
3755
  token.$description && /* @__PURE__ */ jsx("p", {
3267
- style: styles$3.description,
3756
+ style: styles$4.description,
3268
3757
  children: token.$description
3269
3758
  })
3270
3759
  ] });
@@ -3275,10 +3764,10 @@ function TokenUsageSnippet({ path }) {
3275
3764
  const { token, cssVar } = useTokenDetailData(path);
3276
3765
  if (!token) return null;
3277
3766
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
3278
- style: styles$3.sectionHeader,
3767
+ style: styles$4.sectionHeader,
3279
3768
  children: "Usage"
3280
3769
  }), /* @__PURE__ */ jsx("code", {
3281
- style: styles$3.snippet,
3770
+ style: styles$4.snippet,
3282
3771
  children: `color: ${cssVar};`
3283
3772
  })] });
3284
3773
  }
@@ -3291,10 +3780,10 @@ function TokenDetail({ path, heading }) {
3291
3780
  ...themeAttrs(cssVarPrefix, activeTheme),
3292
3781
  style: {
3293
3782
  ...chromeAliases(cssVarPrefix),
3294
- ...styles$3.wrapper
3783
+ ...styles$4.wrapper
3295
3784
  },
3296
3785
  children: /* @__PURE__ */ jsxs("div", {
3297
- style: styles$3.missing,
3786
+ style: styles$4.missing,
3298
3787
  children: [
3299
3788
  "Token ",
3300
3789
  /* @__PURE__ */ jsx("code", { children: path }),
@@ -3305,14 +3794,14 @@ function TokenDetail({ path, heading }) {
3305
3794
  })
3306
3795
  });
3307
3796
  const isColor = token.$type === "color";
3308
- const formatted = isColor ? formatColor(token.$value, colorFormat) : null;
3309
- const value = formatted ? formatted.value : formatValue(token.$value);
3310
- 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;
3311
3800
  return /* @__PURE__ */ jsxs("div", {
3312
3801
  ...themeAttrs(cssVarPrefix, activeTheme),
3313
3802
  style: {
3314
3803
  ...chromeAliases(cssVarPrefix),
3315
- ...styles$3.wrapper
3804
+ ...styles$4.wrapper
3316
3805
  },
3317
3806
  children: [
3318
3807
  /* @__PURE__ */ jsx(TokenHeader, {
@@ -3320,17 +3809,17 @@ function TokenDetail({ path, heading }) {
3320
3809
  ...heading !== void 0 && { heading }
3321
3810
  }),
3322
3811
  /* @__PURE__ */ jsxs("div", {
3323
- style: styles$3.sectionHeader,
3812
+ style: styles$4.sectionHeader,
3324
3813
  children: ["Resolved value · ", activeTheme]
3325
3814
  }),
3326
3815
  /* @__PURE__ */ jsx(CompositePreview, { path }),
3327
3816
  /* @__PURE__ */ jsx(CompositeBreakdown, { path }),
3328
3817
  /* @__PURE__ */ jsxs("div", {
3329
- style: styles$3.chain,
3818
+ style: styles$4.chain,
3330
3819
  children: [
3331
3820
  isColor && /* @__PURE__ */ jsx("span", {
3332
3821
  style: {
3333
- ...styles$3.swatch,
3822
+ ...styles$4.swatch,
3334
3823
  background: cssVar
3335
3824
  },
3336
3825
  "aria-hidden": true
@@ -3353,12 +3842,87 @@ function TokenDetail({ path, heading }) {
3353
3842
  });
3354
3843
  }
3355
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
3356
3920
  //#region src/TokenNavigator.tsx
3357
3921
  const styles$2 = {
3358
3922
  wrapper: surfaceStyle,
3359
3923
  caption: {
3360
3924
  padding: "4px 0 12px",
3361
- color: "var(--sb-color-sys-text-muted, CanvasText)",
3925
+ color: TEXT_MUTED,
3362
3926
  fontSize: 12
3363
3927
  },
3364
3928
  tree: {
@@ -3397,87 +3961,46 @@ const styles$2 = {
3397
3961
  display: "inline-block",
3398
3962
  width: 12,
3399
3963
  textAlign: "center",
3400
- color: "var(--sb-color-sys-text-muted, CanvasText)"
3964
+ color: TEXT_MUTED
3401
3965
  },
3402
3966
  tail: {
3403
3967
  fontFamily: MONO_STACK,
3404
3968
  fontSize: 12
3405
3969
  },
3406
3970
  typePill: {
3407
- display: "inline-block",
3971
+ ...typePillStyle,
3408
3972
  padding: "1px 6px",
3409
- borderRadius: 4,
3410
- fontSize: 10,
3411
- letterSpacing: .5,
3412
- textTransform: "uppercase",
3413
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
3973
+ fontSize: 10
3414
3974
  },
3415
3975
  value: {
3416
3976
  fontSize: 11,
3417
- color: "var(--sb-color-sys-text-muted, CanvasText)",
3977
+ color: TEXT_MUTED,
3418
3978
  marginLeft: "auto",
3419
- wordBreak: "break-all",
3420
- maxWidth: "40%",
3421
- textAlign: "right"
3979
+ minWidth: 0,
3980
+ textAlign: "right",
3981
+ overflow: "hidden",
3982
+ textOverflow: "ellipsis",
3983
+ whiteSpace: "nowrap"
3422
3984
  },
3423
3985
  count: {
3424
3986
  marginLeft: "auto",
3425
3987
  fontSize: 11,
3426
- color: "var(--sb-color-sys-text-default, CanvasText)"
3988
+ color: TEXT_DEFAULT
3427
3989
  },
3428
3990
  colorSwatch: {
3429
3991
  display: "inline-block",
3430
3992
  width: 14,
3431
3993
  height: 14,
3432
3994
  borderRadius: 3,
3433
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
3995
+ border: BORDER_DEFAULT
3434
3996
  },
3435
3997
  previewBox: {
3436
3998
  display: "inline-flex",
3437
3999
  alignItems: "center",
3438
4000
  justifyContent: "flex-end",
3439
4001
  marginLeft: "auto"
3440
- },
3441
- empty: {
3442
- padding: "24px 12px",
3443
- textAlign: "center",
3444
- color: "var(--sb-color-sys-text-muted, CanvasText)"
3445
- },
3446
- backdrop: {
3447
- position: "fixed",
3448
- inset: 0,
3449
- background: "rgba(0,0,0,0.4)",
3450
- zIndex: 1e4,
3451
- display: "flex",
3452
- alignItems: "stretch",
3453
- justifyContent: "flex-end"
3454
- },
3455
- panel: {
3456
- width: "min(560px, 100%)",
3457
- height: "100%",
3458
- overflowY: "auto",
3459
- background: "var(--sb-color-sys-surface-default, Canvas)",
3460
- color: "var(--sb-color-sys-text-default, CanvasText)",
3461
- boxShadow: "-8px 0 24px rgba(0,0,0,0.2)",
3462
- padding: 16,
3463
- position: "relative"
3464
- },
3465
- closeButton: {
3466
- position: "absolute",
3467
- top: 8,
3468
- right: 8,
3469
- width: 32,
3470
- height: 32,
3471
- borderRadius: 4,
3472
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
3473
- background: "transparent",
3474
- color: "inherit",
3475
- cursor: "pointer",
3476
- fontSize: 16,
3477
- lineHeight: 1
3478
4002
  }
3479
4003
  };
3480
- /** @internal Exported for tests; not part of the public API. */
3481
4004
  function buildTree(resolved, root) {
3482
4005
  const rootPrefix = root && root.length > 0 ? `${root}.` : "";
3483
4006
  const rootSegments = root ? root.split(".") : [];
@@ -3579,10 +4102,7 @@ function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
3579
4102
  ...chromeAliases(cssVarPrefix),
3580
4103
  ...styles$2.wrapper
3581
4104
  },
3582
- children: /* @__PURE__ */ jsx("div", {
3583
- style: styles$2.empty,
3584
- children: root ? `No tokens under "${root}".` : "No tokens in the active theme."
3585
- })
4105
+ children: /* @__PURE__ */ jsx(EmptyState, { children: root ? `No tokens under "${root}".` : "No tokens in the active theme." })
3586
4106
  });
3587
4107
  return /* @__PURE__ */ jsxs("div", {
3588
4108
  ...themeAttrs(cssVarPrefix, activeTheme),
@@ -3611,7 +4131,8 @@ function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
3611
4131
  }),
3612
4132
  selectedPath !== null && /* @__PURE__ */ jsx(DetailOverlay, {
3613
4133
  path: selectedPath,
3614
- onClose: () => setSelectedPath(null)
4134
+ onClose: () => setSelectedPath(null),
4135
+ testId: "token-navigator-overlay"
3615
4136
  })
3616
4137
  ]
3617
4138
  });
@@ -3709,12 +4230,11 @@ function LeafPreview({ path, token }) {
3709
4230
  const type = token.$type;
3710
4231
  if (type === "color") {
3711
4232
  const cssVar = makeCssVar(path, cssVarPrefix);
3712
- const formatted = formatColor(token.$value, colorFormat);
3713
4233
  return /* @__PURE__ */ jsxs("span", {
3714
4234
  style: styles$2.previewBox,
3715
4235
  children: [/* @__PURE__ */ jsx("span", {
3716
4236
  style: styles$2.value,
3717
- children: formatted?.value ?? formatValue(token.$value)
4237
+ children: formatTokenValue(token.$value, type, colorFormat)
3718
4238
  }), /* @__PURE__ */ jsx("span", {
3719
4239
  style: {
3720
4240
  ...styles$2.colorSwatch,
@@ -3729,7 +4249,7 @@ function LeafPreview({ path, token }) {
3729
4249
  style: styles$2.previewBox,
3730
4250
  children: [/* @__PURE__ */ jsx("span", {
3731
4251
  style: styles$2.value,
3732
- children: formatValue(token.$value)
4252
+ children: formatTokenValue(token.$value, type, colorFormat)
3733
4253
  }), /* @__PURE__ */ jsx("span", {
3734
4254
  style: {
3735
4255
  marginLeft: 8,
@@ -3782,37 +4302,7 @@ function LeafPreview({ path, token }) {
3782
4302
  style: styles$2.previewBox,
3783
4303
  children: /* @__PURE__ */ jsx("span", {
3784
4304
  style: styles$2.value,
3785
- children: formatValue(token.$value)
3786
- })
3787
- });
3788
- }
3789
- function DetailOverlay({ path, onClose }) {
3790
- useEffect(() => {
3791
- const onKey = (e) => {
3792
- if (e.key === "Escape") onClose();
3793
- };
3794
- window.addEventListener("keydown", onKey);
3795
- return () => window.removeEventListener("keydown", onKey);
3796
- }, [onClose]);
3797
- return /* @__PURE__ */ jsx("div", {
3798
- style: styles$2.backdrop,
3799
- onClick: onClose,
3800
- role: "presentation",
3801
- "data-testid": "token-navigator-overlay",
3802
- children: /* @__PURE__ */ jsxs("div", {
3803
- style: styles$2.panel,
3804
- onClick: (e) => e.stopPropagation(),
3805
- role: "dialog",
3806
- "aria-modal": "true",
3807
- "aria-label": `Token detail for ${path}`,
3808
- children: [/* @__PURE__ */ jsx("button", {
3809
- type: "button",
3810
- style: styles$2.closeButton,
3811
- onClick: onClose,
3812
- "aria-label": "Close",
3813
- "data-testid": "token-navigator-close",
3814
- children: "×"
3815
- }), /* @__PURE__ */ jsx(TokenDetail, { path })]
4305
+ children: formatTokenValue(token.$value, type, colorFormat)
3816
4306
  })
3817
4307
  });
3818
4308
  }
@@ -3825,13 +4315,12 @@ const styles$1 = {
3825
4315
  captionSide: "top",
3826
4316
  textAlign: "left",
3827
4317
  padding: "8px 0",
3828
- opacity: .7,
4318
+ color: TEXT_MUTED,
3829
4319
  fontSize: 12
3830
4320
  },
3831
4321
  table: {
3832
4322
  width: "100%",
3833
- borderCollapse: "collapse",
3834
- tableLayout: "fixed"
4323
+ borderCollapse: "collapse"
3835
4324
  },
3836
4325
  th: {
3837
4326
  textAlign: "left",
@@ -3839,9 +4328,12 @@ const styles$1 = {
3839
4328
  fontSize: 11,
3840
4329
  textTransform: "uppercase",
3841
4330
  letterSpacing: .5,
3842
- opacity: .6,
3843
- borderBottom: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))"
4331
+ color: TEXT_MUTED,
4332
+ borderBottom: BORDER_STRONG
3844
4333
  },
4334
+ thPath: { minWidth: 180 },
4335
+ thValue: { minWidth: 160 },
4336
+ row: { cursor: "pointer" },
3845
4337
  td: {
3846
4338
  padding: "8px 12px",
3847
4339
  borderBottom: BORDER_FAINT,
@@ -3851,48 +4343,61 @@ const styles$1 = {
3851
4343
  fontFamily: MONO_STACK,
3852
4344
  fontSize: 12
3853
4345
  },
4346
+ valueCell: {
4347
+ display: "flex",
4348
+ alignItems: "center",
4349
+ gap: 8,
4350
+ minWidth: 0,
4351
+ fontFamily: MONO_STACK,
4352
+ fontSize: 12
4353
+ },
3854
4354
  typePill: {
3855
4355
  display: "inline-block",
3856
- padding: "2px 6px",
4356
+ padding: "1px 6px",
3857
4357
  borderRadius: 4,
3858
4358
  fontSize: 10,
3859
4359
  letterSpacing: .5,
3860
4360
  textTransform: "uppercase",
3861
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
3862
- },
3863
- value: {
4361
+ background: SURFACE_MUTED,
4362
+ color: TEXT_MUTED,
3864
4363
  fontFamily: MONO_STACK,
3865
- fontSize: 12,
3866
- opacity: .85,
3867
- wordBreak: "break-all"
4364
+ flexShrink: 0
3868
4365
  },
3869
4366
  swatch: {
3870
4367
  display: "inline-block",
3871
- width: 14,
3872
- height: 14,
3873
- verticalAlign: "middle",
3874
- marginRight: 6,
4368
+ width: 16,
4369
+ height: 16,
3875
4370
  borderRadius: 3,
3876
- 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"
3877
4379
  }
3878
4380
  };
3879
- function TokenTable({ filter, type, showVar = true, caption }) {
4381
+ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", onSelect }) {
3880
4382
  const { resolved, activeTheme, cssVarPrefix } = useProject();
3881
4383
  const colorFormat = useColorFormat();
4384
+ const [selectedPath, setSelectedPath] = useState(null);
3882
4385
  const rows = useMemo(() => {
3883
- return Object.entries(resolved).filter(([path, token]) => {
4386
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
3884
4387
  if (!globMatch(path, filter)) return false;
3885
4388
  if (type && token.$type !== type) return false;
3886
4389
  return true;
3887
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => {
4390
+ }), {
4391
+ by: sortBy,
4392
+ dir: sortDir
4393
+ }).map(([path, token]) => {
3888
4394
  const isColor = token.$type === "color";
3889
4395
  const color = isColor ? formatColor(token.$value, colorFormat) : null;
3890
4396
  return {
3891
4397
  path,
3892
4398
  type: token.$type ?? "",
3893
- value: color ? color.value : formatValue(token.$value),
4399
+ value: formatTokenValue(token.$value, token.$type, colorFormat),
3894
4400
  outOfGamut: color?.outOfGamut ?? false,
3895
- description: token.$description ?? "",
3896
4401
  cssVar: makeCssVar(path, cssVarPrefix),
3897
4402
  isColor
3898
4403
  };
@@ -3902,8 +4407,14 @@ function TokenTable({ filter, type, showVar = true, caption }) {
3902
4407
  filter,
3903
4408
  type,
3904
4409
  cssVarPrefix,
3905
- colorFormat
4410
+ colorFormat,
4411
+ sortBy,
4412
+ sortDir
3906
4413
  ]);
4414
+ const handleRowClick = useCallback((path) => {
4415
+ if (onSelect) onSelect(path);
4416
+ else setSelectedPath(path);
4417
+ }, [onSelect]);
3907
4418
  const captionText = caption ?? `${rows.length} token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""}${type ? ` · $type=${type}` : ""} · ${activeTheme}`;
3908
4419
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
3909
4420
  ...themeAttrs(cssVarPrefix, activeTheme),
@@ -3916,99 +4427,89 @@ function TokenTable({ filter, type, showVar = true, caption }) {
3916
4427
  children: "No tokens match this filter."
3917
4428
  })
3918
4429
  });
3919
- return /* @__PURE__ */ jsx("div", {
4430
+ return /* @__PURE__ */ jsxs("div", {
3920
4431
  ...themeAttrs(cssVarPrefix, activeTheme),
3921
4432
  style: {
3922
4433
  ...chromeAliases(cssVarPrefix),
3923
4434
  ...styles$1.wrapper
3924
4435
  },
3925
- children: /* @__PURE__ */ jsxs("table", {
4436
+ children: [/* @__PURE__ */ jsxs("table", {
3926
4437
  style: styles$1.table,
3927
4438
  children: [
3928
4439
  /* @__PURE__ */ jsx("caption", {
3929
4440
  style: styles$1.caption,
3930
4441
  children: captionText
3931
4442
  }),
3932
- /* @__PURE__ */ jsxs("colgroup", { children: [
3933
- /* @__PURE__ */ jsx("col", { style: { width: "30%" } }),
3934
- /* @__PURE__ */ jsx("col", { style: { width: "8%" } }),
3935
- /* @__PURE__ */ jsx("col", { style: { width: showVar ? "28%" : "40%" } }),
3936
- showVar && /* @__PURE__ */ jsx("col", { style: { width: "24%" } }),
3937
- /* @__PURE__ */ jsx("col", {})
3938
- ] }),
3939
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
3940
- /* @__PURE__ */ jsx("th", {
3941
- style: styles$1.th,
3942
- children: "Path"
3943
- }),
3944
- /* @__PURE__ */ jsx("th", {
3945
- style: styles$1.th,
3946
- children: "Type"
3947
- }),
3948
- /* @__PURE__ */ jsx("th", {
3949
- style: styles$1.th,
3950
- children: "Value"
3951
- }),
3952
- showVar && /* @__PURE__ */ jsx("th", {
3953
- style: styles$1.th,
3954
- children: "CSS var"
3955
- }),
3956
- /* @__PURE__ */ jsx("th", {
3957
- style: styles$1.th,
3958
- children: "Description"
3959
- })
3960
- ] }) }),
3961
- /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", { children: [
3962
- /* @__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", {
3963
4470
  style: {
3964
4471
  ...styles$1.td,
3965
4472
  ...styles$1.path
3966
4473
  },
3967
4474
  children: row.path
3968
- }),
3969
- /* @__PURE__ */ jsx("td", {
4475
+ }), /* @__PURE__ */ jsx("td", {
3970
4476
  style: styles$1.td,
3971
- children: row.type && /* @__PURE__ */ jsx("span", {
3972
- style: styles$1.typePill,
3973
- 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
+ ]
3974
4504
  })
3975
- }),
3976
- /* @__PURE__ */ jsxs("td", {
3977
- style: {
3978
- ...styles$1.td,
3979
- ...styles$1.value
3980
- },
3981
- children: [
3982
- row.isColor && /* @__PURE__ */ jsx("span", {
3983
- style: {
3984
- ...styles$1.swatch,
3985
- background: row.cssVar
3986
- },
3987
- "aria-hidden": true
3988
- }),
3989
- /* @__PURE__ */ jsx("span", { children: row.value }),
3990
- row.outOfGamut && /* @__PURE__ */ jsx("span", {
3991
- title: "Out of sRGB gamut for this format",
3992
- "aria-label": "out of gamut",
3993
- style: { marginLeft: 6 },
3994
- children: "⚠"
3995
- })
3996
- ]
3997
- }),
3998
- showVar && /* @__PURE__ */ jsx("td", {
3999
- style: {
4000
- ...styles$1.td,
4001
- ...styles$1.value
4002
- },
4003
- children: row.cssVar
4004
- }),
4005
- /* @__PURE__ */ jsx("td", {
4006
- style: styles$1.td,
4007
- children: row.description
4008
- })
4009
- ] }, row.path)) })
4505
+ })]
4506
+ }, row.path)) })
4010
4507
  ]
4011
- })
4508
+ }), selectedPath !== null && /* @__PURE__ */ jsx(DetailOverlay, {
4509
+ path: selectedPath,
4510
+ onClose: () => setSelectedPath(null),
4511
+ testId: "token-table-overlay"
4512
+ })]
4012
4513
  });
4013
4514
  }
4014
4515
  //#endregion
@@ -4074,13 +4575,16 @@ function buildRow(path, composite) {
4074
4575
  ].filter(Boolean).join(" · ")
4075
4576
  };
4076
4577
  }
4077
- 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" }) {
4078
4579
  const { resolved, activeTheme, cssVarPrefix } = useProject();
4079
4580
  const rows = useMemo(() => {
4080
- return Object.entries(resolved).filter(([path, token]) => {
4581
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
4081
4582
  if (token.$type !== "typography") return false;
4082
4583
  return globMatch(path, filter);
4083
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => {
4584
+ }), {
4585
+ by: sortBy,
4586
+ dir: sortDir
4587
+ }).map(([path, token]) => {
4084
4588
  const value = token.$value;
4085
4589
  if (!value || typeof value !== "object") return {
4086
4590
  path,
@@ -4089,7 +4593,12 @@ function TypographyScale({ filter = "typography", sample = "The quick brown fox
4089
4593
  };
4090
4594
  return buildRow(path, value);
4091
4595
  });
4092
- }, [resolved, filter]);
4596
+ }, [
4597
+ resolved,
4598
+ filter,
4599
+ sortBy,
4600
+ sortDir
4601
+ ]);
4093
4602
  const captionText = caption ?? `${rows.length} typography token${rows.length === 1 ? "" : "s"}${filter && filter !== "typography" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
4094
4603
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
4095
4604
  ...themeAttrs(cssVarPrefix, activeTheme),
@@ -4130,6 +4639,6 @@ function TypographyScale({ filter = "typography", sample = "The quick brown fox
4130
4639
  });
4131
4640
  }
4132
4641
  //#endregion
4133
- 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 };
4134
4643
 
4135
4644
  //# sourceMappingURL=index.mjs.map