@buoy-gg/zustand 7.0.31 → 7.0.33

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.
@@ -35,24 +35,34 @@ var _jsxRuntime = require("react/jsx-runtime");
35
35
  * production behaviour exactly as it was.
36
36
  */
37
37
  const CLIP_OFFSCREEN_CELLS = !(typeof process !== "undefined" && process.env?.EXPO_PUBLIC_E2E);
38
+
39
+ /**
40
+ * The change log's "nothing here". Everything but the nouns is the shared
41
+ * precedence — see `ToolEmptyState` in shared-ui for why it lives there.
42
+ */
38
43
  function EventsEmptyState({
39
- isEnabled
44
+ isEnabled,
45
+ searchText,
46
+ totalChanges
40
47
  }) {
41
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
42
- style: styles.emptyState,
43
- testID: `${_testIDs.ZUSTAND_TEST_ID}-empty`,
44
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Box, {
45
- size: 32,
46
- color: _sharedUi.night.color.textTertiary
47
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
48
- style: styles.emptyTitle,
49
- testID: `${_testIDs.ZUSTAND_TEST_ID}-empty-title`,
50
- children: "No state changes"
51
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
52
- style: styles.emptyText,
53
- testID: `${_testIDs.ZUSTAND_TEST_ID}-empty-text`,
54
- children: isEnabled ? "State changes will appear here as stores update." : "Enable capture to start recording state changes"
55
- })]
48
+ const content = (0, _sharedUi.resolveListEmptyState)({
49
+ subject: "changes",
50
+ search: searchText,
51
+ total: totalChanges,
52
+ capturing: isEnabled,
53
+ waiting: {
54
+ otto: "waiting",
55
+ title: "No state changes",
56
+ message: "State changes will appear here as stores update."
57
+ },
58
+ paused: {
59
+ title: "No state changes",
60
+ message: "Enable capture to start recording state changes"
61
+ }
62
+ });
63
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
64
+ ...content,
65
+ testID: `${_testIDs.ZUSTAND_TEST_ID}-empty`
56
66
  });
57
67
  }
