rn-backstage 1.2.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.
Files changed (100) hide show
  1. package/README.md +2 -0
  2. package/lib/commonjs/Backstage.js +40 -33
  3. package/lib/commonjs/Backstage.js.map +1 -1
  4. package/lib/commonjs/ThemeContext.js +43 -0
  5. package/lib/commonjs/ThemeContext.js.map +1 -0
  6. package/lib/commonjs/components/BackstagePanel.js +17 -10
  7. package/lib/commonjs/components/BackstagePanel.js.map +1 -1
  8. package/lib/commonjs/components/FlagsTab.js +50 -49
  9. package/lib/commonjs/components/FlagsTab.js.map +1 -1
  10. package/lib/commonjs/components/FloatingPill.js +4 -2
  11. package/lib/commonjs/components/FloatingPill.js.map +1 -1
  12. package/lib/commonjs/components/InfoTab.js +73 -107
  13. package/lib/commonjs/components/InfoTab.js.map +1 -1
  14. package/lib/commonjs/components/JsonTreeView.js +39 -34
  15. package/lib/commonjs/components/JsonTreeView.js.map +1 -1
  16. package/lib/commonjs/components/LogItem.js +33 -29
  17. package/lib/commonjs/components/LogItem.js.map +1 -1
  18. package/lib/commonjs/components/LogsTab.js +37 -42
  19. package/lib/commonjs/components/LogsTab.js.map +1 -1
  20. package/lib/commonjs/components/NetworkItem.js +111 -135
  21. package/lib/commonjs/components/NetworkItem.js.map +1 -1
  22. package/lib/commonjs/components/NetworkTab.js +48 -49
  23. package/lib/commonjs/components/NetworkTab.js.map +1 -1
  24. package/lib/commonjs/components/TabBar.js +8 -5
  25. package/lib/commonjs/components/TabBar.js.map +1 -1
  26. package/lib/commonjs/constants.js +24 -1
  27. package/lib/commonjs/constants.js.map +1 -1
  28. package/lib/commonjs/index.js +19 -0
  29. package/lib/commonjs/index.js.map +1 -1
  30. package/lib/commonjs/types.js.map +1 -1
  31. package/lib/module/Backstage.js +40 -33
  32. package/lib/module/Backstage.js.map +1 -1
  33. package/lib/module/ThemeContext.js +37 -0
  34. package/lib/module/ThemeContext.js.map +1 -0
  35. package/lib/module/components/BackstagePanel.js +19 -12
  36. package/lib/module/components/BackstagePanel.js.map +1 -1
  37. package/lib/module/components/FlagsTab.js +51 -50
  38. package/lib/module/components/FlagsTab.js.map +1 -1
  39. package/lib/module/components/FloatingPill.js +5 -3
  40. package/lib/module/components/FloatingPill.js.map +1 -1
  41. package/lib/module/components/InfoTab.js +73 -107
  42. package/lib/module/components/InfoTab.js.map +1 -1
  43. package/lib/module/components/JsonTreeView.js +42 -38
  44. package/lib/module/components/JsonTreeView.js.map +1 -1
  45. package/lib/module/components/LogItem.js +35 -31
  46. package/lib/module/components/LogItem.js.map +1 -1
  47. package/lib/module/components/LogsTab.js +38 -43
  48. package/lib/module/components/LogsTab.js.map +1 -1
  49. package/lib/module/components/NetworkItem.js +113 -137
  50. package/lib/module/components/NetworkItem.js.map +1 -1
  51. package/lib/module/components/NetworkTab.js +49 -50
  52. package/lib/module/components/NetworkTab.js.map +1 -1
  53. package/lib/module/components/TabBar.js +10 -7
  54. package/lib/module/components/TabBar.js.map +1 -1
  55. package/lib/module/constants.js +23 -0
  56. package/lib/module/constants.js.map +1 -1
  57. package/lib/module/index.js +1 -0
  58. package/lib/module/index.js.map +1 -1
  59. package/lib/module/types.js.map +1 -1
  60. package/lib/typescript/Backstage.d.ts.map +1 -1
  61. package/lib/typescript/ThemeContext.d.ts +11 -0
  62. package/lib/typescript/ThemeContext.d.ts.map +1 -0
  63. package/lib/typescript/components/BackstagePanel.d.ts +1 -0
  64. package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
  65. package/lib/typescript/components/FlagsTab.d.ts.map +1 -1
  66. package/lib/typescript/components/FloatingPill.d.ts.map +1 -1
  67. package/lib/typescript/components/InfoTab.d.ts +1 -0
  68. package/lib/typescript/components/InfoTab.d.ts.map +1 -1
  69. package/lib/typescript/components/JsonTreeView.d.ts.map +1 -1
  70. package/lib/typescript/components/LogItem.d.ts +1 -0
  71. package/lib/typescript/components/LogItem.d.ts.map +1 -1
  72. package/lib/typescript/components/LogsTab.d.ts +1 -0
  73. package/lib/typescript/components/LogsTab.d.ts.map +1 -1
  74. package/lib/typescript/components/NetworkItem.d.ts +1 -0
  75. package/lib/typescript/components/NetworkItem.d.ts.map +1 -1
  76. package/lib/typescript/components/NetworkTab.d.ts +1 -0
  77. package/lib/typescript/components/NetworkTab.d.ts.map +1 -1
  78. package/lib/typescript/components/TabBar.d.ts.map +1 -1
  79. package/lib/typescript/constants.d.ts +1 -0
  80. package/lib/typescript/constants.d.ts.map +1 -1
  81. package/lib/typescript/index.d.ts +2 -0
  82. package/lib/typescript/index.d.ts.map +1 -1
  83. package/lib/typescript/types.d.ts +4 -0
  84. package/lib/typescript/types.d.ts.map +1 -1
  85. package/package.json +1 -1
  86. package/src/Backstage.tsx +39 -33
  87. package/src/ThemeContext.tsx +38 -0
  88. package/src/components/BackstagePanel.tsx +66 -57
  89. package/src/components/FlagsTab.tsx +123 -174
  90. package/src/components/FloatingPill.tsx +6 -3
  91. package/src/components/InfoTab.tsx +152 -186
  92. package/src/components/JsonTreeView.tsx +53 -73
  93. package/src/components/LogItem.tsx +55 -100
  94. package/src/components/LogsTab.tsx +110 -132
  95. package/src/components/NetworkItem.tsx +306 -417
  96. package/src/components/NetworkTab.tsx +129 -163
  97. package/src/components/TabBar.tsx +51 -47
  98. package/src/constants.ts +23 -0
  99. package/src/index.ts +2 -0
  100. package/src/types.ts +6 -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 { DarkTheme, MonospaceFont, TestIDs } from '../constants'
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> = ({ entries, onRefresh, onClear, onCopy }) => {
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 }) => <NetworkItem item={item} onCopy={onCopy} />,
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={styles.container}>
64
- {/* Search Bar */}
65
- <View style={styles.searchContainer}>
66
- <View style={styles.searchInputWrapper}>
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={styles.searchInput}
77
+ style={s.searchInput}
71
78
  placeholder="Filter by URL, method, status..."
