rn-backstage 1.2.1 → 1.4.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 (99) hide show
  1. package/README.md +109 -2
  2. package/lib/commonjs/Backstage.js +19 -2
  3. package/lib/commonjs/Backstage.js.map +1 -1
  4. package/lib/commonjs/bug-report.js +173 -0
  5. package/lib/commonjs/bug-report.js.map +1 -0
  6. package/lib/commonjs/components/BackstagePanel.js +100 -17
  7. package/lib/commonjs/components/BackstagePanel.js.map +1 -1
  8. package/lib/commonjs/components/BugReportComposer.js +510 -0
  9. package/lib/commonjs/components/BugReportComposer.js.map +1 -0
  10. package/lib/commonjs/components/FlagsTab.js +21 -11
  11. package/lib/commonjs/components/FlagsTab.js.map +1 -1
  12. package/lib/commonjs/components/FloatingPill.js +82 -22
  13. package/lib/commonjs/components/FloatingPill.js.map +1 -1
  14. package/lib/commonjs/components/InfoTab.js +5 -1
  15. package/lib/commonjs/components/InfoTab.js.map +1 -1
  16. package/lib/commonjs/components/LogItem.js +6 -0
  17. package/lib/commonjs/components/LogItem.js.map +1 -1
  18. package/lib/commonjs/components/LogsTab.js +3 -3
  19. package/lib/commonjs/components/LogsTab.js.map +1 -1
  20. package/lib/commonjs/components/NetworkTab.js +3 -2
  21. package/lib/commonjs/components/NetworkTab.js.map +1 -1
  22. package/lib/commonjs/components/StorageTab.js +672 -0
  23. package/lib/commonjs/components/StorageTab.js.map +1 -0
  24. package/lib/commonjs/constants.js +88 -6
  25. package/lib/commonjs/constants.js.map +1 -1
  26. package/lib/commonjs/index.js +84 -0
  27. package/lib/commonjs/index.js.map +1 -1
  28. package/lib/commonjs/types.js +2 -0
  29. package/lib/commonjs/types.js.map +1 -1
  30. package/lib/module/Backstage.js +19 -2
  31. package/lib/module/Backstage.js.map +1 -1
  32. package/lib/module/bug-report.js +164 -0
  33. package/lib/module/bug-report.js.map +1 -0
  34. package/lib/module/components/BackstagePanel.js +101 -18
  35. package/lib/module/components/BackstagePanel.js.map +1 -1
  36. package/lib/module/components/BugReportComposer.js +505 -0
  37. package/lib/module/components/BugReportComposer.js.map +1 -0
  38. package/lib/module/components/FlagsTab.js +23 -13
  39. package/lib/module/components/FlagsTab.js.map +1 -1
  40. package/lib/module/components/FloatingPill.js +86 -26
  41. package/lib/module/components/FloatingPill.js.map +1 -1
  42. package/lib/module/components/InfoTab.js +6 -2
  43. package/lib/module/components/InfoTab.js.map +1 -1
  44. package/lib/module/components/LogItem.js +7 -1
  45. package/lib/module/components/LogItem.js.map +1 -1
  46. package/lib/module/components/LogsTab.js +3 -3
  47. package/lib/module/components/LogsTab.js.map +1 -1
  48. package/lib/module/components/NetworkTab.js +3 -2
  49. package/lib/module/components/NetworkTab.js.map +1 -1
  50. package/lib/module/components/StorageTab.js +667 -0
  51. package/lib/module/components/StorageTab.js.map +1 -0
  52. package/lib/module/constants.js +88 -6
  53. package/lib/module/constants.js.map +1 -1
  54. package/lib/module/index.js +14 -0
  55. package/lib/module/index.js.map +1 -1
  56. package/lib/module/types.js +4 -0
  57. package/lib/module/types.js.map +1 -1
  58. package/lib/typescript/Backstage.d.ts.map +1 -1
  59. package/lib/typescript/bug-report.d.ts +12 -0
  60. package/lib/typescript/bug-report.d.ts.map +1 -0
  61. package/lib/typescript/components/BackstagePanel.d.ts +4 -1
  62. package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
  63. package/lib/typescript/components/BugReportComposer.d.ts +17 -0
  64. package/lib/typescript/components/BugReportComposer.d.ts.map +1 -0
  65. package/lib/typescript/components/FlagsTab.d.ts.map +1 -1
  66. package/lib/typescript/components/FloatingPill.d.ts +2 -0
  67. package/lib/typescript/components/FloatingPill.d.ts.map +1 -1
  68. package/lib/typescript/components/InfoTab.d.ts.map +1 -1
  69. package/lib/typescript/components/LogItem.d.ts.map +1 -1
  70. package/lib/typescript/components/LogsTab.d.ts.map +1 -1
  71. package/lib/typescript/components/NetworkTab.d.ts.map +1 -1
  72. package/lib/typescript/components/StorageTab.d.ts +9 -0
  73. package/lib/typescript/components/StorageTab.d.ts.map +1 -0
  74. package/lib/typescript/constants.d.ts +74 -4
  75. package/lib/typescript/constants.d.ts.map +1 -1
  76. package/lib/typescript/index.d.ts +13 -1
  77. package/lib/typescript/index.d.ts.map +1 -1
  78. package/lib/typescript/types.d.ts +55 -0
  79. package/lib/typescript/types.d.ts.map +1 -1
  80. package/package.json +7 -6
  81. package/src/Backstage.tsx +0 -209
  82. package/src/ThemeContext.tsx +0 -38
  83. package/src/components/BackstagePanel.tsx +0 -258
  84. package/src/components/FlagsTab.tsx +0 -198
  85. package/src/components/FloatingPill.tsx +0 -153
  86. package/src/components/InfoTab.tsx +0 -230
  87. package/src/components/JsonTreeView.tsx +0 -239
  88. package/src/components/LogItem.tsx +0 -144
  89. package/src/components/LogsTab.tsx +0 -217
  90. package/src/components/NetworkItem.tsx +0 -425
  91. package/src/components/NetworkTab.tsx +0 -240
  92. package/src/components/TabBar.tsx +0 -154
  93. package/src/constants.ts +0 -114
  94. package/src/index.ts +0 -18
  95. package/src/log-interceptor.ts +0 -148
  96. package/src/network-interceptor.ts +0 -468
  97. package/src/types.ts +0 -213
  98. package/src/utils/formatTimestamp.ts +0 -22
  99. package/src/utils/stringify.ts +0 -90