58
68
  function ZustandModal({
@@ -82,6 +92,7 @@ function ZustandModal({
82
92
  const [showFilters, setShowFilters] = (0, _react.useState)(false);
83
93
  const [ignoredPatterns, setIgnoredPatterns] = (0, _react.useState)(new Set());
84
94
  const {
95
+ stateChanges,
85
96
  filteredChanges,
86
97
  filter,
87
98
  setFilter,
@@ -104,6 +115,16 @@ function ZustandModal({
104
115
  return stores.filter(s => !Array.from(ignoredPatterns).some(p => s.name.toLowerCase().includes(p.toLowerCase())));
105
116
  }, [stores, ignoredPatterns]);
106
117
 
118
+ /**
119
+ * How many changes this store has captured, before the search — the
120
+ * denominator the scoped empty state needs so "clear the search" is only
121
+ * offered when clearing it would actually show something.
122
+ */
123
+ const scopedTotalForStore = (0, _react.useMemo)(() => {
124
+ if (!selectedHistoryStore) return 0;
125
+ return stateChanges.filter(c => c.storeName === selectedHistoryStore).length;
126
+ }, [stateChanges, selectedHistoryStore]);
127
+
107
128
  // Scope to a store BEFORE the tier cap, not after.
108
129
  //
109
130
  // The other way round, the cap was applied to the whole log and a store's
@@ -511,19 +532,24 @@ function ZustandModal({
511
532
  maxToRenderPerBatch: 8,
512
533
  windowSize: 5,
513
534
  updateCellsBatchingPeriod: 50
514
- }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
515
- style: styles.emptyState,
535
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
516
536
  testID: `${_testIDs.ZUSTAND_TEST_ID}-history-empty`,
517
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Box, {
518
- size: 32,
519
- color: _sharedUi.night.color.textTertiary
520
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
521
- style: styles.emptyTitle,
522
- children: "No history yet"
523
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
524
- style: styles.emptyText,
525
- children: ["State changes for ", selectedHistoryStore, " will appear here."]
526
- })]
537
+ ...(0, _sharedUi.resolveListEmptyState)({
538
+ subject: "changes",
539
+ search: searchText,
540
+ // Scoped to one store, so the count is this store's, not the log's.
541
+ total: searchText.trim() ? scopedTotalForStore : 0,
542
+ capturing: isEnabled,
543
+ waiting: {
544
+ otto: "waiting",
545
+ title: "No history yet",
546
+ message: `State changes for ${selectedHistoryStore} will appear here.`
547
+ },
548
+ paused: {
549
+ title: "No history yet",
550
+ message: `Capture is off, so nothing is being recorded for ${selectedHistoryStore}.`
551
+ }
552
+ })
527
553
  });
528
554
  }
529
555
 
@@ -539,19 +565,15 @@ function ZustandModal({
539
565
  /*#__PURE__*/
540
566
  // The store fell out of the registry mid-look (a remote mirror
541
567
  // rebuilding, a dev reload). Say so rather than rendering nothing.
542
- (0, _jsxRuntime.jsxs)(_reactNative.View, {
543
- style: styles.emptyState,
544
- testID: `${_testIDs.ZUSTAND_TEST_ID}-store-detail-gone`,
545
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Box, {
546
- size: 32,
547
- color: _sharedUi.night.color.textTertiary
548
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
549
- style: styles.emptyTitle,
550
- children: "Store no longer registered"
551
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
552
- style: styles.emptyText,
553
- children: [selectedStoreName, " is not in the registry any more."]
554
- })]
568
+ (0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
569
+ testID: `${_testIDs.ZUSTAND_TEST_ID}-store-detail-gone`
570
+ // ghost = "stale or detached: an unmounted component, an old
571
+ // snapshot" — exactly what the store you are looking at has become.
572
+ // No other tool has this cause, so it skips the resolver.
573
+ ,
574
+ otto: "ghost",
575
+ title: "Store no longer registered",
576
+ message: `${selectedStoreName} is not in the registry any more.`
555
577
  });
556
578
  }
557
579
 
@@ -611,7 +633,9 @@ function ZustandModal({
611
633
  windowSize: 5,
612
634
  updateCellsBatchingPeriod: 50
613
635
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(EventsEmptyState, {
614
- isEnabled: isEnabled
636
+ isEnabled: isEnabled,
637
+ searchText: searchText,
638
+ totalChanges: stats.totalChanges
615
639
  })]
616
640
  });
617
641
  };
@@ -656,6 +680,7 @@ function ZustandModal({
656
680
  style: styles.inlineRoot,
657
681
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
658
682
  style: styles.inlineHeader,
683
+ ..._sharedUi.windowDragProps,
659
684
  children: renderHeaderContent()
660
685
  }), body, footerNode]
661
686
  }), upgradeModal]
@@ -778,23 +803,6 @@ const styles = _reactNative.StyleSheet.create({
778
803
  paddingTop: 8,
779
804
  paddingBottom: 20
780
805
  },
781
- emptyState: {
782
- alignItems: "center",
783
- paddingVertical: 40
784
- },
785
- emptyTitle: {
786
- color: _sharedUi.night.color.text,
787
- fontSize: 14,
788
- fontWeight: "600",
789
- marginTop: 12,
790
- marginBottom: 6
791
- },
792
- emptyText: {
793
- color: _sharedUi.night.color.textTertiary,
794
- fontSize: 12,
795
- textAlign: "center",
796
- lineHeight: 18
797
- },
798
806
  lockedBanner: {
799
807
  flexDirection: "row",
800
808
  alignItems: "center",
@@ -44,36 +44,26 @@ function getKeysPreview(store) {
44
44
  }
45
45
  }
46
46
  function LoadingBrowserState() {
47
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
48
- style: styles.emptyState,
49
- testID: `${_testIDs.ZUSTAND_TEST_ID}-stores-loading`,
50
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
51
- size: "small",
52
- color: _sharedUi.night.color.textTertiary
53
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
54
- style: styles.emptyTitle,
55
- children: "Loading stores\u2026"
56
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
57
- style: styles.emptyText,
58
- children: "Waiting for the device to send its registered stores."
59
- })]
47
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
48
+ testID: `${_testIDs.ZUSTAND_TEST_ID}-stores-loading`
49
+ // Replaces the spinner rather than joining it: two animated things
50
+ // stacked is clutter, and `loading` IS the indeterminate spinner in
51
+ // mascot form.
52
+ ,
53
+ otto: "loading",
54
+ title: "Loading stores\u2026",
55
+ message: "Waiting for the device to send its registered stores."
60
56
  });
