@unpunnyfuns/swatchbook-blocks 0.2.2 → 0.4.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,10 @@
1
+ import './style.css';
1
2
  import Color from "colorjs.io";
2
3
  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
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { addons } from "storybook/preview-api";
6
+ import { axes, css, cssVarPrefix, defaultTheme, diagnostics, themes, themesResolved } from "virtual:swatchbook/tokens";
7
+ import cx from "clsx";
6
8
  //#region src/format-color.ts
7
9
  const COLOR_FORMATS = [
8
10
  "hex",
@@ -178,59 +180,22 @@ function stringifyFallback(value, fallback) {
178
180
  return fallback;
179
181
  }
180
182
  //#endregion
181
- //#region src/internal/data-attr.ts
182
- /**
183
- * Produce a prefixed `data-*` attribute name when `prefix` is set, bare
184
- * `data-<key>` otherwise. Mirrors `dataAttr` in `@unpunnyfuns/swatchbook-core`
185
- * so block wrappers and emitted-CSS selectors stay in lockstep without
186
- * blocks taking a runtime dep on core.
187
- */
188
- function dataAttr(prefix, key) {
189
- return prefix ? `data-${prefix}-${key}` : `data-${key}`;
190
- }
183
+ //#region src/internal/styles.tsx
184
+ const TEXT_MUTED = "var(--swatchbook-text-muted, CanvasText)";
185
+ const SURFACE_RAISED = "var(--swatchbook-surface-raised, Canvas)";
186
+ const SURFACE_MUTED = "var(--swatchbook-surface-muted, rgba(128,128,128,0.15))";
187
+ const BORDER_FAINT = `1px solid var(--swatchbook-border-default, rgba(128,128,128,0.15))`;
188
+ const BORDER_STRONG = `1px solid var(--swatchbook-border-default, rgba(128,128,128,0.3))`;
191
189
  /**
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)} />`.
190
+ * Inner content for a block's "nothing to render" state. Call sites wrap
191
+ * it in their own block wrapper (which already carries `themeAttrs`), so
192
+ * the message itself just needs the muted type.
195
193
  */
196
- function themeAttrs(prefix, themeName) {
197
- return { [dataAttr(prefix, "theme")]: themeName };
198
- }
199
- /**
200
- * Vars block chrome reads by literal `var(--sb-*)` regardless of what the
201
- * project's actual `cssVarPrefix` is. Keeping the literal spelling means we
202
- * only need to alias these few names on each block wrapper; the bulk of
203
- * block styling stays untouched.
204
- */
205
- const CHROME_VARS = [
206
- "color-sys-border-default",
207
- "color-sys-surface-default",
208
- "color-sys-surface-muted",
209
- "color-sys-surface-raised",
210
- "color-sys-text-default",
211
- "color-sys-text-muted",
212
- "color-sys-accent-bg",
213
- "color-sys-accent-fg",
214
- "typography-sys-body-font-family",
215
- "typography-sys-body-font-size"
216
- ];
217
- /**
218
- * CSS custom-property aliases that redirect the block chrome's `var(--sb-*)`
219
- * reads to the project's actual `cssVarPrefix`. Block components spread the
220
- * return into their wrapper's inline `style`, so the aliases cascade down
221
- * into every nested block / sample / token-detail piece without each one
222
- * re-reading the prefix.
223
- *
224
- * When `prefix === 'sb'` the aliases are self-references — we skip emission
225
- * to keep the inline style shorter. When `prefix === ''` (opt-out) the
226
- * aliases point at the bare `var(--color-sys-…)` names core emitted.
227
- */
228
- function chromeAliases(prefix) {
229
- if (prefix === "sb") return {};
230
- const out = {};
231
- const head = prefix ? `${prefix}-` : "";
232
- for (const name of CHROME_VARS) out[`--sb-${name}`] = `var(--${head}${name})`;
233
- return out;
194
+ function EmptyState({ children }) {
195
+ return /* @__PURE__ */ jsx("div", {
196
+ className: "sb-block__empty",
197
+ children
198
+ });
234
199
  }
235
200
  //#endregion
236
201
  //#region src/internal/channel-globals.ts
@@ -397,6 +362,7 @@ function snapshotToData(snapshot) {
397
362
  themes: snapshot.themes,
398
363
  themesResolved: snapshot.themesResolved,
399
364
  resolved: snapshot.themesResolved[snapshot.activeTheme] ?? {},
365
+ diagnostics: snapshot.diagnostics,
400
366
  cssVarPrefix: snapshot.cssVarPrefix
401
367
  };
402
368
  }
@@ -437,6 +403,7 @@ function useVirtualModuleFallback(enabled) {
437
403
  themes,
438
404
  themesResolved,
439
405
  resolved: themesResolved[activeTheme] ?? {},
406
+ diagnostics,
440
407
  cssVarPrefix
441
408
  };
442
409
  }
@@ -469,23 +436,12 @@ function globMatch(path, glob) {
469
436
  if (glob.endsWith("**")) return path.startsWith(glob.slice(0, -2));
470
437
  return path === glob || path.startsWith(`${glob}.`);
471
438
  }
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
439
  //#endregion
484
440
  //#region src/border-preview/BorderSample.tsx
485
441
  const sampleStyle$1 = {
486
442
  width: 120,
487
443
  height: 56,
488
- background: "var(--sb-color-sys-surface-raised, transparent)",
444
+ background: SURFACE_RAISED,
489
445
  borderRadius: 6
490
446
  };
491
447
  function BorderSample({ path }) {
@@ -493,7 +449,6 @@ function BorderSample({ path }) {
493
449
  const cssVar = makeCssVar(path, cssVarPrefix);
494
450
  return /* @__PURE__ */ jsx("div", {
495
451
  style: {
496
- ...chromeAliases(cssVarPrefix),
497
452
  ...sampleStyle$1,
498
453
  border: cssVar
499
454
  },
@@ -501,76 +456,156 @@ function BorderSample({ path }) {
501
456
  });
502
457
  }
503
458
  //#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
- };
459
+ //#region src/internal/data-attr.ts
460
+ /**
461
+ * Produce a prefixed `data-*` attribute name when `prefix` is set, bare
462
+ * `data-<key>` otherwise. Mirrors `dataAttr` in `@unpunnyfuns/swatchbook-core`
463
+ * so block wrappers and emitted-CSS selectors stay in lockstep without
464
+ * blocks taking a runtime dep on core.
465
+ */
466
+ function dataAttr(prefix, key) {
467
+ return prefix ? `data-${prefix}-${key}` : `data-${key}`;
468
+ }
469
+ /**
470
+ * Marker attribute set on every block wrapper. Retained as a stable hook
471
+ * for consumer-side selectors (e.g. when a host app wants to target or
472
+ * override block chrome without relying on hashed class names).
473
+ */
474
+ const BLOCK_ATTR = "data-swatchbook-block";
475
+ /**
476
+ * Opt-out class that Storybook's `.sbdocs` stylesheet uses to self-exclude
477
+ * on MDX docs pages — every `.sbdocs` house rule is wrapped in
478
+ * `:not(.sb-unstyled, .sb-unstyled *)`, so any descendant of a `.sb-unstyled`
479
+ * container is left alone. Stamped onto every block wrapper so blocks
480
+ * render identically in MDX docs and regular stories without fighting
481
+ * cascade specificity.
482
+ */
483
+ const WRAPPER_CLASSES = "sb-unstyled sb-block";
484
+ /**
485
+ * Spread helper for the common block wrapper. Returns:
486
+ * - `data-<prefix>-theme="<composed theme name>"` — so theme-keyed CSS
487
+ * emitted by `@unpunnyfuns/swatchbook-core` resolves against this
488
+ * subtree.
489
+ * - `data-swatchbook-block` — stable consumer hook for targeting block
490
+ * chrome from outside.
491
+ * - `className="sb-unstyled sb-block"` — Storybook's opt-out class so
492
+ * MDX docs house styles self-exclude the subtree, plus `sb-block`
493
+ * which carries the shared chrome from `internal/styles.css`.
494
+ */
495
+ function themeAttrs(prefix, themeName) {
496
+ return {
497
+ [dataAttr(prefix, "theme")]: themeName,
498
+ [BLOCK_ATTR]: "",
499
+ className: WRAPPER_CLASSES
500
+ };
501
+ }
502
+ //#endregion
503
+ //#region src/internal/sort-tokens.ts
504
+ /**
505
+ * Stable sort for a filtered `[path, token][]` list.
506
+ *
507
+ * `sortBy: 'path'` — lexicographic on the dot-path (locale-aware, numeric).
508
+ * `sortBy: 'value'` — per-`$type` ordering:
509
+ * - `dimension` / `duration` → numeric pixels / ms (via `toMagnitude`).
510
+ * - `fontWeight` / `opacity` / `number` / `lineHeight` → numeric.
511
+ * - `color` → perceptual by oklch L → C → H.
512
+ * - `fontFamily` / `strokeStyle` (string form) → lexicographic.
513
+ * - Composites (`typography`, `shadow`, `border`, `gradient`, `transition`) →
514
+ * fall back to path-alpha. No useful single-axis order.
515
+ * `sortBy: 'none'` — preserve input order (still respects `sortDir: 'desc'`
516
+ * as a reverse).
517
+ */
518
+ function sortTokens(entries, options = {}) {
519
+ const by = options.by ?? "path";
520
+ const dir = options.dir ?? "asc";
521
+ const sign = dir === "desc" ? -1 : 1;
522
+ if (by === "none") return dir === "desc" ? [...entries].toReversed() : [...entries];
523
+ if (by === "path") return [...entries].toSorted(([a], [b]) => sign * a.localeCompare(b, void 0, { numeric: true }));
524
+ return [...entries].toSorted(([aPath, aTok], [bPath, bTok]) => {
525
+ const cmp = compareValue(aTok, bTok);
526
+ if (cmp !== 0) return sign * cmp;
527
+ return sign * aPath.localeCompare(bPath, void 0, { numeric: true });
528
+ });
529
+ }
530
+ function compareValue(a, b) {
531
+ const type = a.$type;
532
+ if (type !== b.$type) return String(type ?? "").localeCompare(String(b.$type ?? ""));
533
+ if (!type) return 0;
534
+ if (type === "dimension" || type === "duration" || type === "fontWeight" || type === "opacity" || type === "number" || type === "lineHeight") {
535
+ const av = toMagnitude(a.$value);
536
+ const bv = toMagnitude(b.$value);
537
+ if (Number.isFinite(av) && Number.isFinite(bv)) return av - bv;
538
+ if (Number.isFinite(av)) return -1;
539
+ if (Number.isFinite(bv)) return 1;
540
+ return 0;
541
+ }
542
+ if (type === "color") {
543
+ const ak = colorKey(a.$value);
544
+ const bk = colorKey(b.$value);
545
+ if (!ak && !bk) return 0;
546
+ if (!ak) return 1;
547
+ if (!bk) return -1;
548
+ if (ak.l !== bk.l) return ak.l - bk.l;
549
+ if (ak.c !== bk.c) return ak.c - bk.c;
550
+ return ak.h - bk.h;
551
+ }
552
+ if (type === "fontFamily" || type === "strokeStyle") {
553
+ const as = toDisplayable(a.$value);
554
+ const bs = toDisplayable(b.$value);
555
+ return as.localeCompare(bs, void 0, { numeric: true });
556
+ }
557
+ return 0;
558
+ }
559
+ function toMagnitude(v) {
560
+ if (typeof v === "number") return v;
561
+ if (v && typeof v === "object") {
562
+ const d = v;
563
+ if (typeof d.value !== "number") return NaN;
564
+ if (typeof d.unit !== "string") return d.value;
565
+ switch (d.unit) {
566
+ case "px":
567
+ case "ms": return d.value;
568
+ case "s": return d.value * 1e3;
569
+ case "rem":
570
+ case "em": return d.value * 16;
571
+ default: return d.value;
572
+ }
573
+ }
574
+ return NaN;
575
+ }
576
+ function colorKey(v) {
577
+ if (!v || typeof v !== "object") return null;
578
+ try {
579
+ const c = v;
580
+ let source;
581
+ if (typeof c.hex === "string") source = c.hex;
582
+ else if (typeof c.colorSpace === "string") {
583
+ const channels = Array.isArray(c.components) ? c.components : Array.isArray(c.channels) ? c.channels : void 0;
584
+ if (!channels) return null;
585
+ source = {
586
+ space: c.colorSpace,
587
+ coords: channels
588
+ };
589
+ } else return null;
590
+ const [l, chroma, h] = new Color(source).to("oklch").coords;
591
+ return {
592
+ l: Number.isFinite(l) ? l : 0,
593
+ c: Number.isFinite(chroma) ? chroma : 0,
594
+ h: Number.isFinite(h) ? h : 0
595
+ };
596
+ } catch {
597
+ return null;
598
+ }
599
+ }
600
+ function toDisplayable(v) {
601
+ if (typeof v === "string") return v;
602
+ if (Array.isArray(v)) return v.map(String).join(", ");
603
+ if (v && typeof v === "object") return JSON.stringify(v);
604
+ return String(v ?? "");
605
+ }
526
606
  //#endregion
527
607
  //#region src/BorderPreview.tsx
528
- const styles$14 = {
529
- wrapper: surfaceStyle,
530
- caption: captionStyle,
531
- empty: emptyStyle,
532
- row: {
533
- display: "grid",
534
- gridTemplateColumns: "minmax(160px, 220px) 140px 1fr",
535
- gap: 16,
536
- alignItems: "center",
537
- padding: "14px 0",
538
- borderBottom: BORDER_DEFAULT
539
- },
540
- meta: {
541
- display: "flex",
542
- flexDirection: "column",
543
- gap: 2,
544
- minWidth: 0
545
- },
546
- path: {
547
- fontFamily: MONO_STACK,
548
- fontSize: 12,
549
- overflow: "hidden",
550
- textOverflow: "ellipsis",
551
- whiteSpace: "nowrap"
552
- },
553
- cssVar: {
554
- fontFamily: MONO_STACK,
555
- fontSize: 11,
556
- opacity: .7
557
- },
558
- sampleCell: {
559
- display: "flex",
560
- alignItems: "center",
561
- justifyContent: "center"
562
- },
563
- breakdown: {
564
- fontFamily: MONO_STACK,
565
- fontSize: 11,
566
- display: "grid",
567
- gridTemplateColumns: "auto 1fr",
568
- columnGap: 12,
569
- rowGap: 2
570
- },
571
- breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" }
572
- };
573
- function formatDimension$1(raw) {
608
+ function formatDimension$2(raw) {
574
609
  if (raw == null) return "—";
575
610
  if (typeof raw === "number") return String(raw);
576
611
  if (typeof raw === "string") return raw;
@@ -593,79 +628,72 @@ function formatColor$2(raw) {
593
628
  }
594
629
  return JSON.stringify(raw);
595
630
  }
596
- function BorderPreview({ filter = "border", caption }) {
631
+ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
597
632
  const { resolved, activeTheme, cssVarPrefix } = useProject();
598
633
  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;
634
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
635
+ if (token.$type !== "border") return false;
636
+ return globMatch(path, filter);
637
+ }), {
638
+ by: sortBy,
639
+ dir: sortDir
640
+ }).map(([path, token]) => ({
641
+ path,
642
+ cssVar: makeCssVar(path, cssVarPrefix),
643
+ value: token.$value ?? {}
644
+ }));
611
645
  }, [
612
646
  resolved,
613
647
  filter,
614
- cssVarPrefix
648
+ cssVarPrefix,
649
+ sortBy,
650
+ sortDir
615
651
  ]);
