@buoy-gg/zustand 7.0.33 → 7.0.34

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.
Files changed (32) hide show
  1. package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +13 -107
  2. package/lib/commonjs/zustand/components/ZustandEventFilterView.js +24 -288
  3. package/lib/commonjs/zustand/components/ZustandModal.js +20 -85
  4. package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +8 -61
  5. package/lib/commonjs/zustand/components/ZustandStateInfoView.js +2 -46
  6. package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +33 -53
  7. package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +24 -78
  8. package/lib/commonjs/zustand/utils/snapshotProvider.js +17 -60
  9. package/lib/commonjs/zustand/utils/zustandStateStore.js +37 -223
  10. package/lib/module/zustand/components/ZustandDetailViewToggle.js +15 -109
  11. package/lib/module/zustand/components/ZustandEventFilterView.js +28 -292
  12. package/lib/module/zustand/components/ZustandModal.js +22 -87
  13. package/lib/module/zustand/components/ZustandStateChangeItem.js +10 -63
  14. package/lib/module/zustand/components/ZustandStateInfoView.js +1 -45
  15. package/lib/module/zustand/hooks/useZustandStateChanges.js +34 -54
  16. package/lib/module/zustand/sync/zustandSyncAdapter.js +25 -79
  17. package/lib/module/zustand/utils/snapshotProvider.js +18 -61
  18. package/lib/module/zustand/utils/zustandStateStore.js +37 -223
  19. package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts +2 -1
  20. package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +1 -1
  21. package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts +6 -0
  22. package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +1 -1
  23. package/lib/typescript/zustand/components/ZustandModal.d.ts.map +1 -1
  24. package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +1 -1
  25. package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +1 -1
  26. package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts +2 -1
  27. package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +1 -1
  28. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts +103 -117
  29. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +1 -1
  30. package/lib/typescript/zustand/utils/zustandStateStore.d.ts +6 -26
  31. package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +1 -1
  32. package/package.json +3 -3
@@ -1,303 +1,39 @@
1
1
  "use strict";
2
2
 
3
- import { useState } from "react";
4
- import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from "react-native";
5
- import { Filter, X, SectionHeader, AddFilterButton, AddFilterInput, night } from "@buoy-gg/shared-ui";
3
+ /**
4
+ * ZustandEventFilterView
5
+ *
6
+ * The shared `EntityFilterView` with zustand's nouns: registered stores you
7
+ * can tap to hide, active store-name patterns, and how it works.
8
+ */
9
+
10
+ import { useMemo } from "react";
11
+ import { EntityFilterView } from "@buoy-gg/shared-ui";
6
12
  import { zustandStateStore } from "../utils/zustandStateStore";
7
13
  import { ZUSTAND_TEST_ID, slugForTestID } from "../utils/testIDs";
