rn-backstage 1.1.0 → 1.2.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 (35) hide show
  1. package/README.md +39 -19
  2. package/lib/commonjs/Backstage.js +4 -0
  3. package/lib/commonjs/Backstage.js.map +1 -1
  4. package/lib/commonjs/components/BackstagePanel.js +21 -6
  5. package/lib/commonjs/components/BackstagePanel.js.map +1 -1
  6. package/lib/commonjs/components/FlagsTab.js +255 -0
  7. package/lib/commonjs/components/FlagsTab.js.map +1 -0
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/types.js +1 -0
  10. package/lib/commonjs/types.js.map +1 -1
  11. package/lib/module/Backstage.js +4 -0
  12. package/lib/module/Backstage.js.map +1 -1
  13. package/lib/module/components/BackstagePanel.js +21 -6
  14. package/lib/module/components/BackstagePanel.js.map +1 -1
  15. package/lib/module/components/FlagsTab.js +250 -0
  16. package/lib/module/components/FlagsTab.js.map +1 -0
  17. package/lib/module/index.js.map +1 -1
  18. package/lib/module/types.js +2 -0
  19. package/lib/module/types.js.map +1 -1
  20. package/lib/typescript/Backstage.d.ts.map +1 -1
  21. package/lib/typescript/components/BackstagePanel.d.ts +3 -1
  22. package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
  23. package/lib/typescript/components/FlagsTab.d.ts +9 -0
  24. package/lib/typescript/components/FlagsTab.d.ts.map +1 -0
  25. package/lib/typescript/index.d.ts +1 -1
  26. package/lib/typescript/index.d.ts.map +1 -1
  27. package/lib/typescript/types.d.ts +14 -0
  28. package/lib/typescript/types.d.ts.map +1 -1
  29. package/package.json +1 -1
  30. package/src/Backstage.tsx +4 -0
  31. package/src/components/BackstagePanel.tsx +24 -5
  32. package/src/components/FlagsTab.tsx +249 -0
  33. package/src/components/InfoTab.tsx +1 -1
  34. package/src/index.ts +1 -0
  35. package/src/types.ts +19 -0