616
652
  const captionText = caption ?? `${rows.length} border${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
617
653
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
618
654
  ...themeAttrs(cssVarPrefix, activeTheme),
619
- style: {
620
- ...chromeAliases(cssVarPrefix),
621
- ...styles$14.wrapper
622
- },
623
655
  children: /* @__PURE__ */ jsx("div", {
624
- style: styles$14.empty,
656
+ className: "sb-block__empty",
625
657
  children: "No border tokens match this filter."
626
658
  })
627
659
  });
628
660
  return /* @__PURE__ */ jsxs("div", {
629
661
  ...themeAttrs(cssVarPrefix, activeTheme),
630
- style: {
631
- ...chromeAliases(cssVarPrefix),
632
- ...styles$14.wrapper
633
- },
634
662
  children: [/* @__PURE__ */ jsx("div", {
635
- style: styles$14.caption,
663
+ className: "sb-block__caption",
636
664
  children: captionText
637
665
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
638
- style: styles$14.row,
666
+ className: "sb-border-preview__row",
639
667
  children: [
640
668
  /* @__PURE__ */ jsxs("div", {
641
- style: styles$14.meta,
669
+ className: "sb-border-preview__meta",
642
670
  children: [/* @__PURE__ */ jsx("span", {
643
- style: styles$14.path,
671
+ className: "sb-border-preview__path",
644
672
  children: row.path
645
673
  }), /* @__PURE__ */ jsx("span", {
646
- style: styles$14.cssVar,
674
+ className: "sb-border-preview__css-var",
647
675
  children: row.cssVar
648
676
  })]
649
677
  }),
650
678
  /* @__PURE__ */ jsx("div", {
651
- style: styles$14.sampleCell,
679
+ className: "sb-border-preview__sample-cell",
652
680
  children: /* @__PURE__ */ jsx(BorderSample, { path: row.path })
653
681
  }),
654
682
  /* @__PURE__ */ jsxs("div", {
655
- style: styles$14.breakdown,
683
+ className: "sb-border-preview__breakdown",
656
684
  children: [
657
685
  /* @__PURE__ */ jsx("span", {
658
- style: styles$14.breakdownKey,
686
+ className: "sb-border-preview__breakdown-key",
659
687
  children: "width"
660
688
  }),
661
- /* @__PURE__ */ jsx("span", { children: formatDimension$1(row.value.width) }),
689
+ /* @__PURE__ */ jsx("span", { children: formatDimension$2(row.value.width) }),
662
690
  /* @__PURE__ */ jsx("span", {
663
- style: styles$14.breakdownKey,
691
+ className: "sb-border-preview__breakdown-key",
664
692
  children: "style"
665
693
  }),
666
694
  /* @__PURE__ */ jsx("span", { children: row.value.style != null ? String(row.value.style) : "—" }),
667
695
  /* @__PURE__ */ jsx("span", {
668
- style: styles$14.breakdownKey,
696
+ className: "sb-border-preview__breakdown-key",
669
697
  children: "color"
670
698
  }),
671
699
  /* @__PURE__ */ jsx("span", { children: formatColor$2(row.value.color) })
@@ -677,57 +705,9 @@ function BorderPreview({ filter = "border", caption }) {
677
705
  }
678
706
  //#endregion
679
707
  //#region src/ColorPalette.tsx
680
- const styles$13 = {
681
- wrapper: surfaceStyle,
682
- caption: captionStyle,
683
- empty: emptyStyle,
684
- group: { marginBottom: 20 },
685
- groupHeader: {
686
- fontFamily: MONO_STACK,
687
- fontSize: 11,
688
- textTransform: "uppercase",
689
- letterSpacing: .5,
690
- opacity: .6,
691
- marginBottom: 8
692
- },
693
- grid: {
694
- display: "grid",
695
- gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))",
696
- gap: 8
697
- },
698
- card: {
699
- border: BORDER_DEFAULT,
700
- borderRadius: 6,
701
- overflow: "hidden",
702
- display: "flex",
703
- flexDirection: "column"
704
- },
705
- swatch: {
706
- height: 56,
707
- width: "100%",
708
- borderBottom: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.08))"
709
- },
710
- meta: {
711
- padding: "8px 10px",
712
- display: "flex",
713
- flexDirection: "column",
714
- gap: 2
715
- },
716
- leaf: {
717
- fontFamily: MONO_STACK,
718
- fontSize: 12
719
- },
720
- value: {
721
- fontFamily: MONO_STACK,
722
- fontSize: 11,
723
- opacity: .7
724
- }
725
- };
726
708
  /**
727
709
  * Count segments in the filter before the first glob (`*` / `**`).
728
710
  * `color.ref.*` → 2; `color.sys.surface.*` → 3; `color` → 1; undefined → 0.
729
- *
730
- * @internal Exported for tests; not part of the public API.
731
711
  */
732
712
  function fixedPrefixLength(filter) {
733
713
  if (!filter) return 0;
@@ -739,22 +719,18 @@ function fixedPrefixLength(filter) {
739
719
  }
740
720
  return fixed;
741
721
  }
742
- function ColorPalette({ filter = "color", groupBy, caption }) {
722
+ function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "asc" }) {
743
723
  const { resolved, activeTheme, cssVarPrefix } = useProject();
744
724
  const colorFormat = useColorFormat();
745
725
  const groups = useMemo(() => {
746
- const entries = Object.entries(resolved).filter(([path, token]) => {
726
+ const entries = sortTokens(Object.entries(resolved).filter(([path, token]) => {
747
727
  if (token.$type !== "color") return false;
748
728
  return globMatch(path, filter);
749
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
729
+ }), {
730
+ by: sortBy,
731
+ dir: sortDir
732
+ });
750
733
  const maxDepth = entries.reduce((m, [p]) => Math.max(m, p.split(".").length), 0);
751
- /**
752
- * Auto-derive: group one level below the filter's fixed prefix, but
753
- * clamp so each swatch retains at least one leaf segment. A filter
754
- * like `color.ref.blue.*` (fixed length 3) with only 4-segment tokens
755
- * would try groupBy=4 → one-per-group; clamp to `maxDepth - 1` so the
756
- * whole ramp lands in one group with each shade as a leaf.
757
- */
758
734
  const effectiveGroupBy = groupBy ?? Math.min(fixedPrefixLength(filter) + 1, Math.max(maxDepth - 1, 1));
759
735
  const bucket = /* @__PURE__ */ new Map();
760
736
  for (const [path, token] of entries) {
@@ -778,56 +754,48 @@ function ColorPalette({ filter = "color", groupBy, caption }) {
778
754
  filter,
779
755
  groupBy,
780
756
  cssVarPrefix,
781
- colorFormat
757
+ colorFormat,
758
+ sortBy,
759
+ sortDir
782
760
  ]);
783
761
  const totalCount = groups.reduce((acc, [, swatches]) => acc + swatches.length, 0);
784
762
  const captionText = caption ?? `${totalCount} color${totalCount === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
785
763
  if (totalCount === 0) return /* @__PURE__ */ jsx("div", {
786
764
  ...themeAttrs(cssVarPrefix, activeTheme),
787
- style: {
788
- ...chromeAliases(cssVarPrefix),
789
- ...styles$13.wrapper
790
- },
791
765
  children: /* @__PURE__ */ jsx("div", {
792
- style: styles$13.empty,
766
+ className: "sb-block__empty",
793
767
  children: "No color tokens match this filter."
794
768
  })
795
769
  });
796
770
  return /* @__PURE__ */ jsxs("div", {
797
771
  ...themeAttrs(cssVarPrefix, activeTheme),
798
- style: {
799
- ...chromeAliases(cssVarPrefix),
800
- ...styles$13.wrapper
801
- },
802
772
  children: [/* @__PURE__ */ jsx("div", {
803
- style: styles$13.caption,
773
+ className: "sb-block__caption",
804
774
  children: captionText
805
775
  }), groups.map(([group, swatches]) => /* @__PURE__ */ jsxs("section", {
806
- style: styles$13.group,
776
+ className: "sb-color-palette__group",
807
777
  children: [/* @__PURE__ */ jsx("div", {
808
- style: styles$13.groupHeader,
778
+ className: "sb-color-palette__group-header",
809
779
  children: group
810
780
  }), /* @__PURE__ */ jsx("div", {
811
- style: styles$13.grid,
781
+ className: "sb-color-palette__grid",
812
782
  children: swatches.map((swatch) => /* @__PURE__ */ jsxs("div", {
813
- style: styles$13.card,
783
+ className: "sb-color-palette__card",
814
784
  children: [/* @__PURE__ */ jsx("div", {
815
- style: {
816
- ...styles$13.swatch,
817
- background: swatch.cssVar
818
- },
785
+ className: "sb-color-palette__swatch",
786
+ style: { background: swatch.cssVar },
819
787
  "aria-hidden": true
820
788
  }), /* @__PURE__ */ jsxs("div", {
821
- style: styles$13.meta,
789
+ className: "sb-color-palette__meta",
822
790
  children: [/* @__PURE__ */ jsx("span", {
823
- style: styles$13.leaf,
791
+ className: "sb-color-palette__leaf",
824
792
  children: swatch.leaf
825
793
  }), /* @__PURE__ */ jsxs("span", {
826
- style: styles$13.value,
794
+ className: "sb-color-palette__value",
827
795
  children: [swatch.value, swatch.outOfGamut && /* @__PURE__ */ jsxs("span", {
828
796
  title: "Out of sRGB gamut for this format",
829
797
  "aria-label": "out of gamut",
830
- style: { marginLeft: 4 },
798
+ className: "sb-color-palette__gamut-warn",
831
799
  children: [" ", "⚠"]
832
800
  })]
833
801
  })]
@@ -838,24 +806,97 @@ function ColorPalette({ filter = "color", groupBy, caption }) {
838
806
  });
839
807
  }
840
808
  //#endregion
809
+ //#region src/Diagnostics.tsx
810
+ const severityLabel = {
811
+ error: "ERROR",
812
+ warn: "WARN",
813
+ info: "INFO"
814
+ };
815
+ function summaryText(diagnostics) {
816
+ if (diagnostics.length === 0) return "✔ OK · no diagnostics";
817
+ const counts = {
818
+ error: 0,
819
+ warn: 0,
820
+ info: 0
821
+ };
822
+ for (const d of diagnostics) counts[d.severity] += 1;
823
+ const parts = [];
824
+ if (counts.error > 0) parts.push(`✖ ${counts.error} error${counts.error === 1 ? "" : "s"}`);
825
+ if (counts.warn > 0) parts.push(`⚠ ${counts.warn} warning${counts.warn === 1 ? "" : "s"}`);
826
+ if (counts.info > 0) parts.push(`${counts.info} info`);
827
+ return parts.join(" · ");
828
+ }
829
+ function diagnosticKey(d, i) {
830
+ return `${d.severity}:${d.group}:${d.filename ?? ""}:${d.line ?? ""}:${d.message}:${i}`;
831
+ }
832
+ function summaryVariant(diagnostics) {
833
+ if (diagnostics.length === 0) return "ok";
834
+ if (diagnostics.some((d) => d.severity === "error")) return "error";
835
+ if (diagnostics.some((d) => d.severity === "warn")) return "warn";
836
+ return null;
837
+ }
838
+ /**
839
+ * Render the project's load diagnostics — parser errors, resolver warnings,
840
+ * disabled-axes validation issues, etc. — as a collapsible list. Auto-opens
841
+ * when the project carries errors or warnings; stays collapsed for clean
842
+ * loads and info-only loads.
843
+ *
844
+ * Replaces the diagnostics section from the addon's (now-retired) Design
845
+ * Tokens panel. Consumers compose it alongside TokenNavigator / TokenTable
846
+ * on their own MDX pages.
847
+ */
848
+ function Diagnostics({ caption } = {}) {
849
+ const { activeTheme, cssVarPrefix, diagnostics } = useProject();
850
+ const hasErrorsOrWarnings = diagnostics.some((d) => d.severity === "error" || d.severity === "warn");
851
+ const headingText = caption ?? `Diagnostics · ${summaryText(diagnostics)}`;
852
+ const variant = summaryVariant(diagnostics);
853
+ return /* @__PURE__ */ jsx("div", {
854
+ ...themeAttrs(cssVarPrefix, activeTheme),
855
+ "data-testid": "diagnostics",
856
+ children: /* @__PURE__ */ jsxs("details", {
857
+ open: hasErrorsOrWarnings,
858
+ children: [/* @__PURE__ */ jsx("summary", {
859
+ className: cx("sb-diagnostics__summary", variant && `sb-diagnostics__summary--${variant}`),
860
+ children: headingText
861
+ }), diagnostics.length > 0 && /* @__PURE__ */ jsx("ul", {
862
+ className: "sb-diagnostics__list",
863
+ children: diagnostics.map((d, i) => /* @__PURE__ */ jsxs("li", {
864
+ className: "sb-diagnostics__row",
865
+ children: [/* @__PURE__ */ jsx("span", {
866
+ className: cx("sb-diagnostics__label", { [`sb-diagnostics__label--${d.severity}`]: d.severity !== "info" }),
867
+ children: severityLabel[d.severity]
868
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: d.message }), (d.group || d.filename) && /* @__PURE__ */ jsx("div", {
869
+ className: "sb-diagnostics__meta",
870
+ children: [
871
+ d.group,
872
+ d.filename,
873
+ d.line ? `:${d.line}` : ""
874
+ ].filter(Boolean).join(" · ")
875
+ })] })]
876
+ }, diagnosticKey(d, i)))
877
+ })]
878
+ })
879
+ });
880
+ }
881
+ //#endregion
841
882
  //#region src/dimension-scale/DimensionBar.tsx
842
883
  const MAX_RENDER_PX$1 = 480;
843
- const styles$12 = {
884
+ const styles$1 = {
844
885
  bar: {
845
886
  height: 14,
846
- background: "var(--sb-color-sys-accent-bg, #3b82f6)",
887
+ background: "var(--swatchbook-accent-bg, #3b82f6)",
847
888
  borderRadius: 2,
848
889
  minWidth: 1
849
890
  },
850
891
  radiusSample: {
851
892
  width: 56,
852
893
  height: 56,
853
- background: "var(--sb-color-sys-accent-bg, #3b82f6)",
854
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))"
894
+ background: "var(--swatchbook-accent-bg, #3b82f6)",
895
+ border: BORDER_STRONG
855
896
  },
856
897
  sizeSample: {
857
- background: "var(--sb-color-sys-accent-bg, #3b82f6)",
858
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
898
+ background: "var(--swatchbook-accent-bg, #3b82f6)",
899
+ border: BORDER_STRONG,
859
900
  minWidth: 1,
860
901
  minHeight: 1
861
902
  }
@@ -883,20 +924,17 @@ function DimensionBar({ path, kind = "length" }) {
883
924
  const token = resolved[path];
884
925
  const pxValue = toPixels$1(token?.$value);
885
926
  const cappedValue = Number.isFinite(pxValue) && pxValue > MAX_RENDER_PX$1 ? `${MAX_RENDER_PX$1}px` : cssVar;
886
- const aliases = chromeAliases(cssVarPrefix);
887
927
  switch (kind) {
888
928
  case "radius": return /* @__PURE__ */ jsx("div", {
889
929
  style: {
890
- ...aliases,
891
- ...styles$12.radiusSample,
930
+ ...styles$1.radiusSample,
892
931
  borderRadius: cssVar
893
932
  },
894
933
  "aria-hidden": true
895
934
  });
896
935
  case "size": return /* @__PURE__ */ jsx("div", {
897
936
  style: {
898
- ...aliases,
899
- ...styles$12.sizeSample,
937
+ ...styles$1.sizeSample,
900
938
  width: cappedValue,
901
939
  height: cappedValue
902
940
  },
@@ -904,8 +942,7 @@ function DimensionBar({ path, kind = "length" }) {
904
942
  });
905
943
  default: return /* @__PURE__ */ jsx("div", {
906
944
  style: {
907
- ...aliases,
908
- ...styles$12.bar,
945
+ ...styles$1.bar,
909
946
  width: cappedValue
910
947
  },
911
948
  "aria-hidden": true
@@ -913,60 +950,150 @@ function DimensionBar({ path, kind = "length" }) {
913
950
  }
914
951
  }
915
952
  //#endregion
916
- //#region src/DimensionScale.tsx
917
- const MAX_RENDER_PX = 480;
918
- const styles$11 = {
919
- wrapper: surfaceStyle,
920
- caption: captionStyle,
921
- empty: emptyStyle,
922
- row: {
923
- display: "grid",
924
- gridTemplateColumns: "minmax(160px, 220px) 1fr auto",
925
- gap: 16,
926
- alignItems: "center",
927
- padding: "10px 0",
928
- borderBottom: BORDER_DEFAULT
929
- },
930
- meta: {
931
- display: "flex",
932
- flexDirection: "column",
933
- gap: 2,
934
- minWidth: 0
935
- },
936
- path: {
937
- fontFamily: MONO_STACK,
938
- fontSize: 12,
939
- overflow: "hidden",
940
- textOverflow: "ellipsis",
941
- whiteSpace: "nowrap"
942
- },
943
- specs: {
944
- fontFamily: MONO_STACK,
945
- fontSize: 11,
946
- opacity: .7
947
- },
948
- visualCell: {
949
- display: "flex",
950
- alignItems: "center",
951
- minWidth: 0
952
- },
953
- cssVar: {
954
- fontFamily: MONO_STACK,
955
- fontSize: 11,
956
- opacity: .7,
957
- whiteSpace: "nowrap"
958
- },
959
- cap: {
960
- fontFamily: MONO_STACK,
961
- fontSize: 10,
962
- opacity: .6,
963
- marginLeft: 6
964
- }
965
- };
953
+ //#region src/internal/format-token-value.ts
966
954
  /**
967
- * Convert a DTCG dimension `$value` (`{ value, unit }`) to pixels for the
968
- * purpose of ordering and deciding whether to show a cap indicator.
955
+ * Produce a single-line display string for any DTCG token `$value`,
956
+ * respecting the active color format for color-typed tokens and the
957
+ * color sub-values of composite types (border, shadow, gradient).
958
+ *
959
+ * Replaces the old `formatValue` one-shot that hex-short-circuited
960
+ * colors and fell through to raw JSON for known composites.
961
+ *
962
+ * Shape by type:
963
+ * - `color` → `formatColor(value, colorFormat)` — e.g. `#3b82f6`, `oklch(...)`, `raw` JSON.
964
+ * - `dimension|duration` → `value + unit` — e.g. `16px`, `200ms`.
965
+ * - `fontFamily` → string or array joined with `, `.
966
+ * - `fontWeight` → primitive.
967
+ * - `cubicBezier` → `cubic-bezier(a, b, c, d)`.
968
+ * - `strokeStyle` → primitive string, or `dashed · dashArray · lineCap` when it's the object shape.
969
+ * - `shadow` → one or more `offsetX offsetY blur spread color` layers joined with `, `.
970
+ * - `border` → `width style color`.
971
+ * - `transition` → `duration easing [delay]`.
972
+ * - `typography` → `family / size / line-height / weight`.
973
+ * - `gradient` → `stops joined with →` — compact representation, not a CSS gradient string (those live in GradientPalette's preview).
974
+ *
975
+ * Unknown object shapes fall through to truncated JSON.
969
976
  */
977
+ function formatTokenValue(value, $type, colorFormat) {
978
+ if (value == null) return "";
979
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
980
+ switch ($type) {
981
+ case "color": return formatColor(value, colorFormat).value;
982
+ case "dimension":
983
+ case "duration": return formatDimension$1(value);
984
+ case "fontFamily": return formatFontFamily$1(value);
985
+ case "fontWeight":
986
+ case "lineHeight":
987
+ case "letterSpacing":
988
+ case "opacity":
989
+ case "number": return formatPrimitive$1(value);
990
+ case "cubicBezier": return formatCubicBezier(value);
991
+ case "strokeStyle": return formatStrokeStyle(value);
992
+ case "shadow": return formatShadow(value, colorFormat);
993
+ case "border": return formatBorder(value, colorFormat);
994
+ case "transition": return formatTransition(value);
995
+ case "typography": return formatTypography(value);
996
+ case "gradient": return formatGradient(value, colorFormat);
997
+ default: return formatUnknown(value);
998
+ }
999
+ }
1000
+ function formatDimension$1(v) {
1001
+ if (typeof v === "string" || typeof v === "number") return String(v);
1002
+ if (v && typeof v === "object") {
1003
+ const d = v;
1004
+ if (typeof d.value === "number" && typeof d.unit === "string") return `${d.value}${d.unit}`;
1005
+ }
1006
+ return formatUnknown(v);
1007
+ }
1008
+ function formatFontFamily$1(v) {
1009
+ if (typeof v === "string") return v;
1010
+ if (Array.isArray(v)) return v.map(String).join(", ");
1011
+ return formatUnknown(v);
1012
+ }
1013
+ function formatPrimitive$1(v) {
1014
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
1015
+ return formatUnknown(v);
1016
+ }
1017
+ function formatCubicBezier(v) {
1018
+ if (Array.isArray(v) && v.length === 4) return `cubic-bezier(${v.map((n) => typeof n === "number" ? n : 0).join(", ")})`;
1019
+ return formatUnknown(v);
1020
+ }
1021
+ function formatStrokeStyle(v) {
1022
+ if (typeof v === "string") return v;
1023
+ if (v && typeof v === "object") {
1024
+ const s = v;
1025
+ const parts = ["dashed"];
1026
+ if (Array.isArray(s.dashArray)) parts.push(s.dashArray.map((n) => formatDimension$1(n)).join(" "));
1027
+ if (typeof s.lineCap === "string") parts.push(s.lineCap);
1028
+ return parts.join(" · ");
1029
+ }
1030
+ return formatUnknown(v);
1031
+ }
1032
+ function formatShadow(v, colorFormat) {
1033
+ return (Array.isArray(v) ? v : [v]).map((layer) => {
1034
+ if (!layer || typeof layer !== "object") return formatUnknown(layer);
1035
+ const s = layer;
1036
+ const pieces = [
1037
+ formatDimension$1(s["offsetX"]),
1038
+ formatDimension$1(s["offsetY"]),
1039
+ formatDimension$1(s["blur"]),
1040
+ formatDimension$1(s["spread"]),
1041
+ formatColor(s["color"], colorFormat).value
1042
+ ].filter((p) => p !== "");
1043
+ if (s["inset"]) pieces.push("inset");
1044
+ return pieces.join(" ");
1045
+ }).join(", ");
1046
+ }
1047
+ function formatBorder(v, colorFormat) {
1048
+ if (!v || typeof v !== "object") return formatUnknown(v);
1049
+ const b = v;
1050
+ return [
1051
+ formatDimension$1(b["width"]),
1052
+ formatPrimitive$1(b["style"]),
1053
+ formatColor(b["color"], colorFormat).value
1054
+ ].filter((p) => p !== "").join(" ");
1055
+ }
1056
+ function formatTransition(v) {
1057
+ if (!v || typeof v !== "object") return formatUnknown(v);
1058
+ const t = v;
1059
+ const duration = formatDimension$1(t["duration"]);
1060
+ const easing = formatPrimitive$1(t["timingFunction"]);
1061
+ const delay = formatDimension$1(t["delay"]);
1062
+ const parts = [duration, easing];
1063
+ if (!/^0\D/.test(delay) && delay !== "") parts.push(delay);
1064
+ return parts.filter((p) => p !== "").join(" ");
1065
+ }
1066
+ function formatTypography(v) {
1067
+ if (!v || typeof v !== "object") return formatUnknown(v);
1068
+ const t = v;
1069
+ return [
1070
+ formatFontFamily$1(t["fontFamily"]),
1071
+ formatDimension$1(t["fontSize"]),
1072
+ formatPrimitive$1(t["lineHeight"]),
1073
+ formatPrimitive$1(t["fontWeight"])
1074
+ ].filter((p) => p !== "").join(" / ");
1075
+ }
1076
+ function formatGradient(v, colorFormat) {
1077
+ if (!Array.isArray(v) || v.length === 0) return formatUnknown(v);
1078
+ return v.map((stop) => {
1079
+ if (!stop || typeof stop !== "object") return formatUnknown(stop);
1080
+ const s = stop;
1081
+ const position = typeof s["position"] === "number" ? `${Math.round(s["position"] * 100)}%` : "?";
1082
+ return `${formatColor(s["color"], colorFormat).value} ${position}`;
1083
+ }).join(" → ");
1084
+ }
1085
+ function formatUnknown(v) {
1086
+ if (v == null) return "";
1087
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
1088
+ try {
1089
+ return JSON.stringify(v).slice(0, 120);
1090
+ } catch {
1091
+ return String(v);
1092
+ }
1093
+ }
1094
+ //#endregion
1095
+ //#region src/DimensionScale.tsx
1096
+ const MAX_RENDER_PX = 480;
970
1097
  function toPixels(raw) {
971
1098
  if (raw == null || typeof raw !== "object") return NaN;
972
1099
  const v = raw;
@@ -978,73 +1105,65 @@ function toPixels(raw) {
978
1105
  default: return NaN;
979
1106
  }
980
1107
  }
981
- function DimensionScale({ filter = "dimension", kind = "length", caption }) {
1108
+ function DimensionScale({ filter, kind = "length", caption, sortBy = "value", sortDir = "asc" }) {
982
1109
  const { resolved, activeTheme, cssVarPrefix } = useProject();
983
1110
  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;
1111
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
1112
+ if (token.$type !== "dimension") return false;
1113
+ return globMatch(path, filter);
1114
+ }), {
1115
+ by: sortBy,
1116
+ dir: sortDir
1117
+ }).map(([path, token]) => {
988
1118
  const pxValue = toPixels(token.$value);
989
- collected.push({
1119
+ return {
990
1120
  path,
991
1121
  cssVar: makeCssVar(path, cssVarPrefix),
992
- displayValue: formatValue(token.$value),
1122
+ displayValue: formatTokenValue(token.$value, token.$type, "raw"),
993
1123
  pxValue,
994
1124
  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 });
1125
+ };
1000
1126
  });
1001
- return collected;
1002
1127
  }, [
1003
1128
  resolved,
1004
1129
  filter,
1005
- cssVarPrefix
1130
+ cssVarPrefix,
1131
+ sortBy,
1132
+ sortDir
1006
1133
  ]);
1007
1134
  const captionText = caption ?? `${rows.length} dimension${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1008
1135
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1009
1136
  ...themeAttrs(cssVarPrefix, activeTheme),
1010
- style: {
1011
- ...chromeAliases(cssVarPrefix),
1012
- ...styles$11.wrapper
1013
- },
1014
1137
  children: /* @__PURE__ */ jsx("div", {
1015
- style: styles$11.empty,
1138
+ className: "sb-block__empty",
1016
1139
  children: "No dimension tokens match this filter."
1017
1140
  })
1018
1141
  });
1019
1142
  return /* @__PURE__ */ jsxs("div", {
1020
1143
  ...themeAttrs(cssVarPrefix, activeTheme),
1021
- style: {
1022
- ...chromeAliases(cssVarPrefix),
1023
- ...styles$11.wrapper
1024
- },
1025
1144
  children: [/* @__PURE__ */ jsx("div", {
1026
- style: styles$11.caption,
1145
+ className: "sb-block__caption",
1027
1146
  children: captionText
1028
1147
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1029
- style: styles$11.row,
1148
+ className: "sb-dimension-scale__row",
1030
1149
  children: [
1031
1150
  /* @__PURE__ */ jsxs("div", {
1032
- style: styles$11.meta,
1151
+ className: "sb-dimension-scale__meta",
1033
1152
  children: [/* @__PURE__ */ jsx("span", {
1034
- style: styles$11.path,
1153
+ className: "sb-dimension-scale__path",
1035
1154
  children: row.path
1036
1155
  }), /* @__PURE__ */ jsx("span", {
1037
- style: styles$11.specs,
1156
+ className: "sb-dimension-scale__specs",
1038
1157
  children: row.displayValue
1039
1158
  })]
1040
1159
  }),
1041
1160
  /* @__PURE__ */ jsxs("div", {
1042
- style: styles$11.visualCell,
1161
+ className: "sb-dimension-scale__visual-cell",
1043
1162
  children: [/* @__PURE__ */ jsx(DimensionBar, {
1044
1163
  path: row.path,
1045
1164
  kind
1046
1165
  }), row.capped && /* @__PURE__ */ jsxs("span", {
1047
- style: styles$11.cap,
1166
+ className: "sb-dimension-scale__cap",
1048
1167
  children: [
1049
1168
  "capped at ",
1050
1169
  MAX_RENDER_PX,
@@ -1053,7 +1172,7 @@ function DimensionScale({ filter = "dimension", kind = "length", caption }) {
1053
1172
  })]
1054
1173
  }),
1055
1174
  /* @__PURE__ */ jsx("span", {
1056
- style: styles$11.cssVar,
1175
+ className: "sb-dimension-scale__css-var",
1057
1176
  children: row.cssVar
1058
1177
  })
1059
1178
  ]
@@ -1062,58 +1181,21 @@ function DimensionScale({ filter = "dimension", kind = "length", caption }) {
1062
1181
  }
1063
1182
  //#endregion
1064
1183
  //#region src/FontFamilySample.tsx
1065
- const styles$10 = {
1066
- wrapper: surfaceStyle,
1067
- caption: captionStyle,
1068
- row: {
1069
- display: "grid",
1070
- gridTemplateColumns: "minmax(160px, 220px) 1fr auto",
1071
- gap: 16,
1072
- alignItems: "baseline",
1073
- padding: "14px 0",
1074
- borderBottom: BORDER_DEFAULT
1075
- },
1076
- meta: {
1077
- display: "flex",
1078
- flexDirection: "column",
1079
- gap: 2,
1080
- minWidth: 0
1081
- },
1082
- path: {
1083
- fontFamily: MONO_STACK,
1084
- fontSize: 12,
1085
- overflow: "hidden",
1086
- textOverflow: "ellipsis",
1087
- whiteSpace: "nowrap"
1088
- },
1089
- stack: {
1090
- fontFamily: MONO_STACK,
1091
- fontSize: 11,
1092
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1093
- },
1094
- sample: {
1095
- fontSize: 22,
1096
- lineHeight: 1.2
1097
- },
1098
- cssVar: {
1099
- fontFamily: MONO_STACK,
1100
- fontSize: 11,
1101
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1102
- },
1103
- empty: emptyStyle
1104
- };
1105
1184
  function stackString(raw) {
1106
1185
  if (typeof raw === "string") return raw;
1107
1186
  if (Array.isArray(raw)) return raw.map(String).join(", ");
1108
1187
  return "";
1109
1188
  }
1110
- function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox jumps over the lazy dog.", caption }) {
1189
+ function FontFamilySample({ filter, sample = "The quick brown fox jumps over the lazy dog.", caption, sortBy = "path", sortDir = "asc" }) {
1111
1190
  const { resolved, activeTheme, cssVarPrefix } = useProject();
1112
1191
  const rows = useMemo(() => {
1113
- return Object.entries(resolved).filter(([path, token]) => {
1192
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
1114
1193
  if (token.$type !== "fontFamily") return false;
1115
1194
  return globMatch(path, filter);
1116
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => ({
1195
+ }), {
1196
+ by: sortBy,
1197
+ dir: sortDir
1198
+ }).map(([path, token]) => ({
1117
1199
  path,
1118
1200
  cssVar: makeCssVar(path, cssVarPrefix),
1119
1201
  stack: stackString(token.$value)
@@ -1121,51 +1203,43 @@ function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox
1121
1203
  }, [
1122
1204
  resolved,
1123
1205
  filter,
1124
- cssVarPrefix
1206
+ cssVarPrefix,
1207
+ sortBy,
1208
+ sortDir
1125
1209
  ]);
1126
1210
  const captionText = caption ?? `${rows.length} fontFamily token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontFamily" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1127
1211
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1128
1212
  ...themeAttrs(cssVarPrefix, activeTheme),
1129
- style: {
1130
- ...chromeAliases(cssVarPrefix),
1131
- ...styles$10.wrapper
1132
- },
1133
1213
  children: /* @__PURE__ */ jsx("div", {
1134
- style: styles$10.empty,
1214
+ className: "sb-block__empty",
1135
1215
  children: "No fontFamily tokens match this filter."
1136
1216
  })
1137
1217
  });
1138
1218
  return /* @__PURE__ */ jsxs("div", {
1139
1219
  ...themeAttrs(cssVarPrefix, activeTheme),
1140
- style: {
1141
- ...chromeAliases(cssVarPrefix),
1142
- ...styles$10.wrapper
1143
- },
1144
1220
  children: [/* @__PURE__ */ jsx("div", {
1145
- style: styles$10.caption,
1221
+ className: "sb-block__caption",
1146
1222
  children: captionText
1147
1223
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1148
- style: styles$10.row,
1224
+ className: "sb-font-family-sample__row",
1149
1225
  children: [
1150
1226
  /* @__PURE__ */ jsxs("div", {
1151
- style: styles$10.meta,
1227
+ className: "sb-font-family-sample__meta",
1152
1228
  children: [/* @__PURE__ */ jsx("span", {
1153
- style: styles$10.path,
1229
+ className: "sb-font-family-sample__path",
1154
1230
  children: row.path
1155
1231
  }), /* @__PURE__ */ jsx("span", {
1156
- style: styles$10.stack,
1232
+ className: "sb-font-family-sample__stack",
1157
1233
  children: row.stack
1158
1234
  })]
1159
1235
  }),
1160
1236
  /* @__PURE__ */ jsx("div", {
1161
- style: {
1162
- ...styles$10.sample,
1163
- fontFamily: row.cssVar
1164
- },
1237
+ className: "sb-font-family-sample__sample",
1238
+ style: { fontFamily: row.cssVar },
1165
1239
  children: sample
1166
1240
  }),
1167
1241
  /* @__PURE__ */ jsx("span", {
1168
- style: styles$10.cssVar,
1242
+ className: "sb-font-family-sample__css-var",
1169
1243
  children: row.cssVar
1170
1244
  })
1171
1245
  ]
@@ -1174,46 +1248,6 @@ function FontFamilySample({ filter = "fontFamily", sample = "The quick brown fox
1174
1248
  }
1175
1249
  //#endregion
1176
1250
  //#region src/FontWeightScale.tsx
1177
- const styles$9 = {
1178
- wrapper: surfaceStyle,
1179
- caption: captionStyle,
1180
- empty: emptyStyle,
1181
- row: {
1182
- display: "grid",
1183
- gridTemplateColumns: "minmax(160px, 220px) 1fr auto",
1184
- gap: 16,
1185
- alignItems: "baseline",
1186
- padding: "12px 0",
1187
- borderBottom: BORDER_DEFAULT
1188
- },
1189
- meta: {
1190
- display: "flex",
1191
- flexDirection: "column",
1192
- gap: 2,
1193
- minWidth: 0
1194
- },
1195
- path: {
1196
- fontFamily: MONO_STACK,
1197
- fontSize: 12,
1198
- overflow: "hidden",
1199
- textOverflow: "ellipsis",
1200
- whiteSpace: "nowrap"
1201
- },
1202
- value: {
1203
- fontFamily: MONO_STACK,
1204
- fontSize: 11,
1205
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1206
- },
1207
- sample: {
1208
- fontSize: 28,
1209
- lineHeight: 1
1210
- },
1211
- cssVar: {
1212
- fontFamily: MONO_STACK,
1213
- fontSize: 11,
1214
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1215
- }
1216
- };
1217
1251
  function toWeight(raw) {
1218
1252
  if (typeof raw === "number") return raw;
1219
1253
  if (typeof raw === "string") {
@@ -1222,73 +1256,61 @@ function toWeight(raw) {
1222
1256
  }
1223
1257
  return NaN;
1224
1258
  }
1225
- function FontWeightScale({ filter = "fontWeight", sample = "Aa", caption }) {
1259
+ function FontWeightScale({ filter, sample = "Aa", caption, sortBy = "value", sortDir = "asc" }) {
1226
1260
  const { resolved, activeTheme, cssVarPrefix } = useProject();
1227
1261
  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;
1262
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
1263
+ if (token.$type !== "fontWeight") return false;
1264
+ return globMatch(path, filter);
1265
+ }), {
1266
+ by: sortBy,
1267
+ dir: sortDir
1268
+ }).map(([path, token]) => ({
1269
+ path,
1270
+ cssVar: makeCssVar(path, cssVarPrefix),
1271
+ display: token.$value == null ? "" : String(token.$value),
1272
+ weight: toWeight(token.$value)
1273
+ }));
1244
1274
  }, [
1245
1275
  resolved,
1246
1276
  filter,
1247
- cssVarPrefix
1277
+ cssVarPrefix,
1278
+ sortBy,
1279
+ sortDir
1248
1280
  ]);
1249
1281
  const captionText = caption ?? `${rows.length} fontWeight token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontWeight" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1250
1282
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1251
1283
  ...themeAttrs(cssVarPrefix, activeTheme),
1252
- style: {
1253
- ...chromeAliases(cssVarPrefix),
1254
- ...styles$9.wrapper
1255
- },
1256
1284
  children: /* @__PURE__ */ jsx("div", {
1257
- style: styles$9.empty,
1285
+ className: "sb-block__empty",
1258
1286
  children: "No fontWeight tokens match this filter."
1259
1287
  })
1260
1288
  });
1261
1289
  return /* @__PURE__ */ jsxs("div", {
1262
1290
  ...themeAttrs(cssVarPrefix, activeTheme),
1263
- style: {
1264
- ...chromeAliases(cssVarPrefix),
1265
- ...styles$9.wrapper
1266
- },
1267
1291
  children: [/* @__PURE__ */ jsx("div", {
1268
- style: styles$9.caption,
1292
+ className: "sb-block__caption",
1269
1293
  children: captionText
1270
1294
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1271
- style: styles$9.row,
1295
+ className: "sb-font-weight-scale__row",
1272
1296
  children: [
1273
1297
  /* @__PURE__ */ jsxs("div", {
1274
- style: styles$9.meta,
1298
+ className: "sb-font-weight-scale__meta",
1275
1299
  children: [/* @__PURE__ */ jsx("span", {
1276
- style: styles$9.path,
1300
+ className: "sb-font-weight-scale__path",
1277
1301
  children: row.path
1278
1302
  }), /* @__PURE__ */ jsx("span", {
1279
- style: styles$9.value,
1303
+ className: "sb-font-weight-scale__value",
1280
1304
  children: row.display
1281
1305
  })]
1282
1306
  }),
1283
1307
  /* @__PURE__ */ jsx("div", {
1284
- style: {
1285
- ...styles$9.sample,
1286
- fontWeight: row.cssVar
1287
- },
1308
+ className: "sb-font-weight-scale__sample",
1309
+ style: { fontWeight: row.cssVar },
1288
1310
  children: sample
1289
1311
  }),
1290
1312
  /* @__PURE__ */ jsx("span", {
1291
- style: styles$9.cssVar,
1313
+ className: "sb-font-weight-scale__css-var",
1292
1314
  children: row.cssVar
1293
1315
  })
1294
1316
  ]
@@ -1297,62 +1319,6 @@ function FontWeightScale({ filter = "fontWeight", sample = "Aa", caption }) {
1297
1319
  }
1298
1320
  //#endregion
1299
1321
  //#region src/GradientPalette.tsx
1300
- const styles$8 = {
1301
- wrapper: surfaceStyle,
1302
- caption: captionStyle,
1303
- empty: emptyStyle,
1304
- row: {
1305
- display: "grid",
1306
- gridTemplateColumns: "minmax(180px, 240px) 1fr minmax(140px, 220px)",
1307
- gap: 16,
1308
- alignItems: "center",
1309
- padding: "16px 0",
1310
- borderBottom: BORDER_DEFAULT
1311
- },
1312
- meta: {
1313
- display: "flex",
1314
- flexDirection: "column",
1315
- gap: 2,
1316
- minWidth: 0
1317
- },
1318
- path: {
1319
- fontFamily: MONO_STACK,
1320
- fontSize: 12,
1321
- overflow: "hidden",
1322
- textOverflow: "ellipsis",
1323
- whiteSpace: "nowrap"
1324
- },
1325
- cssVar: {
1326
- fontFamily: MONO_STACK,
1327
- fontSize: 11,
1328
- opacity: .7
1329
- },
1330
- sample: {
1331
- height: 56,
1332
- borderRadius: 6,
1333
- border: BORDER_FAINT
1334
- },
1335
- stops: {
1336
- fontFamily: MONO_STACK,
1337
- fontSize: 11,
1338
- display: "flex",
1339
- flexDirection: "column",
1340
- gap: 2
1341
- },
1342
- stopRow: {
1343
- display: "flex",
1344
- alignItems: "center",
1345
- gap: 8
1346
- },
1347
- stopSwatch: {
1348
- width: 10,
1349
- height: 10,
1350
- borderRadius: 2,
1351
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))",
1352
- flex: "0 0 auto"
1353
- },
1354
- stopPosition: { opacity: .6 }
1355
- };
1356
1322
  function asStops(raw) {
1357
1323
  if (!Array.isArray(raw)) return [];
1358
1324
  return raw;
@@ -1369,82 +1335,71 @@ function stopCssColor(stop) {
1369
1335
  function stopKey(path, stop, fallback) {
1370
1336
  return `${path}|${stop.position ?? fallback}|${stopCssColor(stop)}`;
1371
1337
  }
1372
- function GradientPalette({ filter = "gradient", caption }) {
1338
+ function GradientPalette({ filter, caption, sortBy = "path", sortDir = "asc" }) {
1373
1339
  const { resolved, activeTheme, cssVarPrefix } = useProject();
1374
1340
  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;
1341
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
1342
+ if (token.$type !== "gradient") return false;
1343
+ return globMatch(path, filter);
1344
+ }), {
1345
+ by: sortBy,
1346
+ dir: sortDir
1347
+ }).map(([path, token]) => ({
1348
+ path,
1349
+ cssVar: makeCssVar(path, cssVarPrefix),
1350
+ stops: asStops(token.$value)
1351
+ }));
1387
1352
  }, [
1388
1353
  resolved,
1389
1354
  filter,
1390
- cssVarPrefix
1355
+ cssVarPrefix,
1356
+ sortBy,
1357
+ sortDir
1391
1358
  ]);
1392
1359
  const captionText = caption ?? `${rows.length} gradient${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1393
1360
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1394
1361
  ...themeAttrs(cssVarPrefix, activeTheme),
1395
- style: {
1396
- ...chromeAliases(cssVarPrefix),
1397
- ...styles$8.wrapper
1398
- },
1399
1362
  children: /* @__PURE__ */ jsx("div", {
1400
- style: styles$8.empty,
1363
+ className: "sb-block__empty",
1401
1364
  children: "No gradient tokens match this filter."
1402
1365
  })
1403
1366
  });
1404
1367
  return /* @__PURE__ */ jsxs("div", {
1405
1368
  ...themeAttrs(cssVarPrefix, activeTheme),
1406
- style: {
1407
- ...chromeAliases(cssVarPrefix),
1408
- ...styles$8.wrapper
1409
- },
1410
1369
  children: [/* @__PURE__ */ jsx("div", {
1411
- style: styles$8.caption,
1370
+ className: "sb-block__caption",
1412
1371
  children: captionText
1413
1372
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
1414
- style: styles$8.row,
1373
+ className: "sb-gradient-palette__row",
1415
1374
  children: [
1416
1375
  /* @__PURE__ */ jsxs("div", {
1417
- style: styles$8.meta,
1376
+ className: "sb-gradient-palette__meta",
1418
1377
  children: [/* @__PURE__ */ jsx("span", {
1419
- style: styles$8.path,
1378
+ className: "sb-gradient-palette__path",
1420
1379
  children: row.path
1421
1380
  }), /* @__PURE__ */ jsx("span", {
1422
- style: styles$8.cssVar,
1381
+ className: "sb-gradient-palette__css-var",
1423
1382
  children: row.cssVar
1424
1383
  })]
1425
1384
  }),
1426
1385
  /* @__PURE__ */ jsx("div", {
1427
- style: {
1428
- ...styles$8.sample,
1429
- background: `linear-gradient(to right, ${row.cssVar})`
1430
- },
1386
+ className: "sb-gradient-palette__sample",
1387
+ style: { background: `linear-gradient(to right, ${row.cssVar})` },
1431
1388
  "aria-hidden": true
1432
1389
  }),
1433
1390
  /* @__PURE__ */ jsx("div", {
1434
- style: styles$8.stops,
1391
+ className: "sb-gradient-palette__stops",
1435
1392
  children: row.stops.map((stop, i) => /* @__PURE__ */ jsxs("div", {
1436
- style: styles$8.stopRow,
1393
+ className: "sb-gradient-palette__stop-row",
1437
1394
  children: [
1438
1395
  /* @__PURE__ */ jsx("span", {
1439
- style: {
1440
- ...styles$8.stopSwatch,
1441
- background: stopCssColor(stop)
1442
- },
1396
+ className: "sb-gradient-palette__stop-swatch",
1397
+ style: { background: stopCssColor(stop) },
1443
1398
  "aria-hidden": true
1444
1399
  }),
1445
1400
  /* @__PURE__ */ jsx("span", { children: stopCssColor(stop) }),
1446
1401
  /* @__PURE__ */ jsxs("span", {
1447
- style: styles$8.stopPosition,
1402
+ className: "sb-gradient-palette__stop-position",
1448
1403
  children: [
1449
1404
  "@ ",
1450
1405
  ((stop.position ?? 0) * 100).toFixed(0),
@@ -1480,11 +1435,11 @@ function usePrefersReducedMotion() {
1480
1435
  //#region src/motion-preview/MotionSample.tsx
1481
1436
  const DEFAULT_DURATION_MS = 300;
1482
1437
  const DEFAULT_EASING = "cubic-bezier(0.2, 0, 0, 1)";
1483
- const styles$7 = {
1438
+ const styles = {
1484
1439
  track: {
1485
1440
  position: "relative",
1486
1441
  height: 36,
1487
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.08))",
1442
+ background: SURFACE_MUTED,
1488
1443
  borderRadius: 18,
1489
1444
  overflow: "hidden"
1490
1445
  },
@@ -1495,11 +1450,11 @@ const styles$7 = {
1495
1450
  height: 28,
1496
1451
  marginTop: -14,
1497
1452
  borderRadius: "50%",
1498
- background: "var(--sb-color-sys-accent-bg, #3b82f6)"
1453
+ background: "var(--swatchbook-accent-bg, #3b82f6)"
1499
1454
  },
1500
1455
  reducedMotion: {
1501
1456
  fontSize: 11,
1502
- color: "var(--sb-color-sys-text-muted, CanvasText)",
1457
+ color: TEXT_MUTED,
1503
1458
  fontStyle: "italic"
1504
1459
  }
1505
1460
  };
@@ -1573,7 +1528,7 @@ function resolveMotionSpec(token, themeTokens) {
1573
1528
  return null;
1574
1529
  }
1575
1530
  function MotionSample({ path, speed = 1, runKey = 0 }) {
1576
- const { resolved, cssVarPrefix } = useProject();
1531
+ const { resolved } = useProject();
1577
1532
  const reducedMotion = usePrefersReducedMotion();
1578
1533
  const spec = useMemo(() => resolveMotionSpec(resolved[path], resolved), [resolved, path]);
1579
1534
  const durationMs = spec?.durationMs ?? DEFAULT_DURATION_MS;
@@ -1597,20 +1552,14 @@ function MotionSample({ path, speed = 1, runKey = 0 }) {
1597
1552
  reducedMotion
1598
1553
  ]);
1599
1554
  if (reducedMotion) return /* @__PURE__ */ jsx("div", {
1600
- style: {
1601
- ...chromeAliases(cssVarPrefix),
1602
- ...styles$7.reducedMotion
1603
- },
1555
+ style: styles.reducedMotion,
1604
1556
  children: "Animation suppressed by `prefers-reduced-motion: reduce`."
1605
1557
  });
1606
1558
  return /* @__PURE__ */ jsx("div", {
1607
- style: {
1608
- ...chromeAliases(cssVarPrefix),
1609
- ...styles$7.track
1610
- },
1559
+ style: styles.track,
1611
1560
  children: /* @__PURE__ */ jsx("div", {
1612
1561
  style: {
1613
- ...styles$7.ball,
1562
+ ...styles.ball,
1614
1563
  left: phase === 1 ? "calc(100% - 32px)" : "4px",
1615
1564
  transition: `left ${scaledDuration}ms ${easing}`
1616
1565
  },
@@ -1626,88 +1575,6 @@ const SPEEDS = [
1626
1575
  1,
1627
1576
  2
1628
1577
  ];
1629
- const styles$6 = {
1630
- wrapper: surfaceStyle,
1631
- caption: {
1632
- padding: "4px 0 4px",
1633
- color: "var(--sb-color-sys-text-muted, CanvasText)",
1634
- fontSize: 12
1635
- },
1636
- controls: {
1637
- display: "flex",
1638
- alignItems: "center",
1639
- gap: 8,
1640
- padding: "8px 0 12px"
1641
- },
1642
- controlLabel: {
1643
- fontSize: 11,
1644
- color: "var(--sb-color-sys-text-muted, CanvasText)",
1645
- textTransform: "uppercase",
1646
- letterSpacing: .5
1647
- },
1648
- speedBtn: {
1649
- fontFamily: MONO_STACK,
1650
- fontSize: 11,
1651
- padding: "4px 8px",
1652
- background: "transparent",
1653
- color: "inherit",
1654
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
1655
- borderRadius: 4,
1656
- cursor: "pointer"
1657
- },
1658
- speedBtnActive: {
1659
- background: "var(--sb-color-sys-accent-bg, #3b82f6)",
1660
- color: "var(--sb-color-sys-accent-fg, #fff)",
1661
- borderColor: "transparent"
1662
- },
1663
- replayBtn: {
1664
- fontSize: 11,
1665
- padding: "4px 10px",
1666
- marginLeft: "auto",
1667
- background: "transparent",
1668
- color: "inherit",
1669
- border: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))",
1670
- borderRadius: 4,
1671
- cursor: "pointer"
1672
- },
1673
- row: {
1674
- display: "grid",
1675
- gridTemplateColumns: "minmax(180px, 240px) 1fr auto",
1676
- gap: 16,
1677
- alignItems: "center",
1678
- padding: "14px 0",
1679
- borderBottom: BORDER_DEFAULT
1680
- },
1681
- meta: {
1682
- display: "flex",
1683
- flexDirection: "column",
1684
- gap: 2,
1685
- minWidth: 0
1686
- },
1687
- path: {
1688
- fontFamily: MONO_STACK,
1689
- fontSize: 12,
1690
- overflow: "hidden",
1691
- textOverflow: "ellipsis",
1692
- whiteSpace: "nowrap"
1693
- },
1694
- specs: {
1695
- fontFamily: MONO_STACK,
1696
- fontSize: 11,
1697
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1698
- },
1699
- cssVar: {
1700
- fontFamily: MONO_STACK,
1701
- fontSize: 11,
1702
- color: "var(--sb-color-sys-text-muted, CanvasText)",
1703
- whiteSpace: "nowrap"
1704
- },
1705
- empty: {
1706
- padding: "24px 12px",
1707
- textAlign: "center",
1708
- color: "var(--sb-color-sys-text-muted, CanvasText)"
1709
- }
1710
- };
1711
1578
  function formatSpec(row) {
1712
1579
  switch (row.kind) {
1713
1580
  case "transition": return `transition · ${Math.round(row.durationMs)}ms · ${row.easing}`;
@@ -1754,45 +1621,34 @@ function MotionPreview({ filter, caption }) {
1754
1621
  const captionText = caption ?? `${rows.length} motion token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1755
1622
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1756
1623
  ...themeAttrs(cssVarPrefix, activeTheme),
1757
- style: {
1758
- ...chromeAliases(cssVarPrefix),
1759
- ...styles$6.wrapper
1760
- },
1761
1624
  children: /* @__PURE__ */ jsx("div", {
1762
- style: styles$6.empty,
1625
+ className: "sb-block__empty",
1763
1626
  children: "No motion tokens match this filter."
1764
1627
  })
1765
1628
  });
1766
1629
  return /* @__PURE__ */ jsxs("div", {
1767
1630
  ...themeAttrs(cssVarPrefix, activeTheme),
1768
- style: {
1769
- ...chromeAliases(cssVarPrefix),
1770
- ...styles$6.wrapper
1771
- },
1772
1631
  children: [
1773
1632
  /* @__PURE__ */ jsx("div", {
1774
- style: styles$6.caption,
1633
+ className: "sb-block__caption",
1775
1634
  children: captionText
1776
1635
  }),
1777
1636
  /* @__PURE__ */ jsxs("div", {
1778
- style: styles$6.controls,
1637
+ className: "sb-motion-preview__controls",
1779
1638
  children: [
1780
1639
  /* @__PURE__ */ jsx("span", {
1781
- style: styles$6.controlLabel,
1640
+ className: "sb-motion-preview__control-label",
1782
1641
  children: "Speed"
1783
1642
  }),
1784
1643
  SPEEDS.map((s) => /* @__PURE__ */ jsxs("button", {
1785
1644
  type: "button",
1786
- style: {
1787
- ...styles$6.speedBtn,
1788
- ...s === speed ? styles$6.speedBtnActive : {}
1789
- },
1645
+ className: cx("sb-motion-preview__speed-btn", { "sb-motion-preview__speed-btn--active": s === speed }),
1790
1646
  onClick: () => setSpeed(s),
1791
1647
  children: [s, "×"]
1792
1648
  }, s)),
1793
1649
  /* @__PURE__ */ jsx("button", {
1794
1650
  type: "button",
1795
- style: styles$6.replayBtn,
1651
+ className: "sb-motion-preview__replay-btn",
1796
1652
  onClick: () => setRun((n) => n + 1),
1797
1653
  disabled: reducedMotion,
1798
1654
  title: reducedMotion ? "Disabled by prefers-reduced-motion" : "Replay all",
@@ -1801,15 +1657,15 @@ function MotionPreview({ filter, caption }) {
1801
1657
  ]
1802
1658
  }),
1803
1659
  rows.map((row) => /* @__PURE__ */ jsxs("div", {
1804
- style: styles$6.row,
1660
+ className: "sb-motion-preview__row",
1805
1661
  children: [
1806
1662
  /* @__PURE__ */ jsxs("div", {
1807
- style: styles$6.meta,
1663
+ className: "sb-motion-preview__meta",
1808
1664
  children: [/* @__PURE__ */ jsx("span", {
1809
- style: styles$6.path,
1665
+ className: "sb-motion-preview__path",
1810
1666
  children: row.path
1811
1667
  }), /* @__PURE__ */ jsx("span", {
1812
- style: styles$6.specs,
1668
+ className: "sb-motion-preview__specs",
1813
1669
  children: formatSpec(row)
1814
1670
  })]
1815
1671
  }),
@@ -1819,7 +1675,7 @@ function MotionPreview({ filter, caption }) {
1819
1675
  runKey: run
1820
1676
  }),
1821
1677
  /* @__PURE__ */ jsx("span", {
1822
- style: styles$6.cssVar,
1678
+ className: "sb-motion-preview__css-var",
1823
1679
  children: row.cssVar
1824
1680
  })
1825
1681
  ]
@@ -1859,8 +1715,8 @@ function useSwatchbookData() {
1859
1715
  const sampleStyle = {
1860
1716
  width: 120,
1861
1717
  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))",
1718
+ background: SURFACE_RAISED,
1719
+ border: BORDER_FAINT,
1864
1720
  borderRadius: 6
1865
1721
  };
1866
1722
  function ShadowSample({ path }) {
@@ -1868,7 +1724,6 @@ function ShadowSample({ path }) {
1868
1724
  const cssVar = makeCssVar(path, cssVarPrefix);
1869
1725
  return /* @__PURE__ */ jsx("div", {
1870
1726
  style: {
1871
- ...chromeAliases(cssVarPrefix),
1872
1727
  ...sampleStyle,
1873
1728
  boxShadow: cssVar
1874
1729
  },
@@ -1877,59 +1732,6 @@ function ShadowSample({ path }) {
1877
1732
  }
1878
1733
  //#endregion
1879
1734
  //#region src/ShadowPreview.tsx
1880
- const styles$5 = {
1881
- wrapper: surfaceStyle,
1882
- caption: captionStyle,
1883
- empty: emptyStyle,
1884
- row: {
1885
- display: "grid",
1886
- gridTemplateColumns: "minmax(160px, 220px) 140px 1fr",
1887
- gap: 16,
1888
- alignItems: "center",
1889
- padding: "16px 0",
1890
- borderBottom: BORDER_DEFAULT
1891
- },
1892
- meta: {
1893
- display: "flex",
1894
- flexDirection: "column",
1895
- gap: 2,
1896
- minWidth: 0
1897
- },
1898
- path: {
1899
- fontFamily: MONO_STACK,
1900
- fontSize: 12,
1901
- overflow: "hidden",
1902
- textOverflow: "ellipsis",
1903
- whiteSpace: "nowrap"
1904
- },
1905
- cssVar: {
1906
- fontFamily: MONO_STACK,
1907
- fontSize: 11,
1908
- opacity: .7
1909
- },
1910
- sampleCell: {
1911
- display: "flex",
1912
- alignItems: "center",
1913
- justifyContent: "center",
1914
- height: 96
1915
- },
1916
- breakdown: {
1917
- fontFamily: MONO_STACK,
1918
- fontSize: 11,
1919
- display: "grid",
1920
- gridTemplateColumns: "auto 1fr",
1921
- columnGap: 12,
1922
- rowGap: 2
1923
- },
1924
- breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" },
1925
- layerHeader: {
1926
- fontSize: 10,
1927
- textTransform: "uppercase",
1928
- letterSpacing: .5,
1929
- color: "var(--sb-color-sys-text-muted, CanvasText)",
1930
- marginTop: 6
1931
- }
1932
- };
1933
1735
  function formatDimension(raw) {
1934
1736
  if (raw == null) return "—";
1935
1737
  if (typeof raw === "number") return String(raw);
@@ -1961,66 +1763,59 @@ function asLayers(raw) {
1961
1763
  function layerKey(path, layer, fallback) {
1962
1764
  return `${path}|${`${formatDimension(layer.offsetX)},${formatDimension(layer.offsetY)}`}|${formatDimension(layer.blur)}|${formatDimension(layer.spread)}|${fallback}`;
1963
1765
  }
1964
- function ShadowPreview({ filter = "shadow", caption }) {
1766
+ function ShadowPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
1965
1767
  const { resolved, activeTheme, cssVarPrefix } = useProject();
1966
1768
  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;
1769
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
1770
+ if (token.$type !== "shadow") return false;
1771
+ return globMatch(path, filter);
1772
+ }), {
1773
+ by: sortBy,
1774
+ dir: sortDir
1775
+ }).map(([path, token]) => ({
1776
+ path,
1777
+ cssVar: makeCssVar(path, cssVarPrefix),
1778
+ layers: asLayers(token.$value)
1779
+ }));
1979
1780
  }, [
1980
1781
  resolved,
1981
1782
  filter,
1982
- cssVarPrefix
1783
+ cssVarPrefix,
1784
+ sortBy,
1785
+ sortDir
1983
1786
  ]);
1984
1787
  const captionText = caption ?? `${rows.length} shadow${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
1985
1788
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
1986
1789
  ...themeAttrs(cssVarPrefix, activeTheme),
1987
- style: {
1988
- ...chromeAliases(cssVarPrefix),
1989
- ...styles$5.wrapper
1990
- },
1991
1790
  children: /* @__PURE__ */ jsx("div", {
1992
- style: styles$5.empty,
1791
+ className: "sb-block__empty",
1993
1792
  children: "No shadow tokens match this filter."
1994
1793
  })
1995
1794
  });
1996
1795
  return /* @__PURE__ */ jsxs("div", {
1997
1796
  ...themeAttrs(cssVarPrefix, activeTheme),
1998
- style: {
1999
- ...chromeAliases(cssVarPrefix),
2000
- ...styles$5.wrapper
2001
- },
2002
1797
  children: [/* @__PURE__ */ jsx("div", {
2003
- style: styles$5.caption,
1798
+ className: "sb-block__caption",
2004
1799
  children: captionText
2005
1800
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
2006
- style: styles$5.row,
1801
+ className: "sb-shadow-preview__row",
2007
1802
  children: [
2008
1803
  /* @__PURE__ */ jsxs("div", {
2009
- style: styles$5.meta,
1804
+ className: "sb-shadow-preview__meta",
2010
1805
  children: [/* @__PURE__ */ jsx("span", {
2011
- style: styles$5.path,
1806
+ className: "sb-shadow-preview__path",
2012
1807
  children: row.path
2013
1808
  }), /* @__PURE__ */ jsx("span", {
2014
- style: styles$5.cssVar,
1809
+ className: "sb-shadow-preview__css-var",
2015
1810
  children: row.cssVar
2016
1811
  })]
2017
1812
  }),
2018
1813
  /* @__PURE__ */ jsx("div", {
2019
- style: styles$5.sampleCell,
1814
+ className: "sb-shadow-preview__sample-cell",
2020
1815
  children: /* @__PURE__ */ jsx(ShadowSample, { path: row.path })
2021
1816
  }),
2022
1817
  /* @__PURE__ */ jsx("div", {
2023
- style: styles$5.breakdown,
1818
+ className: "sb-shadow-preview__breakdown",
2024
1819
  children: row.layers.length === 1 ? renderLayer(row.layers[0]) : row.layers.map((layer, i) => /* @__PURE__ */ jsx(Layer, {
2025
1820
  layer,
2026
1821
  index: i,
@@ -2041,15 +1836,15 @@ function renderLayer(layer) {
2041
1836
  ];
2042
1837
  if (layer.inset) entries.push(["inset", String(layer.inset)]);
2043
1838
  return entries.flatMap(([k, v]) => [/* @__PURE__ */ jsx("span", {
2044
- style: styles$5.breakdownKey,
1839
+ className: "sb-shadow-preview__breakdown-key",
2045
1840
  children: k
2046
1841
  }, `k-${k}`), /* @__PURE__ */ jsx("span", { children: v }, `v-${k}`)]);
2047
1842
  }
2048
1843
  function Layer({ layer, index, total }) {
2049
1844
  return /* @__PURE__ */ jsxs("div", {
2050
- style: { gridColumn: "1 / -1" },
1845
+ className: "sb-shadow-preview__layer",
2051
1846
  children: [/* @__PURE__ */ jsxs("div", {
2052
- style: styles$5.layerHeader,
1847
+ className: "sb-shadow-preview__layer-header",
2053
1848
  children: [
2054
1849
  "layer ",
2055
1850
  index + 1,
@@ -2057,10 +1852,7 @@ function Layer({ layer, index, total }) {
2057
1852
  total
2058
1853
  ]
2059
1854
  }), /* @__PURE__ */ jsx("div", {
2060
- style: {
2061
- ...styles$5.breakdown,
2062
- marginTop: 2
2063
- },
1855
+ className: cx("sb-shadow-preview__breakdown", "sb-shadow-preview__layer-breakdown"),
2064
1856
  children: renderLayer(layer)
2065
1857
  })]
2066
1858
  });
@@ -2077,120 +1869,68 @@ const STRING_STYLES = new Set([
2077
1869
  "outset",
2078
1870
  "inset"
2079
1871
  ]);
2080
- const styles$4 = {
2081
- wrapper: surfaceStyle,
2082
- caption: captionStyle,
2083
- empty: emptyStyle,
2084
- row: {
2085
- display: "grid",
2086
- gridTemplateColumns: "minmax(160px, 220px) 1fr auto",
2087
- gap: 16,
2088
- alignItems: "center",
2089
- padding: "14px 0",
2090
- borderBottom: BORDER_DEFAULT
2091
- },
2092
- meta: {
2093
- display: "flex",
2094
- flexDirection: "column",
2095
- gap: 2,
2096
- minWidth: 0
2097
- },
2098
- path: {
2099
- fontFamily: MONO_STACK,
2100
- fontSize: 12,
2101
- overflow: "hidden",
2102
- textOverflow: "ellipsis",
2103
- whiteSpace: "nowrap"
2104
- },
2105
- value: {
2106
- fontFamily: MONO_STACK,
2107
- fontSize: 11,
2108
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2109
- },
2110
- line: {
2111
- height: 0,
2112
- borderTopWidth: 4,
2113
- borderTopColor: "var(--sb-color-sys-text-default, CanvasText)",
2114
- width: "100%"
2115
- },
2116
- objectFallback: {
2117
- fontFamily: MONO_STACK,
2118
- fontSize: 11,
2119
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2120
- },
2121
- cssVar: {
2122
- fontFamily: MONO_STACK,
2123
- fontSize: 11,
2124
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2125
- }
2126
- };
2127
1872
  function extractCssStyle(value) {
2128
1873
  if (typeof value === "string" && STRING_STYLES.has(value)) return value;
2129
1874
  return null;
2130
1875
  }
2131
- function StrokeStyleSample({ filter = "strokeStyle", caption }) {
1876
+ function StrokeStyleSample({ filter, caption, sortBy = "path", sortDir = "asc" }) {
2132
1877
  const { resolved, activeTheme, cssVarPrefix } = useProject();
2133
1878
  const rows = useMemo(() => {
2134
- return Object.entries(resolved).filter(([path, token]) => {
1879
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
2135
1880
  if (token.$type !== "strokeStyle") return false;
2136
1881
  return globMatch(path, filter);
2137
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => ({
1882
+ }), {
1883
+ by: sortBy,
1884
+ dir: sortDir
1885
+ }).map(([path, token]) => ({
2138
1886
  path,
2139
1887
  cssVar: makeCssVar(path, cssVarPrefix),
2140
- displayValue: formatValue(token.$value),
1888
+ displayValue: formatTokenValue(token.$value, token.$type, "raw"),
2141
1889
  cssStyle: extractCssStyle(token.$value)
2142
1890
  }));
2143
1891
  }, [
2144
1892
  resolved,
2145
1893
  filter,
2146
- cssVarPrefix
1894
+ cssVarPrefix,
1895
+ sortBy,
1896
+ sortDir
2147
1897
  ]);
2148
1898
  const captionText = caption ?? `${rows.length} strokeStyle token${rows.length === 1 ? "" : "s"}${filter && filter !== "strokeStyle" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
2149
1899
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
2150
1900
  ...themeAttrs(cssVarPrefix, activeTheme),
2151
- style: {
2152
- ...chromeAliases(cssVarPrefix),
2153
- ...styles$4.wrapper
2154
- },
2155
1901
  children: /* @__PURE__ */ jsx("div", {
2156
- style: styles$4.empty,
1902
+ className: "sb-block__empty",
2157
1903
  children: "No strokeStyle tokens match this filter."
2158
1904
  })
2159
1905
  });
2160
1906
  return /* @__PURE__ */ jsxs("div", {
2161
- ...themeAttrs(cssVarPrefix, activeTheme),
2162
- style: {
2163
- ...chromeAliases(cssVarPrefix),
2164
- ...styles$4.wrapper
2165
- },
1907
+ ...themeAttrs(cssVarPrefix, activeTheme),
2166
1908
  children: [/* @__PURE__ */ jsx("div", {
2167
- style: styles$4.caption,
1909
+ className: "sb-block__caption",
2168
1910
  children: captionText
2169
1911
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
2170
- style: styles$4.row,
1912
+ className: "sb-stroke-style-sample__row",
2171
1913
  children: [
2172
1914
  /* @__PURE__ */ jsxs("div", {
2173
- style: styles$4.meta,
1915
+ className: "sb-stroke-style-sample__meta",
2174
1916
  children: [/* @__PURE__ */ jsx("span", {
2175
- style: styles$4.path,
1917
+ className: "sb-stroke-style-sample__path",
2176
1918
  children: row.path
2177
1919
  }), /* @__PURE__ */ jsx("span", {
2178
- style: styles$4.value,
1920
+ className: "sb-stroke-style-sample__value",
2179
1921
  children: row.displayValue
2180
1922
  })]
2181
1923
  }),
2182
1924
  row.cssStyle ? /* @__PURE__ */ jsx("div", {
2183
- style: {
2184
- ...styles$4.line,
2185
- borderTopStyle: row.cssStyle
2186
- },
1925
+ className: "sb-stroke-style-sample__line",
1926
+ style: { borderTopStyle: row.cssStyle },
2187
1927
  "aria-hidden": true
2188
1928
  }) : /* @__PURE__ */ jsx("span", {
2189
- style: styles$4.objectFallback,
1929
+ className: "sb-stroke-style-sample__object-fallback",
2190
1930
  children: "Object-form (dashArray + lineCap) — no pure CSS `border-style` equivalent."
2191
1931
  }),
2192
1932
  /* @__PURE__ */ jsx("span", {
2193
- style: styles$4.cssVar,
1933
+ className: "sb-stroke-style-sample__css-var",
2194
1934
  children: row.cssVar
2195
1935
  })
2196
1936
  ]
@@ -2198,275 +1938,6 @@ function StrokeStyleSample({ filter = "strokeStyle", caption }) {
2198
1938
  });
2199
1939
  }
2200
1940
  //#endregion
2201
- //#region src/token-detail/styles.ts
2202
- const styles$3 = {
2203
- wrapper: {
2204
- ...surfaceStyle,
2205
- padding: 16,
2206
- border: BORDER_DEFAULT
2207
- },
2208
- heading: {
2209
- margin: 0,
2210
- fontFamily: MONO_STACK,
2211
- fontSize: 16
2212
- },
2213
- subline: {
2214
- display: "flex",
2215
- alignItems: "center",
2216
- gap: 8,
2217
- margin: "4px 0 12px",
2218
- fontSize: 12,
2219
- opacity: .8
2220
- },
2221
- typePill: {
2222
- display: "inline-block",
2223
- padding: "2px 6px",
2224
- borderRadius: 4,
2225
- fontSize: 10,
2226
- letterSpacing: .5,
2227
- textTransform: "uppercase",
2228
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
2229
- },
2230
- description: {
2231
- margin: "0 0 12px",
2232
- opacity: .85
2233
- },
2234
- sectionHeader: {
2235
- fontFamily: MONO_STACK,
2236
- fontSize: 11,
2237
- textTransform: "uppercase",
2238
- letterSpacing: .5,
2239
- opacity: .6,
2240
- margin: "12px 0 6px"
2241
- },
2242
- chain: {
2243
- display: "flex",
2244
- flexWrap: "wrap",
2245
- gap: 6,
2246
- alignItems: "center",
2247
- fontFamily: MONO_STACK,
2248
- fontSize: 12
2249
- },
2250
- chainNode: {
2251
- padding: "2px 6px",
2252
- borderRadius: 4,
2253
- border: BORDER_DEFAULT
2254
- },
2255
- arrow: { opacity: .5 },
2256
- themeTable: {
2257
- width: "100%",
2258
- borderCollapse: "collapse",
2259
- tableLayout: "fixed",
2260
- fontSize: 12
2261
- },
2262
- themeRow: { borderBottom: BORDER_FAINT },
2263
- themeCell: {
2264
- padding: "6px 8px",
2265
- verticalAlign: "middle"
2266
- },
2267
- swatch: {
2268
- display: "inline-block",
2269
- width: 14,
2270
- height: 14,
2271
- verticalAlign: "middle",
2272
- marginRight: 6,
2273
- borderRadius: 3,
2274
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
2275
- },
2276
- snippet: {
2277
- display: "block",
2278
- padding: "8px 10px",
2279
- borderRadius: 4,
2280
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.1))",
2281
- fontFamily: MONO_STACK,
2282
- fontSize: 12,
2283
- whiteSpace: "pre",
2284
- overflow: "auto"
2285
- },
2286
- missing: {
2287
- padding: 12,
2288
- opacity: .7
2289
- },
2290
- typographySample: { padding: "8px 0" },
2291
- shadowSample: {
2292
- width: 140,
2293
- height: 56,
2294
- background: "var(--sb-color-sys-surface-raised, #fff)",
2295
- border: BORDER_FAINT,
2296
- borderRadius: 6
2297
- },
2298
- borderSample: {
2299
- width: 140,
2300
- height: 56,
2301
- background: "var(--sb-color-sys-surface-raised, transparent)",
2302
- borderRadius: 6
2303
- },
2304
- gradientSample: {
2305
- width: 220,
2306
- height: 56,
2307
- borderRadius: 6,
2308
- border: BORDER_FAINT
2309
- },
2310
- strokeStyleLine: {
2311
- height: 0,
2312
- borderTopWidth: 4,
2313
- borderTopColor: "var(--sb-color-sys-text-default, CanvasText)",
2314
- width: 220
2315
- },
2316
- strokeStyleSvg: {
2317
- width: 220,
2318
- height: 24,
2319
- color: "var(--sb-color-sys-text-default, CanvasText)"
2320
- },
2321
- strokeStyleFallback: {
2322
- fontFamily: MONO_STACK,
2323
- fontSize: 12,
2324
- color: "var(--sb-color-sys-text-muted, CanvasText)"
2325
- },
2326
- colorSwatchRow: {
2327
- display: "flex",
2328
- gap: 1,
2329
- borderRadius: 6,
2330
- overflow: "hidden",
2331
- border: BORDER_DEFAULT,
2332
- width: 220,
2333
- height: 56
2334
- },
2335
- colorSwatchLight: {
2336
- flex: 1,
2337
- boxShadow: "inset 0 0 0 8px rgba(255, 255, 255, 0.9)"
2338
- },
2339
- colorSwatchDark: {
2340
- flex: 1,
2341
- boxShadow: "inset 0 0 0 8px rgba(17, 17, 17, 0.9)"
2342
- },
2343
- breakdownSection: {
2344
- fontFamily: MONO_STACK,
2345
- fontSize: 12,
2346
- display: "grid",
2347
- gridTemplateColumns: "auto 1fr",
2348
- columnGap: 12,
2349
- rowGap: 3,
2350
- marginTop: 6
2351
- },
2352
- breakdownKey: { color: "var(--sb-color-sys-text-muted, CanvasText)" },
2353
- breakdownLayerHeader: {
2354
- gridColumn: "1 / -1",
2355
- fontSize: 10,
2356
- textTransform: "uppercase",
2357
- letterSpacing: .5,
2358
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2359
- marginTop: 4
2360
- },
2361
- fontFamilySample: {
2362
- padding: "4px 0",
2363
- fontSize: 22,
2364
- lineHeight: 1.2
2365
- },
2366
- fontWeightSample: {
2367
- padding: "4px 0",
2368
- fontSize: 32,
2369
- lineHeight: 1
2370
- },
2371
- dimensionTrack: {
2372
- display: "flex",
2373
- alignItems: "center",
2374
- height: 32,
2375
- maxWidth: "100%",
2376
- overflow: "hidden"
2377
- },
2378
- dimensionBar: {
2379
- height: 16,
2380
- background: "var(--sb-color-sys-accent-bg, #3b82f6)",
2381
- borderRadius: 3,
2382
- maxWidth: "100%"
2383
- },
2384
- motionTrack: {
2385
- position: "relative",
2386
- height: 32,
2387
- width: "100%",
2388
- maxWidth: 320,
2389
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.08))",
2390
- borderRadius: 16,
2391
- overflow: "hidden"
2392
- },
2393
- motionBall: {
2394
- position: "absolute",
2395
- top: "50%",
2396
- width: 24,
2397
- height: 24,
2398
- marginTop: -12,
2399
- borderRadius: "50%",
2400
- background: "var(--sb-color-sys-accent-bg, #3b82f6)"
2401
- },
2402
- aliasedByList: {
2403
- listStyle: "none",
2404
- margin: 0,
2405
- padding: 0,
2406
- fontFamily: MONO_STACK,
2407
- fontSize: 12
2408
- },
2409
- aliasedByRow: {
2410
- padding: "2px 0",
2411
- display: "flex",
2412
- alignItems: "center",
2413
- gap: 6
2414
- },
2415
- aliasedByTruncated: {
2416
- fontSize: 11,
2417
- opacity: .6,
2418
- fontStyle: "italic",
2419
- marginTop: 4
2420
- },
2421
- reducedMotion: {
2422
- fontSize: 11,
2423
- color: "var(--sb-color-sys-text-muted, CanvasText)",
2424
- fontStyle: "italic"
2425
- },
2426
- tupleIndicator: {
2427
- fontSize: 11,
2428
- opacity: .7,
2429
- margin: "0 0 6px",
2430
- fontFamily: MONO_STACK
2431
- },
2432
- consumerRow: {
2433
- display: "flex",
2434
- alignItems: "center",
2435
- gap: 8,
2436
- padding: "6px 10px",
2437
- marginBottom: 4,
2438
- borderRadius: 4,
2439
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.1))"
2440
- },
2441
- consumerRowLabel: {
2442
- fontFamily: MONO_STACK,
2443
- fontSize: 10,
2444
- textTransform: "uppercase",
2445
- letterSpacing: .5,
2446
- opacity: .6,
2447
- minWidth: 32,
2448
- flexShrink: 0
2449
- },
2450
- consumerRowValue: {
2451
- flex: 1,
2452
- fontFamily: MONO_STACK,
2453
- fontSize: 12,
2454
- whiteSpace: "nowrap",
2455
- overflow: "auto"
2456
- },
2457
- consumerRowCopy: {
2458
- padding: "3px 8px",
2459
- fontSize: 11,
2460
- 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))",
2464
- borderRadius: 4,
2465
- cursor: "pointer",
2466
- flexShrink: 0
2467
- }
2468
- };
2469
- //#endregion
2470
1941
  //#region src/token-detail/internal.ts
2471
1942
  function useTokenDetailData(path) {
2472
1943
  const { activeTheme, activeAxes, axes, themes, themesResolved, resolved, cssVarPrefix } = useProject();
@@ -2495,17 +1966,17 @@ function AliasChain({ path }) {
2495
1966
  }, [token, path]);
2496
1967
  if (chain.length <= 1) return null;
2497
1968
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2498
- style: styles$3.sectionHeader,
1969
+ className: "sb-token-detail__section-header",
2499
1970
  children: "Alias chain"
2500
1971
  }), /* @__PURE__ */ jsx("div", {
2501
- style: styles$3.chain,
1972
+ className: "sb-token-detail__chain",
2502
1973
  children: chain.map((step, i) => /* @__PURE__ */ jsxs("span", {
2503
- style: styles$3.chain,
1974
+ className: "sb-token-detail__chain",
2504
1975
  children: [/* @__PURE__ */ jsx("span", {
2505
- style: styles$3.chainNode,
1976
+ className: "sb-token-detail__chain-node",
2506
1977
  children: step
2507
1978
  }), i < chain.length - 1 && /* @__PURE__ */ jsx("span", {
2508
- style: styles$3.arrow,
1979
+ className: "sb-token-detail__arrow",
2509
1980
  children: "→"
2510
1981
  })]
2511
1982
  }, step))
@@ -2525,18 +1996,18 @@ function AliasedBy({ path }) {
2525
1996
  if (tree.length === 0) return null;
2526
1997
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2527
1998
  /* @__PURE__ */ jsx("div", {
2528
- style: styles$3.sectionHeader,
1999
+ className: "sb-token-detail__section-header",
2529
2000
  children: "Aliased by"
2530
2001
  }),
2531
2002
  /* @__PURE__ */ jsx("ul", {
2532
- style: styles$3.aliasedByList,
2003
+ className: "sb-token-detail__aliased-by-list",
2533
2004
  children: tree.map((node) => /* @__PURE__ */ jsx(AliasedByRow, {
2534
2005
  node,
2535
2006
  depth: 0
2536
2007
  }, node.path))
2537
2008
  }),
2538
2009
  truncated && /* @__PURE__ */ jsxs("div", {
2539
- style: styles$3.aliasedByTruncated,
2010
+ className: "sb-token-detail__aliased-by-truncated",
2540
2011
  children: [
2541
2012
  "Further descendants truncated at depth ",
2542
2013
  ALIASED_BY_DEPTH_CAP,
@@ -2547,16 +2018,14 @@ function AliasedBy({ path }) {
2547
2018
  }
2548
2019
  function AliasedByRow({ node, depth }) {
2549
2020
  return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("div", {
2550
- style: {
2551
- ...styles$3.aliasedByRow,
2552
- paddingLeft: depth * 16
2553
- },
2021
+ className: "sb-token-detail__aliased-by-row",
2022
+ style: { paddingLeft: depth * 16 },
2554
2023
  children: /* @__PURE__ */ jsx("span", {
2555
- style: styles$3.chainNode,
2024
+ className: "sb-token-detail__chain-node",
2556
2025
  children: node.path
2557
2026
  })
2558
2027
  }), node.children.length > 0 && /* @__PURE__ */ jsx("ul", {
2559
- style: styles$3.aliasedByList,
2028
+ className: "sb-token-detail__aliased-by-list",
2560
2029
  children: node.children.map((child) => /* @__PURE__ */ jsx(AliasedByRow, {
2561
2030
  node: child,
2562
2031
  depth: depth + 1
@@ -2609,8 +2078,9 @@ function treeHasTruncation(nodes) {
2609
2078
  function AxisVariance({ path }) {
2610
2079
  const { token, cssVar, axes, themes, themesResolved, activeAxes, cssVarPrefix } = useTokenDetailData(path);
2611
2080
  const colorFormat = useColorFormat();
2612
- const isColor = token?.$type === "color";
2613
- const formatFn = (t) => valueFor(t, isColor, colorFormat);
2081
+ const tokenType = token?.$type;
2082
+ const isColor = tokenType === "color";
2083
+ const formatFn = (t) => valueFor(t, tokenType, colorFormat);
2614
2084
  const variance = useMemo(() => analyzeVariance(path, axes, themes, themesResolved), [
2615
2085
  path,
2616
2086
  axes,
@@ -2622,22 +2092,20 @@ function AxisVariance({ path }) {
2622
2092
  const anyTheme = themes[0];
2623
2093
  const value = anyTheme ? formatFn(themesResolved[anyTheme.name]?.[path]) : "—";
2624
2094
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2625
- style: styles$3.sectionHeader,
2095
+ className: "sb-token-detail__section-header",
2626
2096
  children: "Values across axes"
2627
2097
  }), /* @__PURE__ */ jsx("table", {
2628
- style: styles$3.themeTable,
2098
+ className: "sb-token-detail__theme-table",
2629
2099
  "data-testid": "token-detail-values",
2630
2100
  children: /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", {
2631
- style: styles$3.themeRow,
2101
+ className: "sb-token-detail__theme-row",
2632
2102
  children: /* @__PURE__ */ jsxs("td", {
2633
- style: styles$3.themeCell,
2103
+ className: "sb-token-detail__theme-cell",
2634
2104
  "data-testid": "token-detail-constant",
2635
2105
  children: [
2636
2106
  isColor && /* @__PURE__ */ jsx("span", {
2637
- style: {
2638
- ...styles$3.swatch,
2639
- background: cssVar
2640
- },
2107
+ className: "sb-token-detail__swatch",
2108
+ style: { background: cssVar },
2641
2109
  "aria-hidden": true
2642
2110
  }),
2643
2111
  value,
@@ -2678,28 +2146,24 @@ function AxisVariance({ path }) {
2678
2146
  };
2679
2147
  });
2680
2148
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
2681
- style: styles$3.sectionHeader,
2149
+ className: "sb-token-detail__section-header",
2682
2150
  children: ["Varies with ", axisName]
2683
2151
  }), /* @__PURE__ */ jsx("table", {
2684
- style: styles$3.themeTable,
2152
+ className: "sb-token-detail__theme-table",
2685
2153
  "data-testid": "token-detail-values",
2686
2154
  children: /* @__PURE__ */ jsx("tbody", { children: contextValues.map((row) => /* @__PURE__ */ jsxs("tr", {
2687
- style: styles$3.themeRow,
2155
+ className: "sb-token-detail__theme-row",
2688
2156
  "data-axis": axisName,
2689
2157
  "data-context": row.ctx,
2690
2158
  children: [/* @__PURE__ */ jsx("td", {
2691
- style: {
2692
- ...styles$3.themeCell,
2693
- width: "30%"
2694
- },
2159
+ className: "sb-token-detail__theme-cell",
2160
+ style: { width: "30%" },
2695
2161
  children: row.ctx
2696
2162
  }), /* @__PURE__ */ jsxs("td", {
2697
- style: styles$3.themeCell,
2163
+ className: "sb-token-detail__theme-cell",
2698
2164
  children: [isColor && row.themeName && /* @__PURE__ */ jsx("span", {
2699
- style: {
2700
- ...styles$3.swatch,
2701
- background: cssVar
2702
- },
2165
+ className: "sb-token-detail__swatch",
2166
+ style: { background: cssVar },
2703
2167
  [dataAttr(cssVarPrefix, "theme")]: row.themeName,
2704
2168
  "aria-hidden": true
2705
2169
  }), row.value]
@@ -2711,17 +2175,17 @@ function AxisVariance({ path }) {
2711
2175
  if (!rowAxis || !colAxis) return /* @__PURE__ */ jsx(Fragment, {});
2712
2176
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2713
2177
  /* @__PURE__ */ jsxs("div", {
2714
- style: styles$3.sectionHeader,
2178
+ className: "sb-token-detail__section-header",
2715
2179
  children: ["Varies with ", variance.varyingAxes.join(" × ")]
2716
2180
  }),
2717
2181
  /* @__PURE__ */ jsxs("table", {
2718
- style: styles$3.themeTable,
2182
+ className: "sb-token-detail__theme-table",
2719
2183
  "data-testid": "token-detail-values",
2720
2184
  children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
2721
- style: styles$3.themeRow,
2185
+ className: "sb-token-detail__theme-row",
2722
2186
  children: [/* @__PURE__ */ jsxs("th", {
2187
+ className: "sb-token-detail__theme-cell",
2723
2188
  style: {
2724
- ...styles$3.themeCell,
2725
2189
  textAlign: "left",
2726
2190
  opacity: .7
2727
2191
  },
@@ -2731,17 +2195,17 @@ function AxisVariance({ path }) {
2731
2195
  colAxis.name
2732
2196
  ]
2733
2197
  }), colAxis.contexts.map((col) => /* @__PURE__ */ jsx("th", {
2198
+ className: "sb-token-detail__theme-cell",
2734
2199
  style: {
2735
- ...styles$3.themeCell,
2736
2200
  textAlign: "left",
2737
2201
  opacity: .7
2738
2202
  },
2739
2203
  children: col
2740
2204
  }, col))]
2741
2205
  }) }), /* @__PURE__ */ jsx("tbody", { children: rowAxis.contexts.map((row) => /* @__PURE__ */ jsxs("tr", {
2742
- style: styles$3.themeRow,
2206
+ className: "sb-token-detail__theme-row",
2743
2207
  children: [/* @__PURE__ */ jsx("td", {
2744
- style: styles$3.themeCell,
2208
+ className: "sb-token-detail__theme-cell",
2745
2209
  children: row
2746
2210
  }), colAxis.contexts.map((col) => {
2747
2211
  const name = tupleName(themes, {
@@ -2751,14 +2215,12 @@ function AxisVariance({ path }) {
2751
2215
  });
2752
2216
  const value = name ? formatFn(themesResolved[name]?.[path]) : "—";
2753
2217
  return /* @__PURE__ */ jsxs("td", {
2754
- style: styles$3.themeCell,
2218
+ className: "sb-token-detail__theme-cell",
2755
2219
  "data-row": row,
2756
2220
  "data-col": col,
2757
2221
  children: [isColor && name && /* @__PURE__ */ jsx("span", {
2758
- style: {
2759
- ...styles$3.swatch,
2760
- background: cssVar
2761
- },
2222
+ className: "sb-token-detail__swatch",
2223
+ style: { background: cssVar },
2762
2224
  [dataAttr(cssVarPrefix, "theme")]: name,
2763
2225
  "aria-hidden": true
2764
2226
  }), value]
@@ -2767,10 +2229,8 @@ function AxisVariance({ path }) {
2767
2229
  }, row)) })]
2768
2230
  }),
2769
2231
  extra.length > 0 && /* @__PURE__ */ jsxs("div", {
2770
- style: {
2771
- ...styles$3.aliasedByTruncated,
2772
- marginTop: 6
2773
- },
2232
+ className: "sb-token-detail__aliased-by-truncated",
2233
+ style: { marginTop: 6 },
2774
2234
  children: [
2775
2235
  "Values also vary with ",
2776
2236
  extra.map((a) => a.name).join(", "),
@@ -2779,14 +2239,20 @@ function AxisVariance({ path }) {
2779
2239
  })
2780
2240
  ] });
2781
2241
  }
2782
- function valueFor(token, isColor, format) {
2242
+ function valueFor(token, $type, format) {
2783
2243
  if (!token) return "—";
2784
- if (isColor) return formatColor(token.$value, format).value;
2785
- return formatValue(token.$value);
2244
+ return formatTokenValue(token.$value, $type, format);
2786
2245
  }
2787
- function themeValue(themesResolved, themeName, path) {
2246
+ /**
2247
+ * Stable key for variance detection — compares structural equality across
2248
+ * themes, not a display string. We pin `raw` so color representation
2249
+ * changes (the toolbar's format dropdown) don't artificially make axes
2250
+ * look like they vary.
2251
+ */
2252
+ function varianceKey(themesResolved, themeName, path) {
2788
2253
  const t = themesResolved[themeName]?.[path];
2789
- return t ? formatValue(t.$value) : "";
2254
+ if (!t) return "";
2255
+ return JSON.stringify(t.$value);
2790
2256
  }
2791
2257
  function tupleName(themes, tuple) {
2792
2258
  return themes.find((t) => {
@@ -2802,7 +2268,7 @@ function analyzeVariance(path, axes, themes, themesResolved) {
2802
2268
  const others = axes.filter((a) => a.name !== axis.name).map((a) => `${a.name}=${theme.input[a.name] ?? ""}`).join("|");
2803
2269
  const ctx = theme.input[axis.name] ?? "";
2804
2270
  const bucket = byOthers.get(others) ?? /* @__PURE__ */ new Map();
2805
- bucket.set(ctx, themeValue(themesResolved, theme.name, path));
2271
+ bucket.set(ctx, varianceKey(themesResolved, theme.name, path));
2806
2272
  byOthers.set(others, bucket);
2807
2273
  }
2808
2274
  let varies = false;
@@ -2829,13 +2295,15 @@ function analyzeVariance(path, axes, themes, themesResolved) {
2829
2295
  //#region src/token-detail/CompositeBreakdown.tsx
2830
2296
  function CompositeBreakdown({ path }) {
2831
2297
  const { token } = useTokenDetailData(path);
2298
+ const colorFormat = useColorFormat();
2832
2299
  if (!token) return null;
2833
2300
  return /* @__PURE__ */ jsx(CompositeBreakdownContent, {
2834
2301
  type: token.$type,
2835
- rawValue: token.$value
2302
+ rawValue: token.$value,
2303
+ colorFormat
2836
2304
  });
2837
2305
  }
2838
- function CompositeBreakdownContent({ type, rawValue }) {
2306
+ function CompositeBreakdownContent({ type, rawValue, colorFormat }) {
2839
2307
  if (!rawValue || typeof rawValue !== "object") return null;
2840
2308
  if (type === "typography") {
2841
2309
  const v = rawValue;
@@ -2850,7 +2318,7 @@ function CompositeBreakdownContent({ type, rawValue }) {
2850
2318
  if (type === "border") {
2851
2319
  const v = rawValue;
2852
2320
  return renderKeyValueList([
2853
- ["color", formatColorValue(v["color"])],
2321
+ ["color", formatColorSubValue(v["color"], colorFormat)],
2854
2322
  ["width", formatDimensionValue(v["width"])],
2855
2323
  ["style", formatPrimitive(v["style"])]
2856
2324
  ]);
@@ -2867,19 +2335,19 @@ function CompositeBreakdownContent({ type, rawValue }) {
2867
2335
  const layers = Array.isArray(rawValue) ? rawValue : [rawValue];
2868
2336
  const multi = layers.length > 1;
2869
2337
  return /* @__PURE__ */ jsx("div", {
2870
- style: styles$3.breakdownSection,
2338
+ className: "sb-token-detail__breakdown-section",
2871
2339
  children: layers.map((layer, i) => {
2872
2340
  const v = layer;
2873
2341
  return /* @__PURE__ */ jsxs("div", {
2874
2342
  style: { display: "contents" },
2875
2343
  children: [
2876
2344
  multi && /* @__PURE__ */ jsxs("div", {
2877
- style: styles$3.breakdownLayerHeader,
2345
+ className: "sb-token-detail__breakdown-layer-header",
2878
2346
  children: ["Layer ", i + 1]
2879
2347
  }),
2880
2348
  /* @__PURE__ */ jsx(KeyValueRow, {
2881
2349
  label: "color",
2882
- value: formatColorValue(v["color"])
2350
+ value: formatColorSubValue(v["color"], colorFormat)
2883
2351
  }),
2884
2352
  /* @__PURE__ */ jsx(KeyValueRow, {
2885
2353
  label: "offsetX",
@@ -2910,12 +2378,12 @@ function CompositeBreakdownContent({ type, rawValue }) {
2910
2378
  const stops = Array.isArray(rawValue) ? rawValue : [];
2911
2379
  if (stops.length === 0) return null;
2912
2380
  return /* @__PURE__ */ jsx("div", {
2913
- style: styles$3.breakdownSection,
2381
+ className: "sb-token-detail__breakdown-section",
2914
2382
  children: stops.map((stop, i) => {
2915
2383
  const v = stop;
2916
2384
  return /* @__PURE__ */ jsx(KeyValueRow, {
2917
2385
  label: `${((typeof v["position"] === "number" ? v["position"] : 0) * 100).toFixed(0)}%`,
2918
- value: formatColorValue(v["color"])
2386
+ value: formatColorSubValue(v["color"], colorFormat)
2919
2387
  }, gradientStopKey(v, i));
2920
2388
  })
2921
2389
  });
@@ -2924,7 +2392,7 @@ function CompositeBreakdownContent({ type, rawValue }) {
2924
2392
  }
2925
2393
  function renderKeyValueList(rows) {
2926
2394
  return /* @__PURE__ */ jsx("div", {
2927
- style: styles$3.breakdownSection,
2395
+ className: "sb-token-detail__breakdown-section",
2928
2396
  children: rows.filter(([, v]) => v !== null).map(([k, v]) => /* @__PURE__ */ jsx(KeyValueRow, {
2929
2397
  label: k,
2930
2398
  value: v ?? ""
@@ -2933,7 +2401,7 @@ function renderKeyValueList(rows) {
2933
2401
  }
2934
2402
  function KeyValueRow({ label, value }) {
2935
2403
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
2936
- style: styles$3.breakdownKey,
2404
+ className: "sb-token-detail__breakdown-key",
2937
2405
  children: label
2938
2406
  }), /* @__PURE__ */ jsx("span", { children: value ?? "—" })] });
2939
2407
  }
@@ -2957,18 +2425,15 @@ function formatDimensionValue(v) {
2957
2425
  }
2958
2426
  return JSON.stringify(v);
2959
2427
  }
2960
- function formatColorValue(v) {
2428
+ /**
2429
+ * Route sub-value colors through `formatColor` so they honor the active
2430
+ * color-format dropdown, just like the standalone `<ColorPalette />` and
2431
+ * `<TokenDetail />` top-line do. Returns `null` for a missing field so
2432
+ * the key/value row drops out entirely.
2433
+ */
2434
+ function formatColorSubValue(v, format) {
2961
2435
  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);
2436
+ return formatColor(v, format).value;
2972
2437
  }
2973
2438
  function shadowLayerKey(layer, fallback) {
2974
2439
  return `shadow|${[
@@ -3009,8 +2474,8 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
3009
2474
  if (type === "typography") {
3010
2475
  const base = cssVar.replace(/^var\(/, "").replace(/\)$/, "");
3011
2476
  return /* @__PURE__ */ jsx("div", {
2477
+ className: "sb-token-detail__typography-sample",
3012
2478
  style: {
3013
- ...styles$3.typographySample,
3014
2479
  fontFamily: `var(${base}-font-family)`,
3015
2480
  fontSize: `var(${base}-font-size)`,
3016
2481
  fontWeight: `var(${base}-font-weight)`,
@@ -3021,85 +2486,71 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
3021
2486
  });
3022
2487
  }
3023
2488
  if (type === "shadow") return /* @__PURE__ */ jsx("div", {
3024
- style: {
3025
- ...styles$3.shadowSample,
3026
- boxShadow: cssVar
3027
- },
2489
+ className: "sb-token-detail__shadow-sample",
2490
+ style: { boxShadow: cssVar },
3028
2491
  "aria-hidden": true
3029
2492
  });
3030
2493
  if (type === "border") return /* @__PURE__ */ jsx("div", {
3031
- style: {
3032
- ...styles$3.borderSample,
3033
- border: cssVar
3034
- },
2494
+ className: "sb-token-detail__border-sample",
2495
+ style: { border: cssVar },
3035
2496
  "aria-hidden": true
3036
2497
  });
3037
2498
  if (type === "transition") return /* @__PURE__ */ jsx(TransitionSample, { transition: cssVar });
3038
2499
  if (type === "dimension") return /* @__PURE__ */ jsx("div", {
3039
- style: styles$3.dimensionTrack,
2500
+ className: "sb-token-detail__dimension-track",
3040
2501
  children: /* @__PURE__ */ jsx("div", {
3041
- style: {
3042
- ...styles$3.dimensionBar,
3043
- width: cssVar
3044
- },
2502
+ className: "sb-token-detail__dimension-bar",
2503
+ style: { width: cssVar },
3045
2504
  "aria-hidden": true
3046
2505
  })
3047
2506
  });
3048
2507
  if (type === "duration") return /* @__PURE__ */ jsx(TransitionSample, { transition: `left ${cssVar} ease` });
3049
2508
  if (type === "fontFamily") return /* @__PURE__ */ jsx("div", {
3050
- style: {
3051
- ...styles$3.fontFamilySample,
3052
- fontFamily: cssVar
3053
- },
2509
+ className: "sb-token-detail__font-family-sample",
2510
+ style: { fontFamily: cssVar },
3054
2511
  children: PANGRAM
3055
2512
  });
3056
2513
  if (type === "fontWeight") return /* @__PURE__ */ jsx("div", {
3057
- style: {
3058
- ...styles$3.fontWeightSample,
3059
- fontWeight: cssVar
3060
- },
2514
+ className: "sb-token-detail__font-weight-sample",
2515
+ style: { fontWeight: cssVar },
3061
2516
  children: "Aa"
3062
2517
  });
3063
2518
  if (type === "cubicBezier") return /* @__PURE__ */ jsx(TransitionSample, { transition: `left 800ms ${cssVar}` });
3064
2519
  if (type === "gradient") return /* @__PURE__ */ jsx("div", {
3065
- style: {
3066
- ...styles$3.gradientSample,
3067
- background: `linear-gradient(to right, ${cssVar})`
3068
- },
2520
+ className: "sb-token-detail__gradient-sample",
2521
+ style: { background: `linear-gradient(to right, ${cssVar})` },
3069
2522
  "aria-hidden": true
3070
2523
  });
3071
2524
  if (type === "strokeStyle") return /* @__PURE__ */ jsx(StrokeStylePreview, { value: rawValue });
3072
2525
  if (type === "color") return /* @__PURE__ */ jsxs("div", {
3073
- style: styles$3.colorSwatchRow,
2526
+ className: "sb-token-detail__color-swatch-row",
3074
2527
  "aria-hidden": true,
3075
- children: [/* @__PURE__ */ jsx("div", { style: {
3076
- ...styles$3.colorSwatchLight,
3077
- background: cssVar
3078
- } }), /* @__PURE__ */ jsx("div", { style: {
3079
- ...styles$3.colorSwatchDark,
3080
- background: cssVar
3081
- } })]
2528
+ children: [/* @__PURE__ */ jsx("div", {
2529
+ className: "sb-token-detail__color-swatch-light",
2530
+ style: { background: cssVar }
2531
+ }), /* @__PURE__ */ jsx("div", {
2532
+ className: "sb-token-detail__color-swatch-dark",
2533
+ style: { background: cssVar }
2534
+ })]
3082
2535
  });
3083
2536
  return null;
3084
2537
  }
3085
2538
  function StrokeStylePreview({ value }) {
3086
2539
  if (typeof value === "string" && STROKE_STYLE_STRINGS.has(value)) return /* @__PURE__ */ jsx("div", {
3087
- style: {
3088
- ...styles$3.strokeStyleLine,
3089
- borderTopStyle: value
3090
- },
2540
+ className: "sb-token-detail__stroke-style-line",
2541
+ style: { borderTopStyle: value },
3091
2542
  "aria-hidden": true
3092
2543
  });
3093
2544
  if (value && typeof value === "object" && "dashArray" in value) {
3094
2545
  const v = value;
3095
2546
  const lengths = asDashLengths(v.dashArray);
3096
2547
  if (lengths.length === 0) return /* @__PURE__ */ jsx("div", {
3097
- style: styles$3.strokeStyleFallback,
2548
+ className: "sb-token-detail__stroke-style-fallback",
3098
2549
  children: "Object-form strokeStyle with no resolvable dashArray."
3099
2550
  });
3100
2551
  const cap = typeof v.lineCap === "string" ? v.lineCap : "butt";
3101
2552
  return /* @__PURE__ */ jsx("svg", {
3102
- style: styles$3.strokeStyleSvg,
2553
+ className: "sb-token-detail__stroke-style-svg",
3103
2554
  viewBox: "0 0 220 24",
3104
2555
  preserveAspectRatio: "none",
3105
2556
  "aria-hidden": true,
@@ -3116,7 +2567,7 @@ function StrokeStylePreview({ value }) {
3116
2567
  });
3117
2568
  }
3118
2569
  return /* @__PURE__ */ jsx("div", {
3119
- style: styles$3.strokeStyleFallback,
2570
+ className: "sb-token-detail__stroke-style-fallback",
3120
2571
  children: "strokeStyle value could not be previewed."
3121
2572
  });
3122
2573
  }
@@ -3150,14 +2601,14 @@ function TransitionSample({ transition }) {
3150
2601
  };
3151
2602
  }, [reduced]);
3152
2603
  if (reduced) return /* @__PURE__ */ jsx("div", {
3153
- style: styles$3.reducedMotion,
2604
+ className: "sb-token-detail__reduced-motion",
3154
2605
  children: "Animation suppressed by `prefers-reduced-motion: reduce`."
3155
2606
  });
3156
2607
  return /* @__PURE__ */ jsx("div", {
3157
- style: styles$3.motionTrack,
2608
+ className: "sb-token-detail__motion-track",
3158
2609
  children: /* @__PURE__ */ jsx("div", {
2610
+ className: "sb-token-detail__motion-ball",
3159
2611
  style: {
3160
- ...styles$3.motionBall,
3161
2612
  left: phase === 1 ? "calc(100% - 28px)" : "4px",
3162
2613
  transition
3163
2614
  },
@@ -3173,11 +2624,11 @@ function ConsumerOutput({ path }) {
3173
2624
  const tupleLabel = Object.entries(activeAxes).map(([k, v]) => `${k}=${v}`).join(", ");
3174
2625
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3175
2626
  /* @__PURE__ */ jsx("div", {
3176
- style: styles$3.sectionHeader,
2627
+ className: "sb-token-detail__section-header",
3177
2628
  children: "Consumer output"
3178
2629
  }),
3179
2630
  tupleLabel && /* @__PURE__ */ jsxs("div", {
3180
- style: styles$3.tupleIndicator,
2631
+ className: "sb-token-detail__tuple-indicator",
3181
2632
  children: ["Active tuple: ", /* @__PURE__ */ jsx("strong", { children: tupleLabel })]
3182
2633
  }),
3183
2634
  /* @__PURE__ */ jsx(OutputRow, {
@@ -3194,14 +2645,14 @@ function ConsumerOutput({ path }) {
3194
2645
  }
3195
2646
  function OutputRow({ label, value, testId }) {
3196
2647
  return /* @__PURE__ */ jsxs("div", {
3197
- style: styles$3.consumerRow,
2648
+ className: "sb-token-detail__consumer-row",
3198
2649
  children: [
3199
2650
  /* @__PURE__ */ jsx("span", {
3200
- style: styles$3.consumerRowLabel,
2651
+ className: "sb-token-detail__consumer-row-label",
3201
2652
  children: label
3202
2653
  }),
3203
2654
  /* @__PURE__ */ jsx("code", {
3204
- style: styles$3.consumerRowValue,
2655
+ className: "sb-token-detail__consumer-row-value",
3205
2656
  "data-testid": testId,
3206
2657
  children: value
3207
2658
  }),
@@ -3216,7 +2667,7 @@ function CopyButton({ text, testId }) {
3216
2667
  const [copied, setCopied] = useState(false);
3217
2668
  return /* @__PURE__ */ jsx("button", {
3218
2669
  type: "button",
3219
- style: styles$3.consumerRowCopy,
2670
+ className: "sb-token-detail__consumer-row-copy",
3220
2671
  "data-testid": testId,
3221
2672
  onClick: () => {
3222
2673
  copyToClipboard(text).then((ok) => {
@@ -3242,7 +2693,7 @@ async function copyToClipboard(text) {
3242
2693
  function TokenHeader({ path, heading }) {
3243
2694
  const { token, cssVar, activeTheme } = useTokenDetailData(path);
3244
2695
  if (!token) return /* @__PURE__ */ jsxs("div", {
3245
- style: styles$3.missing,
2696
+ className: "sb-token-detail__missing",
3246
2697
  children: [
3247
2698
  "Token ",
3248
2699
  /* @__PURE__ */ jsx("code", { children: path }),
@@ -3253,18 +2704,18 @@ function TokenHeader({ path, heading }) {
3253
2704
  });
3254
2705
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3255
2706
  /* @__PURE__ */ jsx("h3", {
3256
- style: styles$3.heading,
2707
+ className: "sb-token-detail__heading",
3257
2708
  children: heading ?? path
3258
2709
  }),
3259
2710
  /* @__PURE__ */ jsxs("div", {
3260
- style: styles$3.subline,
2711
+ className: "sb-token-detail__subline",
3261
2712
  children: [token.$type && /* @__PURE__ */ jsx("span", {
3262
- style: styles$3.typePill,
2713
+ className: "sb-token-detail__type-pill",
3263
2714
  children: token.$type
3264
2715
  }), /* @__PURE__ */ jsx("span", { children: cssVar })]
3265
2716
  }),
3266
2717
  token.$description && /* @__PURE__ */ jsx("p", {
3267
- style: styles$3.description,
2718
+ className: "sb-token-detail__description",
3268
2719
  children: token.$description
3269
2720
  })
3270
2721
  ] });
@@ -3275,10 +2726,10 @@ function TokenUsageSnippet({ path }) {
3275
2726
  const { token, cssVar } = useTokenDetailData(path);
3276
2727
  if (!token) return null;
3277
2728
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
3278
- style: styles$3.sectionHeader,
2729
+ className: "sb-token-detail__section-header",
3279
2730
  children: "Usage"
3280
2731
  }), /* @__PURE__ */ jsx("code", {
3281
- style: styles$3.snippet,
2732
+ className: "sb-token-detail__snippet",
3282
2733
  children: `color: ${cssVar};`
3283
2734
  })] });
3284
2735
  }
@@ -3287,14 +2738,12 @@ function TokenUsageSnippet({ path }) {
3287
2738
  function TokenDetail({ path, heading }) {
3288
2739
  const { token, cssVar, activeTheme, cssVarPrefix } = useTokenDetailData(path);
3289
2740
  const colorFormat = useColorFormat();
2741
+ const theme = themeAttrs(cssVarPrefix, activeTheme);
3290
2742
  if (!token) return /* @__PURE__ */ jsx("div", {
3291
- ...themeAttrs(cssVarPrefix, activeTheme),
3292
- style: {
3293
- ...chromeAliases(cssVarPrefix),
3294
- ...styles$3.wrapper
3295
- },
2743
+ ...theme,
2744
+ className: cx(theme["className"], "sb-token-detail"),
3296
2745
  children: /* @__PURE__ */ jsxs("div", {
3297
- style: styles$3.missing,
2746
+ className: "sb-token-detail__missing",
3298
2747
  children: [
3299
2748
  "Token ",
3300
2749
  /* @__PURE__ */ jsx("code", { children: path }),
@@ -3305,34 +2754,29 @@ function TokenDetail({ path, heading }) {
3305
2754
  })
3306
2755
  });
3307
2756
  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;
2757
+ const gamut = isColor ? formatColor(token.$value, colorFormat) : null;
2758
+ const value = formatTokenValue(token.$value, token.$type, colorFormat);
2759
+ const outOfGamut = gamut?.outOfGamut ?? false;
3311
2760
  return /* @__PURE__ */ jsxs("div", {
3312
- ...themeAttrs(cssVarPrefix, activeTheme),
3313
- style: {
3314
- ...chromeAliases(cssVarPrefix),
3315
- ...styles$3.wrapper
3316
- },
2761
+ ...theme,
2762
+ className: cx(theme["className"], "sb-token-detail"),
3317
2763
  children: [
3318
2764
  /* @__PURE__ */ jsx(TokenHeader, {
3319
2765
  path,
3320
2766
  ...heading !== void 0 && { heading }
3321
2767
  }),
3322
2768
  /* @__PURE__ */ jsxs("div", {
3323
- style: styles$3.sectionHeader,
2769
+ className: "sb-token-detail__section-header",
3324
2770
  children: ["Resolved value · ", activeTheme]
3325
2771
  }),
3326
2772
  /* @__PURE__ */ jsx(CompositePreview, { path }),
3327
2773
  /* @__PURE__ */ jsx(CompositeBreakdown, { path }),
3328
2774
  /* @__PURE__ */ jsxs("div", {
3329
- style: styles$3.chain,
2775
+ className: "sb-token-detail__chain",
3330
2776
  children: [
3331
2777
  isColor && /* @__PURE__ */ jsx("span", {
3332
- style: {
3333
- ...styles$3.swatch,
3334
- background: cssVar
3335
- },
2778
+ className: "sb-token-detail__swatch",
2779
+ style: { background: cssVar },
3336
2780
  "aria-hidden": true
3337
2781
  }),
3338
2782
  /* @__PURE__ */ jsx("span", { children: value }),
@@ -3353,131 +2797,39 @@ function TokenDetail({ path, heading }) {
3353
2797
  });
3354
2798
  }
3355
2799
  //#endregion
2800
+ //#region src/internal/DetailOverlay.tsx
2801
+ function DetailOverlay({ path, onClose, testId = "swatchbook-overlay" }) {
2802
+ useEffect(() => {
2803
+ const onKey = (e) => {
2804
+ if (e.key === "Escape") onClose();
2805
+ };
2806
+ window.addEventListener("keydown", onKey);
2807
+ return () => window.removeEventListener("keydown", onKey);
2808
+ }, [onClose]);
2809
+ return /* @__PURE__ */ jsx("div", {
2810
+ className: "sb-detail-overlay__backdrop",
2811
+ onClick: onClose,
2812
+ role: "presentation",
2813
+ "data-testid": testId,
2814
+ children: /* @__PURE__ */ jsxs("div", {
2815
+ className: "sb-detail-overlay__panel",
2816
+ onClick: (e) => e.stopPropagation(),
2817
+ role: "dialog",
2818
+ "aria-modal": "true",
2819
+ "aria-label": `Token detail for ${path}`,
2820
+ children: [/* @__PURE__ */ jsx("button", {
2821
+ type: "button",
2822
+ className: "sb-detail-overlay__close",
2823
+ onClick: onClose,
2824
+ "aria-label": "Close",
2825
+ "data-testid": `${testId}-close`,
2826
+ children: "×"
2827
+ }), /* @__PURE__ */ jsx(TokenDetail, { path })]
2828
+ })
2829
+ });
2830
+ }
2831
+ //#endregion
3356
2832
  //#region src/TokenNavigator.tsx
3357
- const styles$2 = {
3358
- wrapper: surfaceStyle,
3359
- caption: {
3360
- padding: "4px 0 12px",
3361
- color: "var(--sb-color-sys-text-muted, CanvasText)",
3362
- fontSize: 12
3363
- },
3364
- tree: {
3365
- listStyle: "none",
3366
- margin: 0,
3367
- padding: 0
3368
- },
3369
- nested: {
3370
- listStyle: "none",
3371
- margin: 0,
3372
- paddingLeft: 18,
3373
- borderLeft: BORDER_DEFAULT
3374
- },
3375
- groupRow: {
3376
- display: "flex",
3377
- alignItems: "center",
3378
- gap: 6,
3379
- padding: "4px 6px",
3380
- borderRadius: 4,
3381
- cursor: "pointer",
3382
- userSelect: "none",
3383
- fontFamily: MONO_STACK,
3384
- fontSize: 12
3385
- },
3386
- leafRow: {
3387
- display: "flex",
3388
- alignItems: "center",
3389
- gap: 8,
3390
- padding: "4px 6px",
3391
- borderRadius: 4,
3392
- cursor: "pointer",
3393
- fontFamily: MONO_STACK,
3394
- fontSize: 12
3395
- },
3396
- caret: {
3397
- display: "inline-block",
3398
- width: 12,
3399
- textAlign: "center",
3400
- color: "var(--sb-color-sys-text-muted, CanvasText)"
3401
- },
3402
- tail: {
3403
- fontFamily: MONO_STACK,
3404
- fontSize: 12
3405
- },
3406
- typePill: {
3407
- display: "inline-block",
3408
- 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))"
3414
- },
3415
- value: {
3416
- fontSize: 11,
3417
- color: "var(--sb-color-sys-text-muted, CanvasText)",
3418
- marginLeft: "auto",
3419
- wordBreak: "break-all",
3420
- maxWidth: "40%",
3421
- textAlign: "right"
3422
- },
3423
- count: {
3424
- marginLeft: "auto",
3425
- fontSize: 11,
3426
- color: "var(--sb-color-sys-text-default, CanvasText)"
3427
- },
3428
- colorSwatch: {
3429
- display: "inline-block",
3430
- width: 14,
3431
- height: 14,
3432
- borderRadius: 3,
3433
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
3434
- },
3435
- previewBox: {
3436
- display: "inline-flex",
3437
- alignItems: "center",
3438
- justifyContent: "flex-end",
3439
- 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
- }
3479
- };
3480
- /** @internal Exported for tests; not part of the public API. */
3481
2833
  function buildTree(resolved, root) {
3482
2834
  const rootPrefix = root && root.length > 0 ? `${root}.` : "";
3483
2835
  const rootSegments = root ? root.split(".") : [];
@@ -3575,24 +2927,13 @@ function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
3575
2927
  }, [onSelect]);
3576
2928
  if (tree.length === 0) return /* @__PURE__ */ jsx("div", {
3577
2929
  ...themeAttrs(cssVarPrefix, activeTheme),
3578
- style: {
3579
- ...chromeAliases(cssVarPrefix),
3580
- ...styles$2.wrapper
3581
- },
3582
- children: /* @__PURE__ */ jsx("div", {
3583
- style: styles$2.empty,
3584
- children: root ? `No tokens under "${root}".` : "No tokens in the active theme."
3585
- })
2930
+ children: /* @__PURE__ */ jsx(EmptyState, { children: root ? `No tokens under "${root}".` : "No tokens in the active theme." })
3586
2931
  });
3587
2932
  return /* @__PURE__ */ jsxs("div", {
3588
2933
  ...themeAttrs(cssVarPrefix, activeTheme),
3589
- style: {
3590
- ...chromeAliases(cssVarPrefix),
3591
- ...styles$2.wrapper
3592
- },
3593
2934
  children: [
3594
2935
  /* @__PURE__ */ jsxs("div", {
3595
- style: styles$2.caption,
2936
+ className: "sb-token-navigator__caption",
3596
2937
  children: [
3597
2938
  root ? `Tokens under ${root}` : "Token graph",
3598
2939
  " · ",
@@ -3600,7 +2941,7 @@ function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
3600
2941
  ]
3601
2942
  }),
3602
2943
  /* @__PURE__ */ jsx("ul", {
3603
- style: styles$2.tree,
2944
+ className: "sb-token-navigator__tree",
3604
2945
  role: "tree",
3605
2946
  children: tree.map((node) => /* @__PURE__ */ jsx(TreeNodeRow, {
3606
2947
  node,
@@ -3611,7 +2952,8 @@ function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
3611
2952
  }),
3612
2953
  selectedPath !== null && /* @__PURE__ */ jsx(DetailOverlay, {
3613
2954
  path: selectedPath,
3614
- onClose: () => setSelectedPath(null)
2955
+ onClose: () => setSelectedPath(null),
2956
+ testId: "token-navigator-overlay"
3615
2957
  })
3616
2958
  ]
3617
2959
  });
@@ -3634,25 +2976,25 @@ function TreeNodeRow({ node, expanded, onToggle, onLeafClick }) {
3634
2976
  children: [/* @__PURE__ */ jsxs("div", {
3635
2977
  role: "button",
3636
2978
  tabIndex: 0,
3637
- style: styles$2.groupRow,
2979
+ className: "sb-token-navigator__group-row",
3638
2980
  onClick: () => onToggle(node.path),
3639
2981
  onKeyDown: onKey,
3640
2982
  "data-path": node.path,
3641
2983
  "data-testid": "token-navigator-group",
3642
2984
  children: [
3643
2985
  /* @__PURE__ */ jsx("span", {
3644
- style: styles$2.caret,
2986
+ className: "sb-token-navigator__caret",
3645
2987
  "aria-hidden": true,
3646
2988
  children: isOpen ? "▾" : "▸"
3647
2989
  }),
3648
2990
  /* @__PURE__ */ jsx("span", { children: node.segment }),
3649
2991
  /* @__PURE__ */ jsx("span", {
3650
- style: styles$2.count,
2992
+ className: "sb-token-navigator__count",
3651
2993
  children: countLeaves(node)
3652
2994
  })
3653
2995
  ]
3654
2996
  }), isOpen && /* @__PURE__ */ jsx("ul", {
3655
- style: styles$2.nested,
2997
+ className: "sb-token-navigator__nested",
3656
2998
  role: "group",
3657
2999
  children: node.children.map((c) => /* @__PURE__ */ jsx(TreeNodeRow, {
3658
3000
  node: c,
@@ -3676,23 +3018,23 @@ function LeafRow({ node, onLeafClick }) {
3676
3018
  children: /* @__PURE__ */ jsxs("div", {
3677
3019
  role: "button",
3678
3020
  tabIndex: 0,
3679
- style: styles$2.leafRow,
3021
+ className: "sb-token-navigator__leaf-row",
3680
3022
  onClick: () => onLeafClick(node.path),
3681
3023
  onKeyDown: onKey,
3682
3024
  "data-path": node.path,
3683
3025
  "data-testid": "token-navigator-leaf",
3684
3026
  children: [
3685
3027
  /* @__PURE__ */ jsx("span", {
3686
- style: styles$2.caret,
3028
+ className: "sb-token-navigator__caret",
3687
3029
  "aria-hidden": true,
3688
3030
  children: "•"
3689
3031
  }),
3690
3032
  /* @__PURE__ */ jsx("span", {
3691
- style: styles$2.tail,
3033
+ className: "sb-token-navigator__tail",
3692
3034
  children: node.segment
3693
3035
  }),
3694
3036
  type && /* @__PURE__ */ jsx("span", {
3695
- style: styles$2.typePill,
3037
+ className: "sb-token-navigator__type-pill",
3696
3038
  children: type
3697
3039
  }),
3698
3040
  /* @__PURE__ */ jsx(LeafPreview, {
@@ -3709,34 +3051,25 @@ function LeafPreview({ path, token }) {
3709
3051
  const type = token.$type;
3710
3052
  if (type === "color") {
3711
3053
  const cssVar = makeCssVar(path, cssVarPrefix);
3712
- const formatted = formatColor(token.$value, colorFormat);
3713
3054
  return /* @__PURE__ */ jsxs("span", {
3714
- style: styles$2.previewBox,
3055
+ className: "sb-token-navigator__preview-box",
3715
3056
  children: [/* @__PURE__ */ jsx("span", {
3716
- style: styles$2.value,
3717
- children: formatted?.value ?? formatValue(token.$value)
3057
+ className: "sb-token-navigator__value",
3058
+ children: formatTokenValue(token.$value, type, colorFormat)
3718
3059
  }), /* @__PURE__ */ jsx("span", {
3719
- style: {
3720
- ...styles$2.colorSwatch,
3721
- background: cssVar,
3722
- marginLeft: 8
3723
- },
3060
+ className: "sb-token-navigator__color-swatch",
3061
+ style: { background: cssVar },
3724
3062
  "aria-hidden": true
3725
3063
  })]
3726
3064
  });
3727
3065
  }
3728
3066
  if (type === "dimension") return /* @__PURE__ */ jsxs("span", {
3729
- style: styles$2.previewBox,
3067
+ className: "sb-token-navigator__preview-box",
3730
3068
  children: [/* @__PURE__ */ jsx("span", {
3731
- style: styles$2.value,
3732
- children: formatValue(token.$value)
3069
+ className: "sb-token-navigator__value",
3070
+ children: formatTokenValue(token.$value, type, colorFormat)
3733
3071
  }), /* @__PURE__ */ jsx("span", {
3734
- style: {
3735
- marginLeft: 8,
3736
- display: "inline-block",
3737
- minWidth: 40,
3738
- maxWidth: 120
3739
- },
3072
+ className: "sb-token-navigator__preview-dimension",
3740
3073
  children: /* @__PURE__ */ jsx(DimensionBar, {
3741
3074
  path,
3742
3075
  kind: "length"
@@ -3744,155 +3077,56 @@ function LeafPreview({ path, token }) {
3744
3077
  })]
3745
3078
  });
3746
3079
  if (type === "shadow") return /* @__PURE__ */ jsx("span", {
3747
- style: styles$2.previewBox,
3080
+ className: "sb-token-navigator__preview-box",
3748
3081
  children: /* @__PURE__ */ jsx("span", {
3749
- style: {
3750
- marginLeft: 8,
3751
- display: "inline-block",
3752
- transform: "scale(0.5)",
3753
- transformOrigin: "right center"
3754
- },
3082
+ className: "sb-token-navigator__preview-scaled",
3755
3083
  children: /* @__PURE__ */ jsx(ShadowSample, { path })
3756
3084
  })
3757
3085
  });
3758
3086
  if (type === "border") return /* @__PURE__ */ jsx("span", {
3759
- style: styles$2.previewBox,
3087
+ className: "sb-token-navigator__preview-box",
3760
3088
  children: /* @__PURE__ */ jsx("span", {
3761
- style: {
3762
- marginLeft: 8,
3763
- display: "inline-block",
3764
- transform: "scale(0.5)",
3765
- transformOrigin: "right center"
3766
- },
3089
+ className: "sb-token-navigator__preview-scaled",
3767
3090
  children: /* @__PURE__ */ jsx(BorderSample, { path })
3768
3091
  })
3769
3092
  });
3770
3093
  if (type === "transition" || type === "duration" || type === "cubicBezier") return /* @__PURE__ */ jsx("span", {
3771
- style: styles$2.previewBox,
3094
+ className: "sb-token-navigator__preview-box",
3772
3095
  children: /* @__PURE__ */ jsx("span", {
3773
- style: {
3774
- marginLeft: 8,
3775
- display: "inline-block",
3776
- width: 80
3777
- },
3096
+ className: "sb-token-navigator__preview-motion",
3778
3097
  children: /* @__PURE__ */ jsx(MotionSample, { path })
3779
3098
  })
3780
3099
  });
3781
3100
  return /* @__PURE__ */ jsx("span", {
3782
- style: styles$2.previewBox,
3101
+ className: "sb-token-navigator__preview-box",
3783
3102
  children: /* @__PURE__ */ jsx("span", {
3784
- 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 })]
3103
+ className: "sb-token-navigator__value",
3104
+ children: formatTokenValue(token.$value, type, colorFormat)
3816
3105
  })
3817
3106
  });
3818
3107
  }
3819
3108
  //#endregion
3820
3109
  //#region src/TokenTable.tsx
3821
- const styles$1 = {
3822
- wrapper: surfaceStyle,
3823
- empty: emptyStyle,
3824
- caption: {
3825
- captionSide: "top",
3826
- textAlign: "left",
3827
- padding: "8px 0",
3828
- opacity: .7,
3829
- fontSize: 12
3830
- },
3831
- table: {
3832
- width: "100%",
3833
- borderCollapse: "collapse",
3834
- tableLayout: "fixed"
3835
- },
3836
- th: {
3837
- textAlign: "left",
3838
- padding: "8px 12px",
3839
- fontSize: 11,
3840
- textTransform: "uppercase",
3841
- letterSpacing: .5,
3842
- opacity: .6,
3843
- borderBottom: "1px solid var(--sb-color-sys-border-default, rgba(128,128,128,0.3))"
3844
- },
3845
- td: {
3846
- padding: "8px 12px",
3847
- borderBottom: BORDER_FAINT,
3848
- verticalAlign: "top"
3849
- },
3850
- path: {
3851
- fontFamily: MONO_STACK,
3852
- fontSize: 12
3853
- },
3854
- typePill: {
3855
- display: "inline-block",
3856
- padding: "2px 6px",
3857
- borderRadius: 4,
3858
- fontSize: 10,
3859
- letterSpacing: .5,
3860
- textTransform: "uppercase",
3861
- background: "var(--sb-color-sys-surface-muted, rgba(128,128,128,0.15))"
3862
- },
3863
- value: {
3864
- fontFamily: MONO_STACK,
3865
- fontSize: 12,
3866
- opacity: .85,
3867
- wordBreak: "break-all"
3868
- },
3869
- swatch: {
3870
- display: "inline-block",
3871
- width: 14,
3872
- height: 14,
3873
- verticalAlign: "middle",
3874
- marginRight: 6,
3875
- borderRadius: 3,
3876
- border: "1px solid var(--sb-color-sys-border-default, rgba(0,0,0,0.1))"
3877
- }
3878
- };
3879
- function TokenTable({ filter, type, showVar = true, caption }) {
3110
+ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", onSelect }) {
3880
3111
  const { resolved, activeTheme, cssVarPrefix } = useProject();
3881
3112
  const colorFormat = useColorFormat();
3113
+ const [selectedPath, setSelectedPath] = useState(null);
3882
3114
  const rows = useMemo(() => {
3883
- return Object.entries(resolved).filter(([path, token]) => {
3115
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
3884
3116
  if (!globMatch(path, filter)) return false;
3885
3117
  if (type && token.$type !== type) return false;
3886
3118
  return true;
3887
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => {
3119
+ }), {
3120
+ by: sortBy,
3121
+ dir: sortDir
3122
+ }).map(([path, token]) => {
3888
3123
  const isColor = token.$type === "color";
3889
3124
  const color = isColor ? formatColor(token.$value, colorFormat) : null;
3890
3125
  return {
3891
3126
  path,
3892
3127
  type: token.$type ?? "",
3893
- value: color ? color.value : formatValue(token.$value),
3128
+ value: formatTokenValue(token.$value, token.$type, colorFormat),
3894
3129
  outOfGamut: color?.outOfGamut ?? false,
3895
- description: token.$description ?? "",
3896
3130
  cssVar: makeCssVar(path, cssVarPrefix),
3897
3131
  isColor
3898
3132
  };
@@ -3902,144 +3136,94 @@ function TokenTable({ filter, type, showVar = true, caption }) {
3902
3136
  filter,
3903
3137
  type,
3904
3138
  cssVarPrefix,
3905
- colorFormat
3139
+ colorFormat,
3140
+ sortBy,
3141
+ sortDir
3906
3142
  ]);
3143
+ const handleRowClick = useCallback((path) => {
3144
+ if (onSelect) onSelect(path);
3145
+ else setSelectedPath(path);
3146
+ }, [onSelect]);
3907
3147
  const captionText = caption ?? `${rows.length} token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""}${type ? ` · $type=${type}` : ""} · ${activeTheme}`;
3908
3148
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
3909
3149
  ...themeAttrs(cssVarPrefix, activeTheme),
3910
- style: {
3911
- ...chromeAliases(cssVarPrefix),
3912
- ...styles$1.wrapper
3913
- },
3914
3150
  children: /* @__PURE__ */ jsx("div", {
3915
- style: styles$1.empty,
3151
+ className: "sb-block__empty",
3916
3152
  children: "No tokens match this filter."
3917
3153
  })
3918
3154
  });
3919
- return /* @__PURE__ */ jsx("div", {
3155
+ return /* @__PURE__ */ jsxs("div", {
3920
3156
  ...themeAttrs(cssVarPrefix, activeTheme),
3921
- style: {
3922
- ...chromeAliases(cssVarPrefix),
3923
- ...styles$1.wrapper
3924
- },
3925
- children: /* @__PURE__ */ jsxs("table", {
3926
- style: styles$1.table,
3157
+ children: [/* @__PURE__ */ jsxs("table", {
3158
+ className: "sb-token-table__table",
3927
3159
  children: [
3928
3160
  /* @__PURE__ */ jsx("caption", {
3929
- style: styles$1.caption,
3161
+ className: "sb-token-table__caption",
3930
3162
  children: captionText
3931
3163
  }),
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", {
3963
- style: {
3964
- ...styles$1.td,
3965
- ...styles$1.path
3966
- },
3164
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
3165
+ className: cx("sb-token-table__th", "sb-token-table__th--path"),
3166
+ children: "Path"
3167
+ }), /* @__PURE__ */ jsx("th", {
3168
+ className: cx("sb-token-table__th", "sb-token-table__th--value"),
3169
+ children: "Value"
3170
+ })] }) }),
3171
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", {
3172
+ className: "sb-token-table__row",
3173
+ onClick: () => handleRowClick(row.path),
3174
+ onKeyDown: (e) => {
3175
+ if (e.key === "Enter" || e.key === " ") {
3176
+ e.preventDefault();
3177
+ handleRowClick(row.path);
3178
+ }
3179
+ },
3180
+ tabIndex: 0,
3181
+ "aria-label": `Inspect ${row.path}`,
3182
+ "data-testid": "token-table-row",
3183
+ "data-path": row.path,
3184
+ children: [/* @__PURE__ */ jsx("td", {
3185
+ className: cx("sb-token-table__td", "sb-token-table__path"),
3967
3186
  children: row.path
3968
- }),
3969
- /* @__PURE__ */ jsx("td", {
3970
- style: styles$1.td,
3971
- children: row.type && /* @__PURE__ */ jsx("span", {
3972
- style: styles$1.typePill,
3973
- children: row.type
3187
+ }), /* @__PURE__ */ jsx("td", {
3188
+ className: "sb-token-table__td",
3189
+ children: /* @__PURE__ */ jsxs("span", {
3190
+ className: "sb-token-table__value-cell",
3191
+ children: [
3192
+ row.type && /* @__PURE__ */ jsx("span", {
3193
+ className: "sb-token-table__type-pill",
3194
+ children: row.type
3195
+ }),
3196
+ row.isColor && /* @__PURE__ */ jsx("span", {
3197
+ className: "sb-token-table__swatch",
3198
+ style: { background: row.cssVar },
3199
+ "aria-hidden": true
3200
+ }),
3201
+ /* @__PURE__ */ jsx("span", {
3202
+ className: "sb-token-table__value-text",
3203
+ title: row.value,
3204
+ "data-testid": "token-table-value",
3205
+ children: row.value
3206
+ }),
3207
+ row.outOfGamut && /* @__PURE__ */ jsx("span", {
3208
+ title: "Out of sRGB gamut for this format",
3209
+ "aria-label": "out of gamut",
3210
+ className: "sb-token-table__gamut-warn",
3211
+ children: "⚠"
3212
+ })
3213
+ ]
3974
3214
  })
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)) })
3215
+ })]
3216
+ }, row.path)) })
4010
3217
  ]
4011
- })
3218
+ }), selectedPath !== null && /* @__PURE__ */ jsx(DetailOverlay, {
3219
+ path: selectedPath,
3220
+ onClose: () => setSelectedPath(null),
3221
+ testId: "token-table-overlay"
3222
+ })]
4012
3223
  });
4013
3224
  }
4014
3225
  //#endregion
4015
3226
  //#region src/TypographyScale.tsx
4016
- const styles = {
4017
- wrapper: surfaceStyle,
4018
- caption: captionStyle,
4019
- empty: emptyStyle,
4020
- row: {
4021
- display: "grid",
4022
- gridTemplateColumns: "minmax(160px, 220px) 1fr",
4023
- gap: 16,
4024
- alignItems: "baseline",
4025
- padding: "14px 0",
4026
- borderBottom: BORDER_DEFAULT
4027
- },
4028
- meta: {
4029
- display: "flex",
4030
- flexDirection: "column",
4031
- gap: 2
4032
- },
4033
- path: {
4034
- fontFamily: MONO_STACK,
4035
- fontSize: 12
4036
- },
4037
- specs: {
4038
- fontFamily: MONO_STACK,
4039
- fontSize: 11,
4040
- opacity: .7
4041
- }
4042
- };
4043
3227
  function asDimension(raw) {
4044
3228
  if (raw == null) return void 0;
4045
3229
  if (typeof raw === "string" || typeof raw === "number") return String(raw);
@@ -4074,13 +3258,16 @@ function buildRow(path, composite) {
4074
3258
  ].filter(Boolean).join(" · ")
4075
3259
  };
4076
3260
  }
4077
- function TypographyScale({ filter = "typography", sample = "The quick brown fox jumps over the lazy dog.", caption }) {
3261
+ function TypographyScale({ filter, sample = "The quick brown fox jumps over the lazy dog.", caption, sortBy = "path", sortDir = "asc" }) {
4078
3262
  const { resolved, activeTheme, cssVarPrefix } = useProject();
4079
3263
  const rows = useMemo(() => {
4080
- return Object.entries(resolved).filter(([path, token]) => {
3264
+ return sortTokens(Object.entries(resolved).filter(([path, token]) => {
4081
3265
  if (token.$type !== "typography") return false;
4082
3266
  return globMatch(path, filter);
4083
- }).toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([path, token]) => {
3267
+ }), {
3268
+ by: sortBy,
3269
+ dir: sortDir
3270
+ }).map(([path, token]) => {
4084
3271
  const value = token.$value;
4085
3272
  if (!value || typeof value !== "object") return {
4086
3273
  path,
@@ -4089,37 +3276,34 @@ function TypographyScale({ filter = "typography", sample = "The quick brown fox
4089
3276
  };
4090
3277
  return buildRow(path, value);
4091
3278
  });
4092
- }, [resolved, filter]);
3279
+ }, [
3280
+ resolved,
3281
+ filter,
3282
+ sortBy,
3283
+ sortDir
3284
+ ]);
4093
3285
  const captionText = caption ?? `${rows.length} typography token${rows.length === 1 ? "" : "s"}${filter && filter !== "typography" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
4094
3286
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
4095
3287
  ...themeAttrs(cssVarPrefix, activeTheme),
4096
- style: {
4097
- ...chromeAliases(cssVarPrefix),
4098
- ...styles.wrapper
4099
- },
4100
3288
  children: /* @__PURE__ */ jsx("div", {
4101
- style: styles.empty,
3289
+ className: "sb-block__empty",
4102
3290
  children: "No typography tokens match this filter."
4103
3291
  })
4104
3292
  });
4105
3293
  return /* @__PURE__ */ jsxs("div", {
4106
3294
  ...themeAttrs(cssVarPrefix, activeTheme),
4107
- style: {
4108
- ...chromeAliases(cssVarPrefix),
4109
- ...styles.wrapper
4110
- },
4111
3295
  children: [/* @__PURE__ */ jsx("div", {
4112
- style: styles.caption,
3296
+ className: "sb-block__caption",
4113
3297
  children: captionText
4114
3298
  }), rows.map((row) => /* @__PURE__ */ jsxs("div", {
4115
- style: styles.row,
3299
+ className: "sb-typography-scale__row",
4116
3300
  children: [/* @__PURE__ */ jsxs("div", {
4117
- style: styles.meta,
3301
+ className: "sb-typography-scale__meta",
4118
3302
  children: [/* @__PURE__ */ jsx("span", {
4119
- style: styles.path,
3303
+ className: "sb-typography-scale__path",
4120
3304
  children: row.path
4121
3305
  }), row.specs && /* @__PURE__ */ jsx("span", {
4122
- style: styles.specs,
3306
+ className: "sb-typography-scale__specs",
4123
3307
  children: row.specs
4124
3308
  })]
4125
3309
  }), /* @__PURE__ */ jsx("div", {
@@ -4130,6 +3314,6 @@ function TypographyScale({ filter = "typography", sample = "The quick brown fox
4130
3314
  });
4131
3315
  }
4132
3316
  //#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 };
3317
+ 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
3318
 
4135
3319
  //# sourceMappingURL=index.mjs.map