@unpunnyfuns/swatchbook-blocks 0.62.2 → 0.62.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -271,6 +271,8 @@ interface ColorTableProps {
271
271
  * own row.
272
272
  */
273
273
  variants?: Record<string, string>;
274
+ /** Disambiguates persisted UI state for two identical-prop tables on a page. */
275
+ id?: string;
274
276
  }
275
277
  declare function ColorTable({
276
278
  filter,
@@ -279,7 +281,8 @@ declare function ColorTable({
279
281
  sortDir,
280
282
  searchable,
281
283
  onSelect,
282
- variants
284
+ variants,
285
+ id
283
286
  }: ColorTableProps): ReactElement;
284
287
  //#endregion
285
288
  //#region src/Diagnostics.d.ts
@@ -789,13 +792,21 @@ interface TokenNavigatorProps {
789
792
  * the follow-up UI.
790
793
  */
791
794
  onSelect?(path: string): void;
795
+ /**
796
+ * Disambiguates persisted UI state (expand/collapse, selection, search)
797
+ * when two navigators with otherwise-identical props sit on the same docs
798
+ * page. Only needed in that case; the state key is derived from the other
799
+ * props otherwise.
800
+ */
801
+ id?: string;
792
802
  }
793
803
  declare function TokenNavigator({
794
804
  root,
795
805
  type,
796
806
  initiallyExpanded,
797
807
  searchable,
798
- onSelect
808
+ onSelect,
809
+ id
799
810
  }: TokenNavigatorProps): ReactElement;
800
811
  //#endregion
801
812
  //#region src/TokenTable.d.ts
@@ -836,6 +847,8 @@ interface TokenTableProps {
836
847
  * follow-up UI (inline panel, drill-down route, …).
837
848
  */
838
849
  onSelect?(path: string): void;
850
+ /** Disambiguates persisted UI state for two identical-prop tables on a page. */
851
+ id?: string;
839
852
  }
840
853
  declare function TokenTable({
841
854
  filter,
@@ -844,7 +857,8 @@ declare function TokenTable({
844
857
  sortBy,
845
858
  sortDir,
846
859
  searchable,
847
- onSelect
860
+ onSelect,
861
+ id
848
862
  }: TokenTableProps): ReactElement;
849
863
  //#endregion
850
864
  //#region src/TypographyScale.d.ts
package/dist/index.mjs CHANGED
@@ -809,16 +809,73 @@ function CopyButton$1({ value, label, variant = "icon", className }) {
809
809
  })] });
810
810
  }
811
811
  //#endregion
812
+ //#region src/internal/persistent-state.ts
813
+ /**
814
+ * Block UI state that survives a docs-mode remount.
815
+ *
816
+ * In MDX docs mode Storybook re-renders the docs container on every
817
+ * `updateGlobals` (axis flip), which unmounts and remounts the embedded
818
+ * blocks — destroying any plain `useState` (expand/collapse, selection,
819
+ * search). This is the same problem `channel-globals` solves for the
820
+ * globals: lift the value out of React into module state so it persists
821
+ * across the remount, and re-seed component state from it on mount.
822
+ *
823
+ * `usePersistedState` is a drop-in `useState` whose value is mirrored to a
824
+ * module-level store under a caller-supplied key, and read back from it on
825
+ * (re)mount. `useBlockKey` builds a stable key scoped to the current docs
826
+ * page + block identity so two pages (or two distinct blocks) don't share
827
+ * an entry.
828
+ */
829
+ const store = /* @__PURE__ */ new Map();
830
+ function pageScope() {
831
+ if (typeof window === "undefined") return "";
832
+ try {
833
+ return new URLSearchParams(window.location.search).get("id") ?? window.location.pathname;
834
+ } catch {
835
+ return "";
836
+ }
837
+ }
838
+ const SEP = "";
839
+ /**
840
+ * Build a stable persistence key for a block's UI state: docs page + block
841
+ * type + the props that distinguish one instance from another (and an
842
+ * optional explicit `id` for identical-prop siblings on the same page).
843
+ */
844
+ function useBlockKey(blockType, parts) {
845
+ const partsKey = parts.map((p) => p === void 0 ? "" : String(p)).join(SEP);
846
+ return useMemo(() => `${pageScope()}${SEP}${blockType}${SEP}${partsKey}`, [blockType, partsKey]);
847
+ }
848
+ /**
849
+ * `useState`, but the value persists across remounts under `key`. `initial`
850
+ * may be a value or a lazy initializer (used only on the first mount when the
851
+ * store has no entry yet — never an actual `T` that's a function here).
852
+ */
853
+ function usePersistedState(key, initial) {
854
+ const [value, setValue] = useState(() => {
855
+ if (store.has(key)) return store.get(key);
856
+ return typeof initial === "function" ? initial() : initial;
857
+ });
858
+ useEffect(() => {
859
+ store.set(key, value);
860
+ }, [key, value]);
861
+ return [value, setValue];
862
+ }
863
+ //#endregion
812
864
  //#region src/ColorTable.tsx