@@ -0,0 +1,249 @@
1
+ import React, { useMemo, useState } from 'react'
2
+ import { ScrollView, StyleSheet, Switch, Text, TextInput, View } from 'react-native'
3
+ import { DarkTheme, MonospaceFont } from '../constants'
4
+ import type { FeatureFlag } from '../types'
5
+
6
+ // ─── Types ───────────────────────────────────────────────────────────────────
7
+
8
+ interface FlagsTabProps {
9
+ flags: FeatureFlag[]
10
+ onToggle?: (key: string, value: boolean) => void
11
+ }
12
+
13
+ // ─── Component ───────────────────────────────────────────────────────────────
14
+
15
+ export const FlagsTab: React.FC<FlagsTabProps> = ({ flags, onToggle }) => {
16
+ const [searchText, setSearchText] = useState('')
17
+
18
+ const filteredFlags = useMemo(() => {
19
+ if (!searchText.trim()) return flags
20
+ const query = searchText.toLowerCase()
21
+ return flags.filter(
22
+ f =>
23
+ f.label.toLowerCase().includes(query) ||
24
+ f.key.toLowerCase().includes(query) ||
25
+ (f.description && f.description.toLowerCase().includes(query)),
26
+ )
27
+ }, [flags, searchText])
28
+
29
+ // Stats
30
+ const enabledCount = flags.filter(f => f.value).length
31
+
32
+ return (
33
+ <View style={styles.container}>
34
+ {/* Search Bar */}
35
+ <View style={styles.searchContainer}>
36
+ <View style={styles.searchInputWrapper}>
37
+ <Text style={styles.searchIcon}>⌕</Text>
38
+ <TextInput
39
+ style={styles.searchInput}
40
+ placeholder="Filter flags..."
41
+ placeholderTextColor={DarkTheme.textMuted}
42
+ value={searchText}
43
+ onChangeText={setSearchText}
44
+ autoCapitalize="none"
45
+ autoCorrect={false}
46
+ clearButtonMode="while-editing"
47
+ returnKeyType="search"
48
+ />
49
+ </View>
50
+ </View>
51
+
52
+ {/* Stats Bar */}
53
+ <View style={styles.statsBar}>
54
+ <Text style={styles.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={styles.statText}>
60
+ <Text style={styles.statEnabled}>{enabledCount} on</Text>
61
+ {' · '}
62
+ <Text style={styles.statDisabled}>{flags.length - enabledCount} off</Text>
63
+ </Text>
64
+ </View>
65
+
66
+ {/* Flag List */}
67
+ <ScrollView
68
+ style={styles.list}
69
+ contentContainerStyle={
70
+ filteredFlags.length === 0 ? styles.emptyContainer : styles.listContent
71
+ }
72
+ showsVerticalScrollIndicator={false}
73
+ >
74
+ {filteredFlags.length === 0 ? (
75
+ <View style={styles.emptyState}>
76
+ <Text style={styles.emptyIcon}>🎚</Text>
77
+ <Text style={styles.emptyTitle}>No flags match</Text>
78
+ <Text style={styles.emptySubtitle}>Try adjusting your search</Text>
79
+ </View>
80
+ ) : (
81
+ filteredFlags.map((flag, index) => (
82
+ <View key={flag.key}>
83
+ <View style={styles.flagRow}>
84
+ <View style={styles.flagInfo}>
85
+ <View style={styles.flagHeader}>
86
+ <Text style={styles.flagLabel}>{flag.label}</Text>
87
+ <Text style={[styles.flagKey, flag.value && styles.flagKeyActive]}>
88
+ {flag.key}
89
+ </Text>
90
+ </View>
91
+ {flag.description && (
92
+ <Text style={styles.flagDescription}>{flag.description}</Text>
93
+ )}
94
+ </View>
95
+ <Switch
96
+ testID={`backstage.flag.${flag.key}`}
97
+ value={flag.value}
98
+ onValueChange={(newValue: boolean) => onToggle?.(flag.key, newValue)}
99
+ trackColor={{ false: DarkTheme.border, true: DarkTheme.accent }}
100
+ thumbColor={flag.value ? '#FFFFFF' : DarkTheme.textMuted}
101
+ ios_backgroundColor={DarkTheme.border}
102
+ />
103
+ </View>
104
+ {index < filteredFlags.length - 1 && <View style={styles.divider} />}
105
+ </View>
106
+ ))
107
+ )}
108
+ </ScrollView>
109
+ </View>
110
+ )
111
+ }
112
+
113
+ // ─── Styles ──────────────────────────────────────────────────────────────────
114
+
115
+ const styles = StyleSheet.create({
116
+ container: {
117
+ flex: 1,
118
+ },
119
+ searchContainer: {
120
+ paddingHorizontal: 14,
121
+ paddingVertical: 10,
122
+ borderBottomWidth: 1,
123
+ borderBottomColor: DarkTheme.border,
124
+ },
125
+ searchInputWrapper: {
126
+ flexDirection: 'row',
127
+ alignItems: 'center',
128
+ backgroundColor: DarkTheme.surfaceElevated,
129
+ borderRadius: 10,
130
+ borderWidth: 1,
131
+ borderColor: DarkTheme.border,
132
+ paddingHorizontal: 12,
133
+ height: 38,
134
+ },
135
+ searchIcon: {
136
+ fontSize: 16,
137
+ color: DarkTheme.textMuted,
138
+ marginRight: 8,
139
+ },
140
+ searchInput: {
141
+ flex: 1,
142
+ fontFamily: MonospaceFont,
143
+ fontSize: 13,
144
+ color: DarkTheme.text,
145
+ padding: 0,
146
+ },
147
+ statsBar: {
148
+ flexDirection: 'row',
149
+ justifyContent: 'space-between',
150
+ alignItems: 'center',
151
+ paddingHorizontal: 16,
152
+ paddingVertical: 6,
153
+ backgroundColor: DarkTheme.surface,
154
+ },
155
+ statText: {
156
+ fontFamily: MonospaceFont,
157
+ fontSize: 11,
158
+ color: DarkTheme.textMuted,
159
+ },
160
+ statEnabled: {
161
+ color: DarkTheme.success,
162
+ fontWeight: '700',
163
+ },
164
+ statDisabled: {
165
+ color: DarkTheme.textMuted,
166
+ },
167
+ list: {
168
+ flex: 1,
169
+ },
170
+ listContent: {
171
+ paddingHorizontal: 16,
172
+ paddingVertical: 8,
173
+ },
174
+ emptyContainer: {
175
+ flex: 1,
176
+ justifyContent: 'center',
177
+ },
178
+ emptyState: {
179
+ alignItems: 'center',
180
+ justifyContent: 'center',
181
+ padding: 40,
182
+ },
183
+ emptyIcon: {
184
+ fontSize: 48,
185
+ marginBottom: 16,
186
+ opacity: 0.5,
187
+ },
188
+ emptyTitle: {
189
+ fontFamily: MonospaceFont,
190
+ fontSize: 16,
191
+ fontWeight: '700',
192
+ color: DarkTheme.textSecondary,
193
+ marginBottom: 8,
194
+ },
195
+ emptySubtitle: {
196
+ fontFamily: MonospaceFont,
197
+ fontSize: 13,
198
+ color: DarkTheme.textMuted,
199
+ textAlign: 'center',
200
+ },
201
+ flagRow: {
202
+ flexDirection: 'row',
203
+ justifyContent: 'space-between',
204
+ alignItems: 'center',
205
+ paddingVertical: 14,
206
+ paddingHorizontal: 4,
207
+ },
208
+ flagInfo: {
209
+ flex: 1,
210
+ marginRight: 16,
211
+ },
212
+ flagHeader: {
213
+ flexDirection: 'row',
214
+ alignItems: 'center',
215
+ gap: 8,
216
+ },
217
+ flagLabel: {
218
+ fontFamily: MonospaceFont,
219
+ fontSize: 14,
220
+ color: DarkTheme.text,
221
+ fontWeight: '600',
222
+ },
223
+ flagKey: {
224
+ fontFamily: MonospaceFont,
225
+ fontSize: 10,
226
+ color: DarkTheme.textMuted,
227
+ backgroundColor: DarkTheme.surfaceElevated,
228
+ borderRadius: 4,
229
+ paddingHorizontal: 6,
230
+ paddingVertical: 1,
231
+ overflow: 'hidden',
232
+ },
233
+ flagKeyActive: {
234
+ color: DarkTheme.accent,
235
+ backgroundColor: DarkTheme.accentDim,
236
+ },
237
+ flagDescription: {
238
+ fontFamily: MonospaceFont,
239
+ fontSize: 12,
240
+ color: DarkTheme.textMuted,
241
+ marginTop: 4,
242
+ lineHeight: 18,
243
+ },
244
+ divider: {
245
+ height: 1,
246
+ backgroundColor: DarkTheme.border,
247
+ marginHorizontal: 4,
248
+ },
249
+ })
@@ -148,7 +148,7 @@ export const InfoTab: React.FC<InfoTabProps> = ({
148
148
  </View>
149
149
  )}
150
150
 
151
- {/* ── Quick Actions Section ───────────────────────────────── */}
151
+ {/* ── Quick Actions Section ─────────────────────────────────────── */}
152
152
  {quickActions.length > 0 && (
153
153
  <View style={styles.section}>
154
154
  <SectionHeader title="QUICK ACTIONS" titleStyle={propStyles?.sectionTitleStyle} />
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export type {
7
7
  BackstageStyleOverrides,
8
8
  BackstageTheme,
9
9
  QuickAction,
10
+ FeatureFlag,
10
11
  AppInfoItem,
11
12
  LogEntry,
12
13
  NetworkEntry,
package/src/types.ts CHANGED
@@ -63,6 +63,19 @@ export interface QuickAction {
63
63
  testID?: string
64
64
  }
65
65
 
66
+ // ─── Feature Flags ─────────────────────────────────────────────────────────
67
+
68
+ export interface FeatureFlag {
69
+ /** Unique key for this flag */
70
+ key: string
71
+ /** Display label */
72
+ label: string
73
+ /** Current value */
74
+ value: boolean
75
+ /** Optional description shown below the label */
76
+ description?: string
77
+ }
78
+
66
79
  // ─── Extensible Tabs ─────────────────────────────────────────────────────────
67
80
 
68
81
  export interface BackstageTab {
@@ -119,6 +132,12 @@ export interface BackstageProps {
119
132
  /** Custom action buttons in the Info tab */
120
133
  quickActions?: QuickAction[]
121
134
 
135
+ /** Feature flags to display with toggle switches */
136
+ featureFlags?: FeatureFlag[]
137
+
138
+ /** Callback when a feature flag is toggled */
139
+ onToggleFeatureFlag?: (key: string, value: boolean) => void
140
+
122
141
  /** Maximum number of logs to retain in memory. Default: 500 */
123
142
  maxLogs?: number
124
143