@unpunnyfuns/swatchbook-blocks 0.68.0 → 0.69.1
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.d.mts +43 -5
- package/dist/index.mjs +1064 -532
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -5
package/dist/index.mjs
CHANGED
|
@@ -6,12 +6,35 @@ import { getVariance, listPaths, resolveAllWithProvenanceAt } from "@unpunnyfuns
|
|
|
6
6
|
import { makeCssVar } from "@unpunnyfuns/swatchbook-core/css-var";
|
|
7
7
|
import { SWATCHBOOK_STYLE_ELEMENT_ID, ensureStyleElement } from "@unpunnyfuns/swatchbook-core/style-element";
|
|
8
8
|
import { tupleToName } from "@unpunnyfuns/swatchbook-core/themes";
|
|
9
|
-
import { addons } from "storybook/preview-api";
|
|
10
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
10
|
import { dataAttr } from "@unpunnyfuns/swatchbook-core/data-attr";
|
|
12
11
|
import { matchPath } from "@unpunnyfuns/swatchbook-core/match-path";
|
|
13
12
|
import { fuzzyFilter } from "@unpunnyfuns/swatchbook-core/fuzzy";
|
|
14
13
|
import cx from "clsx";
|
|
14
|
+
//#region src/internal/channel.ts
|
|
15
|
+
let channel = null;
|
|
16
|
+
const pending = /* @__PURE__ */ new Set();
|
|
17
|
+
/**
|
|
18
|
+
* Inject the host channel. Called once by the addon preview at init. Runs any
|
|
19
|
+
* subscribers that registered before the channel existed, then clears the
|
|
20
|
+
* queue. Idempotent for consumers: each subscriber guards its own attach, so a
|
|
21
|
+
* re-register (HMR) does not double-wire.
|
|
22
|
+
*/
|
|
23
|
+
function registerChannel(next) {
|
|
24
|
+
channel = next;
|
|
25
|
+
for (const attach of pending) attach(next);
|
|
26
|
+
pending.clear();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Attach a subscriber to the host channel now if one is registered, else queue
|
|
30
|
+
* it to run the moment {@link registerChannel} fires. Blocks call this at
|
|
31
|
+
* module load; the queue absorbs the gap until the addon injects the channel.
|
|
32
|
+
*/
|
|
33
|
+
function onChannel(attach) {
|
|
34
|
+
if (channel) attach(channel);
|
|
35
|
+
else pending.add(attach);
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
15
38
|
//#region src/internal/channel-globals.ts
|
|
16
39
|
const AXES_GLOBAL_KEY = "swatchbookAxes";
|
|
17
40
|
const COLOR_FORMAT_GLOBAL_KEY = "swatchbookColorFormat";
|
|
@@ -24,10 +47,9 @@ let subscribed$1 = false;
|
|
|
24
47
|
function isColorFormat(value) {
|
|
25
48
|
return typeof value === "string" && COLOR_FORMATS.includes(value);
|
|
26
49
|
}
|
|
27
|
-
function
|
|
28
|
-
if (subscribed$1
|
|
50
|
+
function attach$1(channel) {
|
|
51
|
+
if (subscribed$1) return;
|
|
29
52
|
subscribed$1 = true;
|
|
30
|
-
const channel = addons.getChannel();
|
|
31
53
|
let lastFingerprint = "";
|
|
32
54
|
const onGlobals = (payload) => {
|
|
33
55
|
const globals = payload.globals;
|
|
@@ -49,9 +71,8 @@ function ensureSubscribed$1() {
|
|
|
49
71
|
channel.on("updateGlobals", onGlobals);
|
|
50
72
|
channel.on("setGlobals", onGlobals);
|
|
51
73
|
}
|
|
52
|
-
|
|
74
|
+
onChannel(attach$1);
|
|
53
75
|
function subscribe$2(cb) {
|
|
54
|
-
ensureSubscribed$1();
|
|
55
76
|
listeners$1.add(cb);
|
|
56
77
|
return () => {
|
|
57
78
|
listeners$1.delete(cb);
|
|
@@ -179,16 +200,15 @@ function applyPatch(patch) {
|
|
|
179
200
|
function registerTokenSource(source) {
|
|
180
201
|
applyPatch(source);
|
|
181
202
|
}
|
|
182
|
-
function
|
|
183
|
-
if (subscribed
|
|
203
|
+
function attach(channel) {
|
|
204
|
+
if (subscribed) return;
|
|
184
205
|
subscribed = true;
|
|
185
|
-
|
|
206
|
+
channel.on(TOKENS_UPDATED_EVENT, (payload) => {
|
|
186
207
|
applyPatch(payload);
|
|
187
208
|
});
|
|
188
209
|
}
|
|
189
|
-
|
|
210
|
+
onChannel(attach);
|
|
190
211
|
function subscribe$1(cb) {
|
|
191
|
-
ensureSubscribed();
|
|
192
212
|
listeners.add(cb);
|
|
193
213
|
return () => {
|
|
194
214
|
listeners.delete(cb);
|
|
@@ -235,9 +255,9 @@ function snapshotResolveAt(snapshot) {
|
|
|
235
255
|
*
|
|
236
256
|
* The provider-less path is what makes the hook safe to call from MDX
|
|
237
257
|
* doc blocks and autodocs renders where no story is active. It
|
|
238
|
-
* self-mounts the
|
|
239
|
-
* tuple via the `globalsUpdated`
|
|
240
|
-
*
|
|
258
|
+
* self-mounts the injected snapshot's per-theme CSS and tracks the active
|
|
259
|
+
* tuple via the injected channel's `globalsUpdated` event, since a
|
|
260
|
+
* story-scoped globals hook only works while a story is rendering.
|
|
241
261
|
*/
|
|
242
262
|
function useProject() {
|
|
243
263
|
const snapshot = useOptionalSwatchbookData();
|
|
@@ -363,13 +383,25 @@ function resolveColorValue(path, raw, colorFormat, project) {
|
|
|
363
383
|
}
|
|
364
384
|
//#endregion
|
|
365
385
|
//#region src/border-preview/BorderSample.tsx
|
|
366
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Pure derivation of a single border token's sample data from resolved
|
|
388
|
+
* project data. Extracted so it is unit-testable without React or a store.
|
|
389
|
+
*/
|
|
390
|
+
function deriveBorderSample(path, project) {
|
|
391
|
+
return { cssVar: resolveCssVar(path, project) };
|
|
392
|
+
}
|
|
393
|
+
/** Pure presentation for a single border token's sample. Renders from plain props. */
|
|
394
|
+
function BorderSampleView({ cssVar }) {
|
|
367
395
|
return /* @__PURE__ */ jsx("div", {
|
|
368
396
|
className: "sb-border-sample",
|
|
369
|
-
style: { border:
|
|
397
|
+
style: { border: cssVar },
|
|
370
398
|
"aria-hidden": true
|
|
371
399
|
});
|
|
372
400
|
}
|
|
401
|
+
function BorderSample({ path }) {
|
|
402
|
+
const { cssVar } = deriveBorderSample(path, useProject());
|
|
403
|
+
return /* @__PURE__ */ jsx(BorderSampleView, { cssVar });
|
|
404
|
+
}
|
|
373
405
|
//#endregion
|
|
374
406
|
//#region src/internal/composite-sample-format.ts
|
|
375
407
|
/**
|
|
@@ -552,29 +584,34 @@ function toDisplayable(v) {
|
|
|
552
584
|
}
|
|
553
585
|
//#endregion
|
|
554
586
|
//#region src/BorderPreview.tsx
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
587
|
+
/**
|
|
588
|
+
* Pure derivation of the preview's display rows from resolved project data.
|
|
589
|
+
* Extracted so it is unit-testable without React or a store.
|
|
590
|
+
*/
|
|
591
|
+
function deriveBorderRows(resolved, project, { filter, sortBy, sortDir, colorFormat }) {
|
|
592
|
+
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
593
|
+
if (token.$type !== "border") return false;
|
|
594
|
+
return matchPath(path, filter);
|
|
595
|
+
}), {
|
|
596
|
+
by: sortBy,
|
|
597
|
+
dir: sortDir
|
|
598
|
+
}).map(([path, token]) => {
|
|
599
|
+
const value = token.$value ?? {};
|
|
600
|
+
return {
|
|
567
601
|
path,
|
|
568
602
|
cssVar: resolveCssVar(path, project),
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
603
|
+
width: formatDimension$1(value.width),
|
|
604
|
+
style: value.style != null ? String(value.style) : "—",
|
|
605
|
+
color: formatSubColor(value.color, colorFormat)
|
|
606
|
+
};
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Pure presentation for the border preview. Renders from plain props;
|
|
611
|
+
* composes the connected `BorderSample` as a child (that child reads the
|
|
612
|
+
* project itself).
|
|
613
|
+
*/
|
|
614
|
+
function BorderPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter, caption }) {
|
|
578
615
|
const captionText = caption ?? `${rows.length} border${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
579
616
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
580
617
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -612,23 +649,48 @@ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
612
649
|
className: "sb-border-preview__breakdown-key",
|
|
613
650
|
children: "width"
|
|
614
651
|
}),
|
|
615
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
652
|
+
/* @__PURE__ */ jsx("span", { children: row.width }),
|
|
616
653
|
/* @__PURE__ */ jsx("span", {
|
|
617
654
|
className: "sb-border-preview__breakdown-key",
|
|
618
655
|
children: "style"
|
|
619
656
|
}),
|
|
620
|
-
/* @__PURE__ */ jsx("span", { children: row.
|
|
657
|
+
/* @__PURE__ */ jsx("span", { children: row.style }),
|
|
621
658
|
/* @__PURE__ */ jsx("span", {
|
|
622
659
|
className: "sb-border-preview__breakdown-key",
|
|
623
660
|
children: "color"
|
|
624
661
|
}),
|
|
625
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
662
|
+
/* @__PURE__ */ jsx("span", { children: row.color })
|
|
626
663
|
]
|
|
627
664
|
})
|
|
628
665
|
]
|
|
629
666
|
}, row.path))]
|
|
630
667
|
});
|
|
631
668
|
}
|
|
669
|
+
function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
670
|
+
const project = useProject();
|
|
671
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
672
|
+
const colorFormat = useColorFormat();
|
|
673
|
+
return /* @__PURE__ */ jsx(BorderPreviewView, {
|
|
674
|
+
rows: useMemo(() => deriveBorderRows(resolved, project, {
|
|
675
|
+
filter,
|
|
676
|
+
sortBy,
|
|
677
|
+
sortDir,
|
|
678
|
+
colorFormat
|
|
679
|
+
}), [
|
|
680
|
+
resolved,
|
|
681
|
+
project,
|
|
682
|
+
filter,
|
|
683
|
+
sortBy,
|
|
684
|
+
sortDir,
|
|
685
|
+
colorFormat
|
|
686
|
+
]),
|
|
687
|
+
activeTheme,
|
|
688
|
+
cssVarPrefix,
|
|
689
|
+
activeAxes,
|
|
690
|
+
filter,
|
|
691
|
+
caption
|
|
692
|
+
});
|
|
693
|
+
}
|
|
632
694
|
//#endregion
|
|
633
695
|
//#region src/ColorPalette.tsx
|
|
634
696
|
function fixedPrefixLength(filter) {
|
|
@@ -641,51 +703,48 @@ function fixedPrefixLength(filter) {
|
|
|
641
703
|
}
|
|
642
704
|
return fixed;
|
|
643
705
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
cssVarPrefix
|
|
651
|
-
};
|
|
652
|
-
const entries = sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
653
|
-
if (token.$type !== "color") return false;
|
|
654
|
-
return matchPath(path, filter);
|
|
655
|
-
}), {
|
|
656
|
-
by: sortBy,
|
|
657
|
-
dir: sortDir
|
|
658
|
-
});
|
|
659
|
-
const maxDepth = entries.reduce((m, [p]) => Math.max(m, p.split(".").length), 0);
|
|
660
|
-
const effectiveGroupBy = groupBy ?? Math.min(fixedPrefixLength(filter) + 1, Math.max(maxDepth - 1, 1));
|
|
661
|
-
const bucket = /* @__PURE__ */ new Map();
|
|
662
|
-
for (const [path, token] of entries) {
|
|
663
|
-
const segments = path.split(".");
|
|
664
|
-
const groupKey = segments.slice(0, effectiveGroupBy).join(".");
|
|
665
|
-
const leaf = segments.slice(effectiveGroupBy).join(".") || segments.at(-1) || path;
|
|
666
|
-
const list = bucket.get(groupKey) ?? [];
|
|
667
|
-
const formatted = resolveColorValue(path, token.$value, colorFormat, projectFields);
|
|
668
|
-
list.push({
|
|
669
|
-
path,
|
|
670
|
-
leaf,
|
|
671
|
-
cssVar: resolveCssVar(path, projectFields),
|
|
672
|
-
value: formatted.value,
|
|
673
|
-
outOfGamut: formatted.outOfGamut
|
|
674
|
-
});
|
|
675
|
-
bucket.set(groupKey, list);
|
|
676
|
-
}
|
|
677
|
-
return [...bucket.entries()].toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
|
|
678
|
-
}, [
|
|
679
|
-
resolved,
|
|
706
|
+
/**
|
|
707
|
+
* Pure derivation of the palette's grouped swatch rows from resolved project
|
|
708
|
+
* data. Extracted so it is unit-testable without React or a store.
|
|
709
|
+
*/
|
|
710
|
+
function deriveColorPaletteGroups(resolved, listing, cssVarPrefix, { filter, groupBy, sortBy, sortDir, colorFormat }) {
|
|
711
|
+
const projectFields = {
|
|
680
712
|
listing,
|
|
681
|
-
cssVarPrefix
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
713
|
+
cssVarPrefix
|
|
714
|
+
};
|
|
715
|
+
const entries = sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
716
|
+
if (token.$type !== "color") return false;
|
|
717
|
+
return matchPath(path, filter);
|
|
718
|
+
}), {
|
|
719
|
+
by: sortBy,
|
|
720
|
+
dir: sortDir
|
|
721
|
+
});
|
|
722
|
+
const maxDepth = entries.reduce((m, [p]) => Math.max(m, p.split(".").length), 0);
|
|
723
|
+
const effectiveGroupBy = groupBy ?? Math.min(fixedPrefixLength(filter) + 1, Math.max(maxDepth - 1, 1));
|
|
724
|
+
const bucket = /* @__PURE__ */ new Map();
|
|
725
|
+
for (const [path, token] of entries) {
|
|
726
|
+
const segments = path.split(".");
|
|
727
|
+
const groupKey = segments.slice(0, effectiveGroupBy).join(".");
|
|
728
|
+
const leaf = segments.slice(effectiveGroupBy).join(".") || segments.at(-1) || path;
|
|
729
|
+
const list = bucket.get(groupKey) ?? [];
|
|
730
|
+
const formatted = resolveColorValue(path, token.$value, colorFormat, projectFields);
|
|
731
|
+
list.push({
|
|
732
|
+
path,
|
|
733
|
+
leaf,
|
|
734
|
+
cssVar: resolveCssVar(path, projectFields),
|
|
735
|
+
value: formatted.value,
|
|
736
|
+
outOfGamut: formatted.outOfGamut
|
|
737
|
+
});
|
|
738
|
+
bucket.set(groupKey, list);
|
|
739
|
+
}
|
|
740
|
+
return [...bucket.entries()].toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true })).map(([group, swatches]) => ({
|
|
741
|
+
group,
|
|
742
|
+
swatches
|
|
743
|
+
}));
|
|
744
|
+
}
|
|
745
|
+
/** Pure presentation for the color palette. Renders from plain props. */
|
|
746
|
+
function ColorPaletteView({ groups, activeTheme, cssVarPrefix, activeAxes, filter, caption }) {
|
|
747
|
+
const totalCount = groups.reduce((acc, { swatches }) => acc + swatches.length, 0);
|
|
689
748
|
const captionText = caption ?? `${totalCount} color${totalCount === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
690
749
|
if (totalCount === 0) return /* @__PURE__ */ jsx("div", {
|
|
691
750
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -699,7 +758,7 @@ function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "as
|
|
|
699
758
|
children: [/* @__PURE__ */ jsx("div", {
|
|
700
759
|
className: "sb-block__caption",
|
|
701
760
|
children: captionText
|
|
702
|
-
}), groups.map((
|
|
761
|
+
}), groups.map(({ group, swatches }) => /* @__PURE__ */ jsxs("section", {
|
|
703
762
|
className: "sb-color-palette__group",
|
|
704
763
|
children: [/* @__PURE__ */ jsx("div", {
|
|
705
764
|
className: "sb-color-palette__group-header",
|
|
@@ -732,6 +791,33 @@ function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "as
|
|
|
732
791
|
}, group))]
|
|
733
792
|
});
|
|
734
793
|
}
|
|
794
|
+
function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "asc" }) {
|
|
795
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix, listing } = useProject();
|
|
796
|
+
const colorFormat = useColorFormat();
|
|
797
|
+
return /* @__PURE__ */ jsx(ColorPaletteView, {
|
|
798
|
+
groups: useMemo(() => deriveColorPaletteGroups(resolved, listing, cssVarPrefix, {
|
|
799
|
+
filter,
|
|
800
|
+
groupBy,
|
|
801
|
+
sortBy,
|
|
802
|
+
sortDir,
|
|
803
|
+
colorFormat
|
|
804
|
+
}), [
|
|
805
|
+
resolved,
|
|
806
|
+
listing,
|
|
807
|
+
cssVarPrefix,
|
|
808
|
+
filter,
|
|
809
|
+
groupBy,
|
|
810
|
+
sortBy,
|
|
811
|
+
sortDir,
|
|
812
|
+
colorFormat
|
|
813
|
+
]),
|
|
814
|
+
activeTheme,
|
|
815
|
+
cssVarPrefix,
|
|
816
|
+
activeAxes,
|
|
817
|
+
filter,
|
|
818
|
+
caption
|
|
819
|
+
});
|
|
820
|
+
}
|
|
735
821
|
//#endregion
|
|
736
822
|
//#region src/indicators/resolve.ts
|
|
737
823
|
/** Established-on set; `description` and `composes` are opt-in. */
|
|
@@ -1102,88 +1188,81 @@ function usePersistedState(key, initial) {
|
|
|
1102
1188
|
const NOOP_REFERENCE = () => {};
|
|
1103
1189
|
const BASE_LABEL = "base";
|
|
1104
1190
|
const COLUMN_COUNT = 6;
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1191
|
+
/**
|
|
1192
|
+
* Pure derivation of the table's display groups from resolved project data.
|
|
1193
|
+
* Carries per-variant `isDeprecated` / `token` / `variance` so the View
|
|
1194
|
+
* renders the indicator strip and deprecation state without reaching back
|
|
1195
|
+
* into the project. Extracted so it is unit-testable without React or a
|
|
1196
|
+
* store.
|
|
1197
|
+
*/
|
|
1198
|
+
function deriveColorGroups(resolved, listing, cssVarPrefix, varianceByPath, { filter, variants, sortBy, sortDir, colorFormat }) {
|
|
1199
|
+
const defs = buildVariantDefs(variants);
|
|
1200
|
+
const projectFields = {
|
|
1201
|
+
listing,
|
|
1202
|
+
cssVarPrefix
|
|
1203
|
+
};
|
|
1204
|
+
const sorted = sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
1205
|
+
if (token.$type !== "color") return false;
|
|
1206
|
+
return matchPath(path, filter);
|
|
1207
|
+
}), {
|
|
1208
|
+
by: sortBy,
|
|
1209
|
+
dir: sortDir
|
|
1210
|
+
});
|
|
1211
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
1212
|
+
for (const [path, token] of sorted) {
|
|
1213
|
+
const raw = token.$value;
|
|
1214
|
+
const hex = resolveColorValue(path, raw, "hex", projectFields);
|
|
1215
|
+
const hsl = formatColor(raw, "hsl");
|
|
1216
|
+
const oklch = formatColor(raw, "oklch");
|
|
1217
|
+
const active = pickActiveFormat(raw, colorFormat, hex, hsl, oklch);
|
|
1218
|
+
const match = matchVariant(path, defs.matchOrder);
|
|
1219
|
+
const dep = token.$deprecated;
|
|
1220
|
+
const variant = {
|
|
1221
|
+
label: match?.label ?? BASE_LABEL,
|
|
1222
|
+
path,
|
|
1223
|
+
token,
|
|
1224
|
+
variance: varianceByPath[path],
|
|
1225
|
+
cssVar: resolveCssVar(path, projectFields),
|
|
1226
|
+
value: active.value,
|
|
1227
|
+
outOfGamut: active.outOfGamut,
|
|
1228
|
+
hex: hex.value,
|
|
1229
|
+
hsl: hsl.value,
|
|
1230
|
+
oklch: oklch.value,
|
|
1231
|
+
isDeprecated: dep === true || typeof dep === "string" && dep.length > 0,
|
|
1232
|
+
...token.$description !== void 0 && { description: token.$description },
|
|
1233
|
+
...token.aliasOf !== void 0 && { aliasOf: token.aliasOf },
|
|
1234
|
+
...token.aliasChain !== void 0 && { aliasChain: token.aliasChain }
|
|
1235
|
+
};
|
|
1236
|
+
const basePath = match?.basePath ?? path;
|
|
1237
|
+
const existing = groupMap.get(basePath);
|
|
1238
|
+
if (existing) existing.variants.push(variant);
|
|
1239
|
+
else groupMap.set(basePath, {
|
|
1240
|
+
base: basePath,
|
|
1241
|
+
variants: [variant]
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
const out = [];
|
|
1245
|
+
for (const { base, variants: vs } of groupMap.values()) {
|
|
1246
|
+
vs.sort((a, b) => orderIndex(a.label, defs) - orderIndex(b.label, defs));
|
|
1247
|
+
const searchText = vs.map((v) => `${v.path} ${v.value}`).join(" ");
|
|
1248
|
+
out.push({
|
|
1249
|
+
base,
|
|
1250
|
+
variants: vs,
|
|
1251
|
+
searchText
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
return out;
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Pure presentation for the color table. Owns its own search, variant
|
|
1258
|
+
* selection, and row-expansion UI state; renders from the derived `groups`
|
|
1259
|
+
* view-model.
|
|
1260
|
+
*/
|
|
1261
|
+
function ColorTableView({ groups, activeTheme, cssVarPrefix, activeAxes, colorFormat, enabledIndicators, blockKey, filter, caption, searchable = true, onSelect }) {
|
|
1117
1262
|
const [query, setQuery] = usePersistedState(`${blockKey}::query`, "");
|
|
1118
1263
|
const deferredQuery = useDeferredValue(query);
|
|
1119
1264
|
const [selectedByBase, setSelectedByBase] = usePersistedState(`${blockKey}::selected`, {});
|
|
1120
1265
|
const [expandedByBase, setExpandedByBase] = usePersistedState(`${blockKey}::expanded`, () => /* @__PURE__ */ new Set());
|
|
1121
|
-
const defs = useMemo(() => buildVariantDefs(variants), [variants]);
|
|
1122
|
-
const groups = useMemo(() => {
|
|
1123
|
-
const projectFields = {
|
|
1124
|
-
listing,
|
|
1125
|
-
cssVarPrefix
|
|
1126
|
-
};
|
|
1127
|
-
const sorted = sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
1128
|
-
if (token.$type !== "color") return false;
|
|
1129
|
-
return matchPath(path, filter);
|
|
1130
|
-
}), {
|
|
1131
|
-
by: sortBy,
|
|
1132
|
-
dir: sortDir
|
|
1133
|
-
});
|
|
1134
|
-
const groupMap = /* @__PURE__ */ new Map();
|
|
1135
|
-
for (const [path, token] of sorted) {
|
|
1136
|
-
const raw = token.$value;
|
|
1137
|
-
const hex = resolveColorValue(path, raw, "hex", projectFields);
|
|
1138
|
-
const hsl = formatColor(raw, "hsl");
|
|
1139
|
-
const oklch = formatColor(raw, "oklch");
|
|
1140
|
-
const active = pickActiveFormat(raw, colorFormat, hex, hsl, oklch);
|
|
1141
|
-
const match = matchVariant(path, defs.matchOrder);
|
|
1142
|
-
const variant = {
|
|
1143
|
-
label: match?.label ?? BASE_LABEL,
|
|
1144
|
-
path,
|
|
1145
|
-
token,
|
|
1146
|
-
variance: varianceByPath[path],
|
|
1147
|
-
cssVar: resolveCssVar(path, projectFields),
|
|
1148
|
-
value: active.value,
|
|
1149
|
-
outOfGamut: active.outOfGamut,
|
|
1150
|
-
hex: hex.value,
|
|
1151
|
-
hsl: hsl.value,
|
|
1152
|
-
oklch: oklch.value,
|
|
1153
|
-
...token.$description !== void 0 && { description: token.$description },
|
|
1154
|
-
...token.aliasOf !== void 0 && { aliasOf: token.aliasOf },
|
|
1155
|
-
...token.aliasChain !== void 0 && { aliasChain: token.aliasChain }
|
|
1156
|
-
};
|
|
1157
|
-
const basePath = match?.basePath ?? path;
|
|
1158
|
-
const existing = groupMap.get(basePath);
|
|
1159
|
-
if (existing) existing.variants.push(variant);
|
|
1160
|
-
else groupMap.set(basePath, {
|
|
1161
|
-
base: basePath,
|
|
1162
|
-
variants: [variant]
|
|
1163
|
-
});
|
|
1164
|
-
}
|
|
1165
|
-
const out = [];
|
|
1166
|
-
for (const { base, variants: vs } of groupMap.values()) {
|
|
1167
|
-
vs.sort((a, b) => orderIndex(a.label, defs) - orderIndex(b.label, defs));
|
|
1168
|
-
const searchText = vs.map((v) => `${v.path} ${v.value}`).join(" ");
|
|
1169
|
-
out.push({
|
|
1170
|
-
base,
|
|
1171
|
-
variants: vs,
|
|
1172
|
-
searchText
|
|
1173
|
-
});
|
|
1174
|
-
}
|
|
1175
|
-
return out;
|
|
1176
|
-
}, [
|
|
1177
|
-
resolved,
|
|
1178
|
-
listing,
|
|
1179
|
-
cssVarPrefix,
|
|
1180
|
-
varianceByPath,
|
|
1181
|
-
filter,
|
|
1182
|
-
sortBy,
|
|
1183
|
-
sortDir,
|
|
1184
|
-
defs,
|
|
1185
|
-
colorFormat
|
|
1186
|
-
]);
|
|
1187
1266
|
const visibleGroups = useMemo(() => {
|
|
1188
1267
|
if (!searchable || deferredQuery.trim() === "") return groups;
|
|
1189
1268
|
return fuzzyFilter(groups, deferredQuery, (g) => g.searchText);
|
|
@@ -1296,12 +1375,59 @@ function ColorTable({ filter, caption, sortBy = "path", sortDir = "asc", searcha
|
|
|
1296
1375
|
})]
|
|
1297
1376
|
});
|
|
1298
1377
|
}
|
|
1378
|
+
/**
|
|
1379
|
+
* A grouped, searchable table of `$type: color` tokens. Suffix-matched
|
|
1380
|
+
* variants (see `variants` prop) collapse into a single row with a pill
|
|
1381
|
+
* selector; clicking a row expands it in place to show the description,
|
|
1382
|
+
* alias chain, and (for grouped rows) every variant's hex/HSL/OKLCH values.
|
|
1383
|
+
*/
|
|
1384
|
+
function ColorTable({ filter, caption, sortBy = "path", sortDir = "asc", searchable = true, onSelect, variants, id, indicators }) {
|
|
1385
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix, listing, varianceByPath } = useProject();
|
|
1386
|
+
const colorFormat = useColorFormat();
|
|
1387
|
+
const enabledIndicators = useMemo(() => ({
|
|
1388
|
+
...resolveIndicators(indicators),
|
|
1389
|
+
gamut: false
|
|
1390
|
+
}), [indicators]);
|
|
1391
|
+
const blockKey = useBlockKey("ColorTable", [
|
|
1392
|
+
filter,
|
|
1393
|
+
caption,
|
|
1394
|
+
id
|
|
1395
|
+
]);
|
|
1396
|
+
return /* @__PURE__ */ jsx(ColorTableView, {
|
|
1397
|
+
groups: useMemo(() => deriveColorGroups(resolved, listing, cssVarPrefix, varianceByPath, {
|
|
1398
|
+
filter,
|
|
1399
|
+
variants,
|
|
1400
|
+
sortBy,
|
|
1401
|
+
sortDir,
|
|
1402
|
+
colorFormat
|
|
1403
|
+
}), [
|
|
1404
|
+
resolved,
|
|
1405
|
+
listing,
|
|
1406
|
+
cssVarPrefix,
|
|
1407
|
+
varianceByPath,
|
|
1408
|
+
filter,
|
|
1409
|
+
variants,
|
|
1410
|
+
sortBy,
|
|
1411
|
+
sortDir,
|
|
1412
|
+
colorFormat
|
|
1413
|
+
]),
|
|
1414
|
+
activeTheme,
|
|
1415
|
+
cssVarPrefix,
|
|
1416
|
+
activeAxes,
|
|
1417
|
+
colorFormat,
|
|
1418
|
+
enabledIndicators,
|
|
1419
|
+
blockKey,
|
|
1420
|
+
filter,
|
|
1421
|
+
caption,
|
|
1422
|
+
searchable,
|
|
1423
|
+
onSelect
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1299
1426
|
const GroupRow = memo(function GroupRow({ group, selectedLabel, expanded, onToggleExpand, onSelectVariant, onSelect, enabled, colorFormat }) {
|
|
1300
1427
|
const multi = group.variants.length > 1;
|
|
1301
1428
|
const active = group.variants.find((v) => v.label === selectedLabel) ?? group.variants[0];
|
|
1302
1429
|
const nameText = multi ? group.base : active.path;
|
|
1303
|
-
const
|
|
1304
|
-
const isDeprecated = dep === true || typeof dep === "string" && dep.length > 0;
|
|
1430
|
+
const isDeprecated = active.isDeprecated;
|
|
1305
1431
|
const handleRowActivate = () => {
|
|
1306
1432
|
if (onSelect) onSelect(active.path);
|
|
1307
1433
|
else onToggleExpand(group.base);
|
|
@@ -1590,6 +1716,7 @@ const severityLabel = {
|
|
|
1590
1716
|
warn: "WARN",
|
|
1591
1717
|
info: "INFO"
|
|
1592
1718
|
};
|
|
1719
|
+
/** Aggregate diagnostics into a summary line + severity variant for the header. */
|
|
1593
1720
|
function summarize(diagnostics) {
|
|
1594
1721
|
if (diagnostics.length === 0) return {
|
|
1595
1722
|
text: "✔ OK · no diagnostics",
|
|
@@ -1616,18 +1743,8 @@ function summarize(diagnostics) {
|
|
|
1616
1743
|
function diagnosticKey(d, i) {
|
|
1617
1744
|
return `${d.severity}:${d.group}:${d.filename ?? ""}:${d.line ?? ""}:${d.message}:${i}`;
|
|
1618
1745
|
}
|
|
1619
|
-
/**
|
|
1620
|
-
|
|
1621
|
-
* disabled-axes validation issues, etc. — as a collapsible list. Auto-opens
|
|
1622
|
-
* when the project carries errors or warnings; stays collapsed for clean
|
|
1623
|
-
* loads and info-only loads.
|
|
1624
|
-
*
|
|
1625
|
-
* Replaces the diagnostics section from the addon's (now-retired) Design
|
|
1626
|
-
* Tokens panel. Consumers compose it alongside TokenNavigator / TokenTable
|
|
1627
|
-
* on their own MDX pages.
|
|
1628
|
-
*/
|
|
1629
|
-
function Diagnostics({ caption } = {}) {
|
|
1630
|
-
const { activeAxes, cssVarPrefix, diagnostics } = useProject();
|
|
1746
|
+
/** Pure presentation for the diagnostics list. Renders from plain props. */
|
|
1747
|
+
function DiagnosticsView({ caption, diagnostics, cssVarPrefix, activeAxes }) {
|
|
1631
1748
|
const summary = useMemo(() => summarize(diagnostics), [diagnostics]);
|
|
1632
1749
|
const headingText = caption ?? `Diagnostics · ${summary.text}`;
|
|
1633
1750
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -1661,6 +1778,25 @@ function Diagnostics({ caption } = {}) {
|
|
|
1661
1778
|
})
|
|
1662
1779
|
});
|
|
1663
1780
|
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Render the project's load diagnostics — parser errors, resolver warnings,
|
|
1783
|
+
* disabled-axes validation issues, etc. — as a collapsible list. Auto-opens
|
|
1784
|
+
* when the project carries errors or warnings; stays collapsed for clean
|
|
1785
|
+
* loads and info-only loads.
|
|
1786
|
+
*
|
|
1787
|
+
* Replaces the diagnostics section from the addon's (now-retired) Design
|
|
1788
|
+
* Tokens panel. Consumers compose it alongside TokenNavigator / TokenTable
|
|
1789
|
+
* on their own MDX pages.
|
|
1790
|
+
*/
|
|
1791
|
+
function Diagnostics({ caption } = {}) {
|
|
1792
|
+
const { activeAxes, cssVarPrefix, diagnostics } = useProject();
|
|
1793
|
+
return /* @__PURE__ */ jsx(DiagnosticsView, {
|
|
1794
|
+
caption,
|
|
1795
|
+
diagnostics,
|
|
1796
|
+
cssVarPrefix,
|
|
1797
|
+
activeAxes
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1664
1800
|
//#endregion
|
|
1665
1801
|
//#region src/dimension-scale/dimension-px.ts
|
|
1666
1802
|
/**
|
|
@@ -1709,6 +1845,22 @@ function useRootFontSize() {
|
|
|
1709
1845
|
}
|
|
1710
1846
|
//#endregion
|
|
1711
1847
|
//#region src/dimension-scale/DimensionBar.tsx
|
|
1848
|
+
/**
|
|
1849
|
+
* Pure derivation of a single dimension token's bar geometry from resolved
|
|
1850
|
+
* project data. Extracted so it is unit-testable without React or a store.
|
|
1851
|
+
*/
|
|
1852
|
+
function deriveDimensionBar(path, project, rootFontSizePx) {
|
|
1853
|
+
const cssVar = resolveCssVar(path, project);
|
|
1854
|
+
const token = project.resolved[path];
|
|
1855
|
+
const pxValue = toPixels(token?.$value, rootFontSizePx);
|
|
1856
|
+
const capped = Number.isFinite(pxValue) && pxValue > 480;
|
|
1857
|
+
return {
|
|
1858
|
+
cssVar,
|
|
1859
|
+
pxValue,
|
|
1860
|
+
capped,
|
|
1861
|
+
cappedValue: capped ? `480px` : cssVar
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1712
1864
|
function withCap(visual) {
|
|
1713
1865
|
return /* @__PURE__ */ jsxs("span", {
|
|
1714
1866
|
className: "sb-dimension-bar sb-dimension-bar--capped",
|
|
@@ -1720,15 +1872,8 @@ function withCap(visual) {
|
|
|
1720
1872
|
})]
|
|
1721
1873
|
});
|
|
1722
1874
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
const { resolved } = project;
|
|
1726
|
-
const rootFontSize = useRootFontSize();
|
|
1727
|
-
const cssVar = resolveCssVar(path, project);
|
|
1728
|
-
const token = resolved[path];
|
|
1729
|
-
const pxValue = toPixels(token?.$value, rootFontSize);
|
|
1730
|
-
const capped = Number.isFinite(pxValue) && pxValue > 480;
|
|
1731
|
-
const cappedValue = capped ? `480px` : cssVar;
|
|
1875
|
+
/** Pure presentation for a single dimension token's bar/sample. Renders from plain props. */
|
|
1876
|
+
function DimensionBarView({ cssVar, capped, cappedValue, visual }) {
|
|
1732
1877
|
switch (visual) {
|
|
1733
1878
|
case "radius": return /* @__PURE__ */ jsx("div", {
|
|
1734
1879
|
className: "sb-dimension-bar__radius-sample",
|
|
@@ -1756,6 +1901,15 @@ function DimensionBar({ path, visual = "length" }) {
|
|
|
1756
1901
|
}
|
|
1757
1902
|
}
|
|
1758
1903
|
}
|
|
1904
|
+
function DimensionBar({ path, visual = "length" }) {
|
|
1905
|
+
const { cssVar, capped, cappedValue } = deriveDimensionBar(path, useProject(), useRootFontSize());
|
|
1906
|
+
return /* @__PURE__ */ jsx(DimensionBarView, {
|
|
1907
|
+
cssVar,
|
|
1908
|
+
capped,
|
|
1909
|
+
cappedValue,
|
|
1910
|
+
visual
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1759
1913
|
//#endregion
|
|
1760
1914
|
//#region src/internal/format-token-value.ts
|
|
1761
1915
|
/**
|
|
@@ -1860,31 +2014,30 @@ function formatUnknown(v) {
|
|
|
1860
2014
|
}
|
|
1861
2015
|
//#endregion
|
|
1862
2016
|
//#region src/DimensionScale.tsx
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
]);
|
|
2017
|
+
/**
|
|
2018
|
+
* Pure derivation of the scale's display rows from resolved project data.
|
|
2019
|
+
* Extracted so it is unit-testable without React or a store.
|
|
2020
|
+
*/
|
|
2021
|
+
function deriveDimensionRows(resolved, project, { filter, sortBy, sortDir, rootFontSizePx }) {
|
|
2022
|
+
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
2023
|
+
if (token.$type !== "dimension") return false;
|
|
2024
|
+
return matchPath(path, filter);
|
|
2025
|
+
}), {
|
|
2026
|
+
by: sortBy,
|
|
2027
|
+
dir: sortDir,
|
|
2028
|
+
rootFontSizePx
|
|
2029
|
+
}).map(([path, token]) => ({
|
|
2030
|
+
path,
|
|
2031
|
+
cssVar: resolveCssVar(path, project),
|
|
2032
|
+
displayValue: formatTokenValue(token.$value, token.$type, "raw", project.listing[path])
|
|
2033
|
+
}));
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Pure presentation for the dimension scale. Renders from plain props;
|
|
2037
|
+
* composes the connected `DimensionBar` as a child (that child reads the
|
|
2038
|
+
* project itself).
|
|
2039
|
+
*/
|
|
2040
|
+
function DimensionScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, visual, filter, caption }) {
|
|
1888
2041
|
const captionText = caption ?? `${rows.length} dimension${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
1889
2042
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
1890
2043
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -1926,6 +2079,32 @@ function DimensionScale({ filter, visual = "length", caption, sortBy = "value",
|
|
|
1926
2079
|
}, row.path))]
|
|
1927
2080
|
});
|
|
1928
2081
|
}
|
|
2082
|
+
function DimensionScale({ filter, visual = "length", caption, sortBy = "value", sortDir = "asc" }) {
|
|
2083
|
+
const project = useProject();
|
|
2084
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
2085
|
+
const rootFontSize = useRootFontSize();
|
|
2086
|
+
return /* @__PURE__ */ jsx(DimensionScaleView, {
|
|
2087
|
+
rows: useMemo(() => deriveDimensionRows(resolved, project, {
|
|
2088
|
+
filter,
|
|
2089
|
+
sortBy,
|
|
2090
|
+
sortDir,
|
|
2091
|
+
rootFontSizePx: rootFontSize
|
|
2092
|
+
}), [
|
|
2093
|
+
resolved,
|
|
2094
|
+
project,
|
|
2095
|
+
filter,
|
|
2096
|
+
sortBy,
|
|
2097
|
+
sortDir,
|
|
2098
|
+
rootFontSize
|
|
2099
|
+
]),
|
|
2100
|
+
activeTheme,
|
|
2101
|
+
cssVarPrefix,
|
|
2102
|
+
activeAxes,
|
|
2103
|
+
visual,
|
|
2104
|
+
filter,
|
|
2105
|
+
caption
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
1929
2108
|
//#endregion
|
|
1930
2109
|
//#region src/FontFamilyPreview.tsx
|
|
1931
2110
|
function stackString(raw) {
|
|
@@ -1933,28 +2112,25 @@ function stackString(raw) {
|
|
|
1933
2112
|
if (Array.isArray(raw)) return raw.map(String).join(", ");
|
|
1934
2113
|
return "";
|
|
1935
2114
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
sortBy,
|
|
1956
|
-
sortDir
|
|
1957
|
-
]);
|
|
2115
|
+
/**
|
|
2116
|
+
* Pure derivation of the preview's display rows from resolved project data.
|
|
2117
|
+
* Extracted so it is unit-testable without React or a store.
|
|
2118
|
+
*/
|
|
2119
|
+
function deriveFontFamilyRows(resolved, project, { filter, sortBy, sortDir }) {
|
|
2120
|
+
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
2121
|
+
if (token.$type !== "fontFamily") return false;
|
|
2122
|
+
return matchPath(path, filter);
|
|
2123
|
+
}), {
|
|
2124
|
+
by: sortBy,
|
|
2125
|
+
dir: sortDir
|
|
2126
|
+
}).map(([path, token]) => ({
|
|
2127
|
+
path,
|
|
2128
|
+
cssVar: resolveCssVar(path, project),
|
|
2129
|
+
stack: stackString(token.$value)
|
|
2130
|
+
}));
|
|
2131
|
+
}
|
|
2132
|
+
/** Pure presentation for the font-family preview. Renders from plain props. */
|
|
2133
|
+
function FontFamilyPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, sample, filter, caption }) {
|
|
1958
2134
|
const captionText = caption ?? `${rows.length} fontFamily token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontFamily" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
1959
2135
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
1960
2136
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -1994,6 +2170,29 @@ function FontFamilyPreview({ filter, sample = "The quick brown fox jumps over th
|
|
|
1994
2170
|
}, row.path))]
|
|
1995
2171
|
});
|
|
1996
2172
|
}
|
|
2173
|
+
function FontFamilyPreview({ filter, sample = "The quick brown fox jumps over the lazy dog.", caption, sortBy = "path", sortDir = "asc" }) {
|
|
2174
|
+
const project = useProject();
|
|
2175
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
2176
|
+
return /* @__PURE__ */ jsx(FontFamilyPreviewView, {
|
|
2177
|
+
rows: useMemo(() => deriveFontFamilyRows(resolved, project, {
|
|
2178
|
+
filter,
|
|
2179
|
+
sortBy,
|
|
2180
|
+
sortDir
|
|
2181
|
+
}), [
|
|
2182
|
+
resolved,
|
|
2183
|
+
project,
|
|
2184
|
+
filter,
|
|
2185
|
+
sortBy,
|
|
2186
|
+
sortDir
|
|
2187
|
+
]),
|
|
2188
|
+
activeTheme,
|
|
2189
|
+
cssVarPrefix,
|
|
2190
|
+
activeAxes,
|
|
2191
|
+
sample,
|
|
2192
|
+
filter,
|
|
2193
|
+
caption
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
1997
2196
|
//#endregion
|
|
1998
2197
|
//#region src/internal/css-var-style.ts
|
|
1999
2198
|
/**
|
|
@@ -2019,29 +2218,26 @@ function toWeight(raw) {
|
|
|
2019
2218
|
}
|
|
2020
2219
|
return NaN;
|
|
2021
2220
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
sortBy,
|
|
2043
|
-
sortDir
|
|
2044
|
-
]);
|
|
2221
|
+
/**
|
|
2222
|
+
* Pure derivation of the scale's display rows from resolved project data.
|
|
2223
|
+
* Extracted so it is unit-testable without React or a store.
|
|
2224
|
+
*/
|
|
2225
|
+
function deriveFontWeightRows(resolved, project, { filter, sortBy, sortDir }) {
|
|
2226
|
+
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
2227
|
+
if (token.$type !== "fontWeight") return false;
|
|
2228
|
+
return matchPath(path, filter);
|
|
2229
|
+
}), {
|
|
2230
|
+
by: sortBy,
|
|
2231
|
+
dir: sortDir
|
|
2232
|
+
}).map(([path, token]) => ({
|
|
2233
|
+
path,
|
|
2234
|
+
cssVar: resolveCssVar(path, project),
|
|
2235
|
+
display: token.$value == null ? "" : String(token.$value),
|
|
2236
|
+
weight: toWeight(token.$value)
|
|
2237
|
+
}));
|
|
2238
|
+
}
|
|
2239
|
+
/** Pure presentation for the font-weight scale. Renders from plain props. */
|
|
2240
|
+
function FontWeightScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, sample, filter, caption }) {
|
|
2045
2241
|
const captionText = caption ?? `${rows.length} fontWeight token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontWeight" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2046
2242
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2047
2243
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2081,6 +2277,29 @@ function FontWeightScale({ filter, sample = "Aa", caption, sortBy = "value", sor
|
|
|
2081
2277
|
}, row.path))]
|
|
2082
2278
|
});
|
|
2083
2279
|
}
|
|
2280
|
+
function FontWeightScale({ filter, sample = "Aa", caption, sortBy = "value", sortDir = "asc" }) {
|
|
2281
|
+
const project = useProject();
|
|
2282
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
2283
|
+
return /* @__PURE__ */ jsx(FontWeightScaleView, {
|
|
2284
|
+
rows: useMemo(() => deriveFontWeightRows(resolved, project, {
|
|
2285
|
+
filter,
|
|
2286
|
+
sortBy,
|
|
2287
|
+
sortDir
|
|
2288
|
+
}), [
|
|
2289
|
+
resolved,
|
|
2290
|
+
project,
|
|
2291
|
+
filter,
|
|
2292
|
+
sortBy,
|
|
2293
|
+
sortDir
|
|
2294
|
+
]),
|
|
2295
|
+
activeTheme,
|
|
2296
|
+
cssVarPrefix,
|
|
2297
|
+
activeAxes,
|
|
2298
|
+
sample,
|
|
2299
|
+
filter,
|
|
2300
|
+
caption
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2084
2303
|
//#endregion
|
|
2085
2304
|
//#region src/GradientPalette.tsx
|
|
2086
2305
|
function asStops(raw) {
|
|
@@ -2095,33 +2314,37 @@ function stopCssColor(stop) {
|
|
|
2095
2314
|
function stopKey(path, stop, fallback) {
|
|
2096
2315
|
return `${path}|${stop.position ?? fallback}|${stopCssColor(stop)}`;
|
|
2097
2316
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2317
|
+
/**
|
|
2318
|
+
* Pure derivation of the palette's gradient rows from resolved project
|
|
2319
|
+
* data. Extracted so it is unit-testable without React or a store.
|
|
2320
|
+
*/
|
|
2321
|
+
function deriveGradientRows(resolved, listing, cssVarPrefix, { filter, sortBy, sortDir, colorFormat }) {
|
|
2322
|
+
const projectFields = {
|
|
2323
|
+
listing,
|
|
2324
|
+
cssVarPrefix
|
|
2325
|
+
};
|
|
2326
|
+
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
2327
|
+
if (token.$type !== "gradient") return false;
|
|
2328
|
+
return matchPath(path, filter);
|
|
2329
|
+
}), {
|
|
2330
|
+
by: sortBy,
|
|
2331
|
+
dir: sortDir
|
|
2332
|
+
}).map(([path, token]) => {
|
|
2333
|
+
const stops = asStops(token.$value);
|
|
2334
|
+
return {
|
|
2113
2335
|
path,
|
|
2114
2336
|
cssVar: resolveCssVar(path, projectFields),
|
|
2115
|
-
stops:
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2337
|
+
stops: stops.map((stop, i) => ({
|
|
2338
|
+
key: stopKey(path, stop, i),
|
|
2339
|
+
cssColor: stopCssColor(stop),
|
|
2340
|
+
value: formatColor(stop.color, colorFormat).value,
|
|
2341
|
+
positionPercent: ((stop.position ?? 0) * 100).toFixed(0)
|
|
2342
|
+
}))
|
|
2343
|
+
};
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
/** Pure presentation for the gradient palette. Renders from plain props. */
|
|
2347
|
+
function GradientPaletteView({ rows, activeTheme, cssVarPrefix, activeAxes, filter, caption }) {
|
|
2125
2348
|
const captionText = caption ?? `${rows.length} gradient${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2126
2349
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2127
2350
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2155,30 +2378,55 @@ function GradientPalette({ filter, caption, sortBy = "path", sortDir = "asc" })
|
|
|
2155
2378
|
}),
|
|
2156
2379
|
/* @__PURE__ */ jsx("div", {
|
|
2157
2380
|
className: "sb-gradient-palette__stops",
|
|
2158
|
-
children: row.stops.map((stop
|
|
2381
|
+
children: row.stops.map((stop) => /* @__PURE__ */ jsxs("div", {
|
|
2159
2382
|
className: "sb-gradient-palette__stop-row",
|
|
2160
2383
|
children: [
|
|
2161
2384
|
/* @__PURE__ */ jsx("span", {
|
|
2162
2385
|
className: "sb-gradient-palette__stop-swatch",
|
|
2163
|
-
style: { background:
|
|
2386
|
+
style: { background: stop.cssColor },
|
|
2164
2387
|
"aria-hidden": true
|
|
2165
2388
|
}),
|
|
2166
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
2389
|
+
/* @__PURE__ */ jsx("span", { children: stop.value }),
|
|
2167
2390
|
/* @__PURE__ */ jsxs("span", {
|
|
2168
2391
|
className: "sb-gradient-palette__stop-position",
|
|
2169
2392
|
children: [
|
|
2170
2393
|
"@ ",
|
|
2171
|
-
|
|
2394
|
+
stop.positionPercent,
|
|
2172
2395
|
"%"
|
|
2173
2396
|
]
|
|
2174
2397
|
})
|
|
2175
2398
|
]
|
|
2176
|
-
},
|
|
2399
|
+
}, stop.key))
|
|
2177
2400
|
})
|
|
2178
2401
|
]
|
|
2179
2402
|
}, row.path))]
|
|
2180
2403
|
});
|
|
2181
2404
|
}
|
|
2405
|
+
function GradientPalette({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
2406
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix, listing } = useProject();
|
|
2407
|
+
const colorFormat = useColorFormat();
|
|
2408
|
+
return /* @__PURE__ */ jsx(GradientPaletteView, {
|
|
2409
|
+
rows: useMemo(() => deriveGradientRows(resolved, listing, cssVarPrefix, {
|
|
2410
|
+
filter,
|
|
2411
|
+
sortBy,
|
|
2412
|
+
sortDir,
|
|
2413
|
+
colorFormat
|
|
2414
|
+
}), [
|
|
2415
|
+
resolved,
|
|
2416
|
+
listing,
|
|
2417
|
+
cssVarPrefix,
|
|
2418
|
+
filter,
|
|
2419
|
+
sortBy,
|
|
2420
|
+
sortDir,
|
|
2421
|
+
colorFormat
|
|
2422
|
+
]),
|
|
2423
|
+
activeTheme,
|
|
2424
|
+
cssVarPrefix,
|
|
2425
|
+
activeAxes,
|
|
2426
|
+
filter,
|
|
2427
|
+
caption
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2182
2430
|
//#endregion
|
|
2183
2431
|
//#region src/internal/prefers-reduced-motion.ts
|
|
2184
2432
|
function isChromatic() {
|
|
@@ -2280,10 +2528,16 @@ function resolveMotionSpec(token, themeTokens) {
|
|
|
2280
2528
|
}
|
|
2281
2529
|
return null;
|
|
2282
2530
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2531
|
+
/**
|
|
2532
|
+
* Pure derivation of a single motion token's animation spec from resolved
|
|
2533
|
+
* project data. Extracted so it is unit-testable without React or a store.
|
|
2534
|
+
*/
|
|
2535
|
+
function deriveMotionSample(path, project) {
|
|
2536
|
+
return { spec: resolveMotionSpec(project.resolved[path], project.resolved) };
|
|
2537
|
+
}
|
|
2538
|
+
/** Pure presentation + animation for a single motion token's sample. Renders from plain props. */
|
|
2539
|
+
function MotionSampleView({ spec, speed, runKey }) {
|
|
2285
2540
|
const reducedMotion = usePrefersReducedMotion();
|
|
2286
|
-
const spec = useMemo(() => resolveMotionSpec(resolved[path], resolved), [resolved, path]);
|
|
2287
2541
|
const durationMs = spec?.durationMs ?? DEFAULT_DURATION_MS;
|
|
2288
2542
|
const easing = spec?.easing ?? DEFAULT_EASING;
|
|
2289
2543
|
const scaledDuration = Math.max(1, durationMs / speed);
|
|
@@ -2321,6 +2575,14 @@ function MotionSample({ path, speed = 1, runKey = 0 }) {
|
|
|
2321
2575
|
})
|
|
2322
2576
|
});
|
|
2323
2577
|
}
|
|
2578
|
+
function MotionSample({ path, speed = 1, runKey = 0 }) {
|
|
2579
|
+
const { spec } = deriveMotionSample(path, useProject());
|
|
2580
|
+
return /* @__PURE__ */ jsx(MotionSampleView, {
|
|
2581
|
+
spec,
|
|
2582
|
+
speed,
|
|
2583
|
+
runKey
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2324
2586
|
//#endregion
|
|
2325
2587
|
//#region src/MotionPreview.tsx
|
|
2326
2588
|
const SPEEDS = [
|
|
@@ -2336,43 +2598,48 @@ function formatSpec(row) {
|
|
|
2336
2598
|
case "cubicBezier": return `cubicBezier · ${row.easing}`;
|
|
2337
2599
|
}
|
|
2338
2600
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2601
|
+
/**
|
|
2602
|
+
* Pure derivation of the preview's display rows from resolved project data.
|
|
2603
|
+
* Extracted so it is unit-testable without React or a store.
|
|
2604
|
+
*/
|
|
2605
|
+
function deriveMotionRows(resolved, project, { filter }) {
|
|
2606
|
+
const collected = [];
|
|
2607
|
+
for (const [path, token] of Object.entries(resolved)) {
|
|
2608
|
+
if (filter && !matchPath(path, filter)) continue;
|
|
2609
|
+
if (!filter && ![
|
|
2610
|
+
"transition",
|
|
2611
|
+
"duration",
|
|
2612
|
+
"cubicBezier"
|
|
2613
|
+
].includes(token.$type ?? "")) continue;
|
|
2614
|
+
const kind = token.$type;
|
|
2615
|
+
if (!kind) continue;
|
|
2616
|
+
const spec = resolveMotionSpec(token, resolved);
|
|
2617
|
+
if (!spec) continue;
|
|
2618
|
+
collected.push({
|
|
2619
|
+
path,
|
|
2620
|
+
cssVar: resolveCssVar(path, project),
|
|
2621
|
+
durationMs: spec.durationMs,
|
|
2622
|
+
easing: spec.easing,
|
|
2623
|
+
kind
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
collected.sort((a, b) => {
|
|
2627
|
+
if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
|
|
2628
|
+
return a.path.localeCompare(b.path, void 0, { numeric: true });
|
|
2629
|
+
});
|
|
2630
|
+
return collected;
|
|
2631
|
+
}
|
|
2632
|
+
/**
|
|
2633
|
+
* Pure presentation for the motion preview. Owns the speed/replay controls'
|
|
2634
|
+
* local UI state and the `prefers-reduced-motion` read (a browser-environment
|
|
2635
|
+
* concern, not project data); renders from the derived `rows` view-model.
|
|
2636
|
+
* Composes the connected `MotionSample` as a child (that child reads the
|
|
2637
|
+
* project itself).
|
|
2638
|
+
*/
|
|
2639
|
+
function MotionPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter, caption }) {
|
|
2342
2640
|
const [speed, setSpeed] = useState(1);
|
|
2343
2641
|
const [run, setRun] = useState(0);
|
|
2344
2642
|
const reducedMotion = usePrefersReducedMotion();
|
|
2345
|
-
const rows = useMemo(() => {
|
|
2346
|
-
const collected = [];
|
|
2347
|
-
for (const [path, token] of Object.entries(resolved)) {
|
|
2348
|
-
if (filter && !matchPath(path, filter)) continue;
|
|
2349
|
-
if (!filter && ![
|
|
2350
|
-
"transition",
|
|
2351
|
-
"duration",
|
|
2352
|
-
"cubicBezier"
|
|
2353
|
-
].includes(token.$type ?? "")) continue;
|
|
2354
|
-
const kind = token.$type;
|
|
2355
|
-
if (!kind) continue;
|
|
2356
|
-
const spec = resolveMotionSpec(token, resolved);
|
|
2357
|
-
if (!spec) continue;
|
|
2358
|
-
collected.push({
|
|
2359
|
-
path,
|
|
2360
|
-
cssVar: resolveCssVar(path, project),
|
|
2361
|
-
durationMs: spec.durationMs,
|
|
2362
|
-
easing: spec.easing,
|
|
2363
|
-
kind
|
|
2364
|
-
});
|
|
2365
|
-
}
|
|
2366
|
-
collected.sort((a, b) => {
|
|
2367
|
-
if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
|
|
2368
|
-
return a.path.localeCompare(b.path, void 0, { numeric: true });
|
|
2369
|
-
});
|
|
2370
|
-
return collected;
|
|
2371
|
-
}, [
|
|
2372
|
-
resolved,
|
|
2373
|
-
filter,
|
|
2374
|
-
project
|
|
2375
|
-
]);
|
|
2376
2643
|
const captionText = caption ?? `${rows.length} motion token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2377
2644
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2378
2645
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2438,51 +2705,56 @@ function MotionPreview({ filter, caption }) {
|
|
|
2438
2705
|
]
|
|
2439
2706
|
});
|
|
2440
2707
|
}
|
|
2708
|
+
function MotionPreview({ filter, caption }) {
|
|
2709
|
+
const project = useProject();
|
|
2710
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
2711
|
+
return /* @__PURE__ */ jsx(MotionPreviewView, {
|
|
2712
|
+
rows: useMemo(() => deriveMotionRows(resolved, project, { filter }), [
|
|
2713
|
+
resolved,
|
|
2714
|
+
project,
|
|
2715
|
+
filter
|
|
2716
|
+
]),
|
|
2717
|
+
activeTheme,
|
|
2718
|
+
cssVarPrefix,
|
|
2719
|
+
activeAxes,
|
|
2720
|
+
filter,
|
|
2721
|
+
caption
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2441
2724
|
//#endregion
|
|
2442
2725
|
//#region src/OpacityScale.tsx
|
|
2443
2726
|
function toOpacity(raw) {
|
|
2444
2727
|
if (typeof raw === "number") return raw;
|
|
2445
2728
|
return NaN;
|
|
2446
2729
|
}
|
|
2447
|
-
/**
|
|
2448
|
-
*
|
|
2449
|
-
*
|
|
2450
|
-
*
|
|
2451
|
-
*
|
|
2452
|
-
*
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
displayValue: String(opacity)
|
|
2476
|
-
};
|
|
2477
|
-
});
|
|
2478
|
-
}, [
|
|
2479
|
-
resolved,
|
|
2480
|
-
filter,
|
|
2481
|
-
type,
|
|
2482
|
-
project,
|
|
2483
|
-
sortBy,
|
|
2484
|
-
sortDir
|
|
2485
|
-
]);
|
|
2730
|
+
/**
|
|
2731
|
+
* Pure derivation of the scale's display rows from resolved project data.
|
|
2732
|
+
* Only tokens whose `$value` is a finite number between 0 and 1 inclusive
|
|
2733
|
+
* are included — non-opacity `number` siblings (`line-height`, `z-index`)
|
|
2734
|
+
* fall out naturally. Extracted so it is unit-testable without React or
|
|
2735
|
+
* a store.
|
|
2736
|
+
*/
|
|
2737
|
+
function deriveOpacityRows(resolved, project, { filter, type, sortBy, sortDir }) {
|
|
2738
|
+
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
2739
|
+
if (token.$type !== type) return false;
|
|
2740
|
+
const v = toOpacity(token.$value);
|
|
2741
|
+
if (!Number.isFinite(v) || v < 0 || v > 1) return false;
|
|
2742
|
+
return matchPath(path, filter);
|
|
2743
|
+
}), {
|
|
2744
|
+
by: sortBy,
|
|
2745
|
+
dir: sortDir
|
|
2746
|
+
}).map(([path, token]) => {
|
|
2747
|
+
const opacity = toOpacity(token.$value);
|
|
2748
|
+
return {
|
|
2749
|
+
path,
|
|
2750
|
+
cssVar: resolveCssVar(path, project),
|
|
2751
|
+
opacity,
|
|
2752
|
+
displayValue: String(opacity)
|
|
2753
|
+
};
|
|
2754
|
+
});
|
|
2755
|
+
}
|
|
2756
|
+
/** Pure presentation for the opacity scale. Renders from plain props. */
|
|
2757
|
+
function OpacityScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, sampleColorVar, filter, caption }) {
|
|
2486
2758
|
const captionText = caption ?? `${rows.length} opacity token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2487
2759
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2488
2760
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2491,7 +2763,6 @@ function OpacityScale({ filter, type = "number", sampleColor = "color.accent.bg"
|
|
|
2491
2763
|
children: "No opacity tokens match this filter."
|
|
2492
2764
|
})
|
|
2493
2765
|
});
|
|
2494
|
-
const sampleColorVar = resolveCssVar(sampleColor, project);
|
|
2495
2766
|
return /* @__PURE__ */ jsxs("div", {
|
|
2496
2767
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2497
2768
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -2529,6 +2800,37 @@ function OpacityScale({ filter, type = "number", sampleColor = "color.accent.bg"
|
|
|
2529
2800
|
})]
|
|
2530
2801
|
});
|
|
2531
2802
|
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Render each opacity token as a colored sample at that opacity over a
|
|
2805
|
+
* checkerboard backdrop, so the transparency is visually readable. The
|
|
2806
|
+
* number by itself (`0.4`) doesn't convey what the token looks like
|
|
2807
|
+
* applied to a surface; the sample does.
|
|
2808
|
+
*/
|
|
2809
|
+
function OpacityScale({ filter, type = "number", sampleColor = "color.accent.bg", caption, sortBy = "value", sortDir = "asc" }) {
|
|
2810
|
+
const project = useProject();
|
|
2811
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
2812
|
+
return /* @__PURE__ */ jsx(OpacityScaleView, {
|
|
2813
|
+
rows: useMemo(() => deriveOpacityRows(resolved, project, {
|
|
2814
|
+
filter,
|
|
2815
|
+
type,
|
|
2816
|
+
sortBy,
|
|
2817
|
+
sortDir
|
|
2818
|
+
}), [
|
|
2819
|
+
resolved,
|
|
2820
|
+
project,
|
|
2821
|
+
filter,
|
|
2822
|
+
type,
|
|
2823
|
+
sortBy,
|
|
2824
|
+
sortDir
|
|
2825
|
+
]),
|
|
2826
|
+
activeTheme,
|
|
2827
|
+
cssVarPrefix,
|
|
2828
|
+
activeAxes,
|
|
2829
|
+
sampleColorVar: resolveCssVar(sampleColor, project),
|
|
2830
|
+
filter,
|
|
2831
|
+
caption
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2532
2834
|
//#endregion
|
|
2533
2835
|
//#region src/provider.tsx
|
|
2534
2836
|
/**
|
|
@@ -2558,46 +2860,67 @@ function useSwatchbookData() {
|
|
|
2558
2860
|
}
|
|
2559
2861
|
//#endregion
|
|
2560
2862
|
//#region src/shadow-preview/ShadowSample.tsx
|
|
2561
|
-
|
|
2863
|
+
/**
|
|
2864
|
+
* Pure derivation of a single shadow token's sample data from resolved
|
|
2865
|
+
* project data. Extracted so it is unit-testable without React or a store.
|
|
2866
|
+
*/
|
|
2867
|
+
function deriveShadowSample(path, project) {
|
|
2868
|
+
return { cssVar: resolveCssVar(path, project) };
|
|
2869
|
+
}
|
|
2870
|
+
/** Pure presentation for a single shadow token's sample. Renders from plain props. */
|
|
2871
|
+
function ShadowSampleView({ cssVar }) {
|
|
2562
2872
|
return /* @__PURE__ */ jsx("div", {
|
|
2563
2873
|
className: "sb-shadow-sample",
|
|
2564
|
-
style: { boxShadow:
|
|
2874
|
+
style: { boxShadow: cssVar },
|
|
2565
2875
|
"aria-hidden": true
|
|
2566
2876
|
});
|
|
2567
2877
|
}
|
|
2878
|
+
function ShadowSample({ path }) {
|
|
2879
|
+
const { cssVar } = deriveShadowSample(path, useProject());
|
|
2880
|
+
return /* @__PURE__ */ jsx(ShadowSampleView, { cssVar });
|
|
2881
|
+
}
|
|
2568
2882
|
//#endregion
|
|
2569
2883
|
//#region src/ShadowPreview.tsx
|
|
2884
|
+
function layerKey(path, layer, fallback) {
|
|
2885
|
+
return `${path}|${layer.offset}|${layer.blur}|${layer.spread}|${fallback}`;
|
|
2886
|
+
}
|
|
2570
2887
|
function asLayers(raw) {
|
|
2571
2888
|
if (Array.isArray(raw)) return raw;
|
|
2572
2889
|
if (raw && typeof raw === "object") return [raw];
|
|
2573
2890
|
return [];
|
|
2574
2891
|
}
|
|
2575
|
-
function
|
|
2576
|
-
return
|
|
2892
|
+
function formatLayer(layer, colorFormat) {
|
|
2893
|
+
return {
|
|
2894
|
+
offset: `${formatDimension$1(layer.offsetX)} ${formatDimension$1(layer.offsetY)}`,
|
|
2895
|
+
blur: formatDimension$1(layer.blur),
|
|
2896
|
+
spread: formatDimension$1(layer.spread),
|
|
2897
|
+
color: formatSubColor(layer.color, colorFormat),
|
|
2898
|
+
inset: layer.inset ? String(layer.inset) : void 0
|
|
2899
|
+
};
|
|
2577
2900
|
}
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2901
|
+
/**
|
|
2902
|
+
* Pure derivation of the preview's display rows from resolved project data.
|
|
2903
|
+
* Extracted so it is unit-testable without React or a store.
|
|
2904
|
+
*/
|
|
2905
|
+
function deriveShadowRows(resolved, project, { filter, sortBy, sortDir, colorFormat }) {
|
|
2906
|
+
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
2907
|
+
if (token.$type !== "shadow") return false;
|
|
2908
|
+
return matchPath(path, filter);
|
|
2909
|
+
}), {
|
|
2910
|
+
by: sortBy,
|
|
2911
|
+
dir: sortDir
|
|
2912
|
+
}).map(([path, token]) => ({
|
|
2913
|
+
path,
|
|
2914
|
+
cssVar: resolveCssVar(path, project),
|
|
2915
|
+
layers: asLayers(token.$value).map((layer) => formatLayer(layer, colorFormat))
|
|
2916
|
+
}));
|
|
2917
|
+
}
|
|
2918
|
+
/**
|
|
2919
|
+
* Pure presentation for the shadow preview. Renders from plain props;
|
|
2920
|
+
* composes the connected `ShadowSample` as a child (that child reads the
|
|
2921
|
+
* project itself).
|
|
2922
|
+
*/
|
|
2923
|
+
function ShadowPreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter, caption }) {
|
|
2601
2924
|
const captionText = caption ?? `${rows.length} shadow${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2602
2925
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2603
2926
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2630,32 +2953,31 @@ function ShadowPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
2630
2953
|
}),
|
|
2631
2954
|
/* @__PURE__ */ jsx("div", {
|
|
2632
2955
|
className: "sb-shadow-preview__breakdown",
|
|
2633
|
-
children: row.layers.length === 1 ?
|
|
2956
|
+
children: row.layers.length === 1 ? renderLayerEntries(row.layers[0]) : row.layers.map((layer, i) => /* @__PURE__ */ jsx(Layer, {
|
|
2634
2957
|
layer,
|
|
2635
2958
|
index: i,
|
|
2636
|
-
total: row.layers.length
|
|
2637
|
-
colorFormat
|
|
2959
|
+
total: row.layers.length
|
|
2638
2960
|
}, layerKey(row.path, layer, i)))
|
|
2639
2961
|
})
|
|
2640
2962
|
]
|
|
2641
2963
|
}, row.path))]
|
|
2642
2964
|
});
|
|
2643
2965
|
}
|
|
2644
|
-
function
|
|
2966
|
+
function renderLayerEntries(layer) {
|
|
2645
2967
|
if (!layer) return [];
|
|
2646
2968
|
const entries = [
|
|
2647
|
-
["offset",
|
|
2648
|
-
["blur",
|
|
2649
|
-
["spread",
|
|
2650
|
-
["color",
|
|
2969
|
+
["offset", layer.offset],
|
|
2970
|
+
["blur", layer.blur],
|
|
2971
|
+
["spread", layer.spread],
|
|
2972
|
+
["color", layer.color]
|
|
2651
2973
|
];
|
|
2652
|
-
if (layer.inset) entries.push(["inset",
|
|
2974
|
+
if (layer.inset) entries.push(["inset", layer.inset]);
|
|
2653
2975
|
return entries.flatMap(([k, v]) => [/* @__PURE__ */ jsx("span", {
|
|
2654
2976
|
className: "sb-shadow-preview__breakdown-key",
|
|
2655
2977
|
children: k
|
|
2656
2978
|
}, `k-${k}`), /* @__PURE__ */ jsx("span", { children: v }, `v-${k}`)]);
|
|
2657
2979
|
}
|
|
2658
|
-
function Layer({ layer, index, total
|
|
2980
|
+
function Layer({ layer, index, total }) {
|
|
2659
2981
|
return /* @__PURE__ */ jsxs("div", {
|
|
2660
2982
|
className: "sb-shadow-preview__layer",
|
|
2661
2983
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -2668,10 +2990,35 @@ function Layer({ layer, index, total, colorFormat }) {
|
|
|
2668
2990
|
]
|
|
2669
2991
|
}), /* @__PURE__ */ jsx("div", {
|
|
2670
2992
|
className: cx("sb-shadow-preview__breakdown", "sb-shadow-preview__layer-breakdown"),
|
|
2671
|
-
children:
|
|
2993
|
+
children: renderLayerEntries(layer)
|
|
2672
2994
|
})]
|
|
2673
2995
|
});
|
|
2674
2996
|
}
|
|
2997
|
+
function ShadowPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
2998
|
+
const project = useProject();
|
|
2999
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
3000
|
+
const colorFormat = useColorFormat();
|
|
3001
|
+
return /* @__PURE__ */ jsx(ShadowPreviewView, {
|
|
3002
|
+
rows: useMemo(() => deriveShadowRows(resolved, project, {
|
|
3003
|
+
filter,
|
|
3004
|
+
sortBy,
|
|
3005
|
+
sortDir,
|
|
3006
|
+
colorFormat
|
|
3007
|
+
}), [
|
|
3008
|
+
resolved,
|
|
3009
|
+
project,
|
|
3010
|
+
filter,
|
|
3011
|
+
sortBy,
|
|
3012
|
+
sortDir,
|
|
3013
|
+
colorFormat
|
|
3014
|
+
]),
|
|
3015
|
+
activeTheme,
|
|
3016
|
+
cssVarPrefix,
|
|
3017
|
+
activeAxes,
|
|
3018
|
+
filter,
|
|
3019
|
+
caption
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
2675
3022
|
//#endregion
|
|
2676
3023
|
//#region src/StrokeStylePreview.tsx
|
|
2677
3024
|
const STRING_STYLES = new Set([
|
|
@@ -2688,29 +3035,26 @@ function extractCssStyle(value) {
|
|
|
2688
3035
|
if (typeof value === "string" && STRING_STYLES.has(value)) return value;
|
|
2689
3036
|
return null;
|
|
2690
3037
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
sortBy,
|
|
2712
|
-
sortDir
|
|
2713
|
-
]);
|
|
3038
|
+
/**
|
|
3039
|
+
* Pure derivation of the preview's display rows from resolved project data.
|
|
3040
|
+
* Extracted so it is unit-testable without React or a store.
|
|
3041
|
+
*/
|
|
3042
|
+
function deriveStrokeStyleRows(resolved, project, { filter, sortBy, sortDir }) {
|
|
3043
|
+
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
3044
|
+
if (token.$type !== "strokeStyle") return false;
|
|
3045
|
+
return matchPath(path, filter);
|
|
3046
|
+
}), {
|
|
3047
|
+
by: sortBy,
|
|
3048
|
+
dir: sortDir
|
|
3049
|
+
}).map(([path, token]) => ({
|
|
3050
|
+
path,
|
|
3051
|
+
cssVar: resolveCssVar(path, project),
|
|
3052
|
+
displayValue: formatTokenValue(token.$value, token.$type, "raw", project.listing[path]),
|
|
3053
|
+
cssStyle: extractCssStyle(token.$value)
|
|
3054
|
+
}));
|
|
3055
|
+
}
|
|
3056
|
+
/** Pure presentation for the stroke-style preview. Renders from plain props. */
|
|
3057
|
+
function StrokeStylePreviewView({ rows, activeTheme, cssVarPrefix, activeAxes, filter, caption }) {
|
|
2714
3058
|
const captionText = caption ?? `${rows.length} strokeStyle token${rows.length === 1 ? "" : "s"}${filter && filter !== "strokeStyle" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2715
3059
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2716
3060
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2753,6 +3097,28 @@ function StrokeStylePreview({ filter, caption, sortBy = "path", sortDir = "asc"
|
|
|
2753
3097
|
}, row.path))]
|
|
2754
3098
|
});
|
|
2755
3099
|
}
|
|
3100
|
+
function StrokeStylePreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
3101
|
+
const project = useProject();
|
|
3102
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
3103
|
+
return /* @__PURE__ */ jsx(StrokeStylePreviewView, {
|
|
3104
|
+
rows: useMemo(() => deriveStrokeStyleRows(resolved, project, {
|
|
3105
|
+
filter,
|
|
3106
|
+
sortBy,
|
|
3107
|
+
sortDir
|
|
3108
|
+
}), [
|
|
3109
|
+
resolved,
|
|
3110
|
+
project,
|
|
3111
|
+
filter,
|
|
3112
|
+
sortBy,
|
|
3113
|
+
sortDir
|
|
3114
|
+
]),
|
|
3115
|
+
activeTheme,
|
|
3116
|
+
cssVarPrefix,
|
|
3117
|
+
activeAxes,
|
|
3118
|
+
filter,
|
|
3119
|
+
caption
|
|
3120
|
+
});
|
|
3121
|
+
}
|
|
2756
3122
|
//#endregion
|
|
2757
3123
|
//#region src/token-detail/internal.ts
|
|
2758
3124
|
function useTokenDetailData(path) {
|
|
@@ -3495,13 +3861,22 @@ function TransitionSample({ transition, durationMs }) {
|
|
|
3495
3861
|
}
|
|
3496
3862
|
//#endregion
|
|
3497
3863
|
//#region src/token-detail/ConsumerOutput.tsx
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3864
|
+
/**
|
|
3865
|
+
* Pure derivation of the consumer-output platform rows from the Token
|
|
3866
|
+
* Listing. Extracted so it is unit-testable without React or a store.
|
|
3867
|
+
*/
|
|
3868
|
+
function deriveConsumerOutput(path, listing) {
|
|
3503
3869
|
const names = listing[path]?.names ?? {};
|
|
3504
|
-
|
|
3870
|
+
return { extraPlatforms: Object.keys(names).filter((platform) => platform !== "css" && names[platform]).toSorted().map((platform) => ({
|
|
3871
|
+
platform,
|
|
3872
|
+
label: formatPlatformLabel(platform),
|
|
3873
|
+
value: names[platform]
|
|
3874
|
+
})) };
|
|
3875
|
+
}
|
|
3876
|
+
/** Pure presentation for the consumer-output section. Renders from plain props; owns the copy-button's local feedback state. */
|
|
3877
|
+
function ConsumerOutputView({ path, cssVar, activeAxes, hasToken, extraPlatforms }) {
|
|
3878
|
+
if (!hasToken) return null;
|
|
3879
|
+
const tupleLabel = Object.entries(activeAxes).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
3505
3880
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3506
3881
|
/* @__PURE__ */ jsx("div", {
|
|
3507
3882
|
className: "sb-token-detail__section-header",
|
|
@@ -3521,13 +3896,25 @@ function ConsumerOutput({ path }) {
|
|
|
3521
3896
|
value: cssVar,
|
|
3522
3897
|
testId: "consumer-output-css"
|
|
3523
3898
|
}),
|
|
3524
|
-
extraPlatforms.map((
|
|
3525
|
-
label:
|
|
3526
|
-
value:
|
|
3527
|
-
testId: `consumer-output-${platform}`
|
|
3528
|
-
}, platform))
|
|
3899
|
+
extraPlatforms.map((entry) => /* @__PURE__ */ jsx(OutputRow, {
|
|
3900
|
+
label: entry.label,
|
|
3901
|
+
value: entry.value,
|
|
3902
|
+
testId: `consumer-output-${entry.platform}`
|
|
3903
|
+
}, entry.platform))
|
|
3529
3904
|
] });
|
|
3530
3905
|
}
|
|
3906
|
+
function ConsumerOutput({ path }) {
|
|
3907
|
+
const { token, cssVar, activeAxes } = useTokenDetailData(path);
|
|
3908
|
+
const { listing } = useProject();
|
|
3909
|
+
const { extraPlatforms } = deriveConsumerOutput(path, listing);
|
|
3910
|
+
return /* @__PURE__ */ jsx(ConsumerOutputView, {
|
|
3911
|
+
path,
|
|
3912
|
+
cssVar,
|
|
3913
|
+
activeAxes,
|
|
3914
|
+
hasToken: Boolean(token),
|
|
3915
|
+
extraPlatforms
|
|
3916
|
+
});
|
|
3917
|
+
}
|
|
3531
3918
|
function formatPlatformLabel(platform) {
|
|
3532
3919
|
if (platform.length === 0) return platform;
|
|
3533
3920
|
return platform[0].toUpperCase() + platform.slice(1);
|
|
@@ -3648,10 +4035,43 @@ function TokenUsageSnippet({ path }) {
|
|
|
3648
4035
|
}
|
|
3649
4036
|
//#endregion
|
|
3650
4037
|
//#region src/TokenDetail.tsx
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
4038
|
+
const EMPTY_DERIVED = {
|
|
4039
|
+
value: "",
|
|
4040
|
+
outOfGamut: false,
|
|
4041
|
+
isColor: false,
|
|
4042
|
+
isDeprecated: false,
|
|
4043
|
+
deprecationMessage: void 0
|
|
4044
|
+
};
|
|
4045
|
+
/**
|
|
4046
|
+
* Pure derivation of the token's display value and status flags. Mirrors
|
|
4047
|
+
* `formatTokenValue` + `formatColor`'s gamut check, both of which need the
|
|
4048
|
+
* resolved token's raw `$value`/`$type` — the listing alone (no `$value`)
|
|
4049
|
+
* can't drive this. Extracted so it is unit-testable without React or a
|
|
4050
|
+
* store; the View renders purely from this shape plus the token's presence.
|
|
4051
|
+
*/
|
|
4052
|
+
function deriveTokenDetail(path, token, listing, colorFormat) {
|
|
4053
|
+
if (!token) return EMPTY_DERIVED;
|
|
4054
|
+
const isColor = token.$type === "color";
|
|
4055
|
+
const gamut = isColor ? formatColor(token.$value, colorFormat) : null;
|
|
4056
|
+
const value = formatTokenValue(token.$value, token.$type, colorFormat, listing[path]);
|
|
4057
|
+
const outOfGamut = gamut?.outOfGamut ?? false;
|
|
4058
|
+
const dep = token.$deprecated;
|
|
4059
|
+
return {
|
|
4060
|
+
value,
|
|
4061
|
+
outOfGamut,
|
|
4062
|
+
isColor,
|
|
4063
|
+
isDeprecated: dep === true || typeof dep === "string" && dep.length > 0,
|
|
4064
|
+
deprecationMessage: typeof dep === "string" ? dep : void 0
|
|
4065
|
+
};
|
|
4066
|
+
}
|
|
4067
|
+
/**
|
|
4068
|
+
* Pure presentation for the token detail panel. Renders from plain props;
|
|
4069
|
+
* composes the connected `TokenHeader` / `CompositePreview` /
|
|
4070
|
+
* `CompositeBreakdown` / `AliasChain` / `AliasedBy` / `TokenUsageSnippet` /
|
|
4071
|
+
* `ConsumerOutput` / `AxisVariance` as children (each reads the project
|
|
4072
|
+
* itself for `path`).
|
|
4073
|
+
*/
|
|
4074
|
+
function TokenDetailView({ path, heading, token, cssVar, activeTheme, activeAxes, cssVarPrefix, value, outOfGamut, isColor, isDeprecated, deprecationMessage }) {
|
|
3655
4075
|
const wrapperAttrs = blockWrapperAttrs(cssVarPrefix, activeAxes);
|
|
3656
4076
|
if (!token) return /* @__PURE__ */ jsx("div", {
|
|
3657
4077
|
...wrapperAttrs,
|
|
@@ -3667,12 +4087,6 @@ function TokenDetail({ path, heading }) {
|
|
|
3667
4087
|
]
|
|
3668
4088
|
})
|
|
3669
4089
|
});
|
|
3670
|
-
const isColor = token.$type === "color";
|
|
3671
|
-
const gamut = isColor ? formatColor(token.$value, colorFormat) : null;
|
|
3672
|
-
const value = formatTokenValue(token.$value, token.$type, colorFormat, listing[path]);
|
|
3673
|
-
const outOfGamut = gamut?.outOfGamut ?? false;
|
|
3674
|
-
const dep = token.$deprecated;
|
|
3675
|
-
const isDeprecated = dep === true || typeof dep === "string" && dep.length > 0;
|
|
3676
4090
|
return /* @__PURE__ */ jsxs("div", {
|
|
3677
4091
|
...wrapperAttrs,
|
|
3678
4092
|
className: cx(wrapperAttrs["className"], "sb-token-detail"),
|
|
@@ -3691,7 +4105,7 @@ function TokenDetail({ path, heading }) {
|
|
|
3691
4105
|
children: "⚠ "
|
|
3692
4106
|
}),
|
|
3693
4107
|
"Deprecated",
|
|
3694
|
-
|
|
4108
|
+
deprecationMessage ? `: ${deprecationMessage}` : ""
|
|
3695
4109
|
]
|
|
3696
4110
|
}),
|
|
3697
4111
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -3729,6 +4143,22 @@ function TokenDetail({ path, heading }) {
|
|
|
3729
4143
|
]
|
|
3730
4144
|
});
|
|
3731
4145
|
}
|
|
4146
|
+
function TokenDetail({ path, heading }) {
|
|
4147
|
+
const { token, cssVar, activeTheme, activeAxes, cssVarPrefix } = useTokenDetailData(path);
|
|
4148
|
+
const colorFormat = useColorFormat();
|
|
4149
|
+
const { listing } = useProject();
|
|
4150
|
+
const derived = deriveTokenDetail(path, token, listing, colorFormat);
|
|
4151
|
+
return /* @__PURE__ */ jsx(TokenDetailView, {
|
|
4152
|
+
path,
|
|
4153
|
+
...heading !== void 0 && { heading },
|
|
4154
|
+
token,
|
|
4155
|
+
cssVar,
|
|
4156
|
+
activeTheme,
|
|
4157
|
+
activeAxes,
|
|
4158
|
+
cssVarPrefix,
|
|
4159
|
+
...derived
|
|
4160
|
+
});
|
|
4161
|
+
}
|
|
3732
4162
|
//#endregion
|
|
3733
4163
|
//#region src/internal/DetailOverlay.tsx
|
|
3734
4164
|
const FOCUSABLE_SELECTOR = [
|
|
@@ -3857,6 +4287,13 @@ function isInView(path, ctx) {
|
|
|
3857
4287
|
}
|
|
3858
4288
|
//#endregion
|
|
3859
4289
|
//#region src/TokenNavigator.tsx
|
|
4290
|
+
/**
|
|
4291
|
+
* Pure derivation of the navigator's tree from the resolved token map:
|
|
4292
|
+
* groups paths by dotted segment (scoped to `root` when set, restricted to
|
|
4293
|
+
* `typeFilter`'s `$type`s when set), producing nested `GroupNode`s down to
|
|
4294
|
+
* `LeafNode`s, sorted groups-before-leaves and alphanumeric within a level.
|
|
4295
|
+
* Extracted so it is unit-testable without React or a store.
|
|
4296
|
+
*/
|
|
3860
4297
|
function buildTree(resolved, root, typeFilter) {
|
|
3861
4298
|
const rootPrefix = root && root.length > 0 ? `${root}.` : "";
|
|
3862
4299
|
const rootSegments = root ? root.split(".") : [];
|
|
@@ -3933,6 +4370,12 @@ function collectLeafPaths(nodes, out) {
|
|
|
3933
4370
|
for (const node of nodes) if (node.kind === "leaf") out.push(node.path);
|
|
3934
4371
|
else collectLeafPaths(node.children, out);
|
|
3935
4372
|
}
|
|
4373
|
+
/**
|
|
4374
|
+
* Pure derivation of the flattened, currently-visible treeitem order from a
|
|
4375
|
+
* tree plus the set of expanded group paths — depth-first, only descending
|
|
4376
|
+
* into expanded groups. Appends to `out` (caller passes `[]`). Extracted so
|
|
4377
|
+
* it is unit-testable without React or a store.
|
|
4378
|
+
*/
|
|
3936
4379
|
function flattenVisible(nodes, expanded, parentPath, out) {
|
|
3937
4380
|
for (const node of nodes) {
|
|
3938
4381
|
out.push({
|
|
@@ -3959,18 +4402,18 @@ function pruneTreeForMatches(nodes, matches, expandOut) {
|
|
|
3959
4402
|
}
|
|
3960
4403
|
return out;
|
|
3961
4404
|
}
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
4405
|
+
/**
|
|
4406
|
+
* Pure presentation for the token navigator. Owns its own expand/collapse,
|
|
4407
|
+
* selection, search, and roving-tabindex keyboard-nav state; renders the
|
|
4408
|
+
* tree from the `resolved` view-model. Composes the connected `RowIndicators`
|
|
4409
|
+
* / `DetailOverlay` / sample previews as children (those read the project
|
|
4410
|
+
* themselves).
|
|
4411
|
+
*/
|
|
4412
|
+
function TokenNavigatorView({ resolved, root, enabledIndicators, cssVarPrefix, activeAxes, activeTheme, blockKey, type, initiallyExpanded = 1, searchable = true, onSelect }) {
|
|
3969
4413
|
const typeFilter = useMemo(() => {
|
|
3970
4414
|
if (type === void 0) return void 0;
|
|
3971
4415
|
return new Set(Array.isArray(type) ? type : [type]);
|
|
3972
4416
|
}, [type]);
|
|
3973
|
-
const enabledIndicators = useMemo(() => resolveIndicators(indicators, indicatorBaseline), [indicators, indicatorBaseline]);
|
|
3974
4417
|
const tree = useMemo(() => buildTree(resolved, root, typeFilter), [
|
|
3975
4418
|
resolved,
|
|
3976
4419
|
root,
|
|
@@ -4259,6 +4702,32 @@ function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true,
|
|
|
4259
4702
|
]
|
|
4260
4703
|
});
|
|
4261
4704
|
}
|
|
4705
|
+
/**
|
|
4706
|
+
* A collapsible, searchable tree of tokens with roving-tabindex keyboard
|
|
4707
|
+
* navigation. Click a leaf to inspect it in a slide-over (unless `onSelect`
|
|
4708
|
+
* is provided, which hands the follow-up to the consumer).
|
|
4709
|
+
*/
|
|
4710
|
+
function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true, onSelect, id, indicators }) {
|
|
4711
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix, indicators: indicatorBaseline } = useProject();
|
|
4712
|
+
const blockKey = useBlockKey("TokenNavigator", [
|
|
4713
|
+
root,
|
|
4714
|
+
type === void 0 ? "" : typeof type === "string" ? type : type.join(","),
|
|
4715
|
+
id
|
|
4716
|
+
]);
|
|
4717
|
+
return /* @__PURE__ */ jsx(TokenNavigatorView, {
|
|
4718
|
+
resolved,
|
|
4719
|
+
root,
|
|
4720
|
+
enabledIndicators: useMemo(() => resolveIndicators(indicators, indicatorBaseline), [indicators, indicatorBaseline]),
|
|
4721
|
+
cssVarPrefix,
|
|
4722
|
+
activeAxes,
|
|
4723
|
+
activeTheme,
|
|
4724
|
+
blockKey,
|
|
4725
|
+
type,
|
|
4726
|
+
initiallyExpanded,
|
|
4727
|
+
searchable,
|
|
4728
|
+
onSelect
|
|
4729
|
+
});
|
|
4730
|
+
}
|
|
4262
4731
|
function TreeNodeRow({ node, expanded, focusedPath, registerTreeItem, onToggle, onFocusPath, onLeafClick, root, resolveInView, onNavigate, enabled, level, setsize, posinset }) {
|
|
4263
4732
|
if (node.kind === "leaf") return /* @__PURE__ */ jsx(LeafRow, {
|
|
4264
4733
|
node,
|
|
@@ -4447,56 +4916,51 @@ const LeafPreview = memo(function LeafPreview({ path, token }) {
|
|
|
4447
4916
|
});
|
|
4448
4917
|
//#endregion
|
|
4449
4918
|
//#region src/TokenTable.tsx
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4919
|
+
/**
|
|
4920
|
+
* Pure derivation of the table's display rows from resolved project data.
|
|
4921
|
+
* Carries per-row `isDeprecated` / `token` / `variance` so the View renders
|
|
4922
|
+
* the indicator strip and deprecation state without reaching back into the
|
|
4923
|
+
* project. Extracted so it is unit-testable without React or a store.
|
|
4924
|
+
*/
|
|
4925
|
+
function deriveTokenRows(resolved, listing, cssVarPrefix, varianceByPath, { filter, type, sortBy, sortDir, colorFormat, rootFontSizePx }) {
|
|
4926
|
+
const projectFields = {
|
|
4927
|
+
listing,
|
|
4928
|
+
cssVarPrefix
|
|
4929
|
+
};
|
|
4930
|
+
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
4931
|
+
if (!matchPath(path, filter)) return false;
|
|
4932
|
+
if (type && token.$type !== type) return false;
|
|
4933
|
+
return true;
|
|
4934
|
+
}), {
|
|
4935
|
+
by: sortBy,
|
|
4936
|
+
dir: sortDir,
|
|
4937
|
+
rootFontSizePx
|
|
4938
|
+
}).map(([path, token]) => {
|
|
4939
|
+
const isColor = token.$type === "color";
|
|
4940
|
+
const color = isColor ? resolveColorValue(path, token.$value, colorFormat, projectFields) : null;
|
|
4941
|
+
const dep = token.$deprecated;
|
|
4942
|
+
return {
|
|
4943
|
+
path,
|
|
4944
|
+
type: token.$type ?? "",
|
|
4945
|
+
value: formatTokenValue(token.$value, token.$type, colorFormat, listing[path]),
|
|
4946
|
+
outOfGamut: color?.outOfGamut ?? false,
|
|
4947
|
+
cssVar: resolveCssVar(path, projectFields),
|
|
4948
|
+
isColor,
|
|
4949
|
+
isDeprecated: dep === true || typeof dep === "string" && dep.length > 0,
|
|
4950
|
+
token,
|
|
4951
|
+
variance: varianceByPath[path]
|
|
4952
|
+
};
|
|
4953
|
+
});
|
|
4954
|
+
}
|
|
4955
|
+
/**
|
|
4956
|
+
* Pure presentation for the token table. Owns its own search + selection UI
|
|
4957
|
+
* state; renders from the derived `rows` view-model. Composes the connected
|
|
4958
|
+
* DetailOverlay as a child (that child reads the project itself).
|
|
4959
|
+
*/
|
|
4960
|
+
function TokenTableView({ rows, activeTheme, cssVarPrefix, activeAxes, colorFormat, enabledIndicators, validPaths, blockKey, filter, type, caption, searchable = true, onSelect }) {
|
|
4461
4961
|
const [selectedPath, setSelectedPath] = usePersistedState(`${blockKey}::selected`, null);
|
|
4462
4962
|
const [query, setQuery] = usePersistedState(`${blockKey}::query`, "");
|
|
4463
4963
|
const deferredQuery = useDeferredValue(query);
|
|
4464
|
-
const rows = useMemo(() => {
|
|
4465
|
-
const projectFields = {
|
|
4466
|
-
listing,
|
|
4467
|
-
cssVarPrefix
|
|
4468
|
-
};
|
|
4469
|
-
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
4470
|
-
if (!matchPath(path, filter)) return false;
|
|
4471
|
-
if (type && token.$type !== type) return false;
|
|
4472
|
-
return true;
|
|
4473
|
-
}), {
|
|
4474
|
-
by: sortBy,
|
|
4475
|
-
dir: sortDir,
|
|
4476
|
-
rootFontSizePx: rootFontSize
|
|
4477
|
-
}).map(([path, token]) => {
|
|
4478
|
-
const isColor = token.$type === "color";
|
|
4479
|
-
const color = isColor ? resolveColorValue(path, token.$value, colorFormat, projectFields) : null;
|
|
4480
|
-
return {
|
|
4481
|
-
path,
|
|
4482
|
-
type: token.$type ?? "",
|
|
4483
|
-
value: formatTokenValue(token.$value, token.$type, colorFormat, listing[path]),
|
|
4484
|
-
outOfGamut: color?.outOfGamut ?? false,
|
|
4485
|
-
cssVar: resolveCssVar(path, projectFields),
|
|
4486
|
-
isColor
|
|
4487
|
-
};
|
|
4488
|
-
});
|
|
4489
|
-
}, [
|
|
4490
|
-
resolved,
|
|
4491
|
-
listing,
|
|
4492
|
-
cssVarPrefix,
|
|
4493
|
-
filter,
|
|
4494
|
-
type,
|
|
4495
|
-
colorFormat,
|
|
4496
|
-
sortBy,
|
|
4497
|
-
sortDir,
|
|
4498
|
-
rootFontSize
|
|
4499
|
-
]);
|
|
4500
4964
|
const visibleRows = useMemo(() => {
|
|
4501
4965
|
if (!searchable || deferredQuery.trim() === "") return rows;
|
|
4502
4966
|
return fuzzyFilter(rows, deferredQuery, (row) => `${row.path} ${row.type} ${row.value}`);
|
|
@@ -4572,9 +5036,7 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
|
|
|
4572
5036
|
"\"."
|
|
4573
5037
|
]
|
|
4574
5038
|
}) }), visibleRows.map((row) => {
|
|
4575
|
-
const token =
|
|
4576
|
-
const dep = token?.$deprecated;
|
|
4577
|
-
const isDeprecated = dep === true || typeof dep === "string" && dep.length > 0;
|
|
5039
|
+
const token = row.token;
|
|
4578
5040
|
return /* @__PURE__ */ jsxs("tr", {
|
|
4579
5041
|
className: "sb-token-table__row",
|
|
4580
5042
|
onClick: () => handleRowClick(row.path),
|
|
@@ -4592,7 +5054,7 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
|
|
|
4592
5054
|
children: [
|
|
4593
5055
|
/* @__PURE__ */ jsx("td", {
|
|
4594
5056
|
className: cx("sb-token-table__td", "sb-token-table__path"),
|
|
4595
|
-
"data-deprecated": enabledIndicators.deprecation && isDeprecated ? "true" : void 0,
|
|
5057
|
+
"data-deprecated": enabledIndicators.deprecation && row.isDeprecated ? "true" : void 0,
|
|
4596
5058
|
children: row.path
|
|
4597
5059
|
}),
|
|
4598
5060
|
/* @__PURE__ */ jsx("td", {
|
|
@@ -4641,9 +5103,9 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
|
|
|
4641
5103
|
path: row.path,
|
|
4642
5104
|
token,
|
|
4643
5105
|
root: void 0,
|
|
4644
|
-
variance:
|
|
5106
|
+
variance: row.variance,
|
|
4645
5107
|
colorFormat,
|
|
4646
|
-
canReference: (p) => p
|
|
5108
|
+
canReference: (p) => validPaths.has(p),
|
|
4647
5109
|
onReferenceClick: (p) => setSelectedPath(p),
|
|
4648
5110
|
enabled: enabledIndicators
|
|
4649
5111
|
})
|
|
@@ -4661,6 +5123,56 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
|
|
|
4661
5123
|
]
|
|
4662
5124
|
});
|
|
4663
5125
|
}
|
|
5126
|
+
/**
|
|
5127
|
+
* A sortable, searchable table of tokens. Click a row to inspect it in a
|
|
5128
|
+
* slide-over (unless `onSelect` is provided, which hands the follow-up to the
|
|
5129
|
+
* consumer).
|
|
5130
|
+
*/
|
|
5131
|
+
function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", searchable = true, onSelect, id, indicators }) {
|
|
5132
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix, listing, varianceByPath, indicators: indicatorBaseline } = useProject();
|
|
5133
|
+
const colorFormat = useColorFormat();
|
|
5134
|
+
const rootFontSize = useRootFontSize();
|
|
5135
|
+
const blockKey = useBlockKey("TokenTable", [
|
|
5136
|
+
filter,
|
|
5137
|
+
type,
|
|
5138
|
+
caption,
|
|
5139
|
+
id
|
|
5140
|
+
]);
|
|
5141
|
+
const enabledIndicators = useMemo(() => resolveIndicators(indicators, indicatorBaseline), [indicators, indicatorBaseline]);
|
|
5142
|
+
return /* @__PURE__ */ jsx(TokenTableView, {
|
|
5143
|
+
rows: useMemo(() => deriveTokenRows(resolved, listing, cssVarPrefix, varianceByPath, {
|
|
5144
|
+
filter,
|
|
5145
|
+
type,
|
|
5146
|
+
sortBy,
|
|
5147
|
+
sortDir,
|
|
5148
|
+
colorFormat,
|
|
5149
|
+
rootFontSizePx: rootFontSize
|
|
5150
|
+
}), [
|
|
5151
|
+
resolved,
|
|
5152
|
+
listing,
|
|
5153
|
+
cssVarPrefix,
|
|
5154
|
+
varianceByPath,
|
|
5155
|
+
filter,
|
|
5156
|
+
type,
|
|
5157
|
+
sortBy,
|
|
5158
|
+
sortDir,
|
|
5159
|
+
colorFormat,
|
|
5160
|
+
rootFontSize
|
|
5161
|
+
]),
|
|
5162
|
+
activeTheme,
|
|
5163
|
+
cssVarPrefix,
|
|
5164
|
+
activeAxes,
|
|
5165
|
+
colorFormat,
|
|
5166
|
+
enabledIndicators,
|
|
5167
|
+
validPaths: useMemo(() => new Set(Object.keys(resolved)), [resolved]),
|
|
5168
|
+
blockKey,
|
|
5169
|
+
filter,
|
|
5170
|
+
type,
|
|
5171
|
+
caption,
|
|
5172
|
+
searchable,
|
|
5173
|
+
onSelect
|
|
5174
|
+
});
|
|
5175
|
+
}
|
|
4664
5176
|
//#endregion
|
|
4665
5177
|
//#region src/TypographyScale.tsx
|
|
4666
5178
|
function asDimension(raw) {
|
|
@@ -4697,30 +5209,29 @@ function buildRow(path, composite) {
|
|
|
4697
5209
|
].filter(Boolean).join(" · ")
|
|
4698
5210
|
};
|
|
4699
5211
|
}
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
}
|
|
4716
|
-
|
|
4717
|
-
}
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
]);
|
|
5212
|
+
/**
|
|
5213
|
+
* Pure derivation of the scale's display rows from resolved project data.
|
|
5214
|
+
* Extracted so it is unit-testable without React or a store.
|
|
5215
|
+
*/
|
|
5216
|
+
function deriveTypographyRows(resolved, { filter, sortBy, sortDir }) {
|
|
5217
|
+
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
5218
|
+
if (token.$type !== "typography") return false;
|
|
5219
|
+
return matchPath(path, filter);
|
|
5220
|
+
}), {
|
|
5221
|
+
by: sortBy,
|
|
5222
|
+
dir: sortDir
|
|
5223
|
+
}).map(([path, token]) => {
|
|
5224
|
+
const value = token.$value;
|
|
5225
|
+
if (!value || typeof value !== "object") return {
|
|
5226
|
+
path,
|
|
5227
|
+
sampleStyle: {},
|
|
5228
|
+
specs: ""
|
|
5229
|
+
};
|
|
5230
|
+
return buildRow(path, value);
|
|
5231
|
+
});
|
|
5232
|
+
}
|
|
5233
|
+
/** Pure presentation for the typography scale. Renders from plain props. */
|
|
5234
|
+
function TypographyScaleView({ rows, activeTheme, cssVarPrefix, activeAxes, sample, filter, caption }) {
|
|
4724
5235
|
const captionText = caption ?? `${rows.length} typography token${rows.length === 1 ? "" : "s"}${filter && filter !== "typography" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
4725
5236
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
4726
5237
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -4752,7 +5263,28 @@ function TypographyScale({ filter, sample = "The quick brown fox jumps over the
|
|
|
4752
5263
|
}, row.path))]
|
|
4753
5264
|
});
|
|
4754
5265
|
}
|
|
5266
|
+
function TypographyScale({ filter, sample = "The quick brown fox jumps over the lazy dog.", caption, sortBy = "path", sortDir = "asc" }) {
|
|
5267
|
+
const { resolved, activeTheme, activeAxes, cssVarPrefix } = useProject();
|
|
5268
|
+
return /* @__PURE__ */ jsx(TypographyScaleView, {
|
|
5269
|
+
rows: useMemo(() => deriveTypographyRows(resolved, {
|
|
5270
|
+
filter,
|
|
5271
|
+
sortBy,
|
|
5272
|
+
sortDir
|
|
5273
|
+
}), [
|
|
5274
|
+
resolved,
|
|
5275
|
+
filter,
|
|
5276
|
+
sortBy,
|
|
5277
|
+
sortDir
|
|
5278
|
+
]),
|
|
5279
|
+
activeTheme,
|
|
5280
|
+
cssVarPrefix,
|
|
5281
|
+
activeAxes,
|
|
5282
|
+
sample,
|
|
5283
|
+
filter,
|
|
5284
|
+
caption
|
|
5285
|
+
});
|
|
5286
|
+
}
|
|
4755
5287
|
//#endregion
|
|
4756
|
-
export { AliasChain, AliasedBy, AxesContext, AxisVariance, BorderPreview, BorderSample, COLOR_FORMATS, ColorFormatContext, ColorPalette, ColorTable, CompositeBreakdown, CompositePreview, ConsumerOutput, Diagnostics, DimensionBar, DimensionScale, FontFamilyPreview, FontWeightScale, GradientPalette, MotionPreview, MotionSample, OpacityScale, ShadowPreview, ShadowSample, StrokeStylePreview, SwatchbookContext, SwatchbookProvider, TOKENS_UPDATED_EVENT, ThemeContext, TokenDetail, TokenHeader, TokenNavigator, TokenTable, TokenUsageSnippet, TypographyScale, formatColor, registerTokenSource, useActiveAxes, useActiveTheme, useChannelGlobals, useColorFormat, useOptionalSwatchbookData, useSwatchbookData, useTokenSnapshot };
|
|
5288
|
+
export { AliasChain, AliasedBy, AxesContext, AxisVariance, BorderPreview, BorderSample, COLOR_FORMATS, ColorFormatContext, ColorPalette, ColorTable, CompositeBreakdown, CompositePreview, ConsumerOutput, Diagnostics, DimensionBar, DimensionScale, FontFamilyPreview, FontWeightScale, GradientPalette, MotionPreview, MotionSample, OpacityScale, ShadowPreview, ShadowSample, StrokeStylePreview, SwatchbookContext, SwatchbookProvider, TOKENS_UPDATED_EVENT, ThemeContext, TokenDetail, TokenHeader, TokenNavigator, TokenTable, TokenUsageSnippet, TypographyScale, formatColor, onChannel, registerChannel, registerTokenSource, useActiveAxes, useActiveTheme, useChannelGlobals, useColorFormat, useOptionalSwatchbookData, useSwatchbookData, useTokenSnapshot };
|
|
4757
5289
|
|
|
4758
5290
|
//# sourceMappingURL=index.mjs.map
|