@unpunnyfuns/swatchbook-blocks 0.67.0 → 0.69.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/README.md +2 -2
- package/dist/index.d.mts +43 -5
- package/dist/index.mjs +1082 -675
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +330 -198
- package/package.json +34 -34
package/dist/index.mjs
CHANGED
|
@@ -2,39 +2,37 @@ import './style.css';
|
|
|
2
2
|
import { COLOR_FORMATS } from "@unpunnyfuns/swatchbook-core/color-formats";
|
|
3
3
|
import { formatColor, parseColor } from "@unpunnyfuns/swatchbook-core/format-color";
|
|
4
4
|
import { createContext, memo, useCallback, useContext, useDeferredValue, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
5
|
import { getVariance, listPaths, resolveAllWithProvenanceAt } from "@unpunnyfuns/swatchbook-core/graph";
|
|
7
6
|
import { makeCssVar } from "@unpunnyfuns/swatchbook-core/css-var";
|
|
8
7
|
import { SWATCHBOOK_STYLE_ELEMENT_ID, ensureStyleElement } from "@unpunnyfuns/swatchbook-core/style-element";
|
|
9
8
|
import { tupleToName } from "@unpunnyfuns/swatchbook-core/themes";
|
|
10
|
-
import {
|
|
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";
|
|
15
|
-
//#region src/internal/
|
|
14
|
+
//#region src/internal/channel.ts
|
|
15
|
+
let channel = null;
|
|
16
|
+
const pending = /* @__PURE__ */ new Set();
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* empty-state — lives in `styles.css` and is applied via class names.
|
|
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
22
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
function registerChannel(next) {
|
|
24
|
+
channel = next;
|
|
25
|
+
for (const attach of pending) attach(next);
|
|
26
|
+
pending.clear();
|
|
27
|
+
}
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
* it
|
|
31
|
-
* the
|
|
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
32
|
*/
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
children
|
|
37
|
-
});
|
|
33
|
+
function onChannel(attach) {
|
|
34
|
+
if (channel) attach(channel);
|
|
35
|
+
else pending.add(attach);
|
|
38
36
|
}
|
|
39
37
|
//#endregion
|
|
40
38
|
//#region src/internal/channel-globals.ts
|
|
@@ -49,10 +47,9 @@ let subscribed$1 = false;
|
|
|
49
47
|
function isColorFormat(value) {
|
|
50
48
|
return typeof value === "string" && COLOR_FORMATS.includes(value);
|
|
51
49
|
}
|
|
52
|
-
function
|
|
53
|
-
if (subscribed$1
|
|
50
|
+
function attach$1(channel) {
|
|
51
|
+
if (subscribed$1) return;
|
|
54
52
|
subscribed$1 = true;
|
|
55
|
-
const channel = addons.getChannel();
|
|
56
53
|
let lastFingerprint = "";
|
|
57
54
|
const onGlobals = (payload) => {
|
|
58
55
|
const globals = payload.globals;
|
|
@@ -74,9 +71,8 @@ function ensureSubscribed$1() {
|
|
|
74
71
|
channel.on("updateGlobals", onGlobals);
|
|
75
72
|
channel.on("setGlobals", onGlobals);
|
|
76
73
|
}
|
|
77
|
-
|
|
74
|
+
onChannel(attach$1);
|
|
78
75
|
function subscribe$2(cb) {
|
|
79
|
-
ensureSubscribed$1();
|
|
80
76
|
listeners$1.add(cb);
|
|
81
77
|
return () => {
|
|
82
78
|
listeners$1.delete(cb);
|
|
@@ -204,16 +200,15 @@ function applyPatch(patch) {
|
|
|
204
200
|
function registerTokenSource(source) {
|
|
205
201
|
applyPatch(source);
|
|
206
202
|
}
|
|
207
|
-
function
|
|
208
|
-
if (subscribed
|
|
203
|
+
function attach(channel) {
|
|
204
|
+
if (subscribed) return;
|
|
209
205
|
subscribed = true;
|
|
210
|
-
|
|
206
|
+
channel.on(TOKENS_UPDATED_EVENT, (payload) => {
|
|
211
207
|
applyPatch(payload);
|
|
212
208
|
});
|
|
213
209
|
}
|
|
214
|
-
|
|
210
|
+
onChannel(attach);
|
|
215
211
|
function subscribe$1(cb) {
|
|
216
|
-
ensureSubscribed();
|
|
217
212
|
listeners.add(cb);
|
|
218
213
|
return () => {
|
|
219
214
|
listeners.delete(cb);
|
|
@@ -260,9 +255,9 @@ function snapshotResolveAt(snapshot) {
|
|
|
260
255
|
*
|
|
261
256
|
* The provider-less path is what makes the hook safe to call from MDX
|
|
262
257
|
* doc blocks and autodocs renders where no story is active. It
|
|
263
|
-
* self-mounts the
|
|
264
|
-
* tuple via the `globalsUpdated`
|
|
265
|
-
*
|
|
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.
|
|
266
261
|
*/
|
|
267
262
|
function useProject() {
|
|
268
263
|
const snapshot = useOptionalSwatchbookData();
|
|
@@ -388,22 +383,25 @@ function resolveColorValue(path, raw, colorFormat, project) {
|
|
|
388
383
|
}
|
|
389
384
|
//#endregion
|
|
390
385
|
//#region src/border-preview/BorderSample.tsx
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
|
|
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 }) {
|
|
399
395
|
return /* @__PURE__ */ jsx("div", {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
border: cssVar
|
|
403
|
-
},
|
|
396
|
+
className: "sb-border-sample",
|
|
397
|
+
style: { border: cssVar },
|
|
404
398
|
"aria-hidden": true
|
|
405
399
|
});
|
|
406
400
|
}
|
|
401
|
+
function BorderSample({ path }) {
|
|
402
|
+
const { cssVar } = deriveBorderSample(path, useProject());
|
|
403
|
+
return /* @__PURE__ */ jsx(BorderSampleView, { cssVar });
|
|
404
|
+
}
|
|
407
405
|
//#endregion
|
|
408
406
|
//#region src/internal/composite-sample-format.ts
|
|
409
407
|
/**
|
|
@@ -586,29 +584,34 @@ function toDisplayable(v) {
|
|
|
586
584
|
}
|
|
587
585
|
//#endregion
|
|
588
586
|
//#region src/BorderPreview.tsx
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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 {
|
|
601
601
|
path,
|
|
602
602
|
cssVar: resolveCssVar(path, project),
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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 }) {
|
|
612
615
|
const captionText = caption ?? `${rows.length} border${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
613
616
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
614
617
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -646,23 +649,48 @@ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
646
649
|
className: "sb-border-preview__breakdown-key",
|
|
647
650
|
children: "width"
|
|
648
651
|
}),
|
|
649
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
652
|
+
/* @__PURE__ */ jsx("span", { children: row.width }),
|
|
650
653
|
/* @__PURE__ */ jsx("span", {
|
|
651
654
|
className: "sb-border-preview__breakdown-key",
|
|
652
655
|
children: "style"
|
|
653
656
|
}),
|
|
654
|
-
/* @__PURE__ */ jsx("span", { children: row.
|
|
657
|
+
/* @__PURE__ */ jsx("span", { children: row.style }),
|
|
655
658
|
/* @__PURE__ */ jsx("span", {
|
|
656
659
|
className: "sb-border-preview__breakdown-key",
|
|
657
660
|
children: "color"
|
|
658
661
|
}),
|
|
659
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
662
|
+
/* @__PURE__ */ jsx("span", { children: row.color })
|
|
660
663
|
]
|
|
661
664
|
})
|
|
662
665
|
]
|
|
663
666
|
}, row.path))]
|
|
664
667
|
});
|
|
665
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
|
+
}
|
|
666
694
|
//#endregion
|
|
667
695
|
//#region src/ColorPalette.tsx
|
|
668
696
|
function fixedPrefixLength(filter) {
|
|
@@ -675,51 +703,48 @@ function fixedPrefixLength(filter) {
|
|
|
675
703
|
}
|
|
676
704
|
return fixed;
|
|
677
705
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
cssVarPrefix
|
|
685
|
-
};
|
|
686
|
-
const entries = sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
687
|
-
if (token.$type !== "color") return false;
|
|
688
|
-
return matchPath(path, filter);
|
|
689
|
-
}), {
|
|
690
|
-
by: sortBy,
|
|
691
|
-
dir: sortDir
|
|
692
|
-
});
|
|
693
|
-
const maxDepth = entries.reduce((m, [p]) => Math.max(m, p.split(".").length), 0);
|
|
694
|
-
const effectiveGroupBy = groupBy ?? Math.min(fixedPrefixLength(filter) + 1, Math.max(maxDepth - 1, 1));
|
|
695
|
-
const bucket = /* @__PURE__ */ new Map();
|
|
696
|
-
for (const [path, token] of entries) {
|
|
697
|
-
const segments = path.split(".");
|
|
698
|
-
const groupKey = segments.slice(0, effectiveGroupBy).join(".");
|
|
699
|
-
const leaf = segments.slice(effectiveGroupBy).join(".") || segments.at(-1) || path;
|
|
700
|
-
const list = bucket.get(groupKey) ?? [];
|
|
701
|
-
const formatted = resolveColorValue(path, token.$value, colorFormat, projectFields);
|
|
702
|
-
list.push({
|
|
703
|
-
path,
|
|
704
|
-
leaf,
|
|
705
|
-
cssVar: resolveCssVar(path, projectFields),
|
|
706
|
-
value: formatted.value,
|
|
707
|
-
outOfGamut: formatted.outOfGamut
|
|
708
|
-
});
|
|
709
|
-
bucket.set(groupKey, list);
|
|
710
|
-
}
|
|
711
|
-
return [...bucket.entries()].toSorted(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
|
|
712
|
-
}, [
|
|
713
|
-
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 = {
|
|
714
712
|
listing,
|
|
715
|
-
cssVarPrefix
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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);
|
|
723
748
|
const captionText = caption ?? `${totalCount} color${totalCount === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
724
749
|
if (totalCount === 0) return /* @__PURE__ */ jsx("div", {
|
|
725
750
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -733,7 +758,7 @@ function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "as
|
|
|
733
758
|
children: [/* @__PURE__ */ jsx("div", {
|
|
734
759
|
className: "sb-block__caption",
|
|
735
760
|
children: captionText
|
|
736
|
-
}), groups.map((
|
|
761
|
+
}), groups.map(({ group, swatches }) => /* @__PURE__ */ jsxs("section", {
|
|
737
762
|
className: "sb-color-palette__group",
|
|
738
763
|
children: [/* @__PURE__ */ jsx("div", {
|
|
739
764
|
className: "sb-color-palette__group-header",
|
|
@@ -766,6 +791,33 @@ function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "as
|
|
|
766
791
|
}, group))]
|
|
767
792
|
});
|
|
768
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
|
+
}
|
|
769
821
|
//#endregion
|
|
770
822
|
//#region src/indicators/resolve.ts
|
|
771
823
|
/** Established-on set; `description` and `composes` are opt-in. */
|
|
@@ -1057,12 +1109,9 @@ function CopyButton$1({ value, label, variant = "icon", className }) {
|
|
|
1057
1109
|
timerRef.current = setTimeout(() => setCopied(false), 1500);
|
|
1058
1110
|
}, [value]);
|
|
1059
1111
|
const ariaLabel = label ?? `Copy ${value}`;
|
|
1060
|
-
const classes = ["sb-copy-button", `sb-copy-button--${variant}`];
|
|
1061
|
-
if (copied) classes.push("sb-copy-button--copied");
|
|
1062
|
-
if (className) classes.push(className);
|
|
1063
1112
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
1064
1113
|
type: "button",
|
|
1065
|
-
className:
|
|
1114
|
+
className: cx("sb-copy-button", `sb-copy-button--${variant}`, copied && "sb-copy-button--copied", className),
|
|
1066
1115
|
onClick: handleClick,
|
|
1067
1116
|
"aria-label": ariaLabel,
|
|
1068
1117
|
title: ariaLabel,
|
|
@@ -1139,88 +1188,81 @@ function usePersistedState(key, initial) {
|
|
|
1139
1188
|
const NOOP_REFERENCE = () => {};
|
|
1140
1189
|
const BASE_LABEL = "base";
|
|
1141
1190
|
const COLUMN_COUNT = 6;
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
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 }) {
|
|
1154
1262
|
const [query, setQuery] = usePersistedState(`${blockKey}::query`, "");
|
|
1155
1263
|
const deferredQuery = useDeferredValue(query);
|
|
1156
1264
|
const [selectedByBase, setSelectedByBase] = usePersistedState(`${blockKey}::selected`, {});
|
|
1157
1265
|
const [expandedByBase, setExpandedByBase] = usePersistedState(`${blockKey}::expanded`, () => /* @__PURE__ */ new Set());
|
|
1158
|
-
const defs = useMemo(() => buildVariantDefs(variants), [variants]);
|
|
1159
|
-
const groups = useMemo(() => {
|
|
1160
|
-
const projectFields = {
|
|
1161
|
-
listing,
|
|
1162
|
-
cssVarPrefix
|
|
1163
|
-
};
|
|
1164
|
-
const sorted = sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
1165
|
-
if (token.$type !== "color") return false;
|
|
1166
|
-
return matchPath(path, filter);
|
|
1167
|
-
}), {
|
|
1168
|
-
by: sortBy,
|
|
1169
|
-
dir: sortDir
|
|
1170
|
-
});
|
|
1171
|
-
const groupMap = /* @__PURE__ */ new Map();
|
|
1172
|
-
for (const [path, token] of sorted) {
|
|
1173
|
-
const raw = token.$value;
|
|
1174
|
-
const hex = resolveColorValue(path, raw, "hex", projectFields);
|
|
1175
|
-
const hsl = formatColor(raw, "hsl");
|
|
1176
|
-
const oklch = formatColor(raw, "oklch");
|
|
1177
|
-
const active = pickActiveFormat(raw, colorFormat, hex, hsl, oklch);
|
|
1178
|
-
const match = matchVariant(path, defs.matchOrder);
|
|
1179
|
-
const variant = {
|
|
1180
|
-
label: match?.label ?? BASE_LABEL,
|
|
1181
|
-
path,
|
|
1182
|
-
token,
|
|
1183
|
-
variance: varianceByPath[path],
|
|
1184
|
-
cssVar: resolveCssVar(path, projectFields),
|
|
1185
|
-
value: active.value,
|
|
1186
|
-
outOfGamut: active.outOfGamut,
|
|
1187
|
-
hex: hex.value,
|
|
1188
|
-
hsl: hsl.value,
|
|
1189
|
-
oklch: oklch.value,
|
|
1190
|
-
...token.$description !== void 0 && { description: token.$description },
|
|
1191
|
-
...token.aliasOf !== void 0 && { aliasOf: token.aliasOf },
|
|
1192
|
-
...token.aliasChain !== void 0 && { aliasChain: token.aliasChain }
|
|
1193
|
-
};
|
|
1194
|
-
const basePath = match?.basePath ?? path;
|
|
1195
|
-
const existing = groupMap.get(basePath);
|
|
1196
|
-
if (existing) existing.variants.push(variant);
|
|
1197
|
-
else groupMap.set(basePath, {
|
|
1198
|
-
base: basePath,
|
|
1199
|
-
variants: [variant]
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1202
|
-
const out = [];
|
|
1203
|
-
for (const { base, variants: vs } of groupMap.values()) {
|
|
1204
|
-
vs.sort((a, b) => orderIndex(a.label, defs) - orderIndex(b.label, defs));
|
|
1205
|
-
const searchText = vs.map((v) => `${v.path} ${v.value}`).join(" ");
|
|
1206
|
-
out.push({
|
|
1207
|
-
base,
|
|
1208
|
-
variants: vs,
|
|
1209
|
-
searchText
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
return out;
|
|
1213
|
-
}, [
|
|
1214
|
-
resolved,
|
|
1215
|
-
listing,
|
|
1216
|
-
cssVarPrefix,
|
|
1217
|
-
varianceByPath,
|
|
1218
|
-
filter,
|
|
1219
|
-
sortBy,
|
|
1220
|
-
sortDir,
|
|
1221
|
-
defs,
|
|
1222
|
-
colorFormat
|
|
1223
|
-
]);
|
|
1224
1266
|
const visibleGroups = useMemo(() => {
|
|
1225
1267
|
if (!searchable || deferredQuery.trim() === "") return groups;
|
|
1226
1268
|
return fuzzyFilter(groups, deferredQuery, (g) => g.searchText);
|
|
@@ -1333,12 +1375,59 @@ function ColorTable({ filter, caption, sortBy = "path", sortDir = "asc", searcha
|
|
|
1333
1375
|
})]
|
|
1334
1376
|
});
|
|
1335
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
|
+
}
|
|
1336
1426
|
const GroupRow = memo(function GroupRow({ group, selectedLabel, expanded, onToggleExpand, onSelectVariant, onSelect, enabled, colorFormat }) {
|
|
1337
1427
|
const multi = group.variants.length > 1;
|
|
1338
1428
|
const active = group.variants.find((v) => v.label === selectedLabel) ?? group.variants[0];
|
|
1339
1429
|
const nameText = multi ? group.base : active.path;
|
|
1340
|
-
const
|
|
1341
|
-
const isDeprecated = dep === true || typeof dep === "string" && dep.length > 0;
|
|
1430
|
+
const isDeprecated = active.isDeprecated;
|
|
1342
1431
|
const handleRowActivate = () => {
|
|
1343
1432
|
if (onSelect) onSelect(active.path);
|
|
1344
1433
|
else onToggleExpand(group.base);
|
|
@@ -1627,6 +1716,7 @@ const severityLabel = {
|
|
|
1627
1716
|
warn: "WARN",
|
|
1628
1717
|
info: "INFO"
|
|
1629
1718
|
};
|
|
1719
|
+
/** Aggregate diagnostics into a summary line + severity variant for the header. */
|
|
1630
1720
|
function summarize(diagnostics) {
|
|
1631
1721
|
if (diagnostics.length === 0) return {
|
|
1632
1722
|
text: "✔ OK · no diagnostics",
|
|
@@ -1653,18 +1743,8 @@ function summarize(diagnostics) {
|
|
|
1653
1743
|
function diagnosticKey(d, i) {
|
|
1654
1744
|
return `${d.severity}:${d.group}:${d.filename ?? ""}:${d.line ?? ""}:${d.message}:${i}`;
|
|
1655
1745
|
}
|
|
1656
|
-
/**
|
|
1657
|
-
|
|
1658
|
-
* disabled-axes validation issues, etc. — as a collapsible list. Auto-opens
|
|
1659
|
-
* when the project carries errors or warnings; stays collapsed for clean
|
|
1660
|
-
* loads and info-only loads.
|
|
1661
|
-
*
|
|
1662
|
-
* Replaces the diagnostics section from the addon's (now-retired) Design
|
|
1663
|
-
* Tokens panel. Consumers compose it alongside TokenNavigator / TokenTable
|
|
1664
|
-
* on their own MDX pages.
|
|
1665
|
-
*/
|
|
1666
|
-
function Diagnostics({ caption } = {}) {
|
|
1667
|
-
const { activeAxes, cssVarPrefix, diagnostics } = useProject();
|
|
1746
|
+
/** Pure presentation for the diagnostics list. Renders from plain props. */
|
|
1747
|
+
function DiagnosticsView({ caption, diagnostics, cssVarPrefix, activeAxes }) {
|
|
1668
1748
|
const summary = useMemo(() => summarize(diagnostics), [diagnostics]);
|
|
1669
1749
|
const headingText = caption ?? `Diagnostics · ${summary.text}`;
|
|
1670
1750
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -1698,6 +1778,25 @@ function Diagnostics({ caption } = {}) {
|
|
|
1698
1778
|
})
|
|
1699
1779
|
});
|
|
1700
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
|
+
}
|
|
1701
1800
|
//#endregion
|
|
1702
1801
|
//#region src/dimension-scale/dimension-px.ts
|
|
1703
1802
|
/**
|
|
@@ -1746,44 +1845,25 @@ function useRootFontSize() {
|
|
|
1746
1845
|
}
|
|
1747
1846
|
//#endregion
|
|
1748
1847
|
//#region src/dimension-scale/DimensionBar.tsx
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
background: "var(--swatchbook-accent-bg, #3b82f6)",
|
|
1766
|
-
borderRadius: 2,
|
|
1767
|
-
minWidth: 1,
|
|
1768
|
-
maxWidth: "100%"
|
|
1769
|
-
},
|
|
1770
|
-
radiusSample: {
|
|
1771
|
-
width: 56,
|
|
1772
|
-
height: 56,
|
|
1773
|
-
background: "var(--swatchbook-accent-bg, #3b82f6)",
|
|
1774
|
-
border: BORDER_STRONG
|
|
1775
|
-
},
|
|
1776
|
-
sizeSample: {
|
|
1777
|
-
background: "var(--swatchbook-accent-bg, #3b82f6)",
|
|
1778
|
-
border: BORDER_STRONG,
|
|
1779
|
-
minWidth: 1,
|
|
1780
|
-
minHeight: 1
|
|
1781
|
-
}
|
|
1782
|
-
};
|
|
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
|
+
}
|
|
1783
1864
|
function withCap(visual) {
|
|
1784
1865
|
return /* @__PURE__ */ jsxs("span", {
|
|
1785
1866
|
className: "sb-dimension-bar sb-dimension-bar--capped",
|
|
1786
|
-
style: styles$1.cappedWrap,
|
|
1787
1867
|
title: `capped at 480px`,
|
|
1788
1868
|
children: [visual, /* @__PURE__ */ jsx("span", {
|
|
1789
1869
|
className: "sb-dimension-bar__cap",
|
|
@@ -1792,27 +1872,18 @@ function withCap(visual) {
|
|
|
1792
1872
|
})]
|
|
1793
1873
|
});
|
|
1794
1874
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
const { resolved } = project;
|
|
1798
|
-
const rootFontSize = useRootFontSize();
|
|
1799
|
-
const cssVar = resolveCssVar(path, project);
|
|
1800
|
-
const token = resolved[path];
|
|
1801
|
-
const pxValue = toPixels(token?.$value, rootFontSize);
|
|
1802
|
-
const capped = Number.isFinite(pxValue) && pxValue > 480;
|
|
1803
|
-
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 }) {
|
|
1804
1877
|
switch (visual) {
|
|
1805
1878
|
case "radius": return /* @__PURE__ */ jsx("div", {
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
borderRadius: cssVar
|
|
1809
|
-
},
|
|
1879
|
+
className: "sb-dimension-bar__radius-sample",
|
|
1880
|
+
style: { borderRadius: cssVar },
|
|
1810
1881
|
"aria-hidden": true
|
|
1811
1882
|
});
|
|
1812
1883
|
case "size": {
|
|
1813
1884
|
const sample = /* @__PURE__ */ jsx("div", {
|
|
1885
|
+
className: "sb-dimension-bar__size-sample",
|
|
1814
1886
|
style: {
|
|
1815
|
-
...styles$1.sizeSample,
|
|
1816
1887
|
width: cappedValue,
|
|
1817
1888
|
height: cappedValue
|
|
1818
1889
|
},
|
|
@@ -1822,16 +1893,23 @@ function DimensionBar({ path, visual = "length" }) {
|
|
|
1822
1893
|
}
|
|
1823
1894
|
default: {
|
|
1824
1895
|
const bar = /* @__PURE__ */ jsx("div", {
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
width: cappedValue
|
|
1828
|
-
},
|
|
1896
|
+
className: "sb-dimension-bar__bar",
|
|
1897
|
+
style: { width: cappedValue },
|
|
1829
1898
|
"aria-hidden": true
|
|
1830
1899
|
});
|
|
1831
1900
|
return capped ? withCap(bar) : bar;
|
|
1832
1901
|
}
|
|
1833
1902
|
}
|
|
1834
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
|
+
}
|
|
1835
1913
|
//#endregion
|
|
1836
1914
|
//#region src/internal/format-token-value.ts
|
|
1837
1915
|
/**
|
|
@@ -1936,31 +2014,30 @@ function formatUnknown(v) {
|
|
|
1936
2014
|
}
|
|
1937
2015
|
//#endregion
|
|
1938
2016
|
//#region src/DimensionScale.tsx
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
]);
|
|
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 }) {
|
|
1964
2041
|
const captionText = caption ?? `${rows.length} dimension${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
1965
2042
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
1966
2043
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2002,6 +2079,32 @@ function DimensionScale({ filter, visual = "length", caption, sortBy = "value",
|
|
|
2002
2079
|
}, row.path))]
|
|
2003
2080
|
});
|
|
2004
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
|
+
}
|
|
2005
2108
|
//#endregion
|
|
2006
2109
|
//#region src/FontFamilyPreview.tsx
|
|
2007
2110
|
function stackString(raw) {
|
|
@@ -2009,28 +2112,25 @@ function stackString(raw) {
|
|
|
2009
2112
|
if (Array.isArray(raw)) return raw.map(String).join(", ");
|
|
2010
2113
|
return "";
|
|
2011
2114
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
sortBy,
|
|
2032
|
-
sortDir
|
|
2033
|
-
]);
|
|
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 }) {
|
|
2034
2134
|
const captionText = caption ?? `${rows.length} fontFamily token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontFamily" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2035
2135
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2036
2136
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2070,6 +2170,29 @@ function FontFamilyPreview({ filter, sample = "The quick brown fox jumps over th
|
|
|
2070
2170
|
}, row.path))]
|
|
2071
2171
|
});
|
|
2072
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
|
+
}
|
|
2073
2196
|
//#endregion
|
|
2074
2197
|
//#region src/internal/css-var-style.ts
|
|
2075
2198
|
/**
|
|
@@ -2095,29 +2218,26 @@ function toWeight(raw) {
|
|
|
2095
2218
|
}
|
|
2096
2219
|
return NaN;
|
|
2097
2220
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
sortBy,
|
|
2119
|
-
sortDir
|
|
2120
|
-
]);
|
|
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 }) {
|
|
2121
2241
|
const captionText = caption ?? `${rows.length} fontWeight token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontWeight" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2122
2242
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2123
2243
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2157,6 +2277,29 @@ function FontWeightScale({ filter, sample = "Aa", caption, sortBy = "value", sor
|
|
|
2157
2277
|
}, row.path))]
|
|
2158
2278
|
});
|
|
2159
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
|
+
}
|
|
2160
2303
|
//#endregion
|
|
2161
2304
|
//#region src/GradientPalette.tsx
|
|
2162
2305
|
function asStops(raw) {
|
|
@@ -2171,33 +2314,37 @@ function stopCssColor(stop) {
|
|
|
2171
2314
|
function stopKey(path, stop, fallback) {
|
|
2172
2315
|
return `${path}|${stop.position ?? fallback}|${stopCssColor(stop)}`;
|
|
2173
2316
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
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 {
|
|
2189
2335
|
path,
|
|
2190
2336
|
cssVar: resolveCssVar(path, projectFields),
|
|
2191
|
-
stops:
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
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 }) {
|
|
2201
2348
|
const captionText = caption ?? `${rows.length} gradient${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2202
2349
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2203
2350
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2231,30 +2378,55 @@ function GradientPalette({ filter, caption, sortBy = "path", sortDir = "asc" })
|
|
|
2231
2378
|
}),
|
|
2232
2379
|
/* @__PURE__ */ jsx("div", {
|
|
2233
2380
|
className: "sb-gradient-palette__stops",
|
|
2234
|
-
children: row.stops.map((stop
|
|
2381
|
+
children: row.stops.map((stop) => /* @__PURE__ */ jsxs("div", {
|
|
2235
2382
|
className: "sb-gradient-palette__stop-row",
|
|
2236
2383
|
children: [
|
|
2237
2384
|
/* @__PURE__ */ jsx("span", {
|
|
2238
2385
|
className: "sb-gradient-palette__stop-swatch",
|
|
2239
|
-
style: { background:
|
|
2386
|
+
style: { background: stop.cssColor },
|
|
2240
2387
|
"aria-hidden": true
|
|
2241
2388
|
}),
|
|
2242
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
2389
|
+
/* @__PURE__ */ jsx("span", { children: stop.value }),
|
|
2243
2390
|
/* @__PURE__ */ jsxs("span", {
|
|
2244
2391
|
className: "sb-gradient-palette__stop-position",
|
|
2245
2392
|
children: [
|
|
2246
2393
|
"@ ",
|
|
2247
|
-
|
|
2394
|
+
stop.positionPercent,
|
|
2248
2395
|
"%"
|
|
2249
2396
|
]
|
|
2250
2397
|
})
|
|
2251
2398
|
]
|
|
2252
|
-
},
|
|
2399
|
+
}, stop.key))
|
|
2253
2400
|
})
|
|
2254
2401
|
]
|
|
2255
2402
|
}, row.path))]
|
|
2256
2403
|
});
|
|
2257
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
|
+
}
|
|
2258
2430
|
//#endregion
|
|
2259
2431
|
//#region src/internal/prefers-reduced-motion.ts
|
|
2260
2432
|
function isChromatic() {
|
|
@@ -2287,29 +2459,6 @@ function usePrefersReducedMotion() {
|
|
|
2287
2459
|
//#region src/motion-preview/MotionSample.tsx
|
|
2288
2460
|
const DEFAULT_DURATION_MS = 300;
|
|
2289
2461
|
const DEFAULT_EASING = "cubic-bezier(0.2, 0, 0, 1)";
|
|
2290
|
-
const styles = {
|
|
2291
|
-
track: {
|
|
2292
|
-
position: "relative",
|
|
2293
|
-
height: 36,
|
|
2294
|
-
background: SURFACE_MUTED,
|
|
2295
|
-
borderRadius: 18,
|
|
2296
|
-
overflow: "hidden"
|
|
2297
|
-
},
|
|
2298
|
-
ball: {
|
|
2299
|
-
position: "absolute",
|
|
2300
|
-
top: "50%",
|
|
2301
|
-
width: 28,
|
|
2302
|
-
height: 28,
|
|
2303
|
-
marginTop: -14,
|
|
2304
|
-
borderRadius: "50%",
|
|
2305
|
-
background: "var(--swatchbook-accent-bg, #3b82f6)"
|
|
2306
|
-
},
|
|
2307
|
-
reducedMotion: {
|
|
2308
|
-
fontSize: 11,
|
|
2309
|
-
color: TEXT_MUTED,
|
|
2310
|
-
fontStyle: "italic"
|
|
2311
|
-
}
|
|
2312
|
-
};
|
|
2313
2462
|
function extractDurationMs(raw) {
|
|
2314
2463
|
if (raw == null) return NaN;
|
|
2315
2464
|
if (typeof raw === "object") {
|
|
@@ -2379,10 +2528,16 @@ function resolveMotionSpec(token, themeTokens) {
|
|
|
2379
2528
|
}
|
|
2380
2529
|
return null;
|
|
2381
2530
|
}
|
|
2382
|
-
|
|
2383
|
-
|
|
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 }) {
|
|
2384
2540
|
const reducedMotion = usePrefersReducedMotion();
|
|
2385
|
-
const spec = useMemo(() => resolveMotionSpec(resolved[path], resolved), [resolved, path]);
|
|
2386
2541
|
const durationMs = spec?.durationMs ?? DEFAULT_DURATION_MS;
|
|
2387
2542
|
const easing = spec?.easing ?? DEFAULT_EASING;
|
|
2388
2543
|
const scaledDuration = Math.max(1, durationMs / speed);
|
|
@@ -2404,7 +2559,7 @@ function MotionSample({ path, speed = 1, runKey = 0 }) {
|
|
|
2404
2559
|
reducedMotion
|
|
2405
2560
|
]);
|
|
2406
2561
|
if (reducedMotion) return /* @__PURE__ */ jsxs("div", {
|
|
2407
|
-
|
|
2562
|
+
className: "sb-motion-sample__reduced-motion",
|
|
2408
2563
|
children: [
|
|
2409
2564
|
"Animation suppressed by ",
|
|
2410
2565
|
/* @__PURE__ */ jsx("code", { children: "prefers-reduced-motion: reduce" }),
|
|
@@ -2412,17 +2567,22 @@ function MotionSample({ path, speed = 1, runKey = 0 }) {
|
|
|
2412
2567
|
]
|
|
2413
2568
|
});
|
|
2414
2569
|
return /* @__PURE__ */ jsx("div", {
|
|
2415
|
-
|
|
2570
|
+
className: "sb-motion-sample__track",
|
|
2416
2571
|
children: /* @__PURE__ */ jsx("div", {
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
left: phase === 1 ? "calc(100% - 32px)" : "4px",
|
|
2420
|
-
transition: `left ${scaledDuration}ms ${easing}`
|
|
2421
|
-
},
|
|
2572
|
+
className: cx("sb-motion-sample__ball", phase === 1 ? "sb-motion-sample__ball--end" : "sb-motion-sample__ball--start"),
|
|
2573
|
+
style: { transition: `left ${scaledDuration}ms ${easing}` },
|
|
2422
2574
|
"aria-hidden": true
|
|
2423
2575
|
})
|
|
2424
2576
|
});
|
|
2425
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
|
+
}
|
|
2426
2586
|
//#endregion
|
|
2427
2587
|
//#region src/MotionPreview.tsx
|
|
2428
2588
|
const SPEEDS = [
|
|
@@ -2438,43 +2598,48 @@ function formatSpec(row) {
|
|
|
2438
2598
|
case "cubicBezier": return `cubicBezier · ${row.easing}`;
|
|
2439
2599
|
}
|
|
2440
2600
|
}
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
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 }) {
|
|
2444
2640
|
const [speed, setSpeed] = useState(1);
|
|
2445
2641
|
const [run, setRun] = useState(0);
|
|
2446
2642
|
const reducedMotion = usePrefersReducedMotion();
|
|
2447
|
-
const rows = useMemo(() => {
|
|
2448
|
-
const collected = [];
|
|
2449
|
-
for (const [path, token] of Object.entries(resolved)) {
|
|
2450
|
-
if (filter && !matchPath(path, filter)) continue;
|
|
2451
|
-
if (!filter && ![
|
|
2452
|
-
"transition",
|
|
2453
|
-
"duration",
|
|
2454
|
-
"cubicBezier"
|
|
2455
|
-
].includes(token.$type ?? "")) continue;
|
|
2456
|
-
const kind = token.$type;
|
|
2457
|
-
if (!kind) continue;
|
|
2458
|
-
const spec = resolveMotionSpec(token, resolved);
|
|
2459
|
-
if (!spec) continue;
|
|
2460
|
-
collected.push({
|
|
2461
|
-
path,
|
|
2462
|
-
cssVar: resolveCssVar(path, project),
|
|
2463
|
-
durationMs: spec.durationMs,
|
|
2464
|
-
easing: spec.easing,
|
|
2465
|
-
kind
|
|
2466
|
-
});
|
|
2467
|
-
}
|
|
2468
|
-
collected.sort((a, b) => {
|
|
2469
|
-
if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
|
|
2470
|
-
return a.path.localeCompare(b.path, void 0, { numeric: true });
|
|
2471
|
-
});
|
|
2472
|
-
return collected;
|
|
2473
|
-
}, [
|
|
2474
|
-
resolved,
|
|
2475
|
-
filter,
|
|
2476
|
-
project
|
|
2477
|
-
]);
|
|
2478
2643
|
const captionText = caption ?? `${rows.length} motion token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2479
2644
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2480
2645
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2540,6 +2705,22 @@ function MotionPreview({ filter, caption }) {
|
|
|
2540
2705
|
]
|
|
2541
2706
|
});
|
|
2542
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
|
+
}
|
|
2543
2724
|
//#endregion
|
|
2544
2725
|
//#region src/OpacityScale.tsx
|
|
2545
2726
|
function toOpacity(raw) {
|
|
@@ -2547,44 +2728,33 @@ function toOpacity(raw) {
|
|
|
2547
2728
|
return NaN;
|
|
2548
2729
|
}
|
|
2549
2730
|
/**
|
|
2550
|
-
*
|
|
2551
|
-
*
|
|
2552
|
-
*
|
|
2553
|
-
*
|
|
2554
|
-
*
|
|
2555
|
-
* Only tokens whose `$value` is a finite number between 0 and 1
|
|
2556
|
-
* inclusive are rendered — non-opacity `number` siblings (`line-height`,
|
|
2557
|
-
* `z-index`) fall out naturally.
|
|
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.
|
|
2558
2736
|
*/
|
|
2559
|
-
function
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
}, [
|
|
2581
|
-
resolved,
|
|
2582
|
-
filter,
|
|
2583
|
-
type,
|
|
2584
|
-
project,
|
|
2585
|
-
sortBy,
|
|
2586
|
-
sortDir
|
|
2587
|
-
]);
|
|
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 }) {
|
|
2588
2758
|
const captionText = caption ?? `${rows.length} opacity token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2589
2759
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2590
2760
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2593,7 +2763,6 @@ function OpacityScale({ filter, type = "number", sampleColor = "color.accent.bg"
|
|
|
2593
2763
|
children: "No opacity tokens match this filter."
|
|
2594
2764
|
})
|
|
2595
2765
|
});
|
|
2596
|
-
const sampleColorVar = resolveCssVar(sampleColor, project);
|
|
2597
2766
|
return /* @__PURE__ */ jsxs("div", {
|
|
2598
2767
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2599
2768
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -2631,6 +2800,37 @@ function OpacityScale({ filter, type = "number", sampleColor = "color.accent.bg"
|
|
|
2631
2800
|
})]
|
|
2632
2801
|
});
|
|
2633
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
|
+
}
|
|
2634
2834
|
//#endregion
|
|
2635
2835
|
//#region src/provider.tsx
|
|
2636
2836
|
/**
|
|
@@ -2660,56 +2860,67 @@ function useSwatchbookData() {
|
|
|
2660
2860
|
}
|
|
2661
2861
|
//#endregion
|
|
2662
2862
|
//#region src/shadow-preview/ShadowSample.tsx
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
|
|
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 }) {
|
|
2672
2872
|
return /* @__PURE__ */ jsx("div", {
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
boxShadow: cssVar
|
|
2676
|
-
},
|
|
2873
|
+
className: "sb-shadow-sample",
|
|
2874
|
+
style: { boxShadow: cssVar },
|
|
2677
2875
|
"aria-hidden": true
|
|
2678
2876
|
});
|
|
2679
2877
|
}
|
|
2878
|
+
function ShadowSample({ path }) {
|
|
2879
|
+
const { cssVar } = deriveShadowSample(path, useProject());
|
|
2880
|
+
return /* @__PURE__ */ jsx(ShadowSampleView, { cssVar });
|
|
2881
|
+
}
|
|
2680
2882
|
//#endregion
|
|
2681
2883
|
//#region src/ShadowPreview.tsx
|
|
2884
|
+
function layerKey(path, layer, fallback) {
|
|
2885
|
+
return `${path}|${layer.offset}|${layer.blur}|${layer.spread}|${fallback}`;
|
|
2886
|
+
}
|
|
2682
2887
|
function asLayers(raw) {
|
|
2683
2888
|
if (Array.isArray(raw)) return raw;
|
|
2684
2889
|
if (raw && typeof raw === "object") return [raw];
|
|
2685
2890
|
return [];
|
|
2686
2891
|
}
|
|
2687
|
-
function
|
|
2688
|
-
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
|
+
};
|
|
2689
2900
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
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 }) {
|
|
2713
2924
|
const captionText = caption ?? `${rows.length} shadow${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2714
2925
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2715
2926
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2742,32 +2953,31 @@ function ShadowPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
2742
2953
|
}),
|
|
2743
2954
|
/* @__PURE__ */ jsx("div", {
|
|
2744
2955
|
className: "sb-shadow-preview__breakdown",
|
|
2745
|
-
children: row.layers.length === 1 ?
|
|
2956
|
+
children: row.layers.length === 1 ? renderLayerEntries(row.layers[0]) : row.layers.map((layer, i) => /* @__PURE__ */ jsx(Layer, {
|
|
2746
2957
|
layer,
|
|
2747
2958
|
index: i,
|
|
2748
|
-
total: row.layers.length
|
|
2749
|
-
colorFormat
|
|
2959
|
+
total: row.layers.length
|
|
2750
2960
|
}, layerKey(row.path, layer, i)))
|
|
2751
2961
|
})
|
|
2752
2962
|
]
|
|
2753
2963
|
}, row.path))]
|
|
2754
2964
|
});
|
|
2755
2965
|
}
|
|
2756
|
-
function
|
|
2966
|
+
function renderLayerEntries(layer) {
|
|
2757
2967
|
if (!layer) return [];
|
|
2758
2968
|
const entries = [
|
|
2759
|
-
["offset",
|
|
2760
|
-
["blur",
|
|
2761
|
-
["spread",
|
|
2762
|
-
["color",
|
|
2969
|
+
["offset", layer.offset],
|
|
2970
|
+
["blur", layer.blur],
|
|
2971
|
+
["spread", layer.spread],
|
|
2972
|
+
["color", layer.color]
|
|
2763
2973
|
];
|
|
2764
|
-
if (layer.inset) entries.push(["inset",
|
|
2974
|
+
if (layer.inset) entries.push(["inset", layer.inset]);
|
|
2765
2975
|
return entries.flatMap(([k, v]) => [/* @__PURE__ */ jsx("span", {
|
|
2766
2976
|
className: "sb-shadow-preview__breakdown-key",
|
|
2767
2977
|
children: k
|
|
2768
2978
|
}, `k-${k}`), /* @__PURE__ */ jsx("span", { children: v }, `v-${k}`)]);
|
|
2769
2979
|
}
|
|
2770
|
-
function Layer({ layer, index, total
|
|
2980
|
+
function Layer({ layer, index, total }) {
|
|
2771
2981
|
return /* @__PURE__ */ jsxs("div", {
|
|
2772
2982
|
className: "sb-shadow-preview__layer",
|
|
2773
2983
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -2780,10 +2990,35 @@ function Layer({ layer, index, total, colorFormat }) {
|
|
|
2780
2990
|
]
|
|
2781
2991
|
}), /* @__PURE__ */ jsx("div", {
|
|
2782
2992
|
className: cx("sb-shadow-preview__breakdown", "sb-shadow-preview__layer-breakdown"),
|
|
2783
|
-
children:
|
|
2993
|
+
children: renderLayerEntries(layer)
|
|
2784
2994
|
})]
|
|
2785
2995
|
});
|
|
2786
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
|
+
}
|
|
2787
3022
|
//#endregion
|
|
2788
3023
|
//#region src/StrokeStylePreview.tsx
|
|
2789
3024
|
const STRING_STYLES = new Set([
|
|
@@ -2800,29 +3035,26 @@ function extractCssStyle(value) {
|
|
|
2800
3035
|
if (typeof value === "string" && STRING_STYLES.has(value)) return value;
|
|
2801
3036
|
return null;
|
|
2802
3037
|
}
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
}
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
sortBy,
|
|
2824
|
-
sortDir
|
|
2825
|
-
]);
|
|
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 }) {
|
|
2826
3058
|
const captionText = caption ?? `${rows.length} strokeStyle token${rows.length === 1 ? "" : "s"}${filter && filter !== "strokeStyle" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2827
3059
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2828
3060
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -2865,6 +3097,28 @@ function StrokeStylePreview({ filter, caption, sortBy = "path", sortDir = "asc"
|
|
|
2865
3097
|
}, row.path))]
|
|
2866
3098
|
});
|
|
2867
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
|
+
}
|
|
2868
3122
|
//#endregion
|
|
2869
3123
|
//#region src/token-detail/internal.ts
|
|
2870
3124
|
function useTokenDetailData(path) {
|
|
@@ -3048,10 +3302,7 @@ function AxisVariance({ path }) {
|
|
|
3048
3302
|
}),
|
|
3049
3303
|
value,
|
|
3050
3304
|
/* @__PURE__ */ jsx("span", {
|
|
3051
|
-
|
|
3052
|
-
opacity: .6,
|
|
3053
|
-
marginLeft: 8
|
|
3054
|
-
},
|
|
3305
|
+
className: "sb-token-detail__constant-label-note",
|
|
3055
3306
|
children: "same across every axis"
|
|
3056
3307
|
})
|
|
3057
3308
|
]
|
|
@@ -3085,8 +3336,7 @@ function AxisVariance({ path }) {
|
|
|
3085
3336
|
"data-axis": axisName,
|
|
3086
3337
|
"data-context": row.ctx,
|
|
3087
3338
|
children: [/* @__PURE__ */ jsx("td", {
|
|
3088
|
-
className: "sb-token-detail__theme-cell",
|
|
3089
|
-
style: { width: "30%" },
|
|
3339
|
+
className: "sb-token-detail__theme-cell sb-token-detail__theme-cell--label",
|
|
3090
3340
|
children: row.ctx
|
|
3091
3341
|
}), /* @__PURE__ */ jsxs("td", {
|
|
3092
3342
|
className: "sb-token-detail__theme-cell",
|
|
@@ -3113,22 +3363,14 @@ function AxisVariance({ path }) {
|
|
|
3113
3363
|
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
|
|
3114
3364
|
className: "sb-token-detail__theme-row",
|
|
3115
3365
|
children: [/* @__PURE__ */ jsxs("th", {
|
|
3116
|
-
className: "sb-token-detail__theme-cell",
|
|
3117
|
-
style: {
|
|
3118
|
-
textAlign: "left",
|
|
3119
|
-
opacity: .7
|
|
3120
|
-
},
|
|
3366
|
+
className: "sb-token-detail__theme-cell sb-token-detail__theme-cell--header",
|
|
3121
3367
|
children: [
|
|
3122
3368
|
rowAxis.name,
|
|
3123
3369
|
" \\ ",
|
|
3124
3370
|
colAxis.name
|
|
3125
3371
|
]
|
|
3126
3372
|
}), colAxis.contexts.map((col) => /* @__PURE__ */ jsx("th", {
|
|
3127
|
-
className: "sb-token-detail__theme-cell",
|
|
3128
|
-
style: {
|
|
3129
|
-
textAlign: "left",
|
|
3130
|
-
opacity: .7
|
|
3131
|
-
},
|
|
3373
|
+
className: "sb-token-detail__theme-cell sb-token-detail__theme-cell--header",
|
|
3132
3374
|
children: col
|
|
3133
3375
|
}, col))]
|
|
3134
3376
|
}) }), /* @__PURE__ */ jsx("tbody", { children: rowAxis.contexts.map((row) => /* @__PURE__ */ jsxs("tr", {
|
|
@@ -3158,8 +3400,7 @@ function AxisVariance({ path }) {
|
|
|
3158
3400
|
}, row)) })]
|
|
3159
3401
|
}),
|
|
3160
3402
|
extra.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
3161
|
-
className: "sb-token-detail__aliased-by-truncated",
|
|
3162
|
-
style: { marginTop: 6 },
|
|
3403
|
+
className: "sb-token-detail__aliased-by-truncated sb-token-detail__aliased-by-truncated--axis-note",
|
|
3163
3404
|
children: [
|
|
3164
3405
|
"Values also vary with ",
|
|
3165
3406
|
extra.map((a) => a.name).join(", "),
|
|
@@ -3270,7 +3511,7 @@ function CompositeBreakdownContent({ type, rawValue, partialAliasOf, resolved, c
|
|
|
3270
3511
|
children: layers.map((layer, i) => {
|
|
3271
3512
|
const v = layer;
|
|
3272
3513
|
return /* @__PURE__ */ jsxs("div", {
|
|
3273
|
-
|
|
3514
|
+
className: "sb-token-detail__shadow-layer",
|
|
3274
3515
|
children: [
|
|
3275
3516
|
multi && /* @__PURE__ */ jsxs("div", {
|
|
3276
3517
|
className: "sb-token-detail__breakdown-layer-header",
|
|
@@ -3612,24 +3853,30 @@ function TransitionSample({ transition, durationMs }) {
|
|
|
3612
3853
|
return /* @__PURE__ */ jsx("div", {
|
|
3613
3854
|
className: "sb-token-detail__motion-track",
|
|
3614
3855
|
children: /* @__PURE__ */ jsx("div", {
|
|
3615
|
-
className: "sb-token-detail__motion-ball",
|
|
3616
|
-
style: {
|
|
3617
|
-
left: phase === 1 ? "calc(100% - 28px)" : "4px",
|
|
3618
|
-
transition
|
|
3619
|
-
},
|
|
3856
|
+
className: cx("sb-token-detail__motion-ball", phase === 1 ? "sb-token-detail__motion-ball--end" : "sb-token-detail__motion-ball--start"),
|
|
3857
|
+
style: { transition },
|
|
3620
3858
|
"aria-hidden": true
|
|
3621
3859
|
})
|
|
3622
3860
|
});
|
|
3623
3861
|
}
|
|
3624
3862
|
//#endregion
|
|
3625
3863
|
//#region src/token-detail/ConsumerOutput.tsx
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
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) {
|
|
3631
3869
|
const names = listing[path]?.names ?? {};
|
|
3632
|
-
|
|
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(", ");
|
|
3633
3880
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3634
3881
|
/* @__PURE__ */ jsx("div", {
|
|
3635
3882
|
className: "sb-token-detail__section-header",
|
|
@@ -3649,13 +3896,25 @@ function ConsumerOutput({ path }) {
|
|
|
3649
3896
|
value: cssVar,
|
|
3650
3897
|
testId: "consumer-output-css"
|
|
3651
3898
|
}),
|
|
3652
|
-
extraPlatforms.map((
|
|
3653
|
-
label:
|
|
3654
|
-
value:
|
|
3655
|
-
testId: `consumer-output-${platform}`
|
|
3656
|
-
}, 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))
|
|
3657
3904
|
] });
|
|
3658
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
|
+
}
|
|
3659
3918
|
function formatPlatformLabel(platform) {
|
|
3660
3919
|
if (platform.length === 0) return platform;
|
|
3661
3920
|
return platform[0].toUpperCase() + platform.slice(1);
|
|
@@ -3776,10 +4035,43 @@ function TokenUsageSnippet({ path }) {
|
|
|
3776
4035
|
}
|
|
3777
4036
|
//#endregion
|
|
3778
4037
|
//#region src/TokenDetail.tsx
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
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 }) {
|
|
3783
4075
|
const wrapperAttrs = blockWrapperAttrs(cssVarPrefix, activeAxes);
|
|
3784
4076
|
if (!token) return /* @__PURE__ */ jsx("div", {
|
|
3785
4077
|
...wrapperAttrs,
|
|
@@ -3795,12 +4087,6 @@ function TokenDetail({ path, heading }) {
|
|
|
3795
4087
|
]
|
|
3796
4088
|
})
|
|
3797
4089
|
});
|
|
3798
|
-
const isColor = token.$type === "color";
|
|
3799
|
-
const gamut = isColor ? formatColor(token.$value, colorFormat) : null;
|
|
3800
|
-
const value = formatTokenValue(token.$value, token.$type, colorFormat, listing[path]);
|
|
3801
|
-
const outOfGamut = gamut?.outOfGamut ?? false;
|
|
3802
|
-
const dep = token.$deprecated;
|
|
3803
|
-
const isDeprecated = dep === true || typeof dep === "string" && dep.length > 0;
|
|
3804
4090
|
return /* @__PURE__ */ jsxs("div", {
|
|
3805
4091
|
...wrapperAttrs,
|
|
3806
4092
|
className: cx(wrapperAttrs["className"], "sb-token-detail"),
|
|
@@ -3819,7 +4105,7 @@ function TokenDetail({ path, heading }) {
|
|
|
3819
4105
|
children: "⚠ "
|
|
3820
4106
|
}),
|
|
3821
4107
|
"Deprecated",
|
|
3822
|
-
|
|
4108
|
+
deprecationMessage ? `: ${deprecationMessage}` : ""
|
|
3823
4109
|
]
|
|
3824
4110
|
}),
|
|
3825
4111
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -3840,7 +4126,7 @@ function TokenDetail({ path, heading }) {
|
|
|
3840
4126
|
outOfGamut && /* @__PURE__ */ jsx("span", {
|
|
3841
4127
|
title: "Out of sRGB gamut for this format",
|
|
3842
4128
|
"aria-label": "out of gamut",
|
|
3843
|
-
|
|
4129
|
+
className: "sb-token-detail__out-of-gamut-icon",
|
|
3844
4130
|
children: "⚠"
|
|
3845
4131
|
}),
|
|
3846
4132
|
/* @__PURE__ */ jsx(CopyButton$1, {
|
|
@@ -3857,6 +4143,22 @@ function TokenDetail({ path, heading }) {
|
|
|
3857
4143
|
]
|
|
3858
4144
|
});
|
|
3859
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
|
+
}
|
|
3860
4162
|
//#endregion
|
|
3861
4163
|
//#region src/internal/DetailOverlay.tsx
|
|
3862
4164
|
const FOCUSABLE_SELECTOR = [
|
|
@@ -3985,6 +4287,13 @@ function isInView(path, ctx) {
|
|
|
3985
4287
|
}
|
|
3986
4288
|
//#endregion
|
|
3987
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
|
+
*/
|
|
3988
4297
|
function buildTree(resolved, root, typeFilter) {
|
|
3989
4298
|
const rootPrefix = root && root.length > 0 ? `${root}.` : "";
|
|
3990
4299
|
const rootSegments = root ? root.split(".") : [];
|
|
@@ -4061,6 +4370,12 @@ function collectLeafPaths(nodes, out) {
|
|
|
4061
4370
|
for (const node of nodes) if (node.kind === "leaf") out.push(node.path);
|
|
4062
4371
|
else collectLeafPaths(node.children, out);
|
|
4063
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
|
+
*/
|
|
4064
4379
|
function flattenVisible(nodes, expanded, parentPath, out) {
|
|
4065
4380
|
for (const node of nodes) {
|
|
4066
4381
|
out.push({
|
|
@@ -4087,18 +4402,18 @@ function pruneTreeForMatches(nodes, matches, expandOut) {
|
|
|
4087
4402
|
}
|
|
4088
4403
|
return out;
|
|
4089
4404
|
}
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
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 }) {
|
|
4097
4413
|
const typeFilter = useMemo(() => {
|
|
4098
4414
|
if (type === void 0) return void 0;
|
|
4099
4415
|
return new Set(Array.isArray(type) ? type : [type]);
|
|
4100
4416
|
}, [type]);
|
|
4101
|
-
const enabledIndicators = useMemo(() => resolveIndicators(indicators, indicatorBaseline), [indicators, indicatorBaseline]);
|
|
4102
4417
|
const tree = useMemo(() => buildTree(resolved, root, typeFilter), [
|
|
4103
4418
|
resolved,
|
|
4104
4419
|
root,
|
|
@@ -4314,7 +4629,10 @@ function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true,
|
|
|
4314
4629
|
}, [visibleTree, searchExpanded]);
|
|
4315
4630
|
if (tree.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
4316
4631
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
4317
|
-
children: /* @__PURE__ */ jsx(
|
|
4632
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
4633
|
+
className: "sb-block__empty",
|
|
4634
|
+
children: root ? `No tokens under "${root}"${typeFilter ? ` matching ${typeLabel.slice(3)}` : ""}.` : typeFilter ? `No tokens matching ${typeLabel.slice(3)} in the active theme.` : "No tokens in the active theme."
|
|
4635
|
+
})
|
|
4318
4636
|
});
|
|
4319
4637
|
return /* @__PURE__ */ jsxs("div", {
|
|
4320
4638
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -4384,6 +4702,32 @@ function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true,
|
|
|
4384
4702
|
]
|
|
4385
4703
|
});
|
|
4386
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
|
+
}
|
|
4387
4731
|
function TreeNodeRow({ node, expanded, focusedPath, registerTreeItem, onToggle, onFocusPath, onLeafClick, root, resolveInView, onNavigate, enabled, level, setsize, posinset }) {
|
|
4388
4732
|
if (node.kind === "leaf") return /* @__PURE__ */ jsx(LeafRow, {
|
|
4389
4733
|
node,
|
|
@@ -4572,56 +4916,51 @@ const LeafPreview = memo(function LeafPreview({ path, token }) {
|
|
|
4572
4916
|
});
|
|
4573
4917
|
//#endregion
|
|
4574
4918
|
//#region src/TokenTable.tsx
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
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 }) {
|
|
4586
4961
|
const [selectedPath, setSelectedPath] = usePersistedState(`${blockKey}::selected`, null);
|
|
4587
4962
|
const [query, setQuery] = usePersistedState(`${blockKey}::query`, "");
|
|
4588
4963
|
const deferredQuery = useDeferredValue(query);
|
|
4589
|
-
const rows = useMemo(() => {
|
|
4590
|
-
const projectFields = {
|
|
4591
|
-
listing,
|
|
4592
|
-
cssVarPrefix
|
|
4593
|
-
};
|
|
4594
|
-
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
4595
|
-
if (!matchPath(path, filter)) return false;
|
|
4596
|
-
if (type && token.$type !== type) return false;
|
|
4597
|
-
return true;
|
|
4598
|
-
}), {
|
|
4599
|
-
by: sortBy,
|
|
4600
|
-
dir: sortDir,
|
|
4601
|
-
rootFontSizePx: rootFontSize
|
|
4602
|
-
}).map(([path, token]) => {
|
|
4603
|
-
const isColor = token.$type === "color";
|
|
4604
|
-
const color = isColor ? resolveColorValue(path, token.$value, colorFormat, projectFields) : null;
|
|
4605
|
-
return {
|
|
4606
|
-
path,
|
|
4607
|
-
type: token.$type ?? "",
|
|
4608
|
-
value: formatTokenValue(token.$value, token.$type, colorFormat, listing[path]),
|
|
4609
|
-
outOfGamut: color?.outOfGamut ?? false,
|
|
4610
|
-
cssVar: resolveCssVar(path, projectFields),
|
|
4611
|
-
isColor
|
|
4612
|
-
};
|
|
4613
|
-
});
|
|
4614
|
-
}, [
|
|
4615
|
-
resolved,
|
|
4616
|
-
listing,
|
|
4617
|
-
cssVarPrefix,
|
|
4618
|
-
filter,
|
|
4619
|
-
type,
|
|
4620
|
-
colorFormat,
|
|
4621
|
-
sortBy,
|
|
4622
|
-
sortDir,
|
|
4623
|
-
rootFontSize
|
|
4624
|
-
]);
|
|
4625
4964
|
const visibleRows = useMemo(() => {
|
|
4626
4965
|
if (!searchable || deferredQuery.trim() === "") return rows;
|
|
4627
4966
|
return fuzzyFilter(rows, deferredQuery, (row) => `${row.path} ${row.type} ${row.value}`);
|
|
@@ -4697,9 +5036,7 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
|
|
|
4697
5036
|
"\"."
|
|
4698
5037
|
]
|
|
4699
5038
|
}) }), visibleRows.map((row) => {
|
|
4700
|
-
const token =
|
|
4701
|
-
const dep = token?.$deprecated;
|
|
4702
|
-
const isDeprecated = dep === true || typeof dep === "string" && dep.length > 0;
|
|
5039
|
+
const token = row.token;
|
|
4703
5040
|
return /* @__PURE__ */ jsxs("tr", {
|
|
4704
5041
|
className: "sb-token-table__row",
|
|
4705
5042
|
onClick: () => handleRowClick(row.path),
|
|
@@ -4717,7 +5054,7 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
|
|
|
4717
5054
|
children: [
|
|
4718
5055
|
/* @__PURE__ */ jsx("td", {
|
|
4719
5056
|
className: cx("sb-token-table__td", "sb-token-table__path"),
|
|
4720
|
-
"data-deprecated": enabledIndicators.deprecation && isDeprecated ? "true" : void 0,
|
|
5057
|
+
"data-deprecated": enabledIndicators.deprecation && row.isDeprecated ? "true" : void 0,
|
|
4721
5058
|
children: row.path
|
|
4722
5059
|
}),
|
|
4723
5060
|
/* @__PURE__ */ jsx("td", {
|
|
@@ -4766,9 +5103,9 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
|
|
|
4766
5103
|
path: row.path,
|
|
4767
5104
|
token,
|
|
4768
5105
|
root: void 0,
|
|
4769
|
-
variance:
|
|
5106
|
+
variance: row.variance,
|
|
4770
5107
|
colorFormat,
|
|
4771
|
-
canReference: (p) => p
|
|
5108
|
+
canReference: (p) => validPaths.has(p),
|
|
4772
5109
|
onReferenceClick: (p) => setSelectedPath(p),
|
|
4773
5110
|
enabled: enabledIndicators
|
|
4774
5111
|
})
|
|
@@ -4786,6 +5123,56 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
|
|
|
4786
5123
|
]
|
|
4787
5124
|
});
|
|
4788
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
|
+
}
|
|
4789
5176
|
//#endregion
|
|
4790
5177
|
//#region src/TypographyScale.tsx
|
|
4791
5178
|
function asDimension(raw) {
|
|
@@ -4822,30 +5209,29 @@ function buildRow(path, composite) {
|
|
|
4822
5209
|
].filter(Boolean).join(" · ")
|
|
4823
5210
|
};
|
|
4824
5211
|
}
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
}
|
|
4841
|
-
|
|
4842
|
-
}
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
]);
|
|
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 }) {
|
|
4849
5235
|
const captionText = caption ?? `${rows.length} typography token${rows.length === 1 ? "" : "s"}${filter && filter !== "typography" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
4850
5236
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
4851
5237
|
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
@@ -4877,7 +5263,28 @@ function TypographyScale({ filter, sample = "The quick brown fox jumps over the
|
|
|
4877
5263
|
}, row.path))]
|
|
4878
5264
|
});
|
|
4879
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
|
+
}
|
|
4880
5287
|
//#endregion
|
|
4881
|
-
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 };
|
|
4882
5289
|
|
|
4883
5290
|
//# sourceMappingURL=index.mjs.map
|