@@ -1,198 +0,0 @@
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,153 +0,0 @@
1
- import React, { useRef } from 'react'
2
- import { Animated, Dimensions, PanResponder, StyleSheet, Text } from 'react-native'
3
- import { Metrics, MonospaceFont } from '../constants'
4
- import { useBackstageTheme } from '../ThemeContext'
5
- import type { BackstageStyleOverrides } from '../types'
6
-
7
- // ─── Types ───────────────────────────────────────────────────────────────────
8
-
9
- interface FloatingPillProps {
10
- text: string
11
- hasError: boolean
12
- onPress: () => void
13
- initialX?: number
14
- initialY?: number
15
- styles?: BackstageStyleOverrides
16
- }
17
-
18
- // ─── Helpers ─────────────────────────────────────────────────────────────────
19
-
20
- const PILL_WIDTH = Metrics.pillWidth
21
- const PILL_HEIGHT = Metrics.pillHeight
22
- const DRAG_THRESHOLD = 5
23
-
24
- function clampPosition(x: number, y: number): { x: number; y: number } {
25
- const { width, height } = Dimensions.get('window')
26
- return {
27
- x: Math.max(0, Math.min(x, width - PILL_WIDTH)),
28
- y: Math.max(0, Math.min(y, height - PILL_HEIGHT)),
29
- }
30
- }
31
-
32
- // ─── Component ───────────────────────────────────────────────────────────────
33
-
34
- export const FloatingPill: React.FC<FloatingPillProps> = ({
35
- text,
36
- hasError,
37
- onPress,
38
- initialX,
39
- initialY,
40
- styles: propStyles,
41
- }) => {
42
- const { width: screenW, height: screenH } = Dimensions.get('window')
43
- const defaultX = initialX ?? screenW - PILL_WIDTH - 16
44
- const defaultY = initialY ?? screenH - PILL_HEIGHT - 120
45
-
46
- const pan = useRef(new Animated.ValueXY({ x: defaultX, y: defaultY })).current
47
- const lastPosition = useRef({ x: defaultX, y: defaultY })
48
- const isDragging = useRef(false)
49
- const dragDistance = useRef(0)
50
-
51
- const panResponder = useRef(
52
- PanResponder.create({
53
- onStartShouldSetPanResponder: () => true,
54
- onMoveShouldSetPanResponder: (_, gestureState) => {
55
- return (
56
- Math.abs(gestureState.dx) > DRAG_THRESHOLD || Math.abs(gestureState.dy) > DRAG_THRESHOLD
57
- )
58
- },
59
- onPanResponderGrant: () => {
60
- isDragging.current = false
61
- dragDistance.current = 0
62
- pan.setOffset({
63
- x: lastPosition.current.x,
64
- y: lastPosition.current.y,
65
- })
66
- pan.setValue({ x: 0, y: 0 })
67
- },
68
- onPanResponderMove: (_, gestureState) => {
69
- dragDistance.current = Math.sqrt(
70
- gestureState.dx * gestureState.dx + gestureState.dy * gestureState.dy,
71
- )
72
- if (dragDistance.current > DRAG_THRESHOLD) {
73
- isDragging.current = true
74
- }
75
- Animated.event([null, { dx: pan.x, dy: pan.y }], {
76
- useNativeDriver: false,
77
- })(_, gestureState)
78
- },
79
- onPanResponderRelease: (_, gestureState) => {
80
- pan.flattenOffset()
81
-
82
- const newPos = clampPosition(
83
- lastPosition.current.x + gestureState.dx,
84
- lastPosition.current.y + gestureState.dy,
85
- )
86
-
87
- lastPosition.current = newPos
88
-
89
- Animated.spring(pan, {
90
- toValue: newPos,
91
- useNativeDriver: false,
92
- friction: 7,
93
- tension: 40,
94
- }).start()
95
-
96
- // Only trigger tap if drag distance was minimal
97
- if (!isDragging.current) {
98
- onPress()
99
- }
100
- },
101
- }),
102
- ).current
103
-
104
- const theme = useBackstageTheme()
105
-
106
- const backgroundColor = hasError ? theme.error : theme.accent
107
- const shadowColor = hasError ? theme.error : theme.accent
108
-
109
- return (
110
- <Animated.View
111
- {...panResponder.panHandlers}
112
- style={[
113
- styles.pill,
114
- {
115
- backgroundColor,
116
- transform: pan.getTranslateTransform(),
117
- shadowColor,
118
- },
119
- propStyles?.pillStyle,
120
- ]}
121
- >
122
- <Text style={[styles.pillText, propStyles?.pillTextStyle]} numberOfLines={1}>
123
- {text}
124
- </Text>
125
- </Animated.View>
126
- )
127
- }
128
-
129
- // ─── Styles ──────────────────────────────────────────────────────────────────
130
-
131
- const styles = StyleSheet.create({
132
- pill: {
133
- position: 'absolute',
134
- minWidth: PILL_WIDTH,
135
- height: PILL_HEIGHT,
136
- borderRadius: PILL_HEIGHT / 2,
137
- paddingHorizontal: 14,
138
- alignItems: 'center',
139
- justifyContent: 'center',
140
- zIndex: 99999,
141
- elevation: 10,
142
- shadowOffset: { width: 0, height: 4 },
143
- shadowOpacity: 0.4,
144
- shadowRadius: 8,
145
- },
146
- pillText: {
147
- fontFamily: MonospaceFont,
148
- fontSize: 12,
149
- fontWeight: '700',
150
- color: '#FFFFFF',
151
- letterSpacing: 0.5,
152
- },
153
- })
@@ -1,230 +0,0 @@
1
- import React, { useMemo } from 'react'
2
- import { Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
3
- import { MonospaceFont } from '../constants'
4
- import { useBackstageTheme } from '../ThemeContext'
5
- import { JsonTreeView } from './JsonTreeView'
6
- import type { AppInfoItem, BackstageStyleOverrides, BackstageTheme, QuickAction } from '../types'
7
-
8
- // ─── Types ───────────────────────────────────────────────────────────────────
9
-
10
- interface InfoTabProps {
11
- appVersion?: string
12
- buildNumber?: string
13
- bundleId?: string
14
- deviceInfo?: AppInfoItem[]
15
- state?: Record<string, unknown>
16
- quickActions?: QuickAction[]
17
- jsonMaxDepth?: number
18
- onClosePanel?: () => void
19
- styles?: BackstageStyleOverrides
20
- children?: React.ReactNode
21
- }
22
-
23
- // ─── Main Component ──────────────────────────────────────────────────────────
24
-
25
- export const InfoTab: React.FC<InfoTabProps> = ({
26
- appVersion,
27
- buildNumber,
28
- bundleId,
29
- deviceInfo = [],
30
- state,
31
- quickActions = [],
32
- jsonMaxDepth,
33
- onClosePanel,
34
- styles: propStyles,
35
- children,
36
- }) => {
37
- const theme = useBackstageTheme()
38
- const s = useMemo(() => createStyles(theme), [theme])
39
-
40
- // Built-in device info from Platform API
41
- const builtInInfo: AppInfoItem[] = [
42
- { label: 'Platform', value: Platform.OS.toUpperCase() },
43
- { label: 'OS Version', value: String(Platform.Version) },
44
- ]
45
-
46
- if (appVersion) {
47
- builtInInfo.push({ label: 'App Version', value: appVersion })
48
- }
49
- if (buildNumber) {
50
- builtInInfo.push({ label: 'Build Number', value: buildNumber })
51
- }
52
- if (bundleId) {
53
- builtInInfo.push({ label: 'Bundle ID', value: bundleId })
54
- }
55
-
56
- const allInfo = [...builtInInfo, ...deviceInfo]
57
-
58
- return (
59
- <ScrollView
60
- style={s.container}
61
- contentContainerStyle={s.scrollContent}
62
- showsVerticalScrollIndicator={false}
63
- >
64
- {/* ── Device Info Section ─────────────────────────────────── */}
65
- <View style={s.section}>
66
- <Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>DEVICE INFO</Text>
67
- <View style={s.card}>
68
- {allInfo.map((item, index) => (
69
- <React.Fragment key={`info_${index}`}>
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} />}
79
- </React.Fragment>
80
- ))}
81
- </View>
82
- </View>
83
-
84
- {/* ── State Tree Section ──────────────────────────────────── */}
85
- {state && Object.keys(state).length > 0 && (
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} />
90
- </View>
91
- </View>
92
- )}
93
-
94
- {/* ── Quick Actions Section ─────────────────────────────────── */}
95
- {quickActions.length > 0 && (
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
- })}
131
- </View>
132
- </View>
133
- )}
134
-
135
- {/* ── Custom Children ─────────────────────────────────────── */}
136
- {children && <View style={s.section}>{children}</View>}
137
- </ScrollView>
138
- )
139
- }
140
-
141
- // ─── Styles ──────────────────────────────────────────────────────────────────
142
-
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
- })