rn-backstage 1.3.0 โ†’ 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 (83) hide show
  1. package/README.md +27 -0
  2. package/lib/commonjs/Backstage.js +13 -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 +82 -15
  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 +10 -2
  11. package/lib/commonjs/components/FlagsTab.js.map +1 -1
  12. package/lib/commonjs/components/LogsTab.js +1 -3
  13. package/lib/commonjs/components/LogsTab.js.map +1 -1
  14. package/lib/commonjs/components/NetworkTab.js +1 -2
  15. package/lib/commonjs/components/NetworkTab.js.map +1 -1
  16. package/lib/commonjs/components/StorageTab.js +207 -190
  17. package/lib/commonjs/components/StorageTab.js.map +1 -1
  18. package/lib/commonjs/constants.js +16 -0
  19. package/lib/commonjs/constants.js.map +1 -1
  20. package/lib/commonjs/index.js +7 -0
  21. package/lib/commonjs/index.js.map +1 -1
  22. package/lib/commonjs/types.js +1 -0
  23. package/lib/commonjs/types.js.map +1 -1
  24. package/lib/module/Backstage.js +13 -2
  25. package/lib/module/Backstage.js.map +1 -1
  26. package/lib/module/bug-report.js +164 -0
  27. package/lib/module/bug-report.js.map +1 -0
  28. package/lib/module/components/BackstagePanel.js +83 -16
  29. package/lib/module/components/BackstagePanel.js.map +1 -1
  30. package/lib/module/components/BugReportComposer.js +505 -0
  31. package/lib/module/components/BugReportComposer.js.map +1 -0
  32. package/lib/module/components/FlagsTab.js +12 -4
  33. package/lib/module/components/FlagsTab.js.map +1 -1
  34. package/lib/module/components/LogsTab.js +1 -3
  35. package/lib/module/components/LogsTab.js.map +1 -1
  36. package/lib/module/components/NetworkTab.js +1 -2
  37. package/lib/module/components/NetworkTab.js.map +1 -1
  38. package/lib/module/components/StorageTab.js +208 -191
  39. package/lib/module/components/StorageTab.js.map +1 -1
  40. package/lib/module/constants.js +16 -0
  41. package/lib/module/constants.js.map +1 -1
  42. package/lib/module/index.js +1 -0
  43. package/lib/module/index.js.map +1 -1
  44. package/lib/module/types.js +2 -0
  45. package/lib/module/types.js.map +1 -1
  46. package/lib/typescript/Backstage.d.ts.map +1 -1
  47. package/lib/typescript/bug-report.d.ts +12 -0
  48. package/lib/typescript/bug-report.d.ts.map +1 -0
  49. package/lib/typescript/components/BackstagePanel.d.ts +3 -1
  50. package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
  51. package/lib/typescript/components/BugReportComposer.d.ts +17 -0
  52. package/lib/typescript/components/BugReportComposer.d.ts.map +1 -0
  53. package/lib/typescript/components/FlagsTab.d.ts.map +1 -1
  54. package/lib/typescript/components/LogsTab.d.ts.map +1 -1
  55. package/lib/typescript/components/NetworkTab.d.ts.map +1 -1
  56. package/lib/typescript/components/StorageTab.d.ts.map +1 -1
  57. package/lib/typescript/constants.d.ts +15 -0
  58. package/lib/typescript/constants.d.ts.map +1 -1
  59. package/lib/typescript/index.d.ts +2 -1
  60. package/lib/typescript/index.d.ts.map +1 -1
  61. package/lib/typescript/types.d.ts +39 -0
  62. package/lib/typescript/types.d.ts.map +1 -1
  63. package/package.json +2 -3
  64. package/src/Backstage.tsx +0 -215
  65. package/src/ThemeContext.tsx +0 -38
  66. package/src/components/BackstagePanel.tsx +0 -277
  67. package/src/components/FlagsTab.tsx +0 -200
  68. package/src/components/FloatingPill.tsx +0 -247
  69. package/src/components/InfoTab.tsx +0 -231
  70. package/src/components/JsonTreeView.tsx +0 -239
  71. package/src/components/LogItem.tsx +0 -153
  72. package/src/components/LogsTab.tsx +0 -217
  73. package/src/components/NetworkItem.tsx +0 -425
  74. package/src/components/NetworkTab.tsx +0 -240
  75. package/src/components/StorageTab.tsx +0 -607
  76. package/src/components/TabBar.tsx +0 -154
  77. package/src/constants.ts +0 -190
  78. package/src/index.ts +0 -33
  79. package/src/log-interceptor.ts +0 -148
  80. package/src/network-interceptor.ts +0 -468
  81. package/src/types.ts +0 -235
  82. package/src/utils/formatTimestamp.ts +0 -22
  83. package/src/utils/stringify.ts +0 -90
