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.
Files changed (103) hide show
  1. package/README.md +41 -19
  2. package/lib/commonjs/Backstage.js +42 -31
  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 +38 -16
  7. package/lib/commonjs/components/BackstagePanel.js.map +1 -1
  8. package/lib/commonjs/components/FlagsTab.js +256 -0
  9. package/lib/commonjs/components/FlagsTab.js.map +1 -0
  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 +1 -0
  31. package/lib/commonjs/types.js.map +1 -1
  32. package/lib/module/Backstage.js +42 -31
  33. package/lib/module/Backstage.js.map +1 -1
  34. package/lib/module/ThemeContext.js +37 -0
  35. package/lib/module/ThemeContext.js.map +1 -0
  36. package/lib/module/components/BackstagePanel.js +40 -18
  37. package/lib/module/components/BackstagePanel.js.map +1 -1
  38. package/lib/module/components/FlagsTab.js +251 -0
  39. package/lib/module/components/FlagsTab.js.map +1 -0
  40. package/lib/module/components/FloatingPill.js +5 -3
  41. package/lib/module/components/FloatingPill.js.map +1 -1
  42. package/lib/module/components/InfoTab.js +73 -107
  43. package/lib/module/components/InfoTab.js.map +1 -1
  44. package/lib/module/components/JsonTreeView.js +42 -38
  45. package/lib/module/components/JsonTreeView.js.map +1 -1
  46. package/lib/module/components/LogItem.js +35 -31
  47. package/lib/module/components/LogItem.js.map +1 -1
  48. package/lib/module/components/LogsTab.js +38 -43
  49. package/lib/module/components/LogsTab.js.map +1 -1
  50. package/lib/module/components/NetworkItem.js +113 -137
  51. package/lib/module/components/NetworkItem.js.map +1 -1
  52. package/lib/module/components/NetworkTab.js +49 -50
  53. package/lib/module/components/NetworkTab.js.map +1 -1
  54. package/lib/module/components/TabBar.js +10 -7
  55. package/lib/module/components/TabBar.js.map +1 -1
  56. package/lib/module/constants.js +23 -0
  57. package/lib/module/constants.js.map +1 -1
  58. package/lib/module/index.js +1 -0
  59. package/lib/module/index.js.map +1 -1
  60. package/lib/module/types.js +2 -0
  61. package/lib/module/types.js.map +1 -1
  62. package/lib/typescript/Backstage.d.ts.map +1 -1
  63. package/lib/typescript/ThemeContext.d.ts +11 -0
  64. package/lib/typescript/ThemeContext.d.ts.map +1 -0
  65. package/lib/typescript/components/BackstagePanel.d.ts +4 -1
  66. package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
  67. package/lib/typescript/components/FlagsTab.d.ts +9 -0
  68. package/lib/typescript/components/FlagsTab.d.ts.map +1 -0
  69. package/lib/typescript/components/FloatingPill.d.ts.map +1 -1
  70. package/lib/typescript/components/InfoTab.d.ts +1 -0
  71. package/lib/typescript/components/InfoTab.d.ts.map +1 -1
  72. package/lib/typescript/components/JsonTreeView.d.ts.map +1 -1
  73. package/lib/typescript/components/LogItem.d.ts +1 -0
  74. package/lib/typescript/components/LogItem.d.ts.map +1 -1
  75. package/lib/typescript/components/LogsTab.d.ts +1 -0
  76. package/lib/typescript/components/LogsTab.d.ts.map +1 -1
  77. package/lib/typescript/components/NetworkItem.d.ts +1 -0
  78. package/lib/typescript/components/NetworkItem.d.ts.map +1 -1
  79. package/lib/typescript/components/NetworkTab.d.ts +1 -0
  80. package/lib/typescript/components/NetworkTab.d.ts.map +1 -1
  81. package/lib/typescript/components/TabBar.d.ts.map +1 -1
  82. package/lib/typescript/constants.d.ts +1 -0
  83. package/lib/typescript/constants.d.ts.map +1 -1
  84. package/lib/typescript/index.d.ts +3 -1
  85. package/lib/typescript/index.d.ts.map +1 -1
  86. package/lib/typescript/types.d.ts +18 -0
  87. package/lib/typescript/types.d.ts.map +1 -1
  88. package/package.json +1 -1
  89. package/src/Backstage.tsx +41 -31
  90. package/src/ThemeContext.tsx +38 -0
  91. package/src/components/BackstagePanel.tsx +84 -56
  92. package/src/components/FlagsTab.tsx +198 -0
  93. package/src/components/FloatingPill.tsx +6 -3
  94. package/src/components/InfoTab.tsx +152 -186
  95. package/src/components/JsonTreeView.tsx +53 -73
  96. package/src/components/LogItem.tsx +55 -100
  97. package/src/components/LogsTab.tsx +110 -132
  98. package/src/components/NetworkItem.tsx +306 -417
  99. package/src/components/NetworkTab.tsx +129 -163
  100. package/src/components/TabBar.tsx +51 -47
  101. package/src/constants.ts +23 -0
  102. package/src/index.ts +3 -0
  103. package/src/types.ts +25 -0