813
865
  const BASE_LABEL = "base";
814
866
  const COLUMN_COUNT = 6;
815
- function ColorTable({ filter, caption, sortBy = "path", sortDir = "asc", searchable = true, onSelect, variants }) {
867
+ function ColorTable({ filter, caption, sortBy = "path", sortDir = "asc", searchable = true, onSelect, variants, id }) {
816
868
  const { resolved, activeTheme, activeAxes, cssVarPrefix, listing } = useProject();
817
869
  const colorFormat = useColorFormat();
818
- const [query, setQuery] = useState("");
870
+ const blockKey = useBlockKey("ColorTable", [
871
+ filter,
872
+ caption,
873
+ id
874
+ ]);
875
+ const [query, setQuery] = usePersistedState(`${blockKey}::query`, "");
819
876
  const deferredQuery = useDeferredValue(query);
820
- const [selectedByBase, setSelectedByBase] = useState({});
821
- const [expandedByBase, setExpandedByBase] = useState(() => /* @__PURE__ */ new Set());
877
+ const [selectedByBase, setSelectedByBase] = usePersistedState(`${blockKey}::selected`, {});
878
+ const [expandedByBase, setExpandedByBase] = usePersistedState(`${blockKey}::expanded`, () => /* @__PURE__ */ new Set());
822
879
  const defs = useMemo(() => buildVariantDefs(variants), [variants]);
823
880
  const groups = useMemo(() => {
824
881
  const projectFields = {
@@ -898,13 +955,13 @@ function ColorTable({ filter, caption, sortBy = "path", sortDir = "asc", searcha
898
955
  else next.add(base);
899
956
  return next;
900
957
  });
901
- }, []);
958
+ }, [setExpandedByBase]);
902
959
  const selectVariant = useCallback((base, label) => {
903
960
  setSelectedByBase((prev) => ({
904
961
  ...prev,
905
962
  [base]: label
906
963
  }));
907
- }, []);
964
+ }, [setSelectedByBase]);
908
965
  const matchSuffix = searchable && query.trim() !== "" ? ` · ${visibleGroups.length} matching "${query.trim()}"` : "";
909
966
  const captionText = caption ?? `${totalTokens} color${totalTokens === 1 ? "" : "s"} across ${groups.length} group${groups.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""}${matchSuffix} · ${activeTheme}`;
