rn-backstage 1.2.0 → 1.3.0

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 (110) hide show
  1. package/README.md +84 -2
  2. package/lib/commonjs/Backstage.js +46 -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 +35 -12
  7. package/lib/commonjs/components/BackstagePanel.js.map +1 -1
  8. package/lib/commonjs/components/FlagsTab.js +60 -57
  9. package/lib/commonjs/components/FlagsTab.js.map +1 -1
  10. package/lib/commonjs/components/FloatingPill.js +86 -24
  11. package/lib/commonjs/components/FloatingPill.js.map +1 -1
  12. package/lib/commonjs/components/InfoTab.js +77 -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 +39 -29
  17. package/lib/commonjs/components/LogItem.js.map +1 -1
  18. package/lib/commonjs/components/LogsTab.js +39 -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 +50 -49
  23. package/lib/commonjs/components/NetworkTab.js.map +1 -1
  24. package/lib/commonjs/components/StorageTab.js +655 -0
  25. package/lib/commonjs/components/StorageTab.js.map +1 -0
  26. package/lib/commonjs/components/TabBar.js +8 -5
  27. package/lib/commonjs/components/TabBar.js.map +1 -1
  28. package/lib/commonjs/constants.js +96 -7
  29. package/lib/commonjs/constants.js.map +1 -1
  30. package/lib/commonjs/index.js +96 -0
  31. package/lib/commonjs/index.js.map +1 -1
  32. package/lib/commonjs/types.js +1 -0
  33. package/lib/commonjs/types.js.map +1 -1
  34. package/lib/module/Backstage.js +46 -33
  35. package/lib/module/Backstage.js.map +1 -1
  36. package/lib/module/ThemeContext.js +37 -0
  37. package/lib/module/ThemeContext.js.map +1 -0
  38. package/lib/module/components/BackstagePanel.js +37 -14
  39. package/lib/module/components/BackstagePanel.js.map +1 -1
  40. package/lib/module/components/FlagsTab.js +61 -58
  41. package/lib/module/components/FlagsTab.js.map +1 -1
  42. package/lib/module/components/FloatingPill.js +90 -28
  43. package/lib/module/components/FloatingPill.js.map +1 -1
  44. package/lib/module/components/InfoTab.js +77 -107
  45. package/lib/module/components/InfoTab.js.map +1 -1
  46. package/lib/module/components/JsonTreeView.js +42 -38
  47. package/lib/module/components/JsonTreeView.js.map +1 -1
  48. package/lib/module/components/LogItem.js +41 -31
  49. package/lib/module/components/LogItem.js.map +1 -1
  50. package/lib/module/components/LogsTab.js +40 -43
  51. package/lib/module/components/LogsTab.js.map +1 -1
  52. package/lib/module/components/NetworkItem.js +113 -137
  53. package/lib/module/components/NetworkItem.js.map +1 -1
  54. package/lib/module/components/NetworkTab.js +51 -50
  55. package/lib/module/components/NetworkTab.js.map +1 -1
  56. package/lib/module/components/StorageTab.js +650 -0
  57. package/lib/module/components/StorageTab.js.map +1 -0
  58. package/lib/module/components/TabBar.js +10 -7
  59. package/lib/module/components/TabBar.js.map +1 -1
  60. package/lib/module/constants.js +95 -6
  61. package/lib/module/constants.js.map +1 -1
  62. package/lib/module/index.js +14 -0
  63. package/lib/module/index.js.map +1 -1
  64. package/lib/module/types.js +2 -0
  65. package/lib/module/types.js.map +1 -1
  66. package/lib/typescript/Backstage.d.ts.map +1 -1
  67. package/lib/typescript/ThemeContext.d.ts +11 -0
  68. package/lib/typescript/ThemeContext.d.ts.map +1 -0
  69. package/lib/typescript/components/BackstagePanel.d.ts +3 -1
  70. package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
  71. package/lib/typescript/components/FlagsTab.d.ts.map +1 -1
  72. package/lib/typescript/components/FloatingPill.d.ts +2 -0
  73. package/lib/typescript/components/FloatingPill.d.ts.map +1 -1
  74. package/lib/typescript/components/InfoTab.d.ts +1 -0
  75. package/lib/typescript/components/InfoTab.d.ts.map +1 -1
  76. package/lib/typescript/components/JsonTreeView.d.ts.map +1 -1
  77. package/lib/typescript/components/LogItem.d.ts +1 -0
  78. package/lib/typescript/components/LogItem.d.ts.map +1 -1
  79. package/lib/typescript/components/LogsTab.d.ts +1 -0
  80. package/lib/typescript/components/LogsTab.d.ts.map +1 -1
  81. package/lib/typescript/components/NetworkItem.d.ts +1 -0
  82. package/lib/typescript/components/NetworkItem.d.ts.map +1 -1
  83. package/lib/typescript/components/NetworkTab.d.ts +1 -0
  84. package/lib/typescript/components/NetworkTab.d.ts.map +1 -1
  85. package/lib/typescript/components/StorageTab.d.ts +9 -0
  86. package/lib/typescript/components/StorageTab.d.ts.map +1 -0
  87. package/lib/typescript/components/TabBar.d.ts.map +1 -1
  88. package/lib/typescript/constants.d.ts +60 -4
  89. package/lib/typescript/constants.d.ts.map +1 -1
  90. package/lib/typescript/index.d.ts +14 -1
  91. package/lib/typescript/index.d.ts.map +1 -1
  92. package/lib/typescript/types.d.ts +20 -0
  93. package/lib/typescript/types.d.ts.map +1 -1
  94. package/package.json +6 -4
  95. package/src/Backstage.tsx +45 -33
  96. package/src/ThemeContext.tsx +38 -0
  97. package/src/components/BackstagePanel.tsx +89 -61
  98. package/src/components/FlagsTab.tsx +125 -174
  99. package/src/components/FloatingPill.tsx +127 -30
  100. package/src/components/InfoTab.tsx +153 -186
  101. package/src/components/JsonTreeView.tsx +53 -73
  102. package/src/components/LogItem.tsx +64 -100
  103. package/src/components/LogsTab.tsx +110 -132
  104. package/src/components/NetworkItem.tsx +306 -417
  105. package/src/components/NetworkTab.tsx +129 -163
  106. package/src/components/StorageTab.tsx +607 -0
  107. package/src/components/TabBar.tsx +51 -47
  108. package/src/constants.ts +103 -4
  109. package/src/index.ts +17 -0
  110. package/src/types.ts +28 -0
