@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 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.sys.*" />
53
+ <ColorPalette filter="color.*" />
54
54
 
55
55
  ## Every color token
56
56
 
57
- <TokenTable filter="color.sys.*" type="color" />
57
+ <TokenTable filter="color.*" type="color" />
58
58
 
59
59
  ## Inspect one
60
60
 
61
- <TokenDetail path="color.sys.accent.bg" />
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.sys.*' />
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.sys.*" type="color" />
87
- <ColorPalette filter="color.sys.*" />
86
+ <TokenTable filter="color.*" type="color" />
87
+ <ColorPalette filter="color.*" />
88
88
  <TypographyScale filter="typography" sample="The quick brown fox" />
89
- <TokenDetail path="color.sys.accent.bg" />
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.sys.*"` to scope to the semantic layer.
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.sys.*"` to scope to the semantic layer, or `"color.ref.blue.*"`
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. `2` yields groups like `color.sys`, `color.ref`; `3`
199
- * yields `color.sys.surface`, `color.sys.text`, etc.
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.sys.*"` → groups at
204
- * `color.sys.<family>`; `"color.ref.blue.*"` collapses all shades into
205
- * one `color.ref.blue` group because the tokens have no deeper level.
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.sys.*"` to scope to the spacing scale.
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.ref.family.*"` to scope to the ref layer.
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.ref.weight.*"` to scope to the ref layer.
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.ref.*"` or `"gradient.sys.accent"` to scope.
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. `"motion.sys.*"` to scope to the semantic layer.
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.sys.*"` to scope to the semantic layer.
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.ref.style.*"` to scope to the ref layer.
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.sys.*"` matches every `color.sys.…` token;
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.sys.*"` to scope to the semantic layer.
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.replaceAll(".", "-");
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.sys.*` | any path whose fixed prefix is `color.sys.` |
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.ref.*` → 2; `color.sys.surface.*` → 3; `color` → 1; undefined → 0.
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 true;
2838
- return path === root || path.startsWith(rootPrefix);
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
- function TokenNavigator({ root, initiallyExpanded = 1, onSelect }) {
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 tree = useMemo(() => buildTree(resolved, root), [resolved, root]);
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__ */ jsx("ul", {
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: tree.map((node) => /* @__PURE__ */ jsx(TreeNodeRow, {
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 captionText = caption ?? `${rows.length} token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""}${type ? ` · $type=${type}` : ""} · ${activeTheme}`;
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: [/* @__PURE__ */ jsxs("table", {
3158
- className: "sb-token-table__table",
3159
- children: [
3160
- /* @__PURE__ */ jsx("caption", {
3161
- className: "sb-token-table__caption",
3162
- children: captionText
3163
- }),
3164
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
3165
- className: cx("sb-token-table__th", "sb-token-table__th--path"),
3166
- children: "Path"
3167
- }), /* @__PURE__ */ jsx("th", {
3168
- className: cx("sb-token-table__th", "sb-token-table__th--value"),
3169
- children: "Value"
3170
- })] }) }),
3171
- /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", {
3172
- className: "sb-token-table__row",
3173
- onClick: () => handleRowClick(row.path),
3174
- onKeyDown: (e) => {
3175
- if (e.key === "Enter" || e.key === " ") {
3176
- e.preventDefault();
3177
- handleRowClick(row.path);
3178
- }
3179
- },
3180
- tabIndex: 0,
3181
- "aria-label": `Inspect ${row.path}`,
3182
- "data-testid": "token-table-row",
3183
- "data-path": row.path,
3184
- children: [/* @__PURE__ */ jsx("td", {
3185
- className: cx("sb-token-table__td", "sb-token-table__path"),
3186
- children: row.path
3187
- }), /* @__PURE__ */ jsx("td", {
3188
- className: "sb-token-table__td",
3189
- children: /* @__PURE__ */ jsxs("span", {
3190
- className: "sb-token-table__value-cell",
3191
- children: [
3192
- row.type && /* @__PURE__ */ jsx("span", {
3193
- className: "sb-token-table__type-pill",
3194
- children: row.type
3195
- }),
3196
- row.isColor && /* @__PURE__ */ jsx("span", {
3197
- className: "sb-token-table__swatch",
3198
- style: { background: row.cssVar },
3199
- "aria-hidden": true
3200
- }),
3201
- /* @__PURE__ */ jsx("span", {
3202
- className: "sb-token-table__value-text",
3203
- title: row.value,
3204
- "data-testid": "token-table-value",
3205
- children: row.value
3206
- }),
3207
- row.outOfGamut && /* @__PURE__ */ jsx("span", {
3208
- title: "Out of sRGB gamut for this format",
3209
- "aria-label": "out of gamut",
3210
- className: "sb-token-table__gamut-warn",
3211
- children: ""
3212
- })
3213
- ]
3214
- })
3215
- })]
3216
- }, row.path)) })
3217
- ]
3218
- }), selectedPath !== null && /* @__PURE__ */ jsx(DetailOverlay, {
3219
- path: selectedPath,
3220
- onClose: () => setSelectedPath(null),
3221
- testId: "token-table-overlay"
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