8
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
- /** Returns the store color for a given pattern if it exactly matches a registered store name */
10
- function getStoreColorForPattern(pattern, stores) {
11
- const match = stores.find(s => s.name.toLowerCase() === pattern.toLowerCase());
12
- return match ? zustandStateStore.getStoreColor(match.name) : null;
13
- }
14
-
15
- /** True if any active pattern filters out this store */
16
- function isStoreFiltered(store, ignoredPatterns) {
17
- return Array.from(ignoredPatterns).some(p => store.name.toLowerCase().includes(p.toLowerCase()));
18
- }
14
+ import { jsx as _jsx } from "react/jsx-runtime";
15
+ const EXAMPLES = ["• auth → hides authStore from stores & events", "• temp → hides tempStore, tempUserStore", "• cart → hides cartStore from both tabs"];
19
16
  export function ZustandEventFilterView({
20
17
  ignoredPatterns,
21
18
  onTogglePattern,
22
19
  onAddPattern,
23
20
  stores
24
21
  }) {
25
- const [showAddInput, setShowAddInput] = useState(false);
26
- const [inputValue, setInputValue] = useState("");
27
- const handleAddPattern = () => {
28
- const trimmed = inputValue.trim();
29
- if (trimmed) {
30
- onAddPattern(trimmed);
31
- setInputValue("");
32
- setShowAddInput(false);
33
- }
34
- };
35
- return /*#__PURE__*/_jsxs(ScrollView, {
36
- style: styles.container,
37
- contentContainerStyle: styles.scrollContent,
38
- showsVerticalScrollIndicator: false,
39
- testID: `${ZUSTAND_TEST_ID}-filters-scroll`,
40
- children: [stores.length > 0 && /*#__PURE__*/_jsxs(View, {
41
- style: styles.section,
42
- children: [/*#__PURE__*/_jsxs(SectionHeader, {
43
- children: [/*#__PURE__*/_jsx(SectionHeader.Title, {
44
- children: "REGISTERED STORES"
45
- }), /*#__PURE__*/_jsx(SectionHeader.Badge, {
46
- count: stores.length,
47
- color: night.color.accent,
48
- testID: `${ZUSTAND_TEST_ID}-filters-store-count`
49
- })]
50
- }), /*#__PURE__*/_jsx(ScrollView, {
51
- style: styles.storeList,
52
- contentContainerStyle: styles.storeListContent,
53
- showsVerticalScrollIndicator: true,
54
- nestedScrollEnabled: true,
55
- children: stores.map(store => {
56
- const storeColor = zustandStateStore.getStoreColor(store.name);
57
- const filtered = isStoreFiltered(store, ignoredPatterns);
58
- return /*#__PURE__*/_jsxs(TouchableOpacity, {
59
- style: [styles.storeRow, filtered && styles.storeRowFiltered],
60
- onPress: () => onAddPattern(store.name),
61
- disabled: filtered,
62
- testID: `${ZUSTAND_TEST_ID}-filters-store-${slugForTestID(store.name)}`,
63
- accessibilityRole: "button",
64
- accessibilityLabel: `Hide ${store.name}`,
65
- accessibilityState: {
66
- disabled: filtered
67
- },
68
- children: [/*#__PURE__*/_jsx(View, {
69
- style: [styles.storeDot, {
70
- backgroundColor: filtered ? night.color.textTertiary : storeColor
71
- }]
72
- }), /*#__PURE__*/_jsx(Text, {
73
- style: [styles.storeRowText, filtered && styles.storeRowTextFiltered, !filtered && {
74
- color: storeColor
75
- }],
76
- numberOfLines: 1,
77
- children: store.name
78
- }), filtered ? /*#__PURE__*/_jsx(Text, {
79
- style: styles.filteredLabel,
80
- children: "hidden"
81
- }) : /*#__PURE__*/_jsx(Text, {
82
- style: [styles.addLabel, {
83
- color: storeColor + "99"
84
- }],
85
- children: "+ hide"
86
- })]
87
- }, store.name);
88
- })
89
- })]
90
- }), /*#__PURE__*/_jsxs(View, {
91
- style: styles.section,
92
- children: [/*#__PURE__*/_jsxs(SectionHeader, {
93
- children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
94
- icon: Filter,
95
- color: night.color.accent,
96
- size: 12
97
- }), /*#__PURE__*/_jsx(SectionHeader.Title, {
98
- children: "STORE NAME FILTERS"
99
- }), ignoredPatterns.size > 0 && /*#__PURE__*/_jsx(SectionHeader.Badge, {
100
- count: ignoredPatterns.size,
101
- color: night.color.accent,
102
- testID: `${ZUSTAND_TEST_ID}-filters-count`
103
- })]
104
- }), /*#__PURE__*/_jsx(View, {
105
- style: styles.addInputWrapper,
106
- children: !showAddInput ? /*#__PURE__*/_jsx(AddFilterButton, {
107
- onPress: () => setShowAddInput(true),
108
- color: night.color.accent,
109
- label: "Add store name pattern",
110
- testID: `${ZUSTAND_TEST_ID}-filters-add-open`
111
- }) : /*#__PURE__*/_jsx(AddFilterInput, {
112
- value: inputValue,
113
- onChange: setInputValue,
114
- onSubmit: handleAddPattern,
115
- onCancel: () => {
116
- setShowAddInput(false);
117
- setInputValue("");
118
- },
119
- placeholder: "Enter store name pattern to hide...",
120
- color: night.color.accent,
121
- testID: `${ZUSTAND_TEST_ID}-filters-add-input`
122
- })
123
- }), ignoredPatterns.size > 0 && /*#__PURE__*/_jsx(View, {
124
- style: styles.patternList,
125
- children: Array.from(ignoredPatterns).map(pattern => {
126
- const storeColor = getStoreColorForPattern(pattern, stores);
127
- return /*#__PURE__*/_jsxs(TouchableOpacity, {
128
- style: styles.patternRow,
129
- onPress: () => onTogglePattern(pattern),
130
- activeOpacity: 0.8,
131
- testID: `${ZUSTAND_TEST_ID}-filters-chip-${slugForTestID(pattern)}`,
132
- accessibilityRole: "button",
133
- accessibilityLabel: `Remove filter ${pattern}`,
134
- children: [storeColor ? /*#__PURE__*/_jsx(View, {
135
- style: [styles.storeDot, {
136
- backgroundColor: storeColor
137
- }]
138
- }) : null, /*#__PURE__*/_jsx(Text, {
139
- style: [styles.patternText, storeColor ? {
140
- color: storeColor
141
- } : null],
142
- numberOfLines: 1,
143
- children: pattern
144
- }), /*#__PURE__*/_jsx(X, {
145
- size: 12,
146
- color: night.color.accent + "80"
147
- })]
148
- }, pattern);
149
- })
150
- })]
151
- }), /*#__PURE__*/_jsxs(View, {
152
- style: styles.section,
153
- children: [/*#__PURE__*/_jsxs(SectionHeader, {
154
- children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
155
- icon: Filter,
156
- color: night.color.textSecondary,
157
- size: 12
158
- }), /*#__PURE__*/_jsx(SectionHeader.Title, {
159
- children: "HOW EVENT FILTERS WORK"
160
- })]
161
- }), /*#__PURE__*/_jsx(Text, {
162
- style: styles.howItWorksText,
163
- children: "Patterns hide matching stores from both the Stores tab and the Events tab. Tap a store above to quickly add it as a filter."
164
- }), /*#__PURE__*/_jsxs(View, {
165
- style: styles.examplesContainer,
166
- children: [/*#__PURE__*/_jsx(Text, {
167
- style: styles.examplesTitle,
168
- children: "EXAMPLES:"
169
- }), ["• auth → hides authStore from stores & events", "• temp → hides tempStore, tempUserStore", "• cart → hides cartStore from both tabs"].map(ex => /*#__PURE__*/_jsx(Text, {
170
- style: styles.exampleItem,
171
- children: ex
172
- }, ex))]
173
- })]
174
- })]
22
+ const entities = useMemo(() => stores.map(store => ({
23
+ name: store.name,
24
+ color: zustandStateStore.getStoreColor(store.name)
25
+ })), [stores]);
26
+ return /*#__PURE__*/_jsx(EntityFilterView, {
27
+ entities: entities,
28
+ ignoredPatterns: ignoredPatterns,
29
+ onTogglePattern: onTogglePattern,
30
+ onAddPattern: onAddPattern,
31
+ entityNoun: "STORES",
32
+ entitySingular: "store",
33
+ entityKey: "store",
34
+ testIDPrefix: ZUSTAND_TEST_ID,
35
+ slug: slugForTestID,
36
+ howItWorks: "Patterns hide matching stores from both the Stores tab and the Events tab. Tap a store above to quickly add it as a filter.",
37
+ examples: EXAMPLES
175
38
  });
