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.
- package/README.md +2 -0
- package/lib/commonjs/Backstage.js +40 -33
- 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 +17 -10
- package/lib/commonjs/components/BackstagePanel.js.map +1 -1
- package/lib/commonjs/components/FlagsTab.js +50 -49
- package/lib/commonjs/components/FlagsTab.js.map +1 -1
- 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.map +1 -1
- package/lib/module/Backstage.js +40 -33
- 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 +19 -12
- package/lib/module/components/BackstagePanel.js.map +1 -1
- package/lib/module/components/FlagsTab.js +51 -50
- package/lib/module/components/FlagsTab.js.map +1 -1
- 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.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 +1 -0
- package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
- package/lib/typescript/components/FlagsTab.d.ts.map +1 -1
- 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 +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +4 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Backstage.tsx +39 -33
- package/src/ThemeContext.tsx +38 -0
- package/src/components/BackstagePanel.tsx +66 -57
- package/src/components/FlagsTab.tsx +123 -174
- 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 +2 -0
- package/src/types.ts +6 -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 {
|
|
3
|
+
import { MonospaceFont } 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,25 @@ export const InfoTab: React.FC<InfoTabProps> = ({
|
|
|
116
57
|
|
|
117
58
|
return (
|
|
118
59
|
<ScrollView
|
|
119
|
-
style={
|
|
120
|
-
contentContainerStyle={
|
|
60
|
+
style={s.container}
|
|
61
|
+
contentContainerStyle={s.scrollContent}
|
|
121
62
|
showsVerticalScrollIndicator={false}
|
|
122
63
|
>
|
|
123
64
|
{/* ── Device Info Section ─────────────────────────────────── */}
|
|
124
|
-
<View style={
|
|
125
|
-
<
|
|
126
|
-
<View style={
|
|
65
|
+
<View style={s.section}>
|
|
66
|
+
<Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>DEVICE INFO</Text>
|
|
67
|
+
<View style={s.card}>
|
|
127
68
|
{allInfo.map((item, index) => (
|
|
128
69
|
<React.Fragment key={`info_${index}`}>
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
70
|
+
<View style={s.infoRow}>
|
|
71
|
+
<Text style={[s.infoLabel, propStyles?.infoLabelStyle]} numberOfLines={1}>
|
|
72
|
+
{item.label}
|
|
73
|
+
</Text>
|
|
74
|
+
<Text style={[s.infoValue, propStyles?.infoValueStyle]} selectable>
|
|
75
|
+
{item.value}
|
|
76
|
+
</Text>
|
|
77
|
+
</View>
|
|
78
|
+
{index < allInfo.length - 1 && <View style={s.divider} />}
|
|
136
79
|
</React.Fragment>
|
|
137
80
|
))}
|
|
138
81
|
</View>
|
|
@@ -140,125 +83,148 @@ export const InfoTab: React.FC<InfoTabProps> = ({
|
|
|
140
83
|
|
|
141
84
|
{/* ── State Tree Section ──────────────────────────────────── */}
|
|
142
85
|
{state && Object.keys(state).length > 0 && (
|
|
143
|
-
<View style={
|
|
144
|
-
<
|
|
145
|
-
<View style={
|
|
146
|
-
<JsonTreeView data={state} hideRoot />
|
|
86
|
+
<View style={s.section}>
|
|
87
|
+
<Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>STATE TREE</Text>
|
|
88
|
+
<View style={s.card}>
|
|
89
|
+
<JsonTreeView data={state} hideRoot maxDepth={jsonMaxDepth} />
|
|
147
90
|
</View>
|
|
148
91
|
</View>
|
|
149
92
|
)}
|
|
150
93
|
|
|
151
|
-
{/* ── Quick Actions Section
|
|
94
|
+
{/* ── Quick Actions Section ─────────────────────────────────── */}
|
|
152
95
|
{quickActions.length > 0 && (
|
|
153
|
-
<View style={
|
|
154
|
-
<
|
|
155
|
-
<View style={
|
|
156
|
-
{quickActions.map((action, index) =>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
action
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
96
|
+
<View style={s.section}>
|
|
97
|
+
<Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>QUICK ACTIONS</Text>
|
|
98
|
+
<View style={s.actionsGrid}>
|
|
99
|
+
{quickActions.map((action, index) => {
|
|
100
|
+
const handlePress = () => {
|
|
101
|
+
action.onPress()
|
|
102
|
+
if (action.closeOnPress && onClosePanel) {
|
|
103
|
+
onClosePanel()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return (
|
|
107
|
+
<TouchableOpacity
|
|
108
|
+
key={`action_${index}`}
|
|
109
|
+
testID={action.testID}
|
|
110
|
+
style={[
|
|
111
|
+
s.actionButton,
|
|
112
|
+
action.destructive && s.actionButtonDestructive,
|
|
113
|
+
propStyles?.actionButtonStyle,
|
|
114
|
+
]}
|
|
115
|
+
onPress={handlePress}
|
|
116
|
+
activeOpacity={0.7}
|
|
117
|
+
>
|
|
118
|
+
{action.icon && <Text style={s.actionIcon}>{action.icon}</Text>}
|
|
119
|
+
<Text
|
|
120
|
+
style={[
|
|
121
|
+
s.actionButtonTitle,
|
|
122
|
+
action.destructive && s.actionButtonTitleDestructive,
|
|
123
|
+
propStyles?.actionButtonTitleStyle,
|
|
124
|
+
]}
|
|
125
|
+
>
|
|
126
|
+
{action.title}
|
|
127
|
+
</Text>
|
|
128
|
+
</TouchableOpacity>
|
|
129
|
+
)
|
|
130
|
+
})}
|
|
165
131
|
</View>
|
|
166
132
|
</View>
|
|
167
133
|
)}
|
|
168
134
|
|
|
169
135
|
{/* ── Custom Children ─────────────────────────────────────── */}
|
|
170
|
-
{children && <View style={
|
|
136
|
+
{children && <View style={s.section}>{children}</View>}
|
|
171
137
|
</ScrollView>
|
|
172
138
|
)
|
|
173
139
|
}
|
|
174
140
|
|
|
175
141
|
// ─── Styles ──────────────────────────────────────────────────────────────────
|
|
176
142
|
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
})
|
|
143
|
+
const createStyles = (t: BackstageTheme) =>
|
|
144
|
+
StyleSheet.create({
|
|
145
|
+
container: {
|
|
146
|
+
flex: 1,
|
|
147
|
+
},
|
|
148
|
+
scrollContent: {
|
|
149
|
+
padding: 16,
|
|
150
|
+
paddingBottom: 32,
|
|
151
|
+
},
|
|
152
|
+
section: {
|
|
153
|
+
marginBottom: 20,
|
|
154
|
+
},
|
|
155
|
+
sectionTitle: {
|
|
156
|
+
fontFamily: MonospaceFont,
|
|
157
|
+
fontSize: 11,
|
|
158
|
+
fontWeight: '700',
|
|
159
|
+
color: t.textMuted,
|
|
160
|
+
letterSpacing: 1.5,
|
|
161
|
+
marginBottom: 8,
|
|
162
|
+
paddingLeft: 4,
|
|
163
|
+
},
|
|
164
|
+
card: {
|
|
165
|
+
backgroundColor: t.surfaceElevated,
|
|
166
|
+
borderRadius: 12,
|
|
167
|
+
borderWidth: 1,
|
|
168
|
+
borderColor: t.border,
|
|
169
|
+
padding: 12,
|
|
170
|
+
overflow: 'hidden',
|
|
171
|
+
},
|
|
172
|
+
infoRow: {
|
|
173
|
+
flexDirection: 'row',
|
|
174
|
+
justifyContent: 'space-between',
|
|
175
|
+
alignItems: 'center',
|
|
176
|
+
paddingVertical: 8,
|
|
177
|
+
paddingHorizontal: 4,
|
|
178
|
+
},
|
|
179
|
+
infoLabel: {
|
|
180
|
+
fontFamily: MonospaceFont,
|
|
181
|
+
fontSize: 13,
|
|
182
|
+
color: t.textSecondary,
|
|
183
|
+
},
|
|
184
|
+
infoValue: {
|
|
185
|
+
fontFamily: MonospaceFont,
|
|
186
|
+
fontSize: 13,
|
|
187
|
+
color: t.text,
|
|
188
|
+
fontWeight: '600',
|
|
189
|
+
textAlign: 'right',
|
|
190
|
+
flex: 1,
|
|
191
|
+
marginLeft: 12,
|
|
192
|
+
},
|
|
193
|
+
divider: {
|
|
194
|
+
height: 1,
|
|
195
|
+
backgroundColor: t.border,
|
|
196
|
+
marginHorizontal: 4,
|
|
197
|
+
},
|
|
198
|
+
actionsGrid: {
|
|
199
|
+
flexDirection: 'row',
|
|
200
|
+
flexWrap: 'wrap',
|
|
201
|
+
gap: 10,
|
|
202
|
+
},
|
|
203
|
+
actionButton: {
|
|
204
|
+
backgroundColor: t.accentDim,
|
|
205
|
+
borderRadius: 10,
|
|
206
|
+
borderWidth: 1,
|
|
207
|
+
borderColor: t.accent,
|
|
208
|
+
paddingVertical: 10,
|
|
209
|
+
paddingHorizontal: 18,
|
|
210
|
+
flexDirection: 'row',
|
|
211
|
+
alignItems: 'center',
|
|
212
|
+
},
|
|
213
|
+
actionButtonDestructive: {
|
|
214
|
+
backgroundColor: t.errorDim,
|
|
215
|
+
borderColor: t.error,
|
|
216
|
+
},
|
|
217
|
+
actionIcon: {
|
|
218
|
+
fontSize: 14,
|
|
219
|
+
marginRight: 6,
|
|
220
|
+
},
|
|
221
|
+
actionButtonTitle: {
|
|
222
|
+
fontFamily: MonospaceFont,
|
|
223
|
+
fontSize: 13,
|
|
224
|
+
fontWeight: '600',
|
|
225
|
+
color: t.accent,
|
|
226
|
+
},
|
|
227
|
+
actionButtonTitleDestructive: {
|
|
228
|
+
color: t.error,
|
|
229
|
+
},
|
|
230
|
+
})
|
|
@@ -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 {
|
|
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
|
|
40
|
-
if (typeof value === 'string') return '#98C379'
|
|
41
|
-
if (typeof value === 'number') return '#61AFEF'
|
|
42
|
-
if (typeof value === 'boolean') return '#C678DD'
|
|
43
|
-
return
|
|
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
|
|
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={[
|
|
99
|
-
{!hideKey && keyName !== null && <Text style={
|
|
102
|
+
<View style={[s.row, { paddingLeft: indent }]}>
|
|
103
|
+
{!hideKey && keyName !== null && <Text style={s.key}>{`${keyName}: `}</Text>}
|
|
100
104
|
<Text
|
|
101
105
|
style={[
|
|
102
|
-
|
|
103
|
-
{ color: reachedMaxDepth ?
|
|
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={
|
|
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={[
|
|
131
|
+
style={[s.row, { paddingLeft: indent }]}
|
|
128
132
|
>
|
|
129
|
-
<Text style={
|
|
130
|
-
{!hideKey && keyName !== null && <Text style={
|
|
131
|
-
<Text style={
|
|
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={
|
|
135
|
-
<Text style={
|
|
136
|
-
{!isLast && <Text style={
|
|
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={[
|
|
154
|
-
<Text style={
|
|
155
|
-
{!isLast && <Text style={
|
|
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={[
|
|
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={[
|
|
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={
|
|
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={
|
|
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
|
|
216
|
-
|
|
217
|
-
paddingVertical: 4,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
fontSize:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
+
})
|