@@ -0,0 +1,198 @@
1
+ import React, { useMemo, useState } from 'react'
2
+ import { ScrollView, StyleSheet, Switch, Text, TextInput, View } from 'react-native'
3
+ import { MonospaceFont } from '../constants'
4
+ import { useBackstageTheme } from '../ThemeContext'
5
+ import type { FeatureFlag, BackstageTheme } from '../types'
6
+
7
+ // ─── Types ───────────────────────────────────────────────────────────────────
8
+
9
+ interface FlagsTabProps {
10
+ flags: FeatureFlag[]
11
+ onToggle?: (key: string, value: boolean) => void
12
+ }
13
+
14
+ // ─── Component ───────────────────────────────────────────────────────────────
15
+
16
+ export const FlagsTab: React.FC<FlagsTabProps> = ({ flags, onToggle }) => {
17
+ const theme = useBackstageTheme()
18
+ const s = useMemo(() => createStyles(theme), [theme])
19
+ const [searchText, setSearchText] = useState('')
20
+
21
+ const filteredFlags = useMemo(() => {
22
+ if (!searchText.trim()) return flags
23
+ const query = searchText.toLowerCase()
24
+ return flags.filter(
25
+ f =>
26
+ f.label.toLowerCase().includes(query) ||
27
+ f.key.toLowerCase().includes(query) ||
28
+ (f.description && f.description.toLowerCase().includes(query)),
29
+ )
30
+ }, [flags, searchText])
31
+
32
+ const enabledCount = flags.filter(f => f.value).length
33
+
34
+ return (
35
+ <View style={s.container}>
36
+ <View style={s.searchContainer}>
37
+ <View style={s.searchInputWrapper}>
38
+ <Text style={s.searchIcon}>⌕</Text>
39
+ <TextInput
40
+ style={s.searchInput}
41
+ placeholder="Filter flags..."
42
+ placeholderTextColor={theme.textMuted}
43
+ value={searchText}
44
+ onChangeText={setSearchText}
45
+ autoCapitalize="none"
46
+ autoCorrect={false}
47
+ clearButtonMode="while-editing"
48
+ returnKeyType="search"
49
+ />
50
+ </View>
51
+ </View>
52
+
53
+ <View style={s.statsBar}>
54
+ <Text style={s.statText}>
55
+ {filteredFlags.length === flags.length
56
+ ? `${flags.length} flag${flags.length !== 1 ? 's' : ''}`
57
+ : `${filteredFlags.length} of ${flags.length}`}
58
+ </Text>
59
+ <Text style={s.statText}>
60
+ <Text style={s.statEnabled}>{enabledCount} on</Text>
61
+ {' · '}
62
+ <Text style={s.statDisabled}>{flags.length - enabledCount} off</Text>
63
+ </Text>
64
+ </View>
65
+
66
+ <ScrollView
67
+ style={s.list}
68
+ contentContainerStyle={filteredFlags.length === 0 ? s.emptyContainer : s.listContent}
69
+ showsVerticalScrollIndicator={false}
70
+ >
71
+ {filteredFlags.length === 0 ? (
72
+ <View style={s.emptyState}>
73
+ <Text style={s.emptyIcon}>🎚</Text>
74
+ <Text style={s.emptyTitle}>No flags match</Text>
75
+ <Text style={s.emptySubtitle}>Try adjusting your search</Text>
76
+ </View>
77
+ ) : (
78
+ filteredFlags.map((flag, index) => (
79
+ <View key={flag.key}>
80
+ <View style={s.flagRow}>
81
+ <View style={s.flagInfo}>
82
+ <View style={s.flagHeader}>
83
+ <Text style={s.flagLabel}>{flag.label}</Text>
84
+ <Text style={[s.flagKey, flag.value && s.flagKeyActive]}>{flag.key}</Text>
85
+ </View>
86
+ {flag.description && <Text style={s.flagDescription}>{flag.description}</Text>}
87
+ </View>
88
+ <Switch
89
+ testID={`backstage.flag.${flag.key}`}
90
+ value={flag.value}
91
+ onValueChange={(newValue: boolean) => onToggle?.(flag.key, newValue)}
92
+ trackColor={{ false: theme.border, true: theme.accent }}
93
+ thumbColor={flag.value ? '#FFFFFF' : theme.textMuted}
94
+ ios_backgroundColor={theme.border}
95
+ />
96
+ </View>
97
+ {index < filteredFlags.length - 1 && <View style={s.divider} />}
98
+ </View>
99
+ ))
100
+ )}
101
+ </ScrollView>
102
+ </View>
103
+ )
104
+ }
105
+
106
+ // ─── Styles ──────────────────────────────────────────────────────────────────
107
+
108
+ const createStyles = (t: BackstageTheme) =>
109
+ StyleSheet.create({
110
+ container: { flex: 1 },
111
+ searchContainer: {
112
+ paddingHorizontal: 14,
113
+ paddingVertical: 10,
114
+ borderBottomWidth: 1,
115
+ borderBottomColor: t.border,
116
+ },
117
+ searchInputWrapper: {
118
+ flexDirection: 'row',
119
+ alignItems: 'center',
120
+ backgroundColor: t.surfaceElevated,
121
+ borderRadius: 10,
122
+ borderWidth: 1,
123
+ borderColor: t.border,
124
+ paddingHorizontal: 12,
125
+ height: 38,
126
+ },
127
+ searchIcon: { fontSize: 16, color: t.textMuted, marginRight: 8 },
128
+ searchInput: {
129
+ flex: 1,
130
+ fontFamily: MonospaceFont,
131
+ fontSize: 13,
132
+ color: t.text,
133
+ padding: 0,
134
+ },
135
+ statsBar: {
136
+ flexDirection: 'row',
137
+ justifyContent: 'space-between',
138
+ alignItems: 'center',
139
+ paddingHorizontal: 16,
140
+ paddingVertical: 6,
141
+ backgroundColor: t.surface,
142
+ },
143
+ statText: { fontFamily: MonospaceFont, fontSize: 11, color: t.textMuted },
144
+ statEnabled: { color: t.success, fontWeight: '700' },
145
+ statDisabled: { color: t.textMuted },
146
+ list: { flex: 1 },
147
+ listContent: { paddingHorizontal: 16, paddingVertical: 8 },
148
+ emptyContainer: { flex: 1, justifyContent: 'center' },
149
+ emptyState: { alignItems: 'center', justifyContent: 'center', padding: 40 },
150
+ emptyIcon: { fontSize: 48, marginBottom: 16, opacity: 0.5 },
151
+ emptyTitle: {
152
+ fontFamily: MonospaceFont,
153
+ fontSize: 16,
154
+ fontWeight: '700',
155
+ color: t.textSecondary,
156
+ marginBottom: 8,
157
+ },
158
+ emptySubtitle: {
159
+ fontFamily: MonospaceFont,
160
+ fontSize: 13,
161
+ color: t.textMuted,
162
+ textAlign: 'center',
163
+ },
164
+ flagRow: {
165
+ flexDirection: 'row',
166
+ justifyContent: 'space-between',
167
+ alignItems: 'center',
168
+ paddingVertical: 14,
169
+ paddingHorizontal: 4,
170
+ },
171
+ flagInfo: { flex: 1, marginRight: 16 },
172
+ flagHeader: { flexDirection: 'row', alignItems: 'center', gap: 8 },
173
+ flagLabel: {
174
+ fontFamily: MonospaceFont,
175
+ fontSize: 14,
176
+ color: t.text,
177
+ fontWeight: '600',
178
+ },
179
+ flagKey: {
180
+ fontFamily: MonospaceFont,
181
+ fontSize: 10,
182
+ color: t.textMuted,
183
+ backgroundColor: t.surfaceElevated,
184
+ borderRadius: 4,
185
+ paddingHorizontal: 6,
186
+ paddingVertical: 1,
187
+ overflow: 'hidden',
188
+ },
189
+ flagKeyActive: { color: t.accent, backgroundColor: t.accentDim },
190
+ flagDescription: {
191
+ fontFamily: MonospaceFont,
192
+ fontSize: 12,
193
+ color: t.textMuted,
194
+ marginTop: 4,
195
+ lineHeight: 18,
196
+ },
197
+ divider: { height: 1, backgroundColor: t.border, marginHorizontal: 4 },
198
+ })
@@ -1,6 +1,7 @@
1
1
  import React, { useRef } from 'react'
