@buoy-gg/network 5.0.0 → 6.0.1
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 +29 -353
- package/lib/commonjs/index.js +14 -0
- package/lib/commonjs/network/components/NetworkEventItemCompact.js +32 -18
- package/lib/commonjs/network/components/NetworkHeaderLive.js +199 -0
- package/lib/commonjs/network/components/NetworkListScreen.js +203 -0
- package/lib/commonjs/network/components/NetworkModal.js +153 -630
- package/lib/commonjs/network/hooks/useNetworkBadgeCounts.js +83 -0
- package/lib/commonjs/network/hooks/useNetworkEventList.js +93 -0
- package/lib/commonjs/network/hooks/useNetworkHasMatches.js +56 -0
- package/lib/commonjs/network/utils/filterNetworkEvents.js +96 -0
- package/lib/commonjs/network/utils/generateNetworkCopyText.js +139 -0
- package/lib/module/index.js +5 -0
- package/lib/module/network/components/NetworkEventItemCompact.js +32 -18
- package/lib/module/network/components/NetworkHeaderLive.js +193 -0
- package/lib/module/network/components/NetworkListScreen.js +199 -0
- package/lib/module/network/components/NetworkModal.js +157 -634
- package/lib/module/network/hooks/useNetworkBadgeCounts.js +79 -0
- package/lib/module/network/hooks/useNetworkEventList.js +89 -0
- package/lib/module/network/hooks/useNetworkHasMatches.js +52 -0
- package/lib/module/network/utils/filterNetworkEvents.js +92 -0
- package/lib/module/network/utils/generateNetworkCopyText.js +135 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +0 -4
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts +52 -0
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkListScreen.d.ts +27 -0
- package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
- package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts +24 -0
- package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts.map +1 -0
- package/lib/typescript/network/hooks/useNetworkEventList.d.ts +34 -0
- package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +1 -0
- package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts +23 -0
- package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts.map +1 -0
- package/lib/typescript/network/utils/filterNetworkEvents.d.ts +13 -0
- package/lib/typescript/network/utils/filterNetworkEvents.d.ts.map +1 -0
- package/lib/typescript/network/utils/generateNetworkCopyText.d.ts +11 -0
- package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The header pieces that consume LIVE event data, decomposed out of
|
|
5
|
+
* NetworkModal per rule3 (decompose by responsibility) so each one is its
|
|
6
|
+
* own narrow store subscriber and the modal root/header never re-render
|
|
7
|
+
* per network event:
|
|
8
|
+
*
|
|
9
|
+
* - `NetworkHeaderBadges` — success/error/pending count chips
|
|
10
|
+
* - `NetworkClearButton` — trash button (enabled = store has events;
|
|
11
|
+
* clear handler co-located here per rule2)
|
|
12
|
+
* - `NetworkHeaderCopyButton` — copy-all button; the copy TEXT is
|
|
13
|
+
* resolved lazily at press time (CopyButton accepts a function value —
|
|
14
|
+
* rule2's latest-ref pattern), so no string is built per event
|
|
15
|
+
*
|
|
16
|
+
* Profiling evidence for this decomposition: tool-bench 2026-07-06 —
|
|
17
|
+
* Trash2/CopyButton re-rendered 15× for 15 network events when the
|
|
18
|
+
* subscription lived at the modal root.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
22
|
+
import { CheckCircle, XCircle, Clock, Copy, Trash2, CopyButton, macOSColors } from "@buoy-gg/shared-ui";
|
|
23
|
+
import { networkEventStore } from "../utils/networkEventStore";
|
|
24
|
+
import { useNetworkBadgeCounts } from "../hooks/useNetworkBadgeCounts";
|
|
25
|
+
import { useNetworkHasEvents, useNetworkHasMatches } from "../hooks/useNetworkHasMatches";
|
|
26
|
+
import { generateNetworkCopyText } from "../utils/generateNetworkCopyText";
|
|
27
|
+
import { applyIgnoredPatterns, filterNetworkEvents } from "../utils/filterNetworkEvents";
|
|
28
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
29
|
+
/** Success / error / pending chips — tap to toggle the status filter. */
|
|
30
|
+
export function NetworkHeaderBadges({
|
|
31
|
+
activeStatus,
|
|
32
|
+
onToggleStatus,
|
|
33
|
+
ignoredPatterns,
|
|
34
|
+
paused
|
|
35
|
+
}) {
|
|
36
|
+
const counts = useNetworkBadgeCounts({
|
|
37
|
+
ignoredPatterns,
|
|
38
|
+
paused
|
|
39
|
+
});
|
|
40
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
41
|
+
style: styles.headerChipRow,
|
|
42
|
+
children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
43
|
+
style: [styles.headerChip, activeStatus === "success" && styles.headerChipActive],
|
|
44
|
+
onPress: () => onToggleStatus("success"),
|
|
45
|
+
children: [/*#__PURE__*/_jsx(CheckCircle, {
|
|
46
|
+
size: 12,
|
|
47
|
+
color: macOSColors.semantic.success
|
|
48
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
49
|
+
style: [styles.headerChipValue, {
|
|
50
|
+
color: macOSColors.semantic.success
|
|
51
|
+
}],
|
|
52
|
+
children: counts.successful
|
|
53
|
+
})]
|
|
54
|
+
}), /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
55
|
+
style: [styles.headerChip, activeStatus === "error" && styles.headerChipActive],
|
|
56
|
+
onPress: () => onToggleStatus("error"),
|
|
57
|
+
children: [/*#__PURE__*/_jsx(XCircle, {
|
|
58
|
+
size: 12,
|
|
59
|
+
color: macOSColors.semantic.error
|
|
60
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
61
|
+
style: [styles.headerChipValue, {
|
|
62
|
+
color: macOSColors.semantic.error
|
|
63
|
+
}],
|
|
64
|
+
children: counts.failed
|
|
65
|
+
})]
|
|
66
|
+
}), /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
67
|
+
style: [styles.headerChip, activeStatus === "pending" && styles.headerChipActive],
|
|
68
|
+
onPress: () => onToggleStatus("pending"),
|
|
69
|
+
children: [/*#__PURE__*/_jsx(Clock, {
|
|
70
|
+
size: 12,
|
|
71
|
+
color: macOSColors.semantic.warning
|
|
72
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
73
|
+
style: [styles.headerChipValue, {
|
|
74
|
+
color: macOSColors.semantic.warning
|
|
75
|
+
}],
|
|
76
|
+
children: counts.pending
|
|
77
|
+
})]
|
|
78
|
+
})]
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** Trash button — handler co-located; enabled while the store has events.
|
|
82
|
+
* Subscribes to a flip-guarded boolean, so it renders when the store goes
|
|
83
|
+
* empty↔non-empty, not on every count change. */
|
|
84
|
+
export function NetworkClearButton({
|
|
85
|
+
paused
|
|
86
|
+
}) {
|
|
87
|
+
const hasEvents = useNetworkHasEvents({
|
|
88
|
+
paused
|
|
89
|
+
});
|
|
90
|
+
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
91
|
+
"sentry-label": "ignore clear events",
|
|
92
|
+
onPress: () => networkEventStore.clearEvents(),
|
|
93
|
+
style: [styles.headerActionButton, !hasEvents && styles.headerActionButtonDisabled],
|
|
94
|
+
disabled: !hasEvents,
|
|
95
|
+
children: /*#__PURE__*/_jsx(Trash2, {
|
|
96
|
+
size: 14,
|
|
97
|
+
color: hasEvents ? macOSColors.text.muted : macOSColors.text.disabled
|
|
98
|
+
})
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Copy-all-requests button. Subscribes to a flip-guarded boolean (its
|
|
103
|
+
* enabled state); the copy text is generated at PRESS time from a live
|
|
104
|
+
* store read, so there's no per-event work and no stale closure.
|
|
105
|
+
*/
|
|
106
|
+
export function NetworkHeaderCopyButton({
|
|
107
|
+
isPro,
|
|
108
|
+
filter,
|
|
109
|
+
ignoredPatterns,
|
|
110
|
+
copySettings,
|
|
111
|
+
paused,
|
|
112
|
+
onUpgradePress
|
|
113
|
+
}) {
|
|
114
|
+
const hasEvents = useNetworkHasMatches({
|
|
115
|
+
filter,
|
|
116
|
+
ignoredPatterns,
|
|
117
|
+
paused
|
|
118
|
+
});
|
|
119
|
+
if (!isPro) {
|
|
120
|
+
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
121
|
+
"sentry-label": "ignore copy pro",
|
|
122
|
+
onPress: onUpgradePress,
|
|
123
|
+
style: [styles.headerActionButton, !hasEvents && styles.headerActionButtonDisabled],
|
|
124
|
+
disabled: !hasEvents,
|
|
125
|
+
children: /*#__PURE__*/_jsx(Copy, {
|
|
126
|
+
size: 14,
|
|
127
|
+
color: hasEvents ? macOSColors.text.secondary : macOSColors.text.disabled
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return /*#__PURE__*/_jsx(CopyButton
|
|
132
|
+
// Lazy value: resolved at copy time by CopyButton's latest-ref
|
|
133
|
+
// pattern, reading the LIVE store — no per-event work, no staleness.
|
|
134
|
+
, {
|
|
135
|
+
value: () => generateNetworkCopyText(filterNetworkEvents(applyIgnoredPatterns(networkEventStore.getEvents(), ignoredPatterns), filter), copySettings),
|
|
136
|
+
size: 14,
|
|
137
|
+
buttonStyle: styles.headerActionButton,
|
|
138
|
+
colors: {
|
|
139
|
+
idle: hasEvents ? macOSColors.text.secondary : macOSColors.text.disabled,
|
|
140
|
+
success: macOSColors.semantic.success,
|
|
141
|
+
error: macOSColors.semantic.error
|
|
142
|
+
},
|
|
143
|
+
disabled: !hasEvents
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const styles = StyleSheet.create({
|
|
147
|
+
headerChipRow: {
|
|
148
|
+
flexDirection: "row",
|
|
149
|
+
alignItems: "center",
|
|
150
|
+
gap: 8
|
|
151
|
+
},
|
|
152
|
+
headerChip: {
|
|
153
|
+
flexDirection: "row",
|
|
154
|
+
alignItems: "center",
|
|
155
|
+
gap: 4,
|
|
156
|
+
backgroundColor: macOSColors.background.hover,
|
|
157
|
+
paddingHorizontal: 10,
|
|
158
|
+
paddingVertical: 5,
|
|
159
|
+
borderRadius: 12,
|
|
160
|
+
borderWidth: 1,
|
|
161
|
+
borderColor: macOSColors.border.default
|
|
162
|
+
},
|
|
163
|
+
headerChipActive: {
|
|
164
|
+
backgroundColor: macOSColors.semantic.infoBackground,
|
|
165
|
+
borderColor: macOSColors.semantic.info + "50",
|
|
166
|
+
shadowColor: macOSColors.semantic.info,
|
|
167
|
+
shadowOffset: {
|
|
168
|
+
width: 0,
|
|
169
|
+
height: 1
|
|
170
|
+
},
|
|
171
|
+
shadowOpacity: 0.08,
|
|
172
|
+
shadowRadius: 2,
|
|
173
|
+
elevation: 1
|
|
174
|
+
},
|
|
175
|
+
headerChipValue: {
|
|
176
|
+
fontSize: 12,
|
|
177
|
+
fontWeight: "600",
|
|
178
|
+
fontFamily: "monospace"
|
|
179
|
+
},
|
|
180
|
+
headerActionButton: {
|
|
181
|
+
width: 32,
|
|
182
|
+
height: 32,
|
|
183
|
+
borderRadius: 8,
|
|
184
|
+
backgroundColor: macOSColors.background.hover,
|
|
185
|
+
borderWidth: 1,
|
|
186
|
+
borderColor: macOSColors.border.default,
|
|
187
|
+
alignItems: "center",
|
|
188
|
+
justifyContent: "center"
|
|
189
|
+
},
|
|
190
|
+
headerActionButtonDisabled: {
|
|
191
|
+
opacity: 0.55
|
|
192
|
+
}
|
|
193
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The network tool's LIST screen — owns the event-store subscription, the
|
|
5
|
+
* FlatList, empty state, disabled banner, and the free-tier locked-history
|
|
6
|
+
* footer. Decomposed from NetworkModal per rule3 so per-event re-renders
|
|
7
|
+
* are confined to this subtree (plus the header's live leaves) instead of
|
|
8
|
+
* the whole modal.
|
|
9
|
+
*
|
|
10
|
+
* Stable-tree contract (rule4): hooks run unconditionally, then the
|
|
11
|
+
* component returns null when not visible. The subscription is gated with
|
|
12
|
+
* `paused || !visible` so a hidden list doesn't subscribe at all.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { FlatList, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
16
|
+
import { Globe, Power, ProBadge, Zap, macOSColors } from "@buoy-gg/shared-ui";
|
|
17
|
+
import { NetworkEventItemCompact } from "./NetworkEventItemCompact";
|
|
18
|
+
import { useNetworkEventList } from "../hooks/useNetworkEventList";
|
|
19
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
20
|
+
function EmptyState({
|
|
21
|
+
isEnabled
|
|
22
|
+
}) {
|
|
23
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
24
|
+
style: styles.emptyState,
|
|
25
|
+
children: [/*#__PURE__*/_jsx(Globe, {
|
|
26
|
+
size: 32,
|
|
27
|
+
color: macOSColors.text.muted
|
|
28
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
29
|
+
style: styles.emptyTitle,
|
|
30
|
+
children: "No network events"
|
|
31
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
32
|
+
style: styles.emptyText,
|
|
33
|
+
children: isEnabled ? "Network requests will appear here" : "Enable interception to start capturing"
|
|
34
|
+
})]
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const keyExtractor = item => item.id;
|
|
38
|
+
export function NetworkListScreen({
|
|
39
|
+
visible,
|
|
40
|
+
paused,
|
|
41
|
+
filter,
|
|
42
|
+
ignoredPatterns,
|
|
43
|
+
isPro,
|
|
44
|
+
searchText,
|
|
45
|
+
onEventPress,
|
|
46
|
+
onUpgradePress
|
|
47
|
+
}) {
|
|
48
|
+
const {
|
|
49
|
+
visibleEvents,
|
|
50
|
+
hasLockedEvents,
|
|
51
|
+
lockedEventCount
|
|
52
|
+
} = useNetworkEventList({
|
|
53
|
+
filter,
|
|
54
|
+
ignoredPatterns,
|
|
55
|
+
paused: paused || !visible,
|
|
56
|
+
isPro
|
|
57
|
+
});
|
|
58
|
+
if (!visible) return null;
|
|
59
|
+
const isEnabled = !paused;
|
|
60
|
+
const renderItem = ({
|
|
61
|
+
item
|
|
62
|
+
}) => /*#__PURE__*/_jsx(NetworkEventItemCompact, {
|
|
63
|
+
event: item,
|
|
64
|
+
onPress: onEventPress
|
|
65
|
+
});
|
|
66
|
+
const renderListFooter = () => {
|
|
67
|
+
if (!hasLockedEvents) return null;
|
|
68
|
+
const isSearching = searchText.length > 0;
|
|
69
|
+
const bannerText = isSearching ? `${lockedEventCount} matching ${lockedEventCount === 1 ? "request" : "requests"} in locked history` : `${lockedEventCount} requests locked`;
|
|
70
|
+
const subtextMessage = isSearching ? "Upgrade to Pro to search your full request history" : "Upgrade to Pro to unlock full request history";
|
|
71
|
+
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
72
|
+
style: styles.limitBanner,
|
|
73
|
+
onPress: onUpgradePress,
|
|
74
|
+
activeOpacity: 0.8,
|
|
75
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
76
|
+
style: styles.limitBannerContent,
|
|
77
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
78
|
+
style: styles.limitBannerLeft,
|
|
79
|
+
children: [/*#__PURE__*/_jsx(Zap, {
|
|
80
|
+
size: 16,
|
|
81
|
+
color: "#20C997"
|
|
82
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
83
|
+
style: styles.limitBannerText,
|
|
84
|
+
children: bannerText
|
|
85
|
+
})]
|
|
86
|
+
}), /*#__PURE__*/_jsx(ProBadge, {})]
|
|
87
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
88
|
+
style: styles.limitBannerSubtext,
|
|
89
|
+
children: subtextMessage
|
|
90
|
+
})]
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
94
|
+
children: [!isEnabled ? /*#__PURE__*/_jsxs(View, {
|
|
95
|
+
style: styles.disabledBanner,
|
|
96
|
+
children: [/*#__PURE__*/_jsx(Power, {
|
|
97
|
+
size: 14,
|
|
98
|
+
color: macOSColors.semantic.warning
|
|
99
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
100
|
+
style: styles.disabledText,
|
|
101
|
+
children: "Network interception is disabled"
|
|
102
|
+
})]
|
|
103
|
+
}) : null, visibleEvents.length > 0 || hasLockedEvents ? /*#__PURE__*/_jsx(FlatList, {
|
|
104
|
+
data: visibleEvents,
|
|
105
|
+
renderItem: renderItem,
|
|
106
|
+
keyExtractor: keyExtractor,
|
|
107
|
+
contentContainerStyle: styles.listContent,
|
|
108
|
+
showsVerticalScrollIndicator: true,
|
|
109
|
+
removeClippedSubviews: true,
|
|
110
|
+
onEndReachedThreshold: 0.8
|
|
111
|
+
// The list OWNS scrolling (NetworkModal passes
|
|
112
|
+
// disableScrollWrapper while this screen is visible) so FlatList
|
|
113
|
+
// virtualization actually works. The previous shipped config —
|
|
114
|
+
// scrollEnabled=false inside JsModal's ScrollView — mounted ALL
|
|
115
|
+
// rows: at the store's 500-event cap that measured 17 JS FPS /
|
|
116
|
+
// 133% CPU / 1.4GB vs ~53 JS FPS / 52% / 360MB virtualized
|
|
117
|
+
// (buoy-optimize batch 2026-07-06, batch-1783396983529-6l7w).
|
|
118
|
+
// Window/batch values are the batch's "tuned" winner.
|
|
119
|
+
,
|
|
120
|
+
scrollEnabled: true,
|
|
121
|
+
initialNumToRender: 12,
|
|
122
|
+
maxToRenderPerBatch: 8,
|
|
123
|
+
windowSize: 5,
|
|
124
|
+
updateCellsBatchingPeriod: 50,
|
|
125
|
+
"sentry-label": "ignore network events list",
|
|
126
|
+
ListFooterComponent: renderListFooter
|
|
127
|
+
}) : /*#__PURE__*/_jsx(EmptyState, {
|
|
128
|
+
isEnabled: isEnabled
|
|
129
|
+
})]
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const styles = StyleSheet.create({
|
|
133
|
+
disabledBanner: {
|
|
134
|
+
flexDirection: "row",
|
|
135
|
+
alignItems: "center",
|
|
136
|
+
gap: 8,
|
|
137
|
+
padding: 10,
|
|
138
|
+
marginHorizontal: 12,
|
|
139
|
+
marginTop: 8,
|
|
140
|
+
backgroundColor: macOSColors.semantic.warningBackground,
|
|
141
|
+
borderRadius: 8,
|
|
142
|
+
borderWidth: 1,
|
|
143
|
+
borderColor: macOSColors.semantic.warning + "20"
|
|
144
|
+
},
|
|
145
|
+
disabledText: {
|
|
146
|
+
color: macOSColors.semantic.warning,
|
|
147
|
+
fontSize: 11,
|
|
148
|
+
flex: 1
|
|
149
|
+
},
|
|
150
|
+
listContent: {
|
|
151
|
+
paddingTop: 8
|
|
152
|
+
},
|
|
153
|
+
emptyState: {
|
|
154
|
+
alignItems: "center",
|
|
155
|
+
paddingVertical: 40
|
|
156
|
+
},
|
|
157
|
+
emptyTitle: {
|
|
158
|
+
color: macOSColors.text.primary,
|
|
159
|
+
fontSize: 14,
|
|
160
|
+
fontWeight: "600",
|
|
161
|
+
marginTop: 12,
|
|
162
|
+
marginBottom: 6
|
|
163
|
+
},
|
|
164
|
+
emptyText: {
|
|
165
|
+
color: macOSColors.text.muted,
|
|
166
|
+
fontSize: 12,
|
|
167
|
+
textAlign: "center"
|
|
168
|
+
},
|
|
169
|
+
limitBanner: {
|
|
170
|
+
backgroundColor: "#1A1A1A",
|
|
171
|
+
borderRadius: 10,
|
|
172
|
+
borderWidth: 1,
|
|
173
|
+
borderColor: "#20C997" + "40",
|
|
174
|
+
padding: 12,
|
|
175
|
+
marginHorizontal: 12,
|
|
176
|
+
marginTop: 12,
|
|
177
|
+
marginBottom: 34
|
|
178
|
+
},
|
|
179
|
+
limitBannerContent: {
|
|
180
|
+
flexDirection: "row",
|
|
181
|
+
alignItems: "center",
|
|
182
|
+
justifyContent: "space-between",
|
|
183
|
+
marginBottom: 4
|
|
184
|
+
},
|
|
185
|
+
limitBannerLeft: {
|
|
186
|
+
flexDirection: "row",
|
|
187
|
+
alignItems: "center",
|
|
188
|
+
gap: 8
|
|
189
|
+
},
|
|
190
|
+
limitBannerText: {
|
|
191
|
+
color: "#E0E0E0",
|
|
192
|
+
fontSize: 13,
|
|
193
|
+
fontWeight: "600"
|
|
194
|
+
},
|
|
195
|
+
limitBannerSubtext: {
|
|
196
|
+
color: "#888888",
|
|
197
|
+
fontSize: 11
|
|
198
|
+
}
|
|
199
|
+
});
|