176
- }
177
- const styles = StyleSheet.create({
178
- container: {
179
- flex: 1,
180
- // Transparent so the night sheet's star field shows through.
181
- backgroundColor: "transparent"
182
- },
183
- scrollContent: {
184
- paddingTop: 16,
185
- paddingHorizontal: 16,
186
- paddingBottom: 24,
187
- gap: 16
188
- },
189
- section: {
190
- backgroundColor: night.color.surface,
191
- borderRadius: 16,
192
- borderWidth: 1,
193
- borderColor: night.color.border,
194
- overflow: "hidden"
195
- },
196
- storeList: {
197
- maxHeight: 180
198
- },
199
- storeListContent: {
200
- paddingHorizontal: 16,
201
- paddingTop: 8,
202
- paddingBottom: 16,
203
- gap: 10
204
- },
205
- storeRow: {
206
- flexDirection: "row",
207
- alignItems: "center",
208
- gap: 10,
209
- paddingVertical: 10,
210
- paddingHorizontal: 12,
211
- backgroundColor: night.color.surfaceElevated,
212
- borderRadius: 8,
213
- borderWidth: 1,
214
- borderColor: night.color.border
215
- },
216
- storeRowFiltered: {
217
- opacity: 0.45
218
- },
219
- storeDot: {
220
- width: 7,
221
- height: 7,
222
- borderRadius: 3.5
223
- },
224
- storeRowText: {
225
- flex: 1,
226
- fontSize: 12,
227
- fontWeight: "600",
228
- fontFamily: "monospace"
229
- },
230
- storeRowTextFiltered: {
231
- color: night.color.textTertiary,
232
- textDecorationLine: "line-through"
233
- },
234
- filteredLabel: {
235
- fontSize: 10,
236
- fontWeight: "500",
237
- color: night.color.textTertiary,
238
- fontFamily: "monospace"
239
- },
240
- addLabel: {
241
- fontSize: 10,
242
- fontWeight: "600",
243
- fontFamily: "monospace"
244
- },
245
- addInputWrapper: {
246
- paddingHorizontal: 16,
247
- paddingTop: 8,
248
- paddingBottom: 8
249
- },
250
- patternList: {
251
- paddingHorizontal: 16,
252
- paddingBottom: 16,
253
- gap: 8
254
- },
255
- patternRow: {
256
- flexDirection: "row",
257
- alignItems: "center",
258
- justifyContent: "space-between",
259
- paddingVertical: 10,
260
- paddingHorizontal: 12,
261
- backgroundColor: night.color.surfaceElevated,
262
- borderRadius: 8,
263
- borderWidth: 1,
264
- borderColor: night.color.border,
265
- gap: 8
266
- },
267
- patternText: {
268
- flex: 1,
269
- fontSize: 12,
270
- fontFamily: "monospace",
271
- color: night.color.text
272
- },
273
- howItWorksText: {
274
- fontSize: 11,
275
- color: night.color.textSecondary,
276
- lineHeight: 16,
277
- marginTop: 8,
278
- paddingHorizontal: 16,
279
- fontFamily: "monospace"
280
- },
281
- examplesContainer: {
282
- paddingTop: 8,
283
- paddingHorizontal: 16,
284
- paddingBottom: 16,
285
- borderTopWidth: 1,
286
- borderTopColor: night.color.border,
287
- marginTop: 12
288
- },
289
- examplesTitle: {
290
- fontSize: 10,
291
- fontWeight: "600",
292
- color: night.color.textTertiary,
293
- fontFamily: "monospace",
294
- letterSpacing: 0.5,
295
- marginBottom: 6
296
- },
297
- exampleItem: {
298
- fontSize: 10,
299
- color: night.color.textTertiary,
300
- fontFamily: "monospace",
301
- lineHeight: 16
302
- }
303
- });
39
+ }
@@ -9,8 +9,8 @@
9
9
  */