2
2
  import { Animated, Dimensions, PanResponder, StyleSheet, Text } from 'react-native'
3
- import { DarkTheme, Metrics, MonospaceFont } from '../constants'
3
+ import { Metrics, MonospaceFont } from '../constants'
4
+ import { useBackstageTheme } from '../ThemeContext'
4
5
  import type { BackstageStyleOverrides } from '../types'
5
6
 
6
7
  // ─── Types ───────────────────────────────────────────────────────────────────
@@ -100,8 +101,10 @@ export const FloatingPill: React.FC<FloatingPillProps> = ({
100
101
  }),
101
102
  ).current
102
103
 
103
- const backgroundColor = hasError ? DarkTheme.error : DarkTheme.accent
104
- const shadowColor = hasError ? DarkTheme.error : DarkTheme.accent
104
+ const theme = useBackstageTheme()
105
+
106
+ const backgroundColor = hasError ? theme.error : theme.accent
107
+ const shadowColor = hasError ? theme.error : theme.accent
105
108
 
106
109
  return (
107
110
  <Animated.View
@@ -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 } 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={styles.container}
120
- contentContainerStyle={styles.scrollContent}
60
+ style={s.container}
61
+ contentContainerStyle={s.scrollContent}
121
62
  showsVerticalScrollIndicator={false}
122
63
  >
123
64
  {/* ── Device Info Section ─────────────────────────────────── */}
124
- <View style={styles.section}>
125
- <SectionHeader title="DEVICE INFO" titleStyle={propStyles?.sectionTitleStyle} />
126
- <View style={styles.card}>
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
- <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} />}
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={styles.section}>
144
- <SectionHeader title="STATE TREE" titleStyle={propStyles?.sectionTitleStyle} />
145
- <View style={styles.card}>
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={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
- ))}
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={styles.section}>{children}</View>}
136
+ {children && <View style={s.section}>{children}</View>}
171
137
  </ScrollView>
172
138
  )
173
139
  }
174
140
 
175
141
  // ─── Styles ──────────────────────────────────────────────────────────────────
176
142
 
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
- })
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
+ })