@unpunnyfuns/swatchbook-blocks 0.4.0 → 0.6.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 +7 -7
- package/dist/index.d.mts +43 -16
- package/dist/index.mjs +199 -82
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +51 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,15 +50,15 @@ import { TokenTable, ColorPalette, TokenDetail } from '@unpunnyfuns/swatchbook-b
|
|
|
50
50
|
|
|
51
51
|
# Color tokens
|
|
52
52
|
|
|
53
|
-
<ColorPalette filter="color
|
|
53
|
+
<ColorPalette filter="color.*" />
|
|
54
54
|
|
|
55
55
|
## Every color token
|
|
56
56
|
|
|
57
|
-
<TokenTable filter="color
|
|
57
|
+
<TokenTable filter="color.*" type="color" />
|
|
58
58
|
|
|
59
59
|
## Inspect one
|
|
60
60
|
|
|
61
|
-
<TokenDetail path="color.
|
|
61
|
+
<TokenDetail path="color.accent.bg" />
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
### Outside Storybook
|
|
@@ -72,7 +72,7 @@ import snapshot from './tokens-snapshot.json';
|
|
|
72
72
|
export function TokenDocs() {
|
|
73
73
|
return (
|
|
74
74
|
<SwatchbookProvider value={snapshot}>
|
|
75
|
-
<TokenTable filter='color
|
|
75
|
+
<TokenTable filter='color.*' />
|
|
76
76
|
</SwatchbookProvider>
|
|
77
77
|
);
|
|
78
78
|
}
|
|
@@ -83,10 +83,10 @@ export function TokenDocs() {
|
|
|
83
83
|
## Props
|
|
84
84
|
|
|
85
85
|
```ts
|
|
86
|
-
<TokenTable filter="color
|
|
87
|
-
<ColorPalette filter="color
|
|
86
|
+
<TokenTable filter="color.*" type="color" />
|
|
87
|
+
<ColorPalette filter="color.*" />
|
|
88
88
|
<TypographyScale filter="typography" sample="The quick brown fox" />
|
|
89
|
-
<TokenDetail path="color.
|
|
89
|
+
<TokenDetail path="color.accent.bg" />
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
## Do / don't
|
package/dist/index.d.mts
CHANGED
|
@@ -155,7 +155,7 @@ type SortDir = 'asc' | 'desc';
|
|
|
155
155
|
interface BorderPreviewProps {
|
|
156
156
|
/**
|
|
157
157
|
* Token-path filter. Defaults to every `border` token. Use e.g.
|
|
158
|
-
* `"border
|
|
158
|
+
* `"border.*"` to scope to the semantic layer.
|
|
159
159
|
*/
|
|
160
160
|
filter?: string;
|
|
161
161
|
/** Override the caption. */
|
|
@@ -189,20 +189,20 @@ declare function BorderSample({
|
|
|
189
189
|
interface ColorPaletteProps {
|
|
190
190
|
/**
|
|
191
191
|
* Token-path filter. Defaults to every `color` token. Use e.g.
|
|
192
|
-
* `"color
|
|
192
|
+
* `"color.*"` to scope to the semantic layer, or `"color.palette.blue.*"`
|
|
193
193
|
* for a single ref ramp.
|
|
194
194
|
*/
|
|
195
195
|
filter?: string;
|
|
196
196
|
/**
|
|
197
197
|
* Grouping depth. Tokens are grouped by the first `groupBy` dot-segments
|
|
198
|
-
* of their path. `
|
|
199
|
-
*
|
|
198
|
+
* of their path. `1` yields a single `color` group; `2` yields
|
|
199
|
+
* `color.surface`, `color.text`, `color.blue`, etc.
|
|
200
200
|
*
|
|
201
201
|
* If omitted, groupBy is derived from the filter: one level below the
|
|
202
202
|
* filter's fixed prefix (segments before the first `*`), clamped so each
|
|
203
|
-
* swatch still carries a leaf label. `"color
|
|
204
|
-
* `color
|
|
205
|
-
* one `color.
|
|
203
|
+
* swatch still carries a leaf label. `"color.*"` → groups at
|
|
204
|
+
* `color.<family>`; `"color.palette.blue.*"` collapses all shades into
|
|
205
|
+
* one `color.blue` group because the tokens have no deeper level.
|
|
206
206
|
*/
|
|
207
207
|
groupBy?: number;
|
|
208
208
|
/** Override the section caption. */
|
|
@@ -267,7 +267,7 @@ declare function DimensionBar({
|
|
|
267
267
|
interface DimensionScaleProps {
|
|
268
268
|
/**
|
|
269
269
|
* Token-path filter. Defaults to every `dimension` token. Use e.g.
|
|
270
|
-
* `"space
|
|
270
|
+
* `"space.*"` to scope to the spacing scale.
|
|
271
271
|
*/
|
|
272
272
|
filter?: string;
|
|
273
273
|
/**
|
|
@@ -302,7 +302,7 @@ declare function DimensionScale({
|
|
|
302
302
|
interface FontFamilySampleProps {
|
|
303
303
|
/**
|
|
304
304
|
* Token-path filter. Defaults to every `fontFamily` token. Use e.g.
|
|
305
|
-
* `"font.
|
|
305
|
+
* `"font.family.*"` to scope to the ref layer.
|
|
306
306
|
*/
|
|
307
307
|
filter?: string;
|
|
308
308
|
/** Override the sample text rendered for each token. */
|
|
@@ -330,7 +330,7 @@ declare function FontFamilySample({
|
|
|
330
330
|
interface FontWeightScaleProps {
|
|
331
331
|
/**
|
|
332
332
|
* Token-path filter. Defaults to every `fontWeight` token. Use e.g.
|
|
333
|
-
* `"font.
|
|
333
|
+
* `"font.weight.*"` to scope to the ref layer.
|
|
334
334
|
*/
|
|
335
335
|
filter?: string;
|
|
336
336
|
/** Override the sample text rendered for each token. */
|
|
@@ -359,7 +359,7 @@ declare function FontWeightScale({
|
|
|
359
359
|
interface GradientPaletteProps {
|
|
360
360
|
/**
|
|
361
361
|
* Token-path filter. Defaults to every `gradient` token. Use e.g.
|
|
362
|
-
* `"gradient
|
|
362
|
+
* `"gradient.*"` or `"gradient.accent"` to scope.
|
|
363
363
|
*/
|
|
364
364
|
filter?: string;
|
|
365
365
|
/** Override the caption. */
|
|
@@ -404,7 +404,7 @@ declare function MotionSample({
|
|
|
404
404
|
interface MotionPreviewProps {
|
|
405
405
|
/**
|
|
406
406
|
* Token-path filter. Defaults to transition + duration + cubicBezier
|
|
407
|
-
* tokens. Use e.g. `"
|
|
407
|
+
* tokens. Use e.g. `"transition.*"` to scope to the semantic layer.
|
|
408
408
|
*/
|
|
409
409
|
filter?: string;
|
|
410
410
|
/** Override the caption. */
|
|
@@ -444,7 +444,7 @@ declare function useSwatchbookData(): ProjectSnapshot;
|
|
|
444
444
|
interface ShadowPreviewProps {
|
|
445
445
|
/**
|
|
446
446
|
* Token-path filter. Defaults to every `shadow` token. Use e.g.
|
|
447
|
-
* `"shadow
|
|
447
|
+
* `"shadow.*"` to scope to the semantic layer.
|
|
448
448
|
*/
|
|
449
449
|
filter?: string;
|
|
450
450
|
/** Override the caption. */
|
|
@@ -478,7 +478,7 @@ declare function ShadowSample({
|
|
|
478
478
|
interface StrokeStyleSampleProps {
|
|
479
479
|
/**
|
|
480
480
|
* Token-path filter. Defaults to every `strokeStyle` token. Use e.g.
|
|
481
|
-
* `"stroke.
|
|
481
|
+
* `"stroke.style.*"` to scope to the ref layer.
|
|
482
482
|
*/
|
|
483
483
|
filter?: string;
|
|
484
484
|
/** Override the caption. */
|
|
@@ -590,12 +590,28 @@ declare function TokenUsageSnippet({
|
|
|
590
590
|
interface TokenNavigatorProps {
|
|
591
591
|
/** If provided, mount at this dot-path subtree and hide everything outside it. */
|
|
592
592
|
root?: string;
|
|
593
|
+
/**
|
|
594
|
+
* Restrict the tree to tokens with the given DTCG `$type`(s). Pass a single
|
|
595
|
+
* string to scope to one type (`type="color"`), or an array for a narrow
|
|
596
|
+
* small-multiples view (`type={['duration', 'cubicBezier', 'transition']}`).
|
|
597
|
+
* Composes with `root` — both constraints must hold. Group nodes that end
|
|
598
|
+
* up with no surviving leaves collapse out.
|
|
599
|
+
*/
|
|
600
|
+
type?: string | readonly string[];
|
|
593
601
|
/**
|
|
594
602
|
* Depth (from the mounted root) that is expanded on first render.
|
|
595
603
|
* `0` = everything collapsed, `1` = top-level groups open (default),
|
|
596
604
|
* `2` = one level deeper, etc.
|
|
597
605
|
*/
|
|
598
606
|
initiallyExpanded?: number;
|
|
607
|
+
/**
|
|
608
|
+
* Render a runtime search input above the tree. Matches are
|
|
609
|
+
* case-insensitive substrings against a leaf's token path; groups
|
|
610
|
+
* that contain no matching leaves collapse out, and every group on
|
|
611
|
+
* the path to a match auto-expands so hits are visible without
|
|
612
|
+
* clicking. Defaults to `true`.
|
|
613
|
+
*/
|
|
614
|
+
searchable?: boolean;
|
|
599
615
|
/**
|
|
600
616
|
* Called with a leaf's full dot-path when it is clicked. When set, the
|
|
601
617
|
* inline `<TokenDetail>` slide-over is suppressed — the consumer owns
|
|
@@ -605,14 +621,16 @@ interface TokenNavigatorProps {
|
|
|
605
621
|
}
|
|
606
622
|
declare function TokenNavigator({
|
|
607
623
|
root,
|
|
624
|
+
type,
|
|
608
625
|
initiallyExpanded,
|
|
626
|
+
searchable,
|
|
609
627
|
onSelect
|
|
610
628
|
}: TokenNavigatorProps): ReactElement;
|
|
611
629
|
//#endregion
|
|
612
630
|
//#region src/TokenTable.d.ts
|
|
613
631
|
interface TokenTableProps {
|
|
614
632
|
/**
|
|
615
|
-
* Token-path filter. `"color
|
|
633
|
+
* Token-path filter. `"color.*"` matches every `color.…` token;
|
|
616
634
|
* omit to include everything. Combines with `type` (both must match).
|
|
617
635
|
*/
|
|
618
636
|
filter?: string;
|
|
@@ -632,6 +650,14 @@ interface TokenTableProps {
|
|
|
632
650
|
sortBy?: SortBy;
|
|
633
651
|
/** `'asc'` (default) or `'desc'`. */
|
|
634
652
|
sortDir?: SortDir;
|
|
653
|
+
/**
|
|
654
|
+
* Render a runtime search input above the table that narrows rows by
|
|
655
|
+
* case-insensitive substring against the token path, type, or value.
|
|
656
|
+
* Defaults to `true` because browsing a multi-hundred-token reference
|
|
657
|
+
* without search is painful. Pass `false` to hide the input (the
|
|
658
|
+
* `filter` / `type` props still apply at authoring time).
|
|
659
|
+
*/
|
|
660
|
+
searchable?: boolean;
|
|
635
661
|
/**
|
|
636
662
|
* Called with the clicked row's dot-path. When set, the built-in
|
|
637
663
|
* `<TokenDetail>` slide-over is suppressed — the consumer owns the
|
|
@@ -645,6 +671,7 @@ declare function TokenTable({
|
|
|
645
671
|
caption,
|
|
646
672
|
sortBy,
|
|
647
673
|
sortDir,
|
|
674
|
+
searchable,
|
|
648
675
|
onSelect
|
|
649
676
|
}: TokenTableProps): ReactElement;
|
|
650
677
|
//#endregion
|
|
@@ -652,7 +679,7 @@ declare function TokenTable({
|
|
|
652
679
|
interface TypographyScaleProps {
|
|
653
680
|
/**
|
|
654
681
|
* Token-path filter. Defaults to every `typography` token. Use e.g.
|
|
655
|
-
* `"typography
|
|
682
|
+
* `"typography.*"` to scope to the semantic layer.
|
|
656
683
|
*/
|
|
657
684
|
filter?: string;
|
|
658
685
|
/** Override the sample text rendered for each token. */
|
package/dist/index.mjs
CHANGED
|
@@ -408,7 +408,7 @@ function useVirtualModuleFallback(enabled) {
|
|
|
408
408
|
};
|
|
409
409
|
}
|
|
410
410
|
function makeCssVar(path, prefix) {
|
|
411
|
-
const tail = path.
|
|
411
|
+
const tail = path.split(".").map((segment) => segment.replaceAll(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase()).join("-");
|
|
412
412
|
return prefix ? `var(--${prefix}-${tail})` : `var(--${tail})`;
|
|
413
413
|
}
|
|
414
414
|
/**
|
|
@@ -422,7 +422,7 @@ function makeCssVar(path, prefix) {
|
|
|
422
422
|
* | `undefined` / `''` | everything |
|
|
423
423
|
* | `*` or `**` | everything |
|
|
424
424
|
* | `color` | exact path `color`, or any descendant `color.*` |
|
|
425
|
-
* | `color
|
|
425
|
+
* | `color.*` | any path whose fixed prefix is `color.` |
|
|
426
426
|
* | `color**` | any path starting with `color` |
|
|
427
427
|
*
|
|
428
428
|
* Not supported (all pass through as literal segment matchers): brace
|
|
@@ -707,7 +707,7 @@ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
707
707
|
//#region src/ColorPalette.tsx
|
|
708
708
|
/**
|
|
709
709
|
* Count segments in the filter before the first glob (`*` / `**`).
|
|
710
|
-
* `color
|
|
710
|
+
* `color.*` → 2; `color.surface.*` → 3; `color` → 1; undefined → 0.
|
|
711
711
|
*/
|
|
712
712
|
function fixedPrefixLength(filter) {
|
|
713
713
|
if (!filter) return 0;
|
|
@@ -2830,12 +2830,13 @@ function DetailOverlay({ path, onClose, testId = "swatchbook-overlay" }) {
|
|
|
2830
2830
|
}
|
|
2831
2831
|
//#endregion
|
|
2832
2832
|
//#region src/TokenNavigator.tsx
|
|
2833
|
-
function buildTree(resolved, root) {
|
|
2833
|
+
function buildTree(resolved, root, typeFilter) {
|
|
2834
2834
|
const rootPrefix = root && root.length > 0 ? `${root}.` : "";
|
|
2835
2835
|
const rootSegments = root ? root.split(".") : [];
|
|
2836
|
-
const entries = Object.entries(resolved).filter(([path]) => {
|
|
2837
|
-
if (!root) return
|
|
2838
|
-
|
|
2836
|
+
const entries = Object.entries(resolved).filter(([path, token]) => {
|
|
2837
|
+
if (root && !(path === root || path.startsWith(rootPrefix))) return false;
|
|
2838
|
+
if (typeFilter && !(token.$type && typeFilter.has(token.$type))) return false;
|
|
2839
|
+
return true;
|
|
2839
2840
|
});
|
|
2840
2841
|
const rootNode = {
|
|
2841
2842
|
kind: "group",
|
|
@@ -2900,9 +2901,39 @@ function countLeaves(node) {
|
|
|
2900
2901
|
for (const c of node.children) n += countLeaves(c);
|
|
2901
2902
|
return n;
|
|
2902
2903
|
}
|
|
2903
|
-
|
|
2904
|
+
/**
|
|
2905
|
+
* Return a pruned copy of the tree containing only leaves whose path
|
|
2906
|
+
* matches `needle` (case-insensitive substring) plus the groups on the
|
|
2907
|
+
* way to them. Every surviving group's path is added to `expandOut` so
|
|
2908
|
+
* callers can force those groups open.
|
|
2909
|
+
*/
|
|
2910
|
+
function pruneTreeForSearch(nodes, needle, expandOut) {
|
|
2911
|
+
const out = [];
|
|
2912
|
+
for (const node of nodes) if (node.kind === "leaf") {
|
|
2913
|
+
if (node.path.toLowerCase().includes(needle)) out.push(node);
|
|
2914
|
+
} else {
|
|
2915
|
+
const children = pruneTreeForSearch(node.children, needle, expandOut);
|
|
2916
|
+
if (children.length > 0) {
|
|
2917
|
+
expandOut.add(node.path);
|
|
2918
|
+
out.push({
|
|
2919
|
+
...node,
|
|
2920
|
+
children
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
return out;
|
|
2925
|
+
}
|
|
2926
|
+
function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true, onSelect }) {
|
|
2904
2927
|
const { resolved, activeTheme, cssVarPrefix } = useProject();
|
|
2905
|
-
const
|
|
2928
|
+
const typeFilter = useMemo(() => {
|
|
2929
|
+
if (type === void 0) return void 0;
|
|
2930
|
+
return new Set(Array.isArray(type) ? type : [type]);
|
|
2931
|
+
}, [type]);
|
|
2932
|
+
const tree = useMemo(() => buildTree(resolved, root, typeFilter), [
|
|
2933
|
+
resolved,
|
|
2934
|
+
root,
|
|
2935
|
+
typeFilter
|
|
2936
|
+
]);
|
|
2906
2937
|
const initialExpanded = useMemo(() => {
|
|
2907
2938
|
const out = /* @__PURE__ */ new Set();
|
|
2908
2939
|
collectInitialExpanded(tree, initiallyExpanded, out);
|
|
@@ -2913,6 +2944,29 @@ function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
|
|
|
2913
2944
|
setExpanded(initialExpanded);
|
|
2914
2945
|
}, [initialExpanded]);
|
|
2915
2946
|
const [selectedPath, setSelectedPath] = useState(null);
|
|
2947
|
+
const [query, setQuery] = useState("");
|
|
2948
|
+
const { visibleTree, searchExpanded } = useMemo(() => {
|
|
2949
|
+
if (!searchable || query.trim() === "") return {
|
|
2950
|
+
visibleTree: tree,
|
|
2951
|
+
searchExpanded: null
|
|
2952
|
+
};
|
|
2953
|
+
const needle = query.trim().toLowerCase();
|
|
2954
|
+
const expandOut = /* @__PURE__ */ new Set();
|
|
2955
|
+
return {
|
|
2956
|
+
visibleTree: pruneTreeForSearch(tree, needle, expandOut),
|
|
2957
|
+
searchExpanded: expandOut
|
|
2958
|
+
};
|
|
2959
|
+
}, [
|
|
2960
|
+
tree,
|
|
2961
|
+
query,
|
|
2962
|
+
searchable
|
|
2963
|
+
]);
|
|
2964
|
+
const effectiveExpanded = useMemo(() => {
|
|
2965
|
+
if (!searchExpanded) return expanded;
|
|
2966
|
+
const merged = new Set(expanded);
|
|
2967
|
+
for (const p of searchExpanded) merged.add(p);
|
|
2968
|
+
return merged;
|
|
2969
|
+
}, [expanded, searchExpanded]);
|
|
2916
2970
|
const toggle = useCallback((path) => {
|
|
2917
2971
|
setExpanded((prev) => {
|
|
2918
2972
|
const next = new Set(prev);
|
|
@@ -2925,27 +2979,56 @@ function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
|
|
|
2925
2979
|
if (onSelect) onSelect(path);
|
|
2926
2980
|
else setSelectedPath(path);
|
|
2927
2981
|
}, [onSelect]);
|
|
2982
|
+
const typeLabel = typeFilter ? ` · ${[...typeFilter].map((t) => `$type=${t}`).join(", ")}` : "";
|
|
2928
2983
|
if (tree.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2929
2984
|
...themeAttrs(cssVarPrefix, activeTheme),
|
|
2930
|
-
children: /* @__PURE__ */ jsx(EmptyState, { children: root ? `No tokens under "${root}".` : "No tokens in the active theme." })
|
|
2931
|
-
});
|
|
2985
|
+
children: /* @__PURE__ */ jsx(EmptyState, { 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." })
|
|
2986
|
+
});
|
|
2987
|
+
const trimmedQuery = query.trim();
|
|
2988
|
+
const matchCount = useMemo(() => {
|
|
2989
|
+
if (!searchExpanded) return 0;
|
|
2990
|
+
let n = 0;
|
|
2991
|
+
for (const node of visibleTree) n += countLeaves(node);
|
|
2992
|
+
return n;
|
|
2993
|
+
}, [visibleTree, searchExpanded]);
|
|
2932
2994
|
return /* @__PURE__ */ jsxs("div", {
|
|
2933
2995
|
...themeAttrs(cssVarPrefix, activeTheme),
|
|
2934
2996
|
children: [
|
|
2997
|
+
searchable && /* @__PURE__ */ jsx("div", {
|
|
2998
|
+
className: "sb-token-navigator__search",
|
|
2999
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
3000
|
+
type: "search",
|
|
3001
|
+
className: "sb-token-navigator__search-input",
|
|
3002
|
+
placeholder: "Search tokens…",
|
|
3003
|
+
value: query,
|
|
3004
|
+
onChange: (e) => setQuery(e.target.value),
|
|
3005
|
+
"aria-label": "Search tokens by path",
|
|
3006
|
+
"data-testid": "token-navigator-search"
|
|
3007
|
+
})
|
|
3008
|
+
}),
|
|
2935
3009
|
/* @__PURE__ */ jsxs("div", {
|
|
2936
3010
|
className: "sb-token-navigator__caption",
|
|
2937
3011
|
children: [
|
|
2938
3012
|
root ? `Tokens under ${root}` : "Token graph",
|
|
3013
|
+
typeLabel,
|
|
3014
|
+
trimmedQuery !== "" ? ` · ${matchCount} matching "${trimmedQuery}"` : "",
|
|
2939
3015
|
" · ",
|
|
2940
3016
|
activeTheme
|
|
2941
3017
|
]
|
|
2942
3018
|
}),
|
|
2943
|
-
/* @__PURE__ */
|
|
3019
|
+
visibleTree.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
3020
|
+
className: "sb-block__empty",
|
|
3021
|
+
children: [
|
|
3022
|
+
"No tokens match \"",
|
|
3023
|
+
trimmedQuery,
|
|
3024
|
+
"\"."
|
|
3025
|
+
]
|
|
3026
|
+
}) : /* @__PURE__ */ jsx("ul", {
|
|
2944
3027
|
className: "sb-token-navigator__tree",
|
|
2945
3028
|
role: "tree",
|
|
2946
|
-
children:
|
|
3029
|
+
children: visibleTree.map((node) => /* @__PURE__ */ jsx(TreeNodeRow, {
|
|
2947
3030
|
node,
|
|
2948
|
-
expanded,
|
|
3031
|
+
expanded: effectiveExpanded,
|
|
2949
3032
|
onToggle: toggle,
|
|
2950
3033
|
onLeafClick: handleLeafClick
|
|
2951
3034
|
}, node.path || node.segment))
|
|
@@ -3107,10 +3190,11 @@ function LeafPreview({ path, token }) {
|
|
|
3107
3190
|
}
|
|
3108
3191
|
//#endregion
|
|
3109
3192
|
//#region src/TokenTable.tsx
|
|
3110
|
-
function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", onSelect }) {
|
|
3193
|
+
function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", searchable = true, onSelect }) {
|
|
3111
3194
|
const { resolved, activeTheme, cssVarPrefix } = useProject();
|
|
3112
3195
|
const colorFormat = useColorFormat();
|
|
3113
3196
|
const [selectedPath, setSelectedPath] = useState(null);
|
|
3197
|
+
const [query, setQuery] = useState("");
|
|
3114
3198
|
const rows = useMemo(() => {
|
|
3115
3199
|
return sortTokens(Object.entries(resolved).filter(([path, token]) => {
|
|
3116
3200
|
if (!globMatch(path, filter)) return false;
|
|
@@ -3140,11 +3224,21 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", o
|
|
|
3140
3224
|
sortBy,
|
|
3141
3225
|
sortDir
|
|
3142
3226
|
]);
|
|
3227
|
+
const visibleRows = useMemo(() => {
|
|
3228
|
+
if (!searchable || query.trim() === "") return rows;
|
|
3229
|
+
const needle = query.trim().toLowerCase();
|
|
3230
|
+
return rows.filter((row) => row.path.toLowerCase().includes(needle) || row.type.toLowerCase().includes(needle) || row.value.toLowerCase().includes(needle));
|
|
3231
|
+
}, [
|
|
3232
|
+
rows,
|
|
3233
|
+
query,
|
|
3234
|
+
searchable
|
|
3235
|
+
]);
|
|
3143
3236
|
const handleRowClick = useCallback((path) => {
|
|
3144
3237
|
if (onSelect) onSelect(path);
|
|
3145
3238
|
else setSelectedPath(path);
|
|
3146
3239
|
}, [onSelect]);
|
|
3147
|
-
const
|
|
3240
|
+
const matchSuffix = searchable && query.trim() !== "" ? ` · ${visibleRows.length} matching "${query.trim()}"` : "";
|
|
3241
|
+
const captionText = caption ?? `${rows.length} token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""}${type ? ` · $type=${type}` : ""}${matchSuffix} · ${activeTheme}`;
|
|
3148
3242
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
3149
3243
|
...themeAttrs(cssVarPrefix, activeTheme),
|
|
3150
3244
|
children: /* @__PURE__ */ jsx("div", {
|
|
@@ -3154,72 +3248,95 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", o
|
|
|
3154
3248
|
});
|
|
3155
3249
|
return /* @__PURE__ */ jsxs("div", {
|
|
3156
3250
|
...themeAttrs(cssVarPrefix, activeTheme),
|
|
3157
|
-
children: [
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
/* @__PURE__ */ jsx("
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
children: [/* @__PURE__ */ jsx("td", {
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3251
|
+
children: [
|
|
3252
|
+
searchable && /* @__PURE__ */ jsx("div", {
|
|
3253
|
+
className: "sb-token-table__search",
|
|
3254
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
3255
|
+
type: "search",
|
|
3256
|
+
className: "sb-token-table__search-input",
|
|
3257
|
+
placeholder: "Search tokens…",
|
|
3258
|
+
value: query,
|
|
3259
|
+
onChange: (e) => setQuery(e.target.value),
|
|
3260
|
+
"aria-label": "Search tokens by path, type, or value",
|
|
3261
|
+
"data-testid": "token-table-search"
|
|
3262
|
+
})
|
|
3263
|
+
}),
|
|
3264
|
+
/* @__PURE__ */ jsxs("table", {
|
|
3265
|
+
className: "sb-token-table__table",
|
|
3266
|
+
children: [
|
|
3267
|
+
/* @__PURE__ */ jsx("caption", {
|
|
3268
|
+
className: "sb-token-table__caption",
|
|
3269
|
+
children: captionText
|
|
3270
|
+
}),
|
|
3271
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
|
|
3272
|
+
className: cx("sb-token-table__th", "sb-token-table__th--path"),
|
|
3273
|
+
children: "Path"
|
|
3274
|
+
}), /* @__PURE__ */ jsx("th", {
|
|
3275
|
+
className: cx("sb-token-table__th", "sb-token-table__th--value"),
|
|
3276
|
+
children: "Value"
|
|
3277
|
+
})] }) }),
|
|
3278
|
+
/* @__PURE__ */ jsxs("tbody", { children: [visibleRows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
|
|
3279
|
+
colSpan: 2,
|
|
3280
|
+
className: "sb-token-table__td sb-token-table__empty-row",
|
|
3281
|
+
children: [
|
|
3282
|
+
"No tokens match \"",
|
|
3283
|
+
query.trim(),
|
|
3284
|
+
"\"."
|
|
3285
|
+
]
|
|
3286
|
+
}) }), visibleRows.map((row) => /* @__PURE__ */ jsxs("tr", {
|
|
3287
|
+
className: "sb-token-table__row",
|
|
3288
|
+
onClick: () => handleRowClick(row.path),
|
|
3289
|
+
onKeyDown: (e) => {
|
|
3290
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
3291
|
+
e.preventDefault();
|
|
3292
|
+
handleRowClick(row.path);
|
|
3293
|
+
}
|
|
3294
|
+
},
|
|
3295
|
+
tabIndex: 0,
|
|
3296
|
+
"aria-label": `Inspect ${row.path}`,
|
|
3297
|
+
"data-testid": "token-table-row",
|
|
3298
|
+
"data-path": row.path,
|
|
3299
|
+
children: [/* @__PURE__ */ jsx("td", {
|
|
3300
|
+
className: cx("sb-token-table__td", "sb-token-table__path"),
|
|
3301
|
+
children: row.path
|
|
3302
|
+
}), /* @__PURE__ */ jsx("td", {
|
|
3303
|
+
className: "sb-token-table__td",
|
|
3304
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
3305
|
+
className: "sb-token-table__value-cell",
|
|
3306
|
+
children: [
|
|
3307
|
+
row.type && /* @__PURE__ */ jsx("span", {
|
|
3308
|
+
className: "sb-token-table__type-pill",
|
|
3309
|
+
children: row.type
|
|
3310
|
+
}),
|
|
3311
|
+
row.isColor && /* @__PURE__ */ jsx("span", {
|
|
3312
|
+
className: "sb-token-table__swatch",
|
|
3313
|
+
style: { background: row.cssVar },
|
|
3314
|
+
"aria-hidden": true
|
|
3315
|
+
}),
|
|
3316
|
+
/* @__PURE__ */ jsx("span", {
|
|
3317
|
+
className: "sb-token-table__value-text",
|
|
3318
|
+
title: row.value,
|
|
3319
|
+
"data-testid": "token-table-value",
|
|
3320
|
+
children: row.value
|
|
3321
|
+
}),
|
|
3322
|
+
row.outOfGamut && /* @__PURE__ */ jsx("span", {
|
|
3323
|
+
title: "Out of sRGB gamut for this format",
|
|
3324
|
+
"aria-label": "out of gamut",
|
|
3325
|
+
className: "sb-token-table__gamut-warn",
|
|
3326
|
+
children: "⚠"
|
|
3327
|
+
})
|
|
3328
|
+
]
|
|
3329
|
+
})
|
|
3330
|
+
})]
|
|
3331
|
+
}, row.path))] })
|
|
3332
|
+
]
|
|
3333
|
+
}),
|
|
3334
|
+
selectedPath !== null && /* @__PURE__ */ jsx(DetailOverlay, {
|
|
3335
|
+
path: selectedPath,
|
|
3336
|
+
onClose: () => setSelectedPath(null),
|
|
3337
|
+
testId: "token-table-overlay"
|
|
3338
|
+
})
|
|
3339
|
+
]
|
|
3223
3340
|
});
|
|
3224
3341
|
}
|
|
3225
3342
|
//#endregion
|