@@ -1,8 +1,9 @@
1
- import React from 'react'
1
+ import React, { useMemo } from 'react'
2
2
  import { Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
3
- import { DarkTheme, MonospaceFont } from '../constants'
3
+ import { MonospaceFont, TestIDs } from '../constants'
4
+ import { useBackstageTheme } from '../ThemeContext'
4
5
  import { JsonTreeView } from './JsonTreeView'
5
- import type { AppInfoItem, BackstageStyleOverrides, QuickAction } from '../types'
6
+ import type { AppInfoItem, BackstageStyleOverrides, BackstageTheme, QuickAction } from '../types'
6
7
 
7
8
  // ─── Types ───────────────────────────────────────────────────────────────────
8
9
 
@@ -13,76 +14,12 @@ interface InfoTabProps {
13
14
  deviceInfo?: AppInfoItem[]
14
15
  state?: Record<string, unknown>
15
16
  quickActions?: QuickAction[]
17
+ jsonMaxDepth?: number
16
18
  onClosePanel?: () => void
17
19
  styles?: BackstageStyleOverrides
18
20
  children?: React.ReactNode
19
21
  }
20
22
 
21
- // ─── Info Row ────────────────────────────────────────────────────────────────
22
-
23
- const InfoRow: React.FC<{
24
- label: string
25
- value: string
26
- labelStyle?: BackstageStyleOverrides['infoLabelStyle']
27
- valueStyle?: BackstageStyleOverrides['infoValueStyle']
28
- }> = ({ label, value, labelStyle, valueStyle }) => (
29
- <View style={styles.infoRow}>
30
- <Text style={[styles.infoLabel, labelStyle]} numberOfLines={1}>
31
- {label}
32
- </Text>
33
- <Text style={[styles.infoValue, valueStyle]} numberOfLines={2} selectable>
34
- {value}
35
- </Text>
36
- </View>
37
- )
38
-
39
- // ─── Section Header ──────────────────────────────────────────────────────────
40
-
41
- const SectionHeader: React.FC<{
42
- title: string
43
- titleStyle?: BackstageStyleOverrides['sectionTitleStyle']
44
- }> = ({ title, titleStyle }) => <Text style={[styles.sectionTitle, titleStyle]}>{title}</Text>
45
-
46
- // ─── Action Button ───────────────────────────────────────────────────────────
47
-
48
- const ActionButton: React.FC<{
49
- action: QuickAction
50
- onClosePanel?: () => void
51
- buttonStyle?: BackstageStyleOverrides['actionButtonStyle']
52
- buttonTitleStyle?: BackstageStyleOverrides['actionButtonTitleStyle']
53
- }> = ({ action, onClosePanel, buttonStyle, buttonTitleStyle }) => {
54
- const handlePress = () => {
55
- action.onPress()
56
- if (action.closeOnPress && onClosePanel) {
57
- onClosePanel()
58
- }
59
- }
60
-
61
- return (
62
- <TouchableOpacity
63
- testID={action.testID}
64
- style={[
65
- styles.actionButton,
66
- action.destructive && styles.actionButtonDestructive,
67
- buttonStyle,
68
- ]}
69
- onPress={handlePress}
70
- activeOpacity={0.7}
71
- >
72
- {action.icon && <Text style={styles.actionIcon}>{action.icon}</Text>}
73
- <Text
74
- style={[
75
- styles.actionButtonTitle,
76
- action.destructive && styles.actionButtonTitleDestructive,
77
- buttonTitleStyle,
78
- ]}
79
- >
80
- {action.title}
81
- </Text>
82
- </TouchableOpacity>
83
- )
84
- }
85
-
86
23
  // ─── Main Component ──────────────────────────────────────────────────────────
87
24
 
88
25
  export const InfoTab: React.FC<InfoTabProps> = ({
@@ -92,10 +29,14 @@ export const InfoTab: React.FC<InfoTabProps> = ({
92
29
  deviceInfo = [],
93
30
  state,
94
31
  quickActions = [],
32
+ jsonMaxDepth,
95
33
  onClosePanel,
96
34
  styles: propStyles,
97
35
  children,
98
36
  }) => {
37
+ const theme = useBackstageTheme()
38
+ const s = useMemo(() => createStyles(theme), [theme])
39
+
99
40
  // Built-in device info from Platform API
100
41
  const builtInInfo: AppInfoItem[] = [
101
42
  { label: 'Platform', value: Platform.OS.toUpperCase() },
@@ -116,23 +57,26 @@ export const InfoTab: React.FC<InfoTabProps> = ({
116
57
 
117
58
  return (
118
59
  <ScrollView
119
- style={styles.container}
120
- contentContainerStyle={styles.scrollContent}
60
+ testID={TestIDs.infoTab.container}
61
+ style={s.container}
62
+ contentContainerStyle={s.scrollContent}
121
63
  showsVerticalScrollIndicator={false}
122
64
  >
123
65
  {/* ── Device Info Section ─────────────────────────────────── */}
124
- <View style={styles.section}>
125
- <SectionHeader title="DEVICE INFO" titleStyle={propStyles?.sectionTitleStyle} />
126
- <View style={styles.card}>
66
+ <View testID={TestIDs.infoTab.deviceInfo} style={s.section}>
67
+ <Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>DEVICE INFO</Text>
68
+ <View style={s.card}>
127
69
  {allInfo.map((item, index) => (
128
70
  <React.Fragment key={`info_${index}`}>
129
- <InfoRow
130
- label={item.label}
131
- value={item.value}
132
- labelStyle={propStyles?.infoLabelStyle}
133
- valueStyle={propStyles?.infoValueStyle}
134
- />
135
- {index < allInfo.length - 1 && <View style={styles.divider} />}
71
+ <View style={s.infoRow}>
72
+ <Text style={[s.infoLabel, propStyles?.infoLabelStyle]} numberOfLines={1}>
73
+ {item.label}
74
+ </Text>
75
+ <Text style={[s.infoValue, propStyles?.infoValueStyle]} selectable>
76
+ {item.value}
77
+ </Text>
78
+ </View>
79
+ {index < allInfo.length - 1 && <View style={s.divider} />}
136
80
  </React.Fragment>
137
81
  ))}
138
82
  </View>
@@ -140,125 +84,148 @@ export const InfoTab: React.FC<InfoTabProps> = ({
140
84
 
141
85
  {/* ── State Tree Section ──────────────────────────────────── */}
142
86
  {state && Object.keys(state).length > 0 && (
143
- <View style={styles.section}>
144
- <SectionHeader title="STATE TREE" titleStyle={propStyles?.sectionTitleStyle} />
145
- <View style={styles.card}>
146
- <JsonTreeView data={state} hideRoot />
87
+ <View testID={TestIDs.infoTab.stateTree} style={s.section}>
88
+ <Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>STATE TREE</Text>
89
+ <View style={s.card}>
90
+ <JsonTreeView data={state} hideRoot maxDepth={jsonMaxDepth} />
147
91
  </View>
148
92
  </View>
149
93
  )}
150
94
 
151
- {/* ── Quick Actions Section ─────────────────────────────────────── */}
95
+ {/* ── Quick Actions Section ─────────────────────────────────── */}
152
96
  {quickActions.length > 0 && (
153
- <View style={styles.section}>
154
- <SectionHeader title="QUICK ACTIONS" titleStyle={propStyles?.sectionTitleStyle} />
155
- <View style={styles.actionsGrid}>
156
- {quickActions.map((action, index) => (
157
- <ActionButton
158
- key={`action_${index}`}
159
- action={action}
160
- onClosePanel={onClosePanel}
161
- buttonStyle={propStyles?.actionButtonStyle}
162
- buttonTitleStyle={propStyles?.actionButtonTitleStyle}
163
- />
164
- ))}
97
+ <View testID={TestIDs.infoTab.quickActions} style={s.section}>
98
+ <Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>QUICK ACTIONS</Text>
99
+ <View style={s.actionsGrid}>
100
+ {quickActions.map((action, index) => {
101
+ const handlePress = () => {
102
+ action.onPress()
103
+ if (action.closeOnPress && onClosePanel) {
104
+ onClosePanel()
105
+ }
106
+ }
107
+ return (
108
+ <TouchableOpacity
109
+ key={`action_${index}`}
110
+ testID={action.testID || TestIDs.infoTab.actionButton(index)}
111
+ style={[
112
+ s.actionButton,
113
+ action.destructive && s.actionButtonDestructive,
114
+ propStyles?.actionButtonStyle,
115
+ ]}
116
+ onPress={handlePress}
117
+ activeOpacity={0.7}
118
+ >
119
+ {action.icon && <Text style={s.actionIcon}>{action.icon}</Text>}
120
+ <Text
121
+ style={[
122
+ s.actionButtonTitle,
123
+ action.destructive && s.actionButtonTitleDestructive,
124
+ propStyles?.actionButtonTitleStyle,
125
+ ]}
126
+ >
127
+ {action.title}
128
+ </Text>
129
+ </TouchableOpacity>
130
+ )
131
+ })}
165
132
  </View>
166
133
  </View>
167
134
  )}
168
135
 
169
136
  {/* ── Custom Children ─────────────────────────────────────── */}
170
- {children && <View style={styles.section}>{children}</View>}
137
+ {children && <View style={s.section}>{children}</View>}
171
138
  </ScrollView>
172
139
  )
173
140
  }
174
141
 
175
142
  // ─── Styles ──────────────────────────────────────────────────────────────────
176
143
 
177
- const styles = StyleSheet.create({
178
- container: {
179
- flex: 1,
180
- },
181
- scrollContent: {
182
- padding: 16,
183
- paddingBottom: 32,
184
- },
185
- section: {
186
- marginBottom: 20,
187
- },
188
- sectionTitle: {
189
- fontFamily: MonospaceFont,
190
- fontSize: 11,
191
- fontWeight: '700',
192
- color: DarkTheme.textMuted,
193
- letterSpacing: 1.5,
194
- marginBottom: 8,
195
- paddingLeft: 4,
196
- },
197
- card: {
198
- backgroundColor: DarkTheme.surfaceElevated,
199
- borderRadius: 12,
200
- borderWidth: 1,
201
- borderColor: DarkTheme.border,
202
- padding: 12,
203
- overflow: 'hidden',
204
- },
205
- infoRow: {
206
- flexDirection: 'row',
207
- justifyContent: 'space-between',
208
- alignItems: 'center',
209
- paddingVertical: 8,
210
- paddingHorizontal: 4,
211
- },
212
- infoLabel: {
213
- fontFamily: MonospaceFont,
214
- fontSize: 13,
215
- color: DarkTheme.textSecondary,
216
- flex: 1,
217
- },
218
- infoValue: {
219
- fontFamily: MonospaceFont,
220
- fontSize: 13,
221
- color: DarkTheme.text,
222
- fontWeight: '600',
223
- textAlign: 'right',
224
- flex: 1,
225
- marginLeft: 12,
226
- },
227
- divider: {
228
- height: 1,
229
- backgroundColor: DarkTheme.border,
230
- marginHorizontal: 4,
231
- },
232
- actionsGrid: {
233
- flexDirection: 'row',
234
- flexWrap: 'wrap',
235
- gap: 10,
236
- },
237
- actionButton: {
238
- backgroundColor: DarkTheme.accentDim,
239
- borderRadius: 10,
240
- borderWidth: 1,
241
- borderColor: DarkTheme.accent,
242
- paddingVertical: 10,
243
- paddingHorizontal: 18,
244
- flexDirection: 'row',
245
- alignItems: 'center',
246
- },
247
- actionButtonDestructive: {
248
- backgroundColor: DarkTheme.errorDim,
249
- borderColor: DarkTheme.error,
250
- },
251
- actionIcon: {
252
- fontSize: 14,
253
- marginRight: 6,
254
- },
255
- actionButtonTitle: {
256
- fontFamily: MonospaceFont,
257
- fontSize: 13,
258
- fontWeight: '600',
259
- color: DarkTheme.accent,
260
- },
261
- actionButtonTitleDestructive: {
262
- color: DarkTheme.error,
263
- },
264
- })
144
+ const createStyles = (t: BackstageTheme) =>
145
+ StyleSheet.create({
146
+ container: {
147
+ flex: 1,
148
+ },
149
+ scrollContent: {
150
+ padding: 16,
151
+ paddingBottom: 32,
152
+ },
153
+ section: {
154
+ marginBottom: 20,
155
+ },
156
+ sectionTitle: {
157
+ fontFamily: MonospaceFont,
158
+ fontSize: 11,
159
+ fontWeight: '700',
160
+ color: t.textMuted,
161
+ letterSpacing: 1.5,
162
+ marginBottom: 8,
163
+ paddingLeft: 4,
164
+ },
165
+ card: {
166
+ backgroundColor: t.surfaceElevated,
167
+ borderRadius: 12,
168
+ borderWidth: 1,
169
+ borderColor: t.border,
170
+ padding: 12,
171
+ overflow: 'hidden',
172
+ },
173
+ infoRow: {
174
+ flexDirection: 'row',
175
+ justifyContent: 'space-between',
176
+ alignItems: 'center',
177
+ paddingVertical: 8,
178
+ paddingHorizontal: 4,
179
+ },
180
+ infoLabel: {
181
+ fontFamily: MonospaceFont,
182
+ fontSize: 13,
183
+ color: t.textSecondary,
184
+ },
185
+ infoValue: {
186
+ fontFamily: MonospaceFont,
187
+ fontSize: 13,
188
+ color: t.text,
189
+ fontWeight: '600',
190
+ textAlign: 'right',
191
+ flex: 1,
192
+ marginLeft: 12,
193
+ },
194
+ divider: {
195
+ height: 1,
196
+ backgroundColor: t.border,
197
+ marginHorizontal: 4,
198
+ },
199
+ actionsGrid: {
200
+ flexDirection: 'row',
201
+ flexWrap: 'wrap',
202
+ gap: 10,
203
+ },
204
+ actionButton: {
205
+ backgroundColor: t.accentDim,
206
+ borderRadius: 10,
207
+ borderWidth: 1,
208
+ borderColor: t.accent,
209
+ paddingVertical: 10,
210
+ paddingHorizontal: 18,
211
+ flexDirection: 'row',
212
+ alignItems: 'center',
213
+ },
214
+ actionButtonDestructive: {
215
+ backgroundColor: t.errorDim,
216
+ borderColor: t.error,
217
+ },
218
+ actionIcon: {
219
+ fontSize: 14,
220
+ marginRight: 6,
221
+ },
222
+ actionButtonTitle: {
223
+ fontFamily: MonospaceFont,
224
+ fontSize: 13,
225
+ fontWeight: '600',
226
+ color: t.accent,
227
+ },
228
+ actionButtonTitleDestructive: {
229
+ color: t.error,
230
+ },
231
+ })
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useState } from 'react'
1
+ import React, { useCallback, useMemo, useState } from 'react'
2
2
  import {
3
3
  LayoutAnimation,
4
4
  Platform,
@@ -8,7 +8,9 @@ import {
8
8
  UIManager,
9
9
  View,
10
10
  } from 'react-native'
11
- import { DarkTheme, MonospaceFont } from '../constants'
11
+ import { MonospaceFont } from '../constants'
12
+ import { useBackstageTheme } from '../ThemeContext'
13
+ import type { BackstageTheme } from '../types'
12
14
 
13
15
  // Enable LayoutAnimation on Android
14
16
  if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
@@ -35,12 +37,12 @@ interface JsonNodeProps {
35
37
 
36
38
  // ─── Color Mapping ───────────────────────────────────────────────────────────
37
39
 
38
- function getValueColor(value: unknown): string {
39
- if (value === null || value === undefined) return DarkTheme.textMuted
40
- if (typeof value === 'string') return '#98C379' // green
41
- if (typeof value === 'number') return '#61AFEF' // cyan
42
- if (typeof value === 'boolean') return '#C678DD' // purple
43
- return DarkTheme.text
40
+ function getValueColor(value: unknown, t: BackstageTheme): string {
41
+ if (value === null || value === undefined) return t.textMuted
42
+ if (typeof value === 'string') return '#98C379'
43
+ if (typeof value === 'number') return '#61AFEF'
44
+ if (typeof value === 'boolean') return '#C678DD'
45
+ return t.text
44
46
  }
45
47
 
46
48
  function getPreview(value: unknown): string {
@@ -79,7 +81,9 @@ const JsonNode: React.FC<JsonNodeProps> = ({
79
81
  isLast,
80
82
  hideKey = false,
81
83
  }) => {
82
- const [expanded, setExpanded] = useState(depth < 1)
84
+ const theme = useBackstageTheme()
85
+ const s = useMemo(() => createStyles(theme), [theme])
86
+ const [expanded, setExpanded] = useState(false)
83
87
  const expandable = isExpandable(value)
84
88
  const reachedMaxDepth = depth >= maxDepth
85
89
 
@@ -95,18 +99,18 @@ const JsonNode: React.FC<JsonNodeProps> = ({
95
99
  const displayValue = reachedMaxDepth && expandable ? getPreview(value) : formatValue(value)
96
100
 
97
101
  return (
98
- <View style={[styles.row, { paddingLeft: indent }]}>
99
- {!hideKey && keyName !== null && <Text style={styles.key}>{`${keyName}: `}</Text>}
102
+ <View style={[s.row, { paddingLeft: indent }]}>
103
+ {!hideKey && keyName !== null && <Text style={s.key}>{`${keyName}: `}</Text>}
100
104
  <Text
101
105
  style={[
102
- styles.value,
103
- { color: reachedMaxDepth ? DarkTheme.textMuted : getValueColor(value) },
106
+ s.value,
107
+ { color: reachedMaxDepth ? theme.textMuted : getValueColor(value, theme) },
104
108
  ]}
105
109
  numberOfLines={3}
106
110
  >
107
111
  {displayValue}
108
112
  </Text>
109
- {!isLast && <Text style={styles.comma}>,</Text>}
113
+ {!isLast && <Text style={s.comma}>,</Text>}
110
114
  </View>
111
115
  )
112
116
  }
@@ -124,16 +128,16 @@ const JsonNode: React.FC<JsonNodeProps> = ({
124
128
  <TouchableOpacity
125
129
  onPress={toggleExpand}
126
130
  activeOpacity={0.7}
127
- style={[styles.row, { paddingLeft: indent }]}
131
+ style={[s.row, { paddingLeft: indent }]}
128
132
  >
129
- <Text style={styles.chevron}>{expanded ? '▼' : '▶'}</Text>
130
- {!hideKey && keyName !== null && <Text style={styles.key}>{`${keyName}: `}</Text>}
131
- <Text style={styles.bracket}>{bracketOpen}</Text>
133
+ <Text style={s.chevron}>{expanded ? '▼' : '▶'}</Text>
134
+ {!hideKey && keyName !== null && <Text style={s.key}>{`${keyName}: `}</Text>}
135
+ <Text style={s.bracket}>{bracketOpen}</Text>
132
136
  {!expanded && (
133
137
  <>
134
- <Text style={styles.preview}>{` ${getPreview(value)} `}</Text>
135
- <Text style={styles.bracket}>{bracketClose}</Text>
136
- {!isLast && <Text style={styles.comma}>,</Text>}
138
+ <Text style={s.preview}>{` ${getPreview(value)} `}</Text>
139
+ <Text style={s.bracket}>{bracketClose}</Text>
140
+ {!isLast && <Text style={s.comma}>,</Text>}
137
141
  </>
138
142
  )}
139
143
  </TouchableOpacity>
@@ -150,9 +154,9 @@ const JsonNode: React.FC<JsonNodeProps> = ({
150
154
  isLast={i === entries.length - 1}
151
155
  />
152
156
  ))}
153
- <View style={[styles.row, { paddingLeft: indent }]}>
154
- <Text style={styles.bracket}>{bracketClose}</Text>
155
- {!isLast && <Text style={styles.comma}>,</Text>}
157
+ <View style={[s.row, { paddingLeft: indent }]}>
158
+ <Text style={s.bracket}>{bracketClose}</Text>
159
+ {!isLast && <Text style={s.comma}>,</Text>}
156
160
  </View>
157
161
  </>
158
162
  )}
@@ -167,12 +171,15 @@ export const JsonTreeView: React.FC<JsonTreeViewProps> = ({
167
171
  hideRoot = false,
168
172
  maxDepth = 10,
169
173
  }) => {
174
+ const theme = useBackstageTheme()
175
+ const s = useMemo(() => createStyles(theme), [theme])
176
+
170
177
  if (data === null || data === undefined) {
171
- return <Text style={[styles.value, { color: DarkTheme.textMuted }]}>{String(data)}</Text>
178
+ return <Text style={[s.value, { color: theme.textMuted }]}>{String(data)}</Text>
172
179
  }
173
180
 
174
181
  if (!isExpandable(data)) {
175
- return <Text style={[styles.value, { color: getValueColor(data) }]}>{formatValue(data)}</Text>
182
+ return <Text style={[s.value, { color: getValueColor(data, theme) }]}>{formatValue(data)}</Text>
176
183
  }
177
184
 
178
185
  if (hideRoot) {
@@ -181,7 +188,7 @@ export const JsonTreeView: React.FC<JsonTreeViewProps> = ({
181
188
  : Object.entries(data as Record<string, unknown>)
182
189
 
183
190
  return (
184
- <View style={styles.container}>
191
+ <View style={s.container}>
185
192
  {entries.map(([k, v], i) => (
186
193
  <JsonNode
187
194
  key={`${k}_${i}`}
@@ -197,7 +204,7 @@ export const JsonTreeView: React.FC<JsonTreeViewProps> = ({
197
204
  }
198
205
 
199
206
  return (
200
- <View style={styles.container}>
207
+ <View style={s.container}>
201
208
  <JsonNode
202
209
  keyName={null}
203
210
  value={data}
@@ -212,48 +219,21 @@ export const JsonTreeView: React.FC<JsonTreeViewProps> = ({
212
219
 
213
220
  // ─── Styles ──────────────────────────────────────────────────────────────────
214
221
 
215
- const styles = StyleSheet.create({
216
- container: {
217
- paddingVertical: 4,
218
- },
219
- row: {
220
- flexDirection: 'row',
221
- alignItems: 'flex-start',
222
- paddingVertical: 2,
223
- flexWrap: 'wrap',
224
- },
225
- chevron: {
226
- fontFamily: MonospaceFont,
227
- fontSize: 10,
228
- color: DarkTheme.textMuted,
229
- marginRight: 6,
230
- marginTop: 2,
231
- width: 12,
232
- },
233
- key: {
234
- fontFamily: MonospaceFont,
235
- fontSize: 13,
236
- color: '#E06C75', // red-ish for keys
237
- },
238
- value: {
239
- fontFamily: MonospaceFont,
240
- fontSize: 13,
241
- flexShrink: 1,
242
- },
243
- bracket: {
244
- fontFamily: MonospaceFont,
245
- fontSize: 13,
246
- color: DarkTheme.textSecondary,
247
- },
248
- preview: {
249
- fontFamily: MonospaceFont,
250
- fontSize: 13,
251
- color: DarkTheme.textMuted,
252
- fontStyle: 'italic',
253
- },
254
- comma: {
255
- fontFamily: MonospaceFont,
256
- fontSize: 13,
257
- color: DarkTheme.textMuted,
258
- },
259
- })
222
+ const createStyles = (t: BackstageTheme) =>
223
+ StyleSheet.create({
224
+ container: { paddingVertical: 4 },
225
+ row: { flexDirection: 'row', alignItems: 'flex-start', paddingVertical: 2, flexWrap: 'wrap' },
226
+ chevron: {
227
+ fontFamily: MonospaceFont,
228
+ fontSize: 10,
229
+ color: t.textMuted,
230
+ marginRight: 6,
231
+ marginTop: 2,
232
+ width: 12,
233
+ },
234
+ key: { fontFamily: MonospaceFont, fontSize: 13, color: '#E06C75' },
235
+ value: { fontFamily: MonospaceFont, fontSize: 13, flexShrink: 1 },
236
+ bracket: { fontFamily: MonospaceFont, fontSize: 13, color: t.textSecondary },
237
+ preview: { fontFamily: MonospaceFont, fontSize: 13, color: t.textMuted, fontStyle: 'italic' },
238
+ comma: { fontFamily: MonospaceFont, fontSize: 13, color: t.textMuted },
239
+ })