61
57
  }
62
58
  function EmptyBrowserState() {
63
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
64
- style: styles.emptyState,
65
- testID: `${_testIDs.ZUSTAND_TEST_ID}-stores-empty`,
66
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Box, {
67
- size: 32,
68
- color: _sharedUi.night.color.textTertiary
69
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
70
- style: styles.emptyTitle,
71
- testID: `${_testIDs.ZUSTAND_TEST_ID}-stores-empty-title`,
72
- children: "No stores registered"
73
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
74
- style: styles.emptyText,
75
- children: "Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."
76
- })]
59
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
60
+ testID: `${_testIDs.ZUSTAND_TEST_ID}-stores-empty`
61
+ // scanning = "scanning for targets… and finding none". The registry is
62
+ // discovered, not configured, so an empty one is a scan result.
63
+ ,
64
+ otto: "scanning",
65
+ title: "No stores registered",
66
+ message: "Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."
77
67
  });
78
68
  }
79
69
  function ZustandStoreBrowser({
@@ -92,17 +82,19 @@ function ZustandStoreBrowser({
92
82
  return isLoading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(LoadingBrowserState, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyBrowserState, {});
93
83
  }
94
84
  if (filteredStores.length === 0 && searchQuery) {
95
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
96
- style: styles.emptyState,
85
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
97
86
  testID: `${_testIDs.ZUSTAND_TEST_ID}-stores-no-match`,
98
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
99
- style: styles.emptyTitle,
100
- testID: `${_testIDs.ZUSTAND_TEST_ID}-stores-no-match-title`,
101
- children: "No matching stores"
102
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
103
- style: styles.emptyText,
104
- children: ["No stores match \"", searchQuery, "\""]
105
- })]
87
+ ...(0, _sharedUi.resolveListEmptyState)({
88
+ subject: "stores",
89
+ verb: "registered",
90
+ search: searchQuery,
91
+ total: stores.length,
92
+ waiting: {
93
+ otto: "scanning",
94
+ title: "No stores registered",
95
+ message: "Use watchStores() to register your Zustand stores."
96
+ }
97
+ })
106
98
  });
107
99
  }
108
100
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
@@ -208,22 +200,5 @@ const styles = _reactNative.StyleSheet.create({
208
200
  fontWeight: "700",
209
201
  color: _sharedUi.night.color.accent,
210
202
  fontFamily: "monospace"
211
- },
212
- emptyState: {
213
- alignItems: "center",
214
- paddingVertical: 40
215
- },
216
- emptyTitle: {
217
- color: _sharedUi.night.color.text,
218
- fontSize: 14,
219
- fontWeight: "600",
220
- marginTop: 12,
221
- marginBottom: 6
222
- },
223
- emptyText: {
224
- color: _sharedUi.night.color.textTertiary,
225
- fontSize: 12,
226
- textAlign: "center",
227
- lineHeight: 18
228
203
  }
229
204
  });
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { useState, useMemo, useRef, useCallback, useEffect } from "react";
12
12
  import { View, Text, StyleSheet, FlatList, TextInput, TouchableOpacity } from "react-native";