72
- placeholderTextColor={DarkTheme.textMuted}
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={styles.clearButton}
90
+ style={s.clearButton}
84
91
  onPress={onClear}
85
92
  activeOpacity={0.7}
86
93
  >
87
- <Text style={styles.clearButtonText}>Clear</Text>
94
+ <Text style={s.clearButtonText}>Clear</Text>
88
95
  </TouchableOpacity>
89
96
  </View>
90
97
 
91
- {/* Stats Bar */}
92
- <View style={styles.statsBar}>
93
- <View style={styles.statsRow}>
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={styles.statBadge}>
101
- <Text style={styles.statBadgeError}>
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={[styles.statBadge, styles.statBadgePending]}>
108
- <Text style={styles.statBadgePendingText}>{stats.pending} pending</Text>
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={styles.avgText}>avg {stats.avgDuration}ms</Text>}
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={styles.list}
124
- contentContainerStyle={filteredEntries.length === 0 ? styles.emptyContainer : undefined}
128
+ style={s.list}
129
+ contentContainerStyle={filteredEntries.length === 0 ? s.emptyContainer : undefined}
125
130
  ListEmptyComponent={
126
- <View style={styles.emptyState}>
127
- <Text style={styles.emptyIcon}>🌐</Text>
128
- <Text style={styles.emptyTitle}>No network requests</Text>
129
- <Text style={styles.emptySubtitle}>
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 styles = StyleSheet.create({
147
- container: {
148
- flex: 1,
149
- },
150
- searchContainer: {
151
- flexDirection: 'row',
152
- alignItems: 'center',
153
- paddingHorizontal: 14,
154
- paddingVertical: 10,
155
- gap: 10,
156
- borderBottomWidth: 1,
157
- borderBottomColor: DarkTheme.border,
158
- },
159
- searchInputWrapper: {
160
- flex: 1,
161
- flexDirection: 'row',
162
- alignItems: 'center',
163
- backgroundColor: DarkTheme.surfaceElevated,
164
- borderRadius: 10,
165
- borderWidth: 1,
166
- borderColor: DarkTheme.border,
167
- paddingHorizontal: 12,
168
- height: 38,
169
- },
170
- searchIcon: {
171
- fontSize: 16,
172
- color: DarkTheme.textMuted,
173
- marginRight: 8,
174
- },
175
- searchInput: {
176
- flex: 1,
177
- fontFamily: MonospaceFont,
178
- fontSize: 13,
179
- color: DarkTheme.text,
180
- padding: 0,
181
- },
182
- clearButton: {
183
- backgroundColor: DarkTheme.errorDim,
184
- borderRadius: 8,
185
- borderWidth: 1,
186
- borderColor: DarkTheme.error,
187
- paddingHorizontal: 14,
188
- height: 38,
189
- justifyContent: 'center',
190
- },
191
- clearButtonText: {
192
- fontFamily: MonospaceFont,
193
- fontSize: 12,
194
- fontWeight: '700',
195
- color: DarkTheme.error,
196
- letterSpacing: 0.5,
197
- },
198
- statsBar: {
199
- flexDirection: 'row',
200
- justifyContent: 'space-between',
201
- alignItems: 'center',
202
- paddingHorizontal: 16,
203
- paddingVertical: 6,
204
- backgroundColor: DarkTheme.surface,
205
- },
206
- statsRow: {
207
- flexDirection: 'row',
208
- alignItems: 'center',
209
- gap: 8,
210
- },
211
- statText: {
212
- fontFamily: MonospaceFont,
213
- fontSize: 11,
214
- color: DarkTheme.textMuted,
215
- },
216
- statBadge: {
217
- backgroundColor: DarkTheme.errorDim,
218
- borderRadius: 4,
219
- paddingHorizontal: 6,
220
- paddingVertical: 1,
221
- },
222
- statBadgeError: {
223
- fontFamily: MonospaceFont,
224
- fontSize: 10,
225
- fontWeight: '700',
226
- color: DarkTheme.error,
227
- },
228
- statBadgePending: {
229
- backgroundColor: DarkTheme.warningDim,
230
- },
231
- statBadgePendingText: {
232
- fontFamily: MonospaceFont,
233
- fontSize: 10,
234
- fontWeight: '700',
235
- color: DarkTheme.warning,
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 { DarkTheme, MonospaceFont } from '../constants'
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 styles = StyleSheet.create({
107
- container: {
108
- borderBottomWidth: 1,
109
- borderBottomColor: DarkTheme.border,
110
- },
111
- scrollContent: {
112
- flexDirection: 'row',
113
- position: 'relative',
114
- paddingBottom: 0,
115
- },
116
- tab: {
117
- flexDirection: 'row',
118
- alignItems: 'center',
119
- justifyContent: 'center',
120
- paddingHorizontal: 20,
121
- paddingVertical: 14,
122
- minWidth: 80,
123
- },
124
- tabIcon: {
125
- fontSize: 14,
126
- marginRight: 6,
127
- opacity: 0.5,
128
- },
129
- tabIconActive: {
130
- opacity: 1,
131
- },
132
- tabText: {
133
- fontFamily: MonospaceFont,
134
- fontSize: 13,
135
- fontWeight: '600',
136
- color: DarkTheme.textMuted,
137
- textTransform: 'uppercase',
138
- letterSpacing: 1,
139
- },
140
- tabTextActive: {
141
- color: DarkTheme.accent,
142
- },
143
- indicator: {
144
- position: 'absolute',
145
- bottom: 0,
146
- height: 2,
147
- backgroundColor: DarkTheme.accent,
148
- borderRadius: 1,
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
@@ -14,3 +14,5 @@ export type {
14
14
  } from './types'
15
15
 
16
16
  export { LogLevel, NetworkState } from './types'
17
+ export { TestIDs, DarkTheme, LightTheme } from './constants'
18
+ export type { ThemePreference } from './ThemeContext'
package/src/types.ts CHANGED
@@ -114,6 +114,9 @@ export interface BackstageProps {
114
114
  /** Whether the floating pill trigger is visible. Default: true */
115
115
  visible?: boolean
116
116
 
117
+ /** Theme preference: 'light', 'dark', or 'auto' (follows device setting). Default: 'auto' */
118
+ theme?: 'light' | 'dark' | 'auto'
119
+
117
120
  /** App version string (e.g., "1.2.3") */
118
121
  appVersion?: string
119
122
 
@@ -179,6 +182,9 @@ export interface BackstageProps {
179
182
 
180
183
  /** Auto-filter console.logs from network callbacks (e.g., Axios interceptors) out of the Logs tab. Default: true */
181
184
  autoFilterNetworkLogs?: boolean
185
+
186
+ /** Max nesting depth for JSON tree views (state tree, log data, network bodies). Default: 10 */
187
+ jsonMaxDepth?: number
182
188
  }
183
189
 
184
190
  export interface BackstageStyleOverrides {