rn-backstage 1.1.0 → 1.2.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 +41 -19
- package/lib/commonjs/Backstage.js +42 -31
- package/lib/commonjs/Backstage.js.map +1 -1
- package/lib/commonjs/ThemeContext.js +43 -0
- package/lib/commonjs/ThemeContext.js.map +1 -0
- package/lib/commonjs/components/BackstagePanel.js +38 -16
- package/lib/commonjs/components/BackstagePanel.js.map +1 -1
- package/lib/commonjs/components/FlagsTab.js +256 -0
- package/lib/commonjs/components/FlagsTab.js.map +1 -0
- package/lib/commonjs/components/FloatingPill.js +4 -2
- package/lib/commonjs/components/FloatingPill.js.map +1 -1
- package/lib/commonjs/components/InfoTab.js +73 -107
- package/lib/commonjs/components/InfoTab.js.map +1 -1
- package/lib/commonjs/components/JsonTreeView.js +39 -34
- package/lib/commonjs/components/JsonTreeView.js.map +1 -1
- package/lib/commonjs/components/LogItem.js +33 -29
- package/lib/commonjs/components/LogItem.js.map +1 -1
- package/lib/commonjs/components/LogsTab.js +37 -42
- package/lib/commonjs/components/LogsTab.js.map +1 -1
- package/lib/commonjs/components/NetworkItem.js +111 -135
- package/lib/commonjs/components/NetworkItem.js.map +1 -1
- package/lib/commonjs/components/NetworkTab.js +48 -49
- package/lib/commonjs/components/NetworkTab.js.map +1 -1
- package/lib/commonjs/components/TabBar.js +8 -5
- package/lib/commonjs/components/TabBar.js.map +1 -1
- package/lib/commonjs/constants.js +24 -1
- package/lib/commonjs/constants.js.map +1 -1
- package/lib/commonjs/index.js +19 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/types.js +1 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/Backstage.js +42 -31
- package/lib/module/Backstage.js.map +1 -1
- package/lib/module/ThemeContext.js +37 -0
- package/lib/module/ThemeContext.js.map +1 -0
- package/lib/module/components/BackstagePanel.js +40 -18
- package/lib/module/components/BackstagePanel.js.map +1 -1
- package/lib/module/components/FlagsTab.js +251 -0
- package/lib/module/components/FlagsTab.js.map +1 -0
- package/lib/module/components/FloatingPill.js +5 -3
- package/lib/module/components/FloatingPill.js.map +1 -1
- package/lib/module/components/InfoTab.js +73 -107
- package/lib/module/components/InfoTab.js.map +1 -1
- package/lib/module/components/JsonTreeView.js +42 -38
- package/lib/module/components/JsonTreeView.js.map +1 -1
- package/lib/module/components/LogItem.js +35 -31
- package/lib/module/components/LogItem.js.map +1 -1
- package/lib/module/components/LogsTab.js +38 -43
- package/lib/module/components/LogsTab.js.map +1 -1
- package/lib/module/components/NetworkItem.js +113 -137
- package/lib/module/components/NetworkItem.js.map +1 -1
- package/lib/module/components/NetworkTab.js +49 -50
- package/lib/module/components/NetworkTab.js.map +1 -1
- package/lib/module/components/TabBar.js +10 -7
- package/lib/module/components/TabBar.js.map +1 -1
- package/lib/module/constants.js +23 -0
- package/lib/module/constants.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/Backstage.d.ts.map +1 -1
- package/lib/typescript/ThemeContext.d.ts +11 -0
- package/lib/typescript/ThemeContext.d.ts.map +1 -0
- package/lib/typescript/components/BackstagePanel.d.ts +4 -1
- package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
- package/lib/typescript/components/FlagsTab.d.ts +9 -0
- package/lib/typescript/components/FlagsTab.d.ts.map +1 -0
- package/lib/typescript/components/FloatingPill.d.ts.map +1 -1
- package/lib/typescript/components/InfoTab.d.ts +1 -0
- package/lib/typescript/components/InfoTab.d.ts.map +1 -1
- package/lib/typescript/components/JsonTreeView.d.ts.map +1 -1
- package/lib/typescript/components/LogItem.d.ts +1 -0
- package/lib/typescript/components/LogItem.d.ts.map +1 -1
- package/lib/typescript/components/LogsTab.d.ts +1 -0
- package/lib/typescript/components/LogsTab.d.ts.map +1 -1
- package/lib/typescript/components/NetworkItem.d.ts +1 -0
- package/lib/typescript/components/NetworkItem.d.ts.map +1 -1
- package/lib/typescript/components/NetworkTab.d.ts +1 -0
- package/lib/typescript/components/NetworkTab.d.ts.map +1 -1
- package/lib/typescript/components/TabBar.d.ts.map +1 -1
- package/lib/typescript/constants.d.ts +1 -0
- package/lib/typescript/constants.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +3 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +18 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Backstage.tsx +41 -31
- package/src/ThemeContext.tsx +38 -0
- package/src/components/BackstagePanel.tsx +84 -56
- package/src/components/FlagsTab.tsx +198 -0
- package/src/components/FloatingPill.tsx +6 -3
- package/src/components/InfoTab.tsx +152 -186
- package/src/components/JsonTreeView.tsx +53 -73
- package/src/components/LogItem.tsx +55 -100
- package/src/components/LogsTab.tsx +110 -132
- package/src/components/NetworkItem.tsx +306 -417
- package/src/components/NetworkTab.tsx +129 -163
- package/src/components/TabBar.tsx +51 -47
- package/src/constants.ts +23 -0
- package/src/index.ts +3 -0
- package/src/types.ts +25 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useCallback, useMemo, useState } from 'react'
|
|
2
2
|
import { FlatList, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
|
|
3
|
-
import {
|
|
3
|
+
import { MonospaceFont, TestIDs } from '../constants'
|
|
4
|
+
import { useBackstageTheme } from '../ThemeContext'
|
|
4
5
|
import { NetworkState } from '../types'
|
|
5
|
-
import type { NetworkEntry } from '../types'
|
|
6
|
+
import type { NetworkEntry, BackstageTheme } from '../types'
|
|
6
7
|
import { NetworkItem } from './NetworkItem'
|
|
7
8
|
|
|
8
9
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
@@ -12,17 +13,24 @@ interface NetworkTabProps {
|
|
|
12
13
|
onRefresh: () => void
|
|
13
14
|
onClear: () => void
|
|
14
15
|
onCopy?: (text: string) => void
|
|
16
|
+
jsonMaxDepth?: number
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
18
20
|
|
|
19
|
-
export const NetworkTab: React.FC<NetworkTabProps> = ({
|
|
21
|
+
export const NetworkTab: React.FC<NetworkTabProps> = ({
|
|
22
|
+
entries,
|
|
23
|
+
onRefresh,
|
|
24
|
+
onClear,
|
|
25
|
+
onCopy,
|
|
26
|
+
jsonMaxDepth,
|
|
27
|
+
}) => {
|
|
28
|
+
const theme = useBackstageTheme()
|
|
29
|
+
const s = useMemo(() => createStyles(theme), [theme])
|
|
20
30
|
const [searchText, setSearchText] = useState('')
|
|
21
31
|
|
|
22
|
-
// Filter entries based on search text
|
|
23
32
|
const filteredEntries = useMemo(() => {
|
|
24
33
|
if (!searchText.trim()) return entries
|
|
25
|
-
|
|
26
34
|
const query = searchText.toLowerCase()
|
|
27
35
|
return entries.filter(entry => {
|
|
28
36
|
return (
|
|
@@ -34,7 +42,6 @@ export const NetworkTab: React.FC<NetworkTabProps> = ({ entries, onRefresh, onCl
|
|
|
34
42
|
})
|
|
35
43
|
}, [entries, searchText])
|
|
36
44
|
|
|
37
|
-
// Summary stats
|
|
38
45
|
const stats = useMemo(() => {
|
|
39
46
|
const total = entries.length
|
|
40
47
|
const errors = entries.filter(
|
|
@@ -48,28 +55,28 @@ export const NetworkTab: React.FC<NetworkTabProps> = ({ entries, onRefresh, onCl
|
|
|
48
55
|
completed.length > 0
|
|
49
56
|
? Math.round(completed.reduce((sum, e) => sum + (e.duration ?? 0), 0) / completed.length)
|
|
50
57
|
: 0
|
|
51
|
-
|
|
52
58
|
return { total, errors, pending, avgDuration }
|
|
53
59
|
}, [entries])
|
|
54
60
|
|
|
55
61
|
const renderItem = useCallback(
|
|
56
|
-
({ item }: { item: NetworkEntry }) =>
|
|
62
|
+
({ item }: { item: NetworkEntry }) => (
|
|
63
|
+
<NetworkItem item={item} onCopy={onCopy} jsonMaxDepth={jsonMaxDepth} />
|
|
64
|
+
),
|
|
57
65
|
[onCopy],
|
|
58
66
|
)
|
|
59
67
|
|
|
60
68
|
const keyExtractor = useCallback((item: NetworkEntry) => item.id, [])
|
|
61
69
|
|
|
62
70
|
return (
|
|
63
|
-
<View style={
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<Text style={styles.searchIcon}>⌕</Text>
|
|
71
|
+
<View style={s.container}>
|
|
72
|
+
<View style={s.searchContainer}>
|
|
73
|
+
<View style={s.searchInputWrapper}>
|
|
74
|
+
<Text style={s.searchIcon}>⌕</Text>
|
|
68
75
|
<TextInput
|
|
69
76
|
testID={TestIDs.networkTab.searchInput}
|
|
70
|
-
style={
|
|
77
|
+
style={s.searchInput}
|
|
71
78
|
placeholder="Filter by URL, method, status..."
|
|
72
|
-
placeholderTextColor={
|
|
79
|
+
placeholderTextColor={theme.textMuted}
|
|
73
80
|
value={searchText}
|
|
74
81
|
onChangeText={setSearchText}
|
|
75
82
|
autoCapitalize="none"
|
|
@@ -80,39 +87,37 @@ export const NetworkTab: React.FC<NetworkTabProps> = ({ entries, onRefresh, onCl
|
|
|
80
87
|
</View>
|
|
81
88
|
<TouchableOpacity
|
|
82
89
|
testID={TestIDs.networkTab.clearButton}
|
|
83
|
-
style={
|
|
90
|
+
style={s.clearButton}
|
|
84
91
|
onPress={onClear}
|
|
85
92
|
activeOpacity={0.7}
|
|
86
93
|
>
|
|
87
|
-
<Text style={
|
|
94
|
+
<Text style={s.clearButtonText}>Clear</Text>
|
|
88
95
|
</TouchableOpacity>
|
|
89
96
|
</View>
|
|
90
97
|
|
|
91
|
-
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<Text style={styles.statText}>
|
|
98
|
+
<View style={s.statsBar}>
|
|
99
|
+
<View style={s.statsRow}>
|
|
100
|
+
<Text style={s.statText}>
|
|
95
101
|
{filteredEntries.length === entries.length
|
|
96
102
|
? `${stats.total} request${stats.total !== 1 ? 's' : ''}`
|
|
97
103
|
: `${filteredEntries.length} of ${stats.total}`}
|
|
98
104
|
</Text>
|
|
99
105
|
{stats.errors > 0 && (
|
|
100
|
-
<View style={
|
|
101
|
-
<Text style={
|
|
106
|
+
<View style={s.statBadge}>
|
|
107
|
+
<Text style={s.statBadgeError}>
|
|
102
108
|
{stats.errors} error{stats.errors !== 1 ? 's' : ''}
|
|
103
109
|
</Text>
|
|
104
110
|
</View>
|
|
105
111
|
)}
|
|
106
112
|
{stats.pending > 0 && (
|
|
107
|
-
<View style={[
|
|
108
|
-
<Text style={
|
|
113
|
+
<View style={[s.statBadge, s.statBadgePending]}>
|
|
114
|
+
<Text style={s.statBadgePendingText}>{stats.pending} pending</Text>
|
|
109
115
|
</View>
|
|
110
116
|
)}
|
|
111
117
|
</View>
|
|
112
|
-
{stats.avgDuration > 0 && <Text style={
|
|
118
|
+
{stats.avgDuration > 0 && <Text style={s.avgText}>avg {stats.avgDuration}ms</Text>}
|
|
113
119
|
</View>
|
|
114
120
|
|
|
115
|
-
{/* Request List */}
|
|
116
121
|
<FlatList
|
|
117
122
|
testID={TestIDs.networkTab.list}
|
|
118
123
|
data={filteredEntries}
|
|
@@ -120,18 +125,17 @@ export const NetworkTab: React.FC<NetworkTabProps> = ({ entries, onRefresh, onCl
|
|
|
120
125
|
keyExtractor={keyExtractor}
|
|
121
126
|
refreshing={false}
|
|
122
127
|
onRefresh={onRefresh}
|
|
123
|
-
style={
|
|
124
|
-
contentContainerStyle={filteredEntries.length === 0 ?
|
|
128
|
+
style={s.list}
|
|
129
|
+
contentContainerStyle={filteredEntries.length === 0 ? s.emptyContainer : undefined}
|
|
125
130
|
ListEmptyComponent={
|
|
126
|
-
<View style={
|
|
127
|
-
<Text style={
|
|
128
|
-
<Text style={
|
|
129
|
-
<Text style={
|
|
131
|
+
<View style={s.emptyState}>
|
|
132
|
+
<Text style={s.emptyIcon}>🌐</Text>
|
|
133
|
+
<Text style={s.emptyTitle}>No network requests</Text>
|
|
134
|
+
<Text style={s.emptySubtitle}>
|
|
130
135
|
HTTP requests made via fetch or XMLHttpRequest will appear here
|
|
131
136
|
</Text>
|
|
132
137
|
</View>
|
|
133
138
|
}
|
|
134
|
-
// Perf optimizations
|
|
135
139
|
maxToRenderPerBatch={20}
|
|
136
140
|
windowSize={10}
|
|
137
141
|
initialNumToRender={20}
|
|
@@ -143,132 +147,94 @@ export const NetworkTab: React.FC<NetworkTabProps> = ({ entries, onRefresh, onCl
|
|
|
143
147
|
|
|
144
148
|
// ─── Styles ──────────────────────────────────────────────────────────────────
|
|
145
149
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
flex: 1,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
alignItems: 'center',
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
avgText: {
|
|
238
|
-
fontFamily: MonospaceFont,
|
|
239
|
-
fontSize: 10,
|
|
240
|
-
color: DarkTheme.textMuted,
|
|
241
|
-
fontStyle: 'italic',
|
|
242
|
-
},
|
|
243
|
-
list: {
|
|
244
|
-
flex: 1,
|
|
245
|
-
},
|
|
246
|
-
emptyContainer: {
|
|
247
|
-
flex: 1,
|
|
248
|
-
justifyContent: 'center',
|
|
249
|
-
},
|
|
250
|
-
emptyState: {
|
|
251
|
-
alignItems: 'center',
|
|
252
|
-
justifyContent: 'center',
|
|
253
|
-
padding: 40,
|
|
254
|
-
},
|
|
255
|
-
emptyIcon: {
|
|
256
|
-
fontSize: 48,
|
|
257
|
-
marginBottom: 16,
|
|
258
|
-
opacity: 0.5,
|
|
259
|
-
},
|
|
260
|
-
emptyTitle: {
|
|
261
|
-
fontFamily: MonospaceFont,
|
|
262
|
-
fontSize: 16,
|
|
263
|
-
fontWeight: '700',
|
|
264
|
-
color: DarkTheme.textSecondary,
|
|
265
|
-
marginBottom: 8,
|
|
266
|
-
},
|
|
267
|
-
emptySubtitle: {
|
|
268
|
-
fontFamily: MonospaceFont,
|
|
269
|
-
fontSize: 13,
|
|
270
|
-
color: DarkTheme.textMuted,
|
|
271
|
-
textAlign: 'center',
|
|
272
|
-
lineHeight: 20,
|
|
273
|
-
},
|
|
274
|
-
})
|
|
150
|
+
const createStyles = (t: BackstageTheme) =>
|
|
151
|
+
StyleSheet.create({
|
|
152
|
+
container: { flex: 1 },
|
|
153
|
+
searchContainer: {
|
|
154
|
+
flexDirection: 'row',
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
paddingHorizontal: 14,
|
|
157
|
+
paddingVertical: 10,
|
|
158
|
+
gap: 10,
|
|
159
|
+
borderBottomWidth: 1,
|
|
160
|
+
borderBottomColor: t.border,
|
|
161
|
+
},
|
|
162
|
+
searchInputWrapper: {
|
|
163
|
+
flex: 1,
|
|
164
|
+
flexDirection: 'row',
|
|
165
|
+
alignItems: 'center',
|
|
166
|
+
backgroundColor: t.surfaceElevated,
|
|
167
|
+
borderRadius: 10,
|
|
168
|
+
borderWidth: 1,
|
|
169
|
+
borderColor: t.border,
|
|
170
|
+
paddingHorizontal: 12,
|
|
171
|
+
height: 38,
|
|
172
|
+
},
|
|
173
|
+
searchIcon: { fontSize: 16, color: t.textMuted, marginRight: 8 },
|
|
174
|
+
searchInput: {
|
|
175
|
+
flex: 1,
|
|
176
|
+
fontFamily: MonospaceFont,
|
|
177
|
+
fontSize: 13,
|
|
178
|
+
color: t.text,
|
|
179
|
+
padding: 0,
|
|
180
|
+
},
|
|
181
|
+
clearButton: {
|
|
182
|
+
backgroundColor: t.errorDim,
|
|
183
|
+
borderRadius: 8,
|
|
184
|
+
borderWidth: 1,
|
|
185
|
+
borderColor: t.error,
|
|
186
|
+
paddingHorizontal: 14,
|
|
187
|
+
height: 38,
|
|
188
|
+
justifyContent: 'center',
|
|
189
|
+
},
|
|
190
|
+
clearButtonText: {
|
|
191
|
+
fontFamily: MonospaceFont,
|
|
192
|
+
fontSize: 12,
|
|
193
|
+
fontWeight: '700',
|
|
194
|
+
color: t.error,
|
|
195
|
+
letterSpacing: 0.5,
|
|
196
|
+
},
|
|
197
|
+
statsBar: {
|
|
198
|
+
flexDirection: 'row',
|
|
199
|
+
justifyContent: 'space-between',
|
|
200
|
+
alignItems: 'center',
|
|
201
|
+
paddingHorizontal: 16,
|
|
202
|
+
paddingVertical: 6,
|
|
203
|
+
backgroundColor: t.surface,
|
|
204
|
+
},
|
|
205
|
+
statsRow: { flexDirection: 'row', alignItems: 'center', gap: 8 },
|
|
206
|
+
statText: { fontFamily: MonospaceFont, fontSize: 11, color: t.textMuted },
|
|
207
|
+
statBadge: {
|
|
208
|
+
backgroundColor: t.errorDim,
|
|
209
|
+
borderRadius: 4,
|
|
210
|
+
paddingHorizontal: 6,
|
|
211
|
+
paddingVertical: 1,
|
|
212
|
+
},
|
|
213
|
+
statBadgeError: { fontFamily: MonospaceFont, fontSize: 10, fontWeight: '700', color: t.error },
|
|
214
|
+
statBadgePending: { backgroundColor: t.warningDim },
|
|
215
|
+
statBadgePendingText: {
|
|
216
|
+
fontFamily: MonospaceFont,
|
|
217
|
+
fontSize: 10,
|
|
218
|
+
fontWeight: '700',
|
|
219
|
+
color: t.warning,
|
|
220
|
+
},
|
|
221
|
+
avgText: { fontFamily: MonospaceFont, fontSize: 10, color: t.textMuted, fontStyle: 'italic' },
|
|
222
|
+
list: { flex: 1 },
|
|
223
|
+
emptyContainer: { flex: 1, justifyContent: 'center' },
|
|
224
|
+
emptyState: { alignItems: 'center', justifyContent: 'center', padding: 40 },
|
|
225
|
+
emptyIcon: { fontSize: 48, marginBottom: 16, opacity: 0.5 },
|
|
226
|
+
emptyTitle: {
|
|
227
|
+
fontFamily: MonospaceFont,
|
|
228
|
+
fontSize: 16,
|
|
229
|
+
fontWeight: '700',
|
|
230
|
+
color: t.textSecondary,
|
|
231
|
+
marginBottom: 8,
|
|
232
|
+
},
|
|
233
|
+
emptySubtitle: {
|
|
234
|
+
fontFamily: MonospaceFont,
|
|
235
|
+
fontSize: 13,
|
|
236
|
+
color: t.textMuted,
|
|
237
|
+
textAlign: 'center',
|
|
238
|
+
lineHeight: 20,
|
|
239
|
+
},
|
|
240
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useRef, useEffect } from 'react'
|
|
1
|
+
import React, { useCallback, useMemo, useRef, useEffect } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
Animated,
|
|
4
4
|
LayoutChangeEvent,
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
TouchableOpacity,
|
|
9
9
|
View,
|
|
10
10
|
} from 'react-native'
|
|
11
|
-
import {
|
|
11
|
+
import { MonospaceFont } from '../constants'
|
|
12
|
+
import { useBackstageTheme } from '../ThemeContext'
|
|
12
13
|
|
|
13
14
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
14
15
|
|
|
@@ -27,6 +28,8 @@ interface TabBarProps {
|
|
|
27
28
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
28
29
|
|
|
29
30
|
export const TabBar: React.FC<TabBarProps> = ({ tabs, activeTab, onTabChange }) => {
|
|
31
|
+
const theme = useBackstageTheme()
|
|
32
|
+
const styles = useMemo(() => createStyles(theme), [theme])
|
|
30
33
|
const indicatorAnim = useRef(new Animated.Value(0)).current
|
|
31
34
|
const tabWidths = useRef<Record<string, number>>({})
|
|
32
35
|
const tabOffsets = useRef<Record<string, number>>({})
|
|
@@ -103,48 +106,49 @@ export const TabBar: React.FC<TabBarProps> = ({ tabs, activeTab, onTabChange })
|
|
|
103
106
|
|
|
104
107
|
// ─── Styles ──────────────────────────────────────────────────────────────────
|
|
105
108
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
109
|
+
const createStyles = (t: import('../types').BackstageTheme) =>
|
|
110
|
+
StyleSheet.create({
|
|
111
|
+
container: {
|
|
112
|
+
borderBottomWidth: 1,
|
|
113
|
+
borderBottomColor: t.border,
|
|
114
|
+
},
|
|
115
|
+
scrollContent: {
|
|
116
|
+
flexDirection: 'row',
|
|
117
|
+
position: 'relative',
|
|
118
|
+
paddingBottom: 0,
|
|
119
|
+
},
|
|
120
|
+
tab: {
|
|
121
|
+
flexDirection: 'row',
|
|
122
|
+
alignItems: 'center',
|
|
123
|
+
justifyContent: 'center',
|
|
124
|
+
paddingHorizontal: 20,
|
|
125
|
+
paddingVertical: 14,
|
|
126
|
+
minWidth: 80,
|
|
127
|
+
},
|
|
128
|
+
tabIcon: {
|
|
129
|
+
fontSize: 14,
|
|
130
|
+
marginRight: 6,
|
|
131
|
+
opacity: 0.5,
|
|
132
|
+
},
|
|
133
|
+
tabIconActive: {
|
|
134
|
+
opacity: 1,
|
|
135
|
+
},
|
|
136
|
+
tabText: {
|
|
137
|
+
fontFamily: MonospaceFont,
|
|
138
|
+
fontSize: 13,
|
|
139
|
+
fontWeight: '600',
|
|
140
|
+
color: t.textMuted,
|
|
141
|
+
textTransform: 'uppercase',
|
|
142
|
+
letterSpacing: 1,
|
|
143
|
+
},
|
|
144
|
+
tabTextActive: {
|
|
145
|
+
color: t.accent,
|
|
146
|
+
},
|
|
147
|
+
indicator: {
|
|
148
|
+
position: 'absolute',
|
|
149
|
+
bottom: 0,
|
|
150
|
+
height: 2,
|
|
151
|
+
backgroundColor: t.accent,
|
|
152
|
+
borderRadius: 1,
|
|
153
|
+
},
|
|
154
|
+
})
|
package/src/constants.ts
CHANGED
|
@@ -41,6 +41,29 @@ export const DarkTheme: BackstageTheme = {
|
|
|
41
41
|
debugDim: 'rgba(167, 139, 250, 0.12)',
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// ─── Light Theme ─────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
export const LightTheme: BackstageTheme = {
|
|
47
|
+
background: '#F5F5F7',
|
|
48
|
+
surface: '#FFFFFF',
|
|
49
|
+
surfaceElevated: '#F0F0F4',
|
|
50
|
+
border: '#D8D8DC',
|
|
51
|
+
text: '#1C1C1E',
|
|
52
|
+
textSecondary: '#636366',
|
|
53
|
+
textMuted: '#AEAEB2',
|
|
54
|
+
accent: '#6A3DE8',
|
|
55
|
+
accentDim: 'rgba(106, 61, 232, 0.10)',
|
|
56
|
+
error: '#E5325F',
|
|
57
|
+
errorDim: 'rgba(229, 50, 95, 0.10)',
|
|
58
|
+
warning: '#E09400',
|
|
59
|
+
warningDim: 'rgba(224, 148, 0, 0.10)',
|
|
60
|
+
success: '#28A770',
|
|
61
|
+
info: '#0E8AD6',
|
|
62
|
+
infoDim: 'rgba(14, 138, 214, 0.10)',
|
|
63
|
+
debugColor: '#7C4DFF',
|
|
64
|
+
debugDim: 'rgba(124, 77, 255, 0.10)',
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
export const DEFAULT_MAX_LOGS = 500
|
|
45
68
|
|
|
46
69
|
// ─── Network Defaults ────────────────────────────────────────────────────────
|
package/src/index.ts
CHANGED
|
@@ -7,9 +7,12 @@ export type {
|
|
|
7
7
|
BackstageStyleOverrides,
|
|
8
8
|
BackstageTheme,
|
|
9
9
|
QuickAction,
|
|
10
|
+
FeatureFlag,
|
|
10
11
|
AppInfoItem,
|
|
11
12
|
LogEntry,
|
|
12
13
|
NetworkEntry,
|
|
13
14
|
} from './types'
|
|
14
15
|
|
|
15
16
|
export { LogLevel, NetworkState } from './types'
|
|
17
|
+
export { TestIDs, DarkTheme, LightTheme } from './constants'
|
|
18
|
+
export type { ThemePreference } from './ThemeContext'
|
package/src/types.ts
CHANGED
|
@@ -63,6 +63,19 @@ export interface QuickAction {
|
|
|
63
63
|
testID?: string
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// ─── Feature Flags ─────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
export interface FeatureFlag {
|
|
69
|
+
/** Unique key for this flag */
|
|
70
|
+
key: string
|
|
71
|
+
/** Display label */
|
|
72
|
+
label: string
|
|
73
|
+
/** Current value */
|
|
74
|
+
value: boolean
|
|
75
|
+
/** Optional description shown below the label */
|
|
76
|
+
description?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
// ─── Extensible Tabs ─────────────────────────────────────────────────────────
|
|
67
80
|
|
|
68
81
|
export interface BackstageTab {
|
|
@@ -101,6 +114,9 @@ export interface BackstageProps {
|
|
|
101
114
|
/** Whether the floating pill trigger is visible. Default: true */
|
|
102
115
|
visible?: boolean
|
|
103
116
|
|
|
117
|
+
/** Theme preference: 'light', 'dark', or 'auto' (follows device setting). Default: 'auto' */
|
|
118
|
+
theme?: 'light' | 'dark' | 'auto'
|
|
119
|
+
|
|
104
120
|
/** App version string (e.g., "1.2.3") */
|
|
105
121
|
appVersion?: string
|
|
106
122
|
|
|
@@ -119,6 +135,12 @@ export interface BackstageProps {
|
|
|
119
135
|
/** Custom action buttons in the Info tab */
|
|
120
136
|
quickActions?: QuickAction[]
|
|
121
137
|
|
|
138
|
+
/** Feature flags to display with toggle switches */
|
|
139
|
+
featureFlags?: FeatureFlag[]
|
|
140
|
+
|
|
141
|
+
/** Callback when a feature flag is toggled */
|
|
142
|
+
onToggleFeatureFlag?: (key: string, value: boolean) => void
|
|
143
|
+
|
|
122
144
|
/** Maximum number of logs to retain in memory. Default: 500 */
|
|
123
145
|
maxLogs?: number
|
|
124
146
|
|
|
@@ -160,6 +182,9 @@ export interface BackstageProps {
|
|
|
160
182
|
|
|
161
183
|
/** Auto-filter console.logs from network callbacks (e.g., Axios interceptors) out of the Logs tab. Default: true */
|
|
162
184
|
autoFilterNetworkLogs?: boolean
|
|
185
|
+
|
|
186
|
+
/** Max nesting depth for JSON tree views (state tree, log data, network bodies). Default: 10 */
|
|
187
|
+
jsonMaxDepth?: number
|
|
163
188
|
}
|
|
164
189
|
|
|
165
190
|
export interface BackstageStyleOverrides {
|