13
- import { JsModal, ModalHeader, TabSelector, macOSColors, night, Search, Filter, Power, X, Lock, Box, devToolsStorageKeys, ProUpgradeModal, PowerToggleButton, ToolbarCopyButton, ToolbarClearButton, truncatePayload, TickProvider, useLimit } from "@buoy-gg/shared-ui";
13
+ import { JsModal, ModalHeader, TabSelector, macOSColors, night, ToolEmptyState, resolveListEmptyState, Search, Filter, Power, X, Lock, devToolsStorageKeys, ProUpgradeModal, PowerToggleButton, ToolbarCopyButton, ToolbarClearButton, truncatePayload, TickProvider, useLimit, windowDragProps } from "@buoy-gg/shared-ui";
14
14
  import { useIsPro } from "@buoy-gg/license";
15
15
  import { useZustandStateChanges } from "../hooks/useZustandStateChanges";
16
16
  import { ZustandStateChangeItem } from "./ZustandStateChangeItem";
@@ -30,24 +30,34 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
30
30
  * production behaviour exactly as it was.
31
31
  */
32
32
  const CLIP_OFFSCREEN_CELLS = !(typeof process !== "undefined" && process.env?.EXPO_PUBLIC_E2E);
33
+
34
+ /**
35
+ * The change log's "nothing here". Everything but the nouns is the shared
36
+ * precedence — see `ToolEmptyState` in shared-ui for why it lives there.
37
+ */
33
38
  function EventsEmptyState({
34
- isEnabled
39
+ isEnabled,
40
+ searchText,
41
+ totalChanges
35
42
  }) {
36
- return /*#__PURE__*/_jsxs(View, {
37
- style: styles.emptyState,
38
- testID: `${ZUSTAND_TEST_ID}-empty`,
39
- children: [/*#__PURE__*/_jsx(Box, {
40
- size: 32,
41
- color: night.color.textTertiary
42
- }), /*#__PURE__*/_jsx(Text, {
43
- style: styles.emptyTitle,
44
- testID: `${ZUSTAND_TEST_ID}-empty-title`,
45
- children: "No state changes"
46
- }), /*#__PURE__*/_jsx(Text, {
47
- style: styles.emptyText,
48
- testID: `${ZUSTAND_TEST_ID}-empty-text`,
49
- children: isEnabled ? "State changes will appear here as stores update." : "Enable capture to start recording state changes"
50
- })]
43
+ const content = resolveListEmptyState({
44
+ subject: "changes",
45
+ search: searchText,
46
+ total: totalChanges,
47
+ capturing: isEnabled,
48
+ waiting: {
49
+ otto: "waiting",
50
+ title: "No state changes",
51
+ message: "State changes will appear here as stores update."
52
+ },
53
+ paused: {
54
+ title: "No state changes",
55
+ message: "Enable capture to start recording state changes"
56
+ }
57
+ });
58
+ return /*#__PURE__*/_jsx(ToolEmptyState, {
59
+ ...content,
60
+ testID: `${ZUSTAND_TEST_ID}-empty`
51
61
  });
52
62
  }
53
63
  export function ZustandModal({
@@ -77,6 +87,7 @@ export function ZustandModal({
77
87
  const [showFilters, setShowFilters] = useState(false);
78
88
  const [ignoredPatterns, setIgnoredPatterns] = useState(new Set());
79
89
  const {
90
+ stateChanges,
80
91
  filteredChanges,
81
92
  filter,
82
93
  setFilter,
@@ -99,6 +110,16 @@ export function ZustandModal({
99
110
  return stores.filter(s => !Array.from(ignoredPatterns).some(p => s.name.toLowerCase().includes(p.toLowerCase())));
100
111
  }, [stores, ignoredPatterns]);
101
112
 
113
+ /**
114
+ * How many changes this store has captured, before the search — the
115
+ * denominator the scoped empty state needs so "clear the search" is only
116
+ * offered when clearing it would actually show something.
117
+ */
118
+ const scopedTotalForStore = useMemo(() => {
119
+ if (!selectedHistoryStore) return 0;
120
+ return stateChanges.filter(c => c.storeName === selectedHistoryStore).length;
121
+ }, [stateChanges, selectedHistoryStore]);
122
+
102
123
  // Scope to a store BEFORE the tier cap, not after.
103
124
  //
104
125
  // The other way round, the cap was applied to the whole log and a store's
@@ -506,19 +527,24 @@ export function ZustandModal({
506
527
  maxToRenderPerBatch: 8,
507
528
  windowSize: 5,
508
529
  updateCellsBatchingPeriod: 50
509
- }) : /*#__PURE__*/_jsxs(View, {
510
- style: styles.emptyState,
530
+ }) : /*#__PURE__*/_jsx(ToolEmptyState, {
511
531
  testID: `${ZUSTAND_TEST_ID}-history-empty`,
512
- children: [/*#__PURE__*/_jsx(Box, {
513
- size: 32,
514
- color: night.color.textTertiary
515
- }), /*#__PURE__*/_jsx(Text, {
516
- style: styles.emptyTitle,
517
- children: "No history yet"
518
- }), /*#__PURE__*/_jsxs(Text, {
519
- style: styles.emptyText,
520
- children: ["State changes for ", selectedHistoryStore, " will appear here."]
521
- })]
532
+ ...resolveListEmptyState({
533
+ subject: "changes",
534
+ search: searchText,
535
+ // Scoped to one store, so the count is this store's, not the log's.
536
+ total: searchText.trim() ? scopedTotalForStore : 0,
537
+ capturing: isEnabled,
538
+ waiting: {
539
+ otto: "waiting",
540
+ title: "No history yet",
541
+ message: `State changes for ${selectedHistoryStore} will appear here.`
542
+ },
543
+ paused: {
544
+ title: "No history yet",
545
+ message: `Capture is off, so nothing is being recorded for ${selectedHistoryStore}.`
546
+ }
547
+ })
522
548
  });
523
549
  }
524
550
 
@@ -534,19 +560,15 @@ export function ZustandModal({
534
560
  /*#__PURE__*/
535
561
  // The store fell out of the registry mid-look (a remote mirror
536
562
  // rebuilding, a dev reload). Say so rather than rendering nothing.
537
- _jsxs(View, {
538
- style: styles.emptyState,
539
- testID: `${ZUSTAND_TEST_ID}-store-detail-gone`,
540
- children: [/*#__PURE__*/_jsx(Box, {
541
- size: 32,
542
- color: night.color.textTertiary
543
- }), /*#__PURE__*/_jsx(Text, {
544
- style: styles.emptyTitle,
545
- children: "Store no longer registered"
546
- }), /*#__PURE__*/_jsxs(Text, {
547
- style: styles.emptyText,
548
- children: [selectedStoreName, " is not in the registry any more."]
549
- })]
563
+ _jsx(ToolEmptyState, {
564
+ testID: `${ZUSTAND_TEST_ID}-store-detail-gone`
565
+ // ghost = "stale or detached: an unmounted component, an old
566
+ // snapshot" — exactly what the store you are looking at has become.
567
+ // No other tool has this cause, so it skips the resolver.
568
+ ,
569
+ otto: "ghost",
570
+ title: "Store no longer registered",
571
+ message: `${selectedStoreName} is not in the registry any more.`
550
572
  });
551
573
  }
552
574
 
@@ -606,7 +628,9 @@ export function ZustandModal({
606
628
  windowSize: 5,
607
629
  updateCellsBatchingPeriod: 50
608
630
  }) : /*#__PURE__*/_jsx(EventsEmptyState, {
609
- isEnabled: isEnabled
631
+ isEnabled: isEnabled,
632
+ searchText: searchText,
633
+ totalChanges: stats.totalChanges
610
634
  })]
611
635
  });
612
636
  };
@@ -651,6 +675,7 @@ export function ZustandModal({
651
675
  style: styles.inlineRoot,
652
676
  children: [/*#__PURE__*/_jsx(View, {
653
677
  style: styles.inlineHeader,
678
+ ...windowDragProps,
654
679
  children: renderHeaderContent()
655
680
  }), body, footerNode]
656
681
  }), upgradeModal]
@@ -773,23 +798,6 @@ const styles = StyleSheet.create({
773
798
  paddingTop: 8,
774
799
  paddingBottom: 20
775
800
  },
776
- emptyState: {
777
- alignItems: "center",
778
- paddingVertical: 40
779
- },
780
- emptyTitle: {
781
- color: night.color.text,
782
- fontSize: 14,
783
- fontWeight: "600",
784
- marginTop: 12,
785
- marginBottom: 6
786
- },
787
- emptyText: {
788
- color: night.color.textTertiary,
789
- fontSize: 12,
790
- textAlign: "center",
791
- lineHeight: 18
792
- },
793
801
  lockedBanner: {
794
802
  flexDirection: "row",
795
803
  alignItems: "center",
@@ -10,8 +10,8 @@
10
10
  */
11
11
 
12
12
  import { useMemo } from "react";
13
- import { View, Text, StyleSheet, ScrollView, ActivityIndicator } from "react-native";
14
- import { CompactRow, night, Box } from "@buoy-gg/shared-ui";
13
+ import { View, Text, StyleSheet, ScrollView } from "react-native";
14
+ import { CompactRow, night, ToolEmptyState, resolveListEmptyState } from "@buoy-gg/shared-ui";
15
15
  import { zustandStateStore } from "../utils/zustandStateStore";
16
16
  import { ZUSTAND_TEST_ID, storeRowTestID } from "../utils/testIDs";
17
17
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -40,36 +40,26 @@ function getKeysPreview(store) {
40
40
  }
41
41
  }
42
42
  function LoadingBrowserState() {
43
- return /*#__PURE__*/_jsxs(View, {
44
- style: styles.emptyState,
45
- testID: `${ZUSTAND_TEST_ID}-stores-loading`,
46
- children: [/*#__PURE__*/_jsx(ActivityIndicator, {
47
- size: "small",
48
- color: night.color.textTertiary
49
- }), /*#__PURE__*/_jsx(Text, {
50
- style: styles.emptyTitle,
51
- children: "Loading stores\u2026"
52
- }), /*#__PURE__*/_jsx(Text, {
53
- style: styles.emptyText,
54
- children: "Waiting for the device to send its registered stores."
55
- })]
43
+ return /*#__PURE__*/_jsx(ToolEmptyState, {
44
+ testID: `${ZUSTAND_TEST_ID}-stores-loading`
45
+ // Replaces the spinner rather than joining it: two animated things
46
+ // stacked is clutter, and `loading` IS the indeterminate spinner in
47
+ // mascot form.
48
+ ,
49
+ otto: "loading",
50
+ title: "Loading stores\u2026",
51
+ message: "Waiting for the device to send its registered stores."
56
52
  });
57
53
  }
58
54
  function EmptyBrowserState() {
59
- return /*#__PURE__*/_jsxs(View, {
60
- style: styles.emptyState,
61
- testID: `${ZUSTAND_TEST_ID}-stores-empty`,
62
- children: [/*#__PURE__*/_jsx(Box, {
63
- size: 32,
64
- color: night.color.textTertiary
65
- }), /*#__PURE__*/_jsx(Text, {
66
- style: styles.emptyTitle,
67
- testID: `${ZUSTAND_TEST_ID}-stores-empty-title`,
68
- children: "No stores registered"
69
- }), /*#__PURE__*/_jsx(Text, {
70
- style: styles.emptyText,
71
- children: "Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."
72
- })]
55
+ return /*#__PURE__*/_jsx(ToolEmptyState, {
56
+ testID: `${ZUSTAND_TEST_ID}-stores-empty`
57
+ // scanning = "scanning for targets… and finding none". The registry is
58
+ // discovered, not configured, so an empty one is a scan result.
59
+ ,
60
+ otto: "scanning",
61
+ title: "No stores registered",
62
+ message: "Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."
73
63
  });
74
64
  }
75
65
  export function ZustandStoreBrowser({
@@ -88,17 +78,19 @@ export function ZustandStoreBrowser({
88
78
  return isLoading ? /*#__PURE__*/_jsx(LoadingBrowserState, {}) : /*#__PURE__*/_jsx(EmptyBrowserState, {});
89
79
  }
90
80
  if (filteredStores.length === 0 && searchQuery) {
91
- return /*#__PURE__*/_jsxs(View, {
92
- style: styles.emptyState,
81
+ return /*#__PURE__*/_jsx(ToolEmptyState, {
93
82
  testID: `${ZUSTAND_TEST_ID}-stores-no-match`,
94
- children: [/*#__PURE__*/_jsx(Text, {
95
- style: styles.emptyTitle,
96
- testID: `${ZUSTAND_TEST_ID}-stores-no-match-title`,
97
- children: "No matching stores"
98
- }), /*#__PURE__*/_jsxs(Text, {
99
- style: styles.emptyText,
100
- children: ["No stores match \"", searchQuery, "\""]
101
- })]
83
+ ...resolveListEmptyState({
84
+ subject: "stores",
85
+ verb: "registered",
86
+ search: searchQuery,
87
+ total: stores.length,
88
+ waiting: {
89
+ otto: "scanning",
90
+ title: "No stores registered",
91
+ message: "Use watchStores() to register your Zustand stores."
92
+ }
93
+ })
102
94
  });
103
95
  }
104
96
  return /*#__PURE__*/_jsxs(ScrollView, {
@@ -204,22 +196,5 @@ const styles = StyleSheet.create({
204
196
  fontWeight: "700",
205
197
  color: night.color.accent,
206
198
  fontFamily: "monospace"
207
- },
208
- emptyState: {
209
- alignItems: "center",
210
- paddingVertical: 40
211
- },
212
- emptyTitle: {
213
- color: night.color.text,
214
- fontSize: 14,
215
- fontWeight: "600",
216
- marginTop: 12,
217
- marginBottom: 6
218
- },
219
- emptyText: {
220
- color: night.color.textTertiary,
221
- fontSize: 12,
222
- textAlign: "center",
223
- lineHeight: 18
224
199
  }
225
200
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ZustandModal.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandModal.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA6CH,OAAO,KAAK,EAAE,iBAAiB,EAAsB,MAAM,UAAU,CAAC;AAkCtE,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,MAAc,EACd,aAAqB,EACrB,UAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,EACd,aAAa,GACd,EAAE,iBAAiB,sCAyuBnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,GAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAM,+BAGtE"}
1
+ {"version":3,"file":"ZustandModal.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandModal.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiDH,OAAO,KAAK,EAAE,iBAAiB,EAAsB,MAAM,UAAU,CAAC;AAkDtE,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,MAAc,EACd,aAAqB,EACrB,UAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,EACd,aAAa,GACd,EAAE,iBAAiB,sCAiwBnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,GAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAM,+BAGtE"}
@@ -1 +1 @@
1
- {"version":3,"file":"ZustandStoreBrowser.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStoreBrowser.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAIjD,UAAU,wBAAwB;IAChC,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AA4DD,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,WAAW,EACX,SAAiB,EACjB,WAAW,GACZ,EAAE,wBAAwB,+BA6F1B"}
1
+ {"version":3,"file":"ZustandStoreBrowser.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStoreBrowser.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAIjD,UAAU,wBAAwB;IAChC,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AA4DD,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,WAAW,EACX,SAAiB,EACjB,WAAW,GACZ,EAAE,wBAAwB,+BAgG1B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/zustand",
3
- "version": "7.0.31",
3
+ "version": "7.0.33",
4
4
  "description": "Zustand devtools inside your React Native app — state diffs, jump-to-state & one-tap store reset, no middleware. Part of Buoy devtools.",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -29,8 +29,8 @@
29
29
  "**/snapshotProvider.ts"
30
30
  ],
31
31
  "dependencies": {
32
- "@buoy-gg/license": "^7.0.31",
33
- "@buoy-gg/shared-ui": "^7.0.31"
32
+ "@buoy-gg/shared-ui": "^7.0.33",
33
+ "@buoy-gg/license": "^7.0.33"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": "*",