@@ -1,277 +0,0 @@
1
- import React, { useMemo, useState } from 'react'
2
- import {
3
- Modal,
4
- SafeAreaView,
5
- StatusBar,
6
- StyleSheet,
7
- Text,
8
- TouchableOpacity,
9
- View,
10
- } from 'react-native'
11
- import { MonospaceFont, TestIDs } from '../constants'
12
- import { useBackstageTheme } from '../ThemeContext'
13
- import { TabBar } from './TabBar'
14
- import { InfoTab } from './InfoTab'
15
- import { LogsTab } from './LogsTab'
16
- import { NetworkTab } from './NetworkTab'
17
- import { FlagsTab } from './FlagsTab'
18
- import { StorageTab } from './StorageTab'
19
- import type {
20
- AppInfoItem,
21
- BackstageStyleOverrides,
22
- BackstageTab,
23
- FeatureFlag,
24
- LogEntry,
25
- NetworkEntry,
26
- QuickAction,
27
- StorageAdapter,
28
- } from '../types'
29
-
30
- // โ”€โ”€โ”€ Types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
31
-
32
- interface BackstagePanelProps {
33
- visible: boolean
34
- onClose: () => void
35
-
36
- // Info tab props
37
- appVersion?: string
38
- buildNumber?: string
39
- bundleId?: string
40
- deviceInfo?: AppInfoItem[]
41
- state?: Record<string, unknown>
42
- quickActions?: QuickAction[]
43
- featureFlags?: FeatureFlag[]
44
- onToggleFeatureFlag?: (key: string, value: boolean) => void
45
-
46
- // Logs tab props
47
- logs: LogEntry[]
48
- onRefreshLogs: () => void
49
- onCopyLogs?: (logs: string) => void
50
-
51
- // Network tab props
52
- networkEntries: NetworkEntry[]
53
- onRefreshNetwork: () => void
54
- onClearNetwork: () => void
55
- onCopyNetwork?: (text: string) => void
56
-
57
- // Extensibility
58
- extraTabs?: BackstageTab[]
59
-
60
- // JSON tree
61
- jsonMaxDepth?: number
62
-
63
- // Storage
64
- storageAdapter?: StorageAdapter
65
-
66
- // Styling
67
- styles?: BackstageStyleOverrides
68
- children?: React.ReactNode
69
- }
70
-
71
- // โ”€โ”€โ”€ Built-in Tab Definitions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
72
-
73
- const ALWAYS_ON_TABS = [
74
- { key: 'info', title: 'Info', icon: 'โ„น๏ธ' },
75
- { key: 'network', title: 'Network', icon: '๐ŸŒ' },
76
- ]
77
-
78
- const FLAGS_TAB = { key: 'flags', title: 'Flags', icon: '๐ŸŽš' }
79
-
80
- const STORAGE_TAB = { key: 'storage', title: 'Storage', icon: '๐Ÿ—„' }
81
-
82
- const LOGS_TAB = { key: 'logs', title: 'Logs', icon: '๐Ÿ“‹' }
83
-
84
- // โ”€โ”€โ”€ Component โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
85
-
86
- export const BackstagePanel: React.FC<BackstagePanelProps> = ({
87
- visible,
88
- onClose,
89
- appVersion,
90
- buildNumber,
91
- bundleId,
92
- deviceInfo,
93
- state,
94
- quickActions,
95
- featureFlags,
96
- onToggleFeatureFlag,
97
- logs,
98
- onRefreshLogs,
99
- onCopyLogs,
100
- networkEntries,
101
- onRefreshNetwork,
102
- onClearNetwork,
103
- onCopyNetwork,
104
- extraTabs = [],
105
- jsonMaxDepth,
106
- storageAdapter,
107
- styles: propStyles,
108
- children,
109
- }) => {
110
- const [activeTab, setActiveTab] = useState('info')
111
- const theme = useBackstageTheme()
112
- const styles = useMemo(() => createStyles(theme), [theme])
113
-
114
- // Compose all tabs: built-in (conditionally including Flags) + extra
115
- const hasFlags = featureFlags && featureFlags.length > 0
116
- const hasStorage = !!storageAdapter
117
- const allTabs = [
118
- ...ALWAYS_ON_TABS,
119
- ...(hasFlags ? [FLAGS_TAB] : []),
120
- ...(hasStorage ? [STORAGE_TAB] : []),
121
- LOGS_TAB,
122
- ...extraTabs.map(t => ({ key: t.key, title: t.title, icon: t.icon })),
123
- ]
124
-
125
- // Render active tab content
126
- const renderTabContent = () => {
127
- switch (activeTab) {
128
- case 'info':
129
- return (
130
- <InfoTab
131
- appVersion={appVersion}
132
- buildNumber={buildNumber}
133
- bundleId={bundleId}
134
- deviceInfo={deviceInfo}
135
- state={state}
136
- quickActions={quickActions}
137
- jsonMaxDepth={jsonMaxDepth}
138
- onClosePanel={onClose}
139
- styles={propStyles}
140
- >
141
- {children}
142
- </InfoTab>
143
- )
144
- case 'flags':
145
- return <FlagsTab flags={featureFlags ?? []} onToggle={onToggleFeatureFlag} />
146
- case 'storage':
147
- return storageAdapter ? (
148
- <StorageTab adapter={storageAdapter} jsonMaxDepth={jsonMaxDepth} />
149
- ) : null
150
- case 'network':
151
- return (
152
- <NetworkTab
153
- entries={networkEntries}
154
- onRefresh={onRefreshNetwork}
155
- onClear={onClearNetwork}
156
- onCopy={onCopyNetwork}
157
- jsonMaxDepth={jsonMaxDepth}
158
- />
159
- )
160
- case 'logs':
161
- return (
162
- <LogsTab
163
- logs={logs}
164
- onRefresh={onRefreshLogs}
165
- onCopyLogs={onCopyLogs}
166
- jsonMaxDepth={jsonMaxDepth}
167
- styles={propStyles}
168
- />
169
- )
170
- default: {
171
- // Render extra tab content
172
- const extraTab = extraTabs.find(t => t.key === activeTab)
173
- if (extraTab) {
174
- return <>{extraTab.render()}</>
175
- }
176
- return null
177
- }
178
- }
179
- }
180
-
181
- return (
182
- <Modal
183
- testID={TestIDs.panelModal}
184
- animationType="slide"
185
- transparent={false}
186
- visible={visible}
187
- onRequestClose={onClose}
188
- statusBarTranslucent
189
- >
190
- <StatusBar
191
- barStyle={theme.background < '#888' ? 'light-content' : 'dark-content'}
192
- backgroundColor={theme.background}
193
- />
194
- <SafeAreaView testID={TestIDs.panel} style={[styles.safeArea, propStyles?.panelStyle]}>
195
- {/* โ”€โ”€ Header โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
196
- <View testID={TestIDs.header.container} style={styles.header}>
197
- <View style={styles.headerLeft}>
198
- <Text style={styles.headerBrand}>โš™</Text>
199
- <Text
200
- testID={TestIDs.header.title}
201
- style={[styles.headerTitle, propStyles?.headerTitleStyle]}
202
- >
203
- Backstage
204
- </Text>
205
- </View>
206
- <TouchableOpacity
207
- testID={TestIDs.header.closeButton}
208
- style={styles.closeButton}
209
- onPress={onClose}
210
- activeOpacity={0.7}
211
- hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
212
- >
213
- <Text style={styles.closeButtonText}>โœ•</Text>
214
- </TouchableOpacity>
215
- </View>
216
-
217
- {/* โ”€โ”€ Tab Bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
218
- <TabBar tabs={allTabs} activeTab={activeTab} onTabChange={setActiveTab} />
219
-
220
- {/* โ”€โ”€ Tab Content โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
221
- <View style={styles.content}>{renderTabContent()}</View>
222
- </SafeAreaView>
223
- </Modal>
224
- )
225
- }
226
-
227
- // โ”€โ”€โ”€ Styles โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
228
-
229
- const createStyles = (t: import('../types').BackstageTheme) =>
230
- StyleSheet.create({
231
- safeArea: {
232
- flex: 1,
233
- backgroundColor: t.background,
234
- },
235
- header: {
236
- flexDirection: 'row',
237
- alignItems: 'center',
238
- justifyContent: 'space-between',
239
- paddingHorizontal: 16,
240
- paddingVertical: 12,
241
- borderBottomWidth: 1,
242
- borderBottomColor: t.border,
243
- },
244
- headerLeft: {
245
- flexDirection: 'row',
246
- alignItems: 'center',
247
- },
248
- headerBrand: {
249
- fontSize: 20,
250
- marginRight: 8,
251
- },
252
- headerTitle: {
253
- fontFamily: MonospaceFont,
254
- fontSize: 18,
255
- fontWeight: '800',
256
- color: t.text,
257
- letterSpacing: 1,
258
- },
259
- closeButton: {
260
- width: 32,
261
- height: 32,
262
- borderRadius: 16,
263
- backgroundColor: t.surfaceElevated,
264
- borderWidth: 1,
265
- borderColor: t.border,
266
- alignItems: 'center',
267
- justifyContent: 'center',
268
- },
269
- closeButtonText: {
270
- fontSize: 14,
271
- color: t.textSecondary,
272
- fontWeight: '700',
273
- },
274
- content: {
275
- flex: 1,
276
- },
277
- })
@@ -1,200 +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} numberOfLines={2}>{flag.label}</Text>
84
- </View>
85
- <Text style={[s.flagKey, flag.value && s.flagKeyActive]}>{flag.key}</Text>
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' },
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
- alignSelf: 'flex-start',
189
- marginTop: 4,
190
- },
191
- flagKeyActive: { color: t.accent, backgroundColor: t.accentDim },
192
- flagDescription: {
193
- fontFamily: MonospaceFont,
194
- fontSize: 12,
195
- color: t.textMuted,
196
- marginTop: 4,
197
- lineHeight: 18,
198
- },
199
- divider: { height: 1, backgroundColor: t.border, marginHorizontal: 4 },
200
- })