910
967
  if (groups.length === 0) return /* @__PURE__ */ jsx("div", {
@@ -3639,8 +3696,13 @@ function pruneTreeForMatches(nodes, matches, expandOut) {
3639
3696
  }
3640
3697
  return out;
3641
3698
  }
3642
- function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true, onSelect }) {
3699
+ function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true, onSelect, id }) {
3643
3700
  const { resolved, activeTheme, activeAxes, cssVarPrefix } = useProject();
3701
+ const blockKey = useBlockKey("TokenNavigator", [
3702
+ root,
3703
+ type === void 0 ? "" : typeof type === "string" ? type : type.join(","),
3704
+ id
3705
+ ]);
3644
3706
  const typeFilter = useMemo(() => {
3645
3707
  if (type === void 0) return void 0;
3646
3708
  return new Set(Array.isArray(type) ? type : [type]);
@@ -3655,15 +3717,19 @@ function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true,
3655
3717
  collectInitialExpanded(tree, initiallyExpanded, out);
3656
3718
  return out;
3657
3719
  }, [tree, initiallyExpanded]);
3658
- const [expanded, setExpanded] = useState(initialExpanded);
3720
+ const [expanded, setExpanded] = usePersistedState(`${blockKey}::expanded`, initialExpanded);
3659
3721
  const initiallyExpandedRef = useRef(initiallyExpanded);
3660
3722
  useEffect(() => {
3661
3723
  if (initiallyExpandedRef.current === initiallyExpanded) return;
3662
3724
  initiallyExpandedRef.current = initiallyExpanded;
3663
3725
  setExpanded(initialExpanded);
3664
- }, [initiallyExpanded, initialExpanded]);
3665
- const [selectedPath, setSelectedPath] = useState(null);
3666
- const [query, setQuery] = useState("");
3726
+ }, [
3727
+ initiallyExpanded,
3728
+ initialExpanded,
3729
+ setExpanded
3730
+ ]);
3731
+ const [selectedPath, setSelectedPath] = usePersistedState(`${blockKey}::selected`, null);
3732
+ const [query, setQuery] = usePersistedState(`${blockKey}::query`, "");
3667
3733
  const deferredQuery = useDeferredValue(query);
3668
3734
  const { visibleTree, searchExpanded } = useMemo(() => {
3669
3735
  if (!searchable || deferredQuery.trim() === "") return {
@@ -3696,11 +3762,11 @@ function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true,
3696
3762
  else next.add(path);
3697
3763
  return next;
3698
3764
  });
3699
- }, []);
3765
+ }, [setExpanded]);
3700
3766
  const handleLeafClick = useCallback((path) => {
3701
3767
  if (onSelect) onSelect(path);
3702
3768
  else setSelectedPath(path);
3703
- }, [onSelect]);
3769
+ }, [onSelect, setSelectedPath]);
3704
3770
  const [storedFocus, setStoredFocus] = useState(null);
3705
3771
  const treeItemRefs = useRef(/* @__PURE__ */ new Map());
3706
3772
  const registerTreeItem = useCallback((path) => (el) => {
@@ -4055,11 +4121,17 @@ const LeafPreview = memo(function LeafPreview({ path, token }) {
4055
4121
  });
4056
4122
  //#endregion
4057
4123
  //#region src/TokenTable.tsx
4058
- function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", searchable = true, onSelect }) {
4124
+ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", searchable = true, onSelect, id }) {
4059
4125
  const { resolved, activeTheme, activeAxes, cssVarPrefix, listing } = useProject();
4060
4126
  const colorFormat = useColorFormat();
4061
- const [selectedPath, setSelectedPath] = useState(null);
4062
- const [query, setQuery] = useState("");
4127
+ const blockKey = useBlockKey("TokenTable", [
4128
+ filter,
4129
+ type,
4130
+ caption,
4131
+ id
4132
+ ]);
4133
+ const [selectedPath, setSelectedPath] = usePersistedState(`${blockKey}::selected`, null);
4134
+ const [query, setQuery] = usePersistedState(`${blockKey}::query`, "");
4063
4135
  const deferredQuery = useDeferredValue(query);
4064
4136
  const rows = useMemo(() => {
4065
4137
  const projectFields = {
@@ -4106,7 +4178,7 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
4106
4178
  const handleRowClick = useCallback((path) => {
4107
4179
  if (onSelect) onSelect(path);
4108
4180
  else setSelectedPath(path);
4109
- }, [onSelect]);
4181
+ }, [onSelect, setSelectedPath]);
4110
4182
  const matchSuffix = searchable && query.trim() !== "" ? ` · ${visibleRows.length} matching "${query.trim()}"` : "";
4111
4183
  const captionText = caption ?? `${rows.length} token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""}${type ? ` · $type=${type}` : ""}${matchSuffix} · ${activeTheme}`;
4112
4184
  if (rows.length === 0) return /* @__PURE__ */ jsx("div", {