10
10
 
11
11
  import { useState, useMemo, useRef, useCallback, useEffect } from "react";
12
- import { View, Text, StyleSheet, FlatList, TextInput, TouchableOpacity } from "react-native";
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";
12
+ import { View, Text, StyleSheet, FlatList, TouchableOpacity } from "react-native";
13
+ import { JsModal, ModalHeader, TabSelector, macOSColors, night, ToolEmptyState, resolveListEmptyState, Search, Filter, Power, Lock, devToolsStorageKeys, ProUpgradeModal, PowerToggleButton, ToolbarCopyButton, ToolbarClearButton, truncatePayload, TickProvider, useLimit, windowDragProps, EVENT_LIST_PRESET } 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";
@@ -20,17 +20,6 @@ import { ZustandStoreDetailContent } from "./ZustandStoreDetailContent";
20
20
  import { ZustandEventFilterView } from "./ZustandEventFilterView";
21
21
  import { ZUSTAND_TEST_ID, buildChangeRowTestIDs } from "../utils/testIDs";
22
22
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
23
- /**
24
- * Cell clipping, off in e2e bundles only.
25
- *
26
- * `removeClippedSubviews` unmounts the views of rows scrolled out of the
27
- * window, which is what makes a long change list cheap — and which makes
28
- * Detox report a perfectly rendered row as un-hittable. The env var is
29
- * inlined at bundle time, so a bundler that does NOT inline it leaves the
30
- * production behaviour exactly as it was.
31
- */
32
- const CLIP_OFFSCREEN_CELLS = !(typeof process !== "undefined" && process.env?.EXPO_PUBLIC_E2E);
33
-
34
23
  /**
35
24
  * The change log's "nothing here". Everything but the nouns is the shared
36
25
  * precedence — see `ToolEmptyState` in shared-ui for why it lives there.
@@ -385,49 +374,24 @@ export function ZustandModal({
385
374
  onBack: onBack
386
375
  }), /*#__PURE__*/_jsx(ModalHeader.Content, {
387
376
  title: "",
388
- children: isSearchActive ? /*#__PURE__*/_jsxs(View, {
389
- style: styles.headerSearchContainer,
390
- testID: `${ZUSTAND_TEST_ID}-search`,
391
- children: [/*#__PURE__*/_jsx(Search, {
392
- size: 14,
393
- color: night.color.textSecondary
394
- }), /*#__PURE__*/_jsx(TextInput, {
395
- ref: searchInputRef,
396
- testID: `${ZUSTAND_TEST_ID}-search-field`,
397
- style: styles.headerSearchInput,
398
- placeholder: activeTab === "stores" ? "Search stores..." : "Search events...",
399
- placeholderTextColor: night.color.placeholder,
400
- value: searchText,
401
- onChangeText: handleSearch,
402
- onSubmitEditing: () => searchInputRef.current?.blur()
403
- // Stay open while the query is still narrowing the list.
404
- //
405
- // Collapsing on blur hid the reason rows were missing: the
406
- // header went back to its TABS — whose counts are of the
407
- // WHOLE capture on the stores side — while the list stayed
408
- // filtered, so the tool showed "Stores (4)" above a single
409
- // row and offered nothing to explain the gap. The only way
410
- // back was to reopen a field the user had no reason to think
411
- // was still armed.
412
- ,
413
- onBlur: () => {
414
- if (searchText.length === 0) setIsSearchActive(false);
415
- },
416
- accessibilityLabel: "Search Zustand stores and events",
417
- autoCapitalize: "none",
418
- autoCorrect: false,
419
- returnKeyType: "search"
420
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
421
- onPress: closeSearch,
422
- style: styles.headerSearchClear,
423
- testID: `${ZUSTAND_TEST_ID}-search-clear`,
424
- accessibilityRole: "button",
425
- accessibilityLabel: "Close search",
426
- children: /*#__PURE__*/_jsx(X, {
427
- size: 14,
428
- color: night.color.textSecondary
429
- })
430
- })]
377
+ children: isSearchActive ? /*#__PURE__*/_jsx(ModalHeader.Search, {
378
+ value: searchText,
379
+ onChangeText: handleSearch,
380
+ onCancel: closeSearch,
381
+ placeholder: activeTab === "stores" ? "Search stores..." : "Search events...",
382
+ accessibilityLabel: "Search Zustand stores and events",
383
+ iconSize: 14,
384
+ testID: `${ZUSTAND_TEST_ID}-search-field`,
385
+ containerTestID: `${ZUSTAND_TEST_ID}-search`,
386
+ clearTestID: `${ZUSTAND_TEST_ID}-search-clear`
387
+ // Stay open while the query is still narrowing the list: the
388
+ // header would otherwise go back to its TABS — whose counts are
389
+ // of the WHOLE capture — above a filtered list, with nothing to
390
+ // explain the gap.
391
+ ,
392
+ onBlur: () => {
393
+ if (searchText.length === 0) setIsSearchActive(false);
394
+ }
431
395
  }) : /*#__PURE__*/_jsx(TabSelector, {
432
396
  tabs: [{
433
397
  key: "stores",
@@ -522,11 +486,7 @@ export function ZustandModal({
522
486
  keyExtractor: keyExtractor,
523
487
  contentContainerStyle: styles.listContent,
524
488
  showsVerticalScrollIndicator: true,
525
- removeClippedSubviews: CLIP_OFFSCREEN_CELLS,
526
- initialNumToRender: 12,
527
- maxToRenderPerBatch: 8,
528
- windowSize: 5,
529
- updateCellsBatchingPeriod: 50
489
+ ...EVENT_LIST_PRESET
530
490
  }) : /*#__PURE__*/_jsx(ToolEmptyState, {
531
491
  testID: `${ZUSTAND_TEST_ID}-history-empty`,
532
492
  ...resolveListEmptyState({
@@ -622,11 +582,7 @@ export function ZustandModal({
622
582
  keyExtractor: keyExtractor,
623
583
  contentContainerStyle: styles.listContent,
624
584
  showsVerticalScrollIndicator: true,
625
- removeClippedSubviews: CLIP_OFFSCREEN_CELLS,
626
- initialNumToRender: 12,
627
- maxToRenderPerBatch: 8,
628
- windowSize: 5,
629
- updateCellsBatchingPeriod: 50
585
+ ...EVENT_LIST_PRESET
630
586
  }) : /*#__PURE__*/_jsx(EventsEmptyState, {
631
587
  isEnabled: isEnabled,
632
588
  searchText: searchText,
@@ -739,27 +695,6 @@ const styles = StyleSheet.create({
739
695
  // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
740
696
  backgroundColor: "transparent"
741
697
  },
742
- headerSearchContainer: {
743
- flexDirection: "row",
744
- alignItems: "center",
745
- backgroundColor: night.color.surfaceElevated,
746
- borderRadius: night.radius.row,
747
- borderWidth: 1,
748
- borderColor: night.color.border,
749
- paddingHorizontal: 12,
750
- paddingVertical: 5
751
- },
752
- headerSearchInput: {
753
- flex: 1,
754
- color: night.color.text,
755
- fontSize: 13,
756
- marginLeft: 6,
757
- paddingVertical: 2
758
- },
759
- headerSearchClear: {
760
- marginLeft: 6,
761
- padding: 4
762
- },
763
698
  headerActionButton: {
764
699
  width: 32,
765
700
  height: 32,
@@ -7,10 +7,9 @@
7
7
  */
8
8
 
9
9
  import { memo } from "react";
10
- import { View, Text, StyleSheet } from "react-native";
11
- import { CompactRow, buoyColors, night, Zap, Clock, RelativeTime } from "@buoy-gg/shared-ui";
10
+ import { ChangeRow, buoyColors, night } from "@buoy-gg/shared-ui";
12
11
  import { zustandStateStore } from "../utils/zustandStateStore";
13
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
+ import { jsx as _jsx } from "react/jsx-runtime";
14
13
  /**
15
14
  * Get status label based on category
16
15
  */
@@ -99,68 +98,16 @@ export const ZustandStateChangeItem = /*#__PURE__*/memo(function ZustandStateCha
99
98
  const sublabel = getSublabel(change);
100
99
  const primaryText = getPrimaryText(change);
101
100
  const badgeText = getBadgeText(change);
102
-
103
- // Always a custom badge, even when there is no icon to hang off it. The
104
- // alternative — letting CompactRow draw `badgeText` itself — leaves the one
105
- // row whose badge matters most (a set that changed NOTHING) as the only row
106
- // in the list with no handle on its badge.
107
- const customBadge = /*#__PURE__*/_jsxs(View, {
108
- style: styles.badgeContainer,
109
- children: [/*#__PURE__*/_jsx(Text, {
110
- style: [styles.badgeText, {
111
- color: statusColor
112
- }],
113
- testID: testID ? `${testID}-badge` : undefined,
114
- children: badgeText
115
- }), change.isSlowUpdate ? /*#__PURE__*/_jsx(View, {
116
- style: styles.slowBadge,
117
- testID: testID ? `${testID}-slow` : undefined,
118
- children: /*#__PURE__*/_jsx(Clock, {
119
- size: 12,
120
- color: buoyColors.error
121
- })
122
- }) : change.hasStateChange ? /*#__PURE__*/_jsx(View, {
123
- style: styles.changeBadge,
124
- testID: testID ? `${testID}-change` : undefined,
125
- children: /*#__PURE__*/_jsx(Zap, {
126
- size: 12,
127
- color: buoyColors.warning
128
- })
129
- }) : null]
130
- });
131
- return /*#__PURE__*/_jsx(CompactRow, {
132
- statusDotColor: statusColor,
101
+ return /*#__PURE__*/_jsx(ChangeRow, {
102
+ statusColor: statusColor,
133
103
  statusLabel: statusLabel,
134
- statusSublabel: sublabel,
104
+ sublabel: sublabel,
135
105
  primaryText: primaryText,
136
- bottomRightText: /*#__PURE__*/_jsx(RelativeTime, {
137
- timestamp: change.timestamp
138
- }),
139
- customBadge: customBadge,
140
- badgeColor: statusColor,
141
- showChevron: true,
106
+ timestamp: change.timestamp,
107
+ badgeText: badgeText,
108
+ changed: change.hasStateChange,
109
+ slow: change.isSlowUpdate,
142
110
  onPress: () => onPress(change),
143
- testID: testID,
144
- statusTestID: testID ? `${testID}-status` : undefined,
145
- statusSublabelTestID: testID ? `${testID}-sublabel` : undefined,
146
- primaryTestID: testID ? `${testID}-keys` : undefined
111
+ testID: testID
147
112
  });
148
- });
149
- const styles = StyleSheet.create({
150
- badgeContainer: {
151
- flexDirection: "row",
152
- alignItems: "center",
153
- gap: 4
154
- },
155
- badgeText: {
156
- fontSize: 11,
157
- fontWeight: "600",
158
- fontFamily: "monospace"
159
- },
160
- changeBadge: {
161
- paddingHorizontal: 4
162
- },
163
- slowBadge: {
164
- paddingHorizontal: 4
165
- }
166
113
  });
@@ -13,7 +13,7 @@
13
13
 
14
14
  import React, { memo, useCallback, useState, useEffect } from "react";
15
15
  import { View, Text, StyleSheet, ScrollView } from "react-native";
16
- import { buoyColors, night, Clock, AlertTriangle, Zap, Info, Box, Database, parseValue, ProUpgradeModal, copyToClipboard } from "@buoy-gg/shared-ui";
16
+ import { buoyColors, night, Clock, AlertTriangle, Zap, Info, Box, Database, parseValue, ProUpgradeModal, copyToClipboard, InfoRow } from "@buoy-gg/shared-ui";
17
17
  import { useIsPro } from "@buoy-gg/license";
18
18
  import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
19
19
  import { ZustandActionButton } from "./ZustandActionButton";
@@ -74,29 +74,6 @@ function SectionHeader({
74
74
  }), badge]
75
75
  });
76
76
  }
77
- function InfoRow({
78
- label,
79
- value,
80
- valueColor,
81
- mono = false,
82
- testID
83
- }) {
84
- return /*#__PURE__*/_jsxs(View, {
85
- style: styles.infoRow,
86
- children: [/*#__PURE__*/_jsx(Text, {
87
- style: styles.infoLabel,
88
- children: label
89
- }), /*#__PURE__*/_jsx(Text, {
90
- testID: testID,
91
- style: [styles.infoValue, mono && styles.infoValueMono, valueColor ? {
92
- color: valueColor
93
- } : undefined],
94
- numberOfLines: 1,
95
- ellipsizeMode: "middle",
96
- children: value
97
- })]
98
- });
99
- }
100
77
  function formatTime(timestamp) {
101
78
  const date = new Date(timestamp);
102
79
  const hours = date.getHours().toString().padStart(2, "0");
@@ -361,27 +338,6 @@ const styles = StyleSheet.create({
361
338
  padding: 14,
362
339
  gap: 10
363
340
  },
364
- infoRow: {
365
- flexDirection: "row",
366
- alignItems: "center",
367
- justifyContent: "space-between"
368
- },
369
- infoLabel: {
370
- fontSize: 11,
371
- color: night.color.textTertiary,
372
- fontWeight: "500"
373
- },
374
- infoValue: {
375
- fontSize: 11,
376
- color: night.color.text,
377
- fontWeight: "500",
378
- flex: 1,
379
- textAlign: "right",
380
- marginLeft: 12
381
- },
382
- infoValueMono: {
383
- fontFamily: "monospace"
384
- },
385
341
  badgeRow: {
386
342
  flexDirection: "row",
387
343
  flexWrap: "wrap",