react-native-debug-toolkit 2.0.0 → 2.1.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 (228) hide show
  1. package/README.md +72 -72
  2. package/README.zh-CN.md +209 -0
  3. package/lib/commonjs/components/ClipboardTab.js +15 -16
  4. package/lib/commonjs/components/ClipboardTab.js.map +1 -1
  5. package/lib/commonjs/components/ConsoleLogTab.js +69 -202
  6. package/lib/commonjs/components/ConsoleLogTab.js.map +1 -1
  7. package/lib/commonjs/components/DebugPanel.js +230 -0
  8. package/lib/commonjs/components/DebugPanel.js.map +1 -0
  9. package/lib/commonjs/components/DebugView.js +66 -0
  10. package/lib/commonjs/components/DebugView.js.map +1 -0
  11. package/lib/commonjs/components/EnvironmentTab.js +22 -23
  12. package/lib/commonjs/components/EnvironmentTab.js.map +1 -1
  13. package/lib/commonjs/components/FeatureTabBar.js +182 -0
  14. package/lib/commonjs/components/FeatureTabBar.js.map +1 -0
  15. package/lib/commonjs/components/FloatIcon.js +187 -0
  16. package/lib/commonjs/components/FloatIcon.js.map +1 -0
  17. package/lib/commonjs/components/FloatPanelView.js +140 -723
  18. package/lib/commonjs/components/FloatPanelView.js.map +1 -1
  19. package/lib/commonjs/components/NavigationLogTab.js +8 -8
  20. package/lib/commonjs/components/NavigationLogTab.js.map +1 -1
  21. package/lib/commonjs/components/NetworkLogTab.js +179 -350
  22. package/lib/commonjs/components/NetworkLogTab.js.map +1 -1
  23. package/lib/commonjs/components/ThirdPartyLibsTab.js +8 -8
  24. package/lib/commonjs/components/ThirdPartyLibsTab.js.map +1 -1
  25. package/lib/commonjs/components/TrackLogTab.js +106 -248
  26. package/lib/commonjs/components/TrackLogTab.js.map +1 -1
  27. package/lib/commonjs/components/ZustandLogTab.js +148 -288
  28. package/lib/commonjs/components/ZustandLogTab.js.map +1 -1
  29. package/lib/commonjs/components/shared/CollapsibleSection.js +4 -4
  30. package/lib/commonjs/components/shared/CollapsibleSection.js.map +1 -1
  31. package/lib/commonjs/components/shared/CopyButton.js +3 -3
  32. package/lib/commonjs/components/shared/CopyButton.js.map +1 -1
  33. package/lib/commonjs/components/shared/LogListScreen.js +174 -0
  34. package/lib/commonjs/components/shared/LogListScreen.js.map +1 -0
  35. package/lib/commonjs/core/DebugToolkit.js +92 -112
  36. package/lib/commonjs/core/DebugToolkit.js.map +1 -1
  37. package/lib/commonjs/core/DebugToolkitProvider.js +30 -28
  38. package/lib/commonjs/core/DebugToolkitProvider.js.map +1 -1
  39. package/lib/commonjs/features/ClipboardFeature.js +6 -2
  40. package/lib/commonjs/features/ClipboardFeature.js.map +1 -1
  41. package/lib/commonjs/features/ConsoleLogFeature.js +66 -49
  42. package/lib/commonjs/features/ConsoleLogFeature.js.map +1 -1
  43. package/lib/commonjs/features/EnvironmentFeature.js +5 -13
  44. package/lib/commonjs/features/EnvironmentFeature.js.map +1 -1
  45. package/lib/commonjs/features/NavigationLogFeature.js +17 -38
  46. package/lib/commonjs/features/NavigationLogFeature.js.map +1 -1
  47. package/lib/commonjs/features/NetworkFeature.js +35 -256
  48. package/lib/commonjs/features/NetworkFeature.js.map +1 -1
  49. package/lib/commonjs/features/TrackFeature.js +17 -38
  50. package/lib/commonjs/features/TrackFeature.js.map +1 -1
  51. package/lib/commonjs/features/ZustandLogFeature.js +21 -38
  52. package/lib/commonjs/features/ZustandLogFeature.js.map +1 -1
  53. package/lib/commonjs/hooks/useNavigationLogger.js.map +1 -1
  54. package/lib/commonjs/index.js +7 -20
  55. package/lib/commonjs/index.js.map +1 -1
  56. package/lib/commonjs/initialize.js +19 -96
  57. package/lib/commonjs/initialize.js.map +1 -1
  58. package/lib/commonjs/interceptors/networkInterceptor.js +466 -0
  59. package/lib/commonjs/interceptors/networkInterceptor.js.map +1 -0
  60. package/lib/commonjs/utils/colors.js +48 -0
  61. package/lib/commonjs/utils/colors.js.map +1 -0
  62. package/lib/commonjs/utils/createChannelFeature.js +48 -0
  63. package/lib/commonjs/utils/createChannelFeature.js.map +1 -0
  64. package/lib/commonjs/utils/layout.js +8 -0
  65. package/lib/commonjs/utils/layout.js.map +1 -0
  66. package/lib/commonjs/utils/urlRewriterRegistry.js +14 -0
  67. package/lib/commonjs/utils/urlRewriterRegistry.js.map +1 -0
  68. package/lib/module/components/ClipboardTab.js +8 -8
  69. package/lib/module/components/ClipboardTab.js.map +1 -1
  70. package/lib/module/components/ConsoleLogTab.js +66 -199
  71. package/lib/module/components/ConsoleLogTab.js.map +1 -1
  72. package/lib/module/components/DebugPanel.js +225 -0
  73. package/lib/module/components/DebugPanel.js.map +1 -0
  74. package/lib/module/components/DebugView.js +61 -0
  75. package/lib/module/components/DebugView.js.map +1 -0
  76. package/lib/module/components/EnvironmentTab.js +3 -3
  77. package/lib/module/components/EnvironmentTab.js.map +1 -1
  78. package/lib/module/components/FeatureTabBar.js +177 -0
  79. package/lib/module/components/FeatureTabBar.js.map +1 -0
  80. package/lib/module/components/FloatIcon.js +182 -0
  81. package/lib/module/components/FloatIcon.js.map +1 -0
  82. package/lib/module/components/FloatPanelView.js +141 -723
  83. package/lib/module/components/FloatPanelView.js.map +1 -1
  84. package/lib/module/components/NavigationLogTab.js +1 -1
  85. package/lib/module/components/NavigationLogTab.js.map +1 -1
  86. package/lib/module/components/NetworkLogTab.js +167 -338
  87. package/lib/module/components/NetworkLogTab.js.map +1 -1
  88. package/lib/module/components/ThirdPartyLibsTab.js +1 -1
  89. package/lib/module/components/ThirdPartyLibsTab.js.map +1 -1
  90. package/lib/module/components/TrackLogTab.js +94 -236
  91. package/lib/module/components/TrackLogTab.js.map +1 -1
  92. package/lib/module/components/ZustandLogTab.js +136 -276
  93. package/lib/module/components/ZustandLogTab.js.map +1 -1
  94. package/lib/module/components/shared/CollapsibleSection.js +1 -1
  95. package/lib/module/components/shared/CollapsibleSection.js.map +1 -1
  96. package/lib/module/components/shared/CopyButton.js +1 -1
  97. package/lib/module/components/shared/CopyButton.js.map +1 -1
  98. package/lib/module/components/shared/LogListScreen.js +169 -0
  99. package/lib/module/components/shared/LogListScreen.js.map +1 -0
  100. package/lib/module/core/DebugToolkit.js +92 -111
  101. package/lib/module/core/DebugToolkit.js.map +1 -1
  102. package/lib/module/core/DebugToolkitProvider.js +31 -29
  103. package/lib/module/core/DebugToolkitProvider.js.map +1 -1
  104. package/lib/module/features/ClipboardFeature.js +6 -2
  105. package/lib/module/features/ClipboardFeature.js.map +1 -1
  106. package/lib/module/features/ConsoleLogFeature.js +65 -49
  107. package/lib/module/features/ConsoleLogFeature.js.map +1 -1
  108. package/lib/module/features/EnvironmentFeature.js +5 -13
  109. package/lib/module/features/EnvironmentFeature.js.map +1 -1
  110. package/lib/module/features/NavigationLogFeature.js +16 -38
  111. package/lib/module/features/NavigationLogFeature.js.map +1 -1
  112. package/lib/module/features/NetworkFeature.js +34 -255
  113. package/lib/module/features/NetworkFeature.js.map +1 -1
  114. package/lib/module/features/TrackFeature.js +16 -38
  115. package/lib/module/features/TrackFeature.js.map +1 -1
  116. package/lib/module/features/ZustandLogFeature.js +22 -38
  117. package/lib/module/features/ZustandLogFeature.js.map +1 -1
  118. package/lib/module/hooks/useNavigationLogger.js.map +1 -1
  119. package/lib/module/index.js +2 -3
  120. package/lib/module/index.js.map +1 -1
  121. package/lib/module/initialize.js +19 -95
  122. package/lib/module/initialize.js.map +1 -1
  123. package/lib/module/interceptors/networkInterceptor.js +460 -0
  124. package/lib/module/interceptors/networkInterceptor.js.map +1 -0
  125. package/lib/module/utils/colors.js +43 -0
  126. package/lib/module/utils/colors.js.map +1 -0
  127. package/lib/module/utils/createChannelFeature.js +44 -0
  128. package/lib/module/utils/createChannelFeature.js.map +1 -0
  129. package/lib/module/utils/layout.js +4 -0
  130. package/lib/module/utils/layout.js.map +1 -0
  131. package/lib/module/utils/urlRewriterRegistry.js +10 -0
  132. package/lib/module/utils/urlRewriterRegistry.js.map +1 -0
  133. package/lib/typescript/src/components/ClipboardTab.d.ts.map +1 -1
  134. package/lib/typescript/src/components/ConsoleLogTab.d.ts.map +1 -1
  135. package/lib/typescript/src/components/DebugPanel.d.ts +9 -0
  136. package/lib/typescript/src/components/DebugPanel.d.ts.map +1 -0
  137. package/lib/typescript/src/components/DebugView.d.ts +19 -0
  138. package/lib/typescript/src/components/DebugView.d.ts.map +1 -0
  139. package/lib/typescript/src/components/EnvironmentTab.d.ts.map +1 -1
  140. package/lib/typescript/src/components/FeatureTabBar.d.ts +13 -0
  141. package/lib/typescript/src/components/FeatureTabBar.d.ts.map +1 -0
  142. package/lib/typescript/src/components/FloatIcon.d.ts +12 -0
  143. package/lib/typescript/src/components/FloatIcon.d.ts.map +1 -0
  144. package/lib/typescript/src/components/FloatPanelView.d.ts +4 -59
  145. package/lib/typescript/src/components/FloatPanelView.d.ts.map +1 -1
  146. package/lib/typescript/src/components/NetworkLogTab.d.ts.map +1 -1
  147. package/lib/typescript/src/components/ThirdPartyLibsTab.d.ts.map +1 -1
  148. package/lib/typescript/src/components/TrackLogTab.d.ts.map +1 -1
  149. package/lib/typescript/src/components/ZustandLogTab.d.ts.map +1 -1
  150. package/lib/typescript/src/components/shared/LogListScreen.d.ts +21 -0
  151. package/lib/typescript/src/components/shared/LogListScreen.d.ts.map +1 -0
  152. package/lib/typescript/src/core/DebugToolkit.d.ts +11 -18
  153. package/lib/typescript/src/core/DebugToolkit.d.ts.map +1 -1
  154. package/lib/typescript/src/core/DebugToolkitProvider.d.ts +2 -5
  155. package/lib/typescript/src/core/DebugToolkitProvider.d.ts.map +1 -1
  156. package/lib/typescript/src/features/ClipboardFeature.d.ts +4 -0
  157. package/lib/typescript/src/features/ClipboardFeature.d.ts.map +1 -1
  158. package/lib/typescript/src/features/ConsoleLogFeature.d.ts +2 -0
  159. package/lib/typescript/src/features/ConsoleLogFeature.d.ts.map +1 -1
  160. package/lib/typescript/src/features/EnvironmentFeature.d.ts.map +1 -1
  161. package/lib/typescript/src/features/NavigationLogFeature.d.ts +2 -0
  162. package/lib/typescript/src/features/NavigationLogFeature.d.ts.map +1 -1
  163. package/lib/typescript/src/features/NetworkFeature.d.ts +7 -20
  164. package/lib/typescript/src/features/NetworkFeature.d.ts.map +1 -1
  165. package/lib/typescript/src/features/TrackFeature.d.ts +2 -0
  166. package/lib/typescript/src/features/TrackFeature.d.ts.map +1 -1
  167. package/lib/typescript/src/features/ZustandLogFeature.d.ts +2 -0
  168. package/lib/typescript/src/features/ZustandLogFeature.d.ts.map +1 -1
  169. package/lib/typescript/src/hooks/useNavigationLogger.d.ts +1 -8
  170. package/lib/typescript/src/hooks/useNavigationLogger.d.ts.map +1 -1
  171. package/lib/typescript/src/index.d.ts +5 -5
  172. package/lib/typescript/src/index.d.ts.map +1 -1
  173. package/lib/typescript/src/initialize.d.ts +3 -22
  174. package/lib/typescript/src/initialize.d.ts.map +1 -1
  175. package/lib/typescript/src/interceptors/networkInterceptor.d.ts +18 -0
  176. package/lib/typescript/src/interceptors/networkInterceptor.d.ts.map +1 -0
  177. package/lib/typescript/src/types/index.d.ts +26 -29
  178. package/lib/typescript/src/types/index.d.ts.map +1 -1
  179. package/lib/typescript/src/utils/colors.d.ts +21 -0
  180. package/lib/typescript/src/utils/colors.d.ts.map +1 -0
  181. package/lib/typescript/src/utils/createChannelFeature.d.ts +18 -0
  182. package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -0
  183. package/lib/typescript/src/utils/layout.d.ts +2 -0
  184. package/lib/typescript/src/utils/layout.d.ts.map +1 -0
  185. package/lib/typescript/src/utils/urlRewriterRegistry.d.ts +7 -0
  186. package/lib/typescript/src/utils/urlRewriterRegistry.d.ts.map +1 -0
  187. package/package.json +5 -1
  188. package/src/components/ClipboardTab.tsx +8 -8
  189. package/src/components/ConsoleLogTab.tsx +49 -163
  190. package/src/components/DebugPanel.tsx +215 -0
  191. package/src/components/DebugView.tsx +80 -0
  192. package/src/components/EnvironmentTab.tsx +3 -3
  193. package/src/components/FeatureTabBar.tsx +204 -0
  194. package/src/components/FloatIcon.tsx +171 -0
  195. package/src/components/FloatPanelView.tsx +135 -647
  196. package/src/components/NavigationLogTab.tsx +1 -1
  197. package/src/components/NetworkLogTab.tsx +128 -269
  198. package/src/components/ThirdPartyLibsTab.tsx +3 -3
  199. package/src/components/TrackLogTab.tsx +53 -188
  200. package/src/components/ZustandLogTab.tsx +79 -181
  201. package/src/components/shared/CollapsibleSection.tsx +1 -1
  202. package/src/components/shared/CopyButton.tsx +1 -1
  203. package/src/components/shared/LogListScreen.tsx +164 -0
  204. package/src/core/DebugToolkit.tsx +114 -138
  205. package/src/core/DebugToolkitProvider.tsx +32 -38
  206. package/src/features/ClipboardFeature.ts +6 -2
  207. package/src/features/ConsoleLogFeature.ts +66 -68
  208. package/src/features/EnvironmentFeature.ts +5 -13
  209. package/src/features/NavigationLogFeature.ts +12 -42
  210. package/src/features/NetworkFeature.ts +43 -405
  211. package/src/features/TrackFeature.ts +14 -49
  212. package/src/features/ZustandLogFeature.ts +16 -42
  213. package/src/hooks/useNavigationLogger.ts +1 -6
  214. package/src/index.ts +5 -9
  215. package/src/initialize.ts +28 -120
  216. package/src/interceptors/networkInterceptor.ts +646 -0
  217. package/src/types/index.ts +25 -36
  218. package/src/utils/colors.ts +38 -0
  219. package/src/utils/createChannelFeature.ts +55 -0
  220. package/src/utils/layout.ts +1 -0
  221. package/src/utils/urlRewriterRegistry.ts +10 -0
  222. package/lib/commonjs/utils/constants.js +0 -135
  223. package/lib/commonjs/utils/constants.js.map +0 -1
  224. package/lib/module/utils/constants.js +0 -130
  225. package/lib/module/utils/constants.js.map +0 -1
  226. package/lib/typescript/src/utils/constants.d.ts +0 -96
  227. package/lib/typescript/src/utils/constants.d.ts.map +0 -1
  228. package/src/utils/constants.ts +0 -91
@@ -0,0 +1,215 @@
1
+ import React, { useCallback, useEffect, useRef } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ Animated,
7
+ PanResponder,
8
+ Pressable,
9
+ TouchableOpacity,
10
+ useWindowDimensions,
11
+ } from 'react-native';
12
+ import { Colors } from '../utils/colors';
13
+
14
+ interface DebugPanelProps {
15
+ onClose: () => void;
16
+ onClearAll: () => void;
17
+ children: React.ReactNode;
18
+ }
19
+
20
+ export function DebugPanel({ onClose, onClearAll, children }: DebugPanelProps) {
21
+ const { height: screenHeight } = useWindowDimensions();
22
+ const panelTranslateY = useRef(new Animated.Value(screenHeight)).current;
23
+ const backdropOpacity = useRef(new Animated.Value(0)).current;
24
+
25
+ useEffect(() => {
26
+ requestAnimationFrame(() => {
27
+ Animated.parallel([
28
+ Animated.spring(panelTranslateY, {
29
+ toValue: 0,
30
+ friction: 8,
31
+ tension: 65,
32
+ useNativeDriver: true,
33
+ }),
34
+ Animated.timing(backdropOpacity, {
35
+ toValue: 1,
36
+ duration: 250,
37
+ useNativeDriver: true,
38
+ }),
39
+ ]).start();
40
+ });
41
+ }, [panelTranslateY, backdropOpacity]);
42
+
43
+ const closePanel = useCallback(() => {
44
+ Animated.parallel([
45
+ Animated.spring(panelTranslateY, {
46
+ toValue: screenHeight,
47
+ friction: 8,
48
+ tension: 65,
49
+ useNativeDriver: true,
50
+ }),
51
+ Animated.timing(backdropOpacity, {
52
+ toValue: 0,
53
+ duration: 200,
54
+ useNativeDriver: true,
55
+ }),
56
+ ]).start(() => onClose());
57
+ }, [panelTranslateY, backdropOpacity, onClose, screenHeight]);
58
+
59
+ const panelResponder = useRef(
60
+ PanResponder.create({
61
+ onStartShouldSetPanResponder: () => true,
62
+ onMoveShouldSetPanResponder: (_, gs) => gs.dy > 5,
63
+ onPanResponderMove: (_, gs) => {
64
+ if (gs.dy > 0) {
65
+ panelTranslateY.setValue(gs.dy);
66
+ backdropOpacity.setValue(Math.max(0, 1 - gs.dy / 200));
67
+ }
68
+ },
69
+ onPanResponderRelease: (_, gs) => {
70
+ if (gs.dy > 100) {
71
+ closePanel();
72
+ } else {
73
+ Animated.spring(panelTranslateY, {
74
+ toValue: 0,
75
+ friction: 8,
76
+ tension: 50,
77
+ useNativeDriver: true,
78
+ }).start();
79
+ Animated.timing(backdropOpacity, {
80
+ toValue: 1,
81
+ duration: 200,
82
+ useNativeDriver: true,
83
+ }).start();
84
+ }
85
+ },
86
+ }),
87
+ ).current;
88
+
89
+ return (
90
+ <View style={styles.container}>
91
+ <Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]}>
92
+ <Pressable style={styles.backdropPressable} onPress={closePanel} />
93
+ </Animated.View>
94
+ <Animated.View
95
+ style={[styles.panel, { transform: [{ translateY: panelTranslateY }] }]}
96
+ >
97
+ <View {...panelResponder.panHandlers}>
98
+ <View style={styles.dragHandle}>
99
+ <View style={styles.dragIndicator} />
100
+ </View>
101
+ <View style={styles.header}>
102
+ <Text style={styles.headerTitle}>Debug Toolkit</Text>
103
+ <View style={styles.headerButtons}>
104
+ <TouchableOpacity
105
+ onPress={() => {
106
+ onClearAll();
107
+ closePanel();
108
+ }}
109
+ style={styles.clearButton}
110
+ activeOpacity={0.6}
111
+ >
112
+ <Text style={styles.clearButtonText}>Clear</Text>
113
+ </TouchableOpacity>
114
+ <Pressable onPress={closePanel} style={styles.closeButton}>
115
+ <Text style={styles.closeButtonText}>✕</Text>
116
+ </Pressable>
117
+ </View>
118
+ </View>
119
+ </View>
120
+ <View style={styles.panelContent}>{children}</View>
121
+ </Animated.View>
122
+ </View>
123
+ );
124
+ }
125
+
126
+ const styles = StyleSheet.create({
127
+ container: {
128
+ position: 'absolute',
129
+ top: 0,
130
+ left: 0,
131
+ right: 0,
132
+ bottom: 0,
133
+ justifyContent: 'flex-end',
134
+ },
135
+ backdrop: {
136
+ position: 'absolute',
137
+ top: 0,
138
+ left: 0,
139
+ right: 0,
140
+ bottom: 0,
141
+ backgroundColor: 'rgba(0,0,0,0.35)',
142
+ },
143
+ backdropPressable: { flex: 1 },
144
+ panel: {
145
+ width: '100%',
146
+ height: '90%',
147
+ backgroundColor: Colors.background,
148
+ borderTopLeftRadius: 28,
149
+ borderTopRightRadius: 28,
150
+ overflow: 'hidden',
151
+ elevation: 24,
152
+ shadowColor: '#000',
153
+ shadowOffset: { width: 0, height: -4 },
154
+ shadowOpacity: 0.08,
155
+ shadowRadius: 12,
156
+ },
157
+ dragHandle: {
158
+ width: '100%',
159
+ height: 20,
160
+ alignItems: 'center',
161
+ justifyContent: 'center',
162
+ backgroundColor: Colors.surface,
163
+ },
164
+ dragIndicator: {
165
+ width: 40,
166
+ height: 4,
167
+ borderRadius: 2,
168
+ backgroundColor: '#D1D1D6',
169
+ },
170
+ panelContent: { flex: 1 },
171
+ header: {
172
+ flexDirection: 'row',
173
+ justifyContent: 'space-between',
174
+ alignItems: 'center',
175
+ paddingHorizontal: 20,
176
+ paddingTop: 6,
177
+ paddingBottom: 10,
178
+ backgroundColor: Colors.surface,
179
+ },
180
+ headerTitle: {
181
+ fontSize: 17,
182
+ fontWeight: '600',
183
+ color: Colors.text,
184
+ },
185
+ headerButtons: {
186
+ flexDirection: 'row',
187
+ alignItems: 'center',
188
+ gap: 12,
189
+ },
190
+ clearButton: {
191
+ paddingHorizontal: 12,
192
+ paddingVertical: 6,
193
+ borderRadius: 8,
194
+ backgroundColor: 'rgba(255,59,48,0.06)',
195
+ },
196
+ clearButtonText: {
197
+ color: Colors.error,
198
+ fontSize: 14,
199
+ fontWeight: '500',
200
+ },
201
+ closeButton: {
202
+ width: 30,
203
+ height: 30,
204
+ borderRadius: 15,
205
+ backgroundColor: Colors.background,
206
+ alignItems: 'center',
207
+ justifyContent: 'center',
208
+ },
209
+ closeButtonText: {
210
+ fontSize: 16,
211
+ fontWeight: '400',
212
+ color: Colors.textSecondary,
213
+ lineHeight: 16,
214
+ },
215
+ });
@@ -0,0 +1,80 @@
1
+ import React, { useEffect } from 'react';
2
+ import { DebugToolkitProvider } from '../core/DebugToolkitProvider';
3
+ import { initializeDebugToolkit } from '../initialize';
4
+ import type { FeatureConfigs } from '../initialize';
5
+ import { useNavigationLogger } from '../hooks/useNavigationLogger';
6
+ import type { EnvironmentConfig, NavigationContainerRef } from '../types';
7
+
8
+ // --- Types ---
9
+
10
+ export interface DebugViewProps {
11
+ children: React.ReactNode;
12
+ /**
13
+ * Enable/disable specific features. Omitted features default to enabled.
14
+ * Set to `false` to disable a feature.
15
+ */
16
+ features?: Partial<FeatureConfigs>;
17
+ /** Navigation container ref for route tracking. */
18
+ navigationRef?: React.RefObject<NavigationContainerRef | null>;
19
+ /** Environment configs for runtime host switching. */
20
+ environments?: EnvironmentConfig[];
21
+ /** Force enable/disable (default: `__DEV__`). */
22
+ enabled?: boolean;
23
+ }
24
+
25
+ // --- Inner component for navigation hook (satisfies rules of hooks) ---
26
+
27
+ function NavigationLoggerInner({
28
+ navigationRef,
29
+ }: {
30
+ navigationRef: React.RefObject<NavigationContainerRef | null>;
31
+ }) {
32
+ useNavigationLogger(navigationRef);
33
+ return null;
34
+ }
35
+
36
+ // --- Main component ---
37
+
38
+ export function DebugView({
39
+ children,
40
+ features,
41
+ navigationRef,
42
+ environments,
43
+ enabled,
44
+ }: DebugViewProps) {
45
+ useEffect(() => {
46
+ // Build feature config: all enabled by default, user overrides take precedence
47
+ const resolvedFeatures: FeatureConfigs = {
48
+ network: true,
49
+ console: true,
50
+ zustand: true,
51
+ navigation: true,
52
+ track: true,
53
+ clipboard: true,
54
+ ...features,
55
+ };
56
+
57
+ // Apply environments prop
58
+ if (environments) {
59
+ resolvedFeatures.environment = environments;
60
+ }
61
+
62
+ // Initialize toolkit
63
+ const toolkit = initializeDebugToolkit({
64
+ features: resolvedFeatures,
65
+ enabled,
66
+ });
67
+
68
+ return () => {
69
+ toolkit.destroy();
70
+ };
71
+ // eslint-disable-next-line react-hooks/exhaustive-deps
72
+ }, []);
73
+
74
+ return (
75
+ <DebugToolkitProvider>
76
+ {navigationRef && <NavigationLoggerInner navigationRef={navigationRef} />}
77
+ {children}
78
+ </DebugToolkitProvider>
79
+ );
80
+ }
@@ -6,7 +6,7 @@ import {
6
6
  StyleSheet,
7
7
  ScrollView,
8
8
  } from 'react-native';
9
- import { Colors } from '../utils/constants';
9
+ import { Colors } from '../utils/colors';
10
10
  import type { DebugFeatureRenderProps, EnvironmentState } from '../types';
11
11
  import type { EnvironmentFeatureAPI } from '../features/EnvironmentFeature';
12
12
 
@@ -19,7 +19,7 @@ const DEFAULT_COLORS: Record<string, string> = {
19
19
  prod: '#FF3B30',
20
20
  };
21
21
 
22
- export const EnvironmentTab: React.FC<DebugFeatureRenderProps<EnvironmentState>> = ({
22
+ export const EnvironmentTab: React.FC<DebugFeatureRenderProps<EnvironmentState>> = React.memo(({
23
23
  data,
24
24
  feature,
25
25
  }) => {
@@ -114,7 +114,7 @@ export const EnvironmentTab: React.FC<DebugFeatureRenderProps<EnvironmentState>>
114
114
  ) : null}
115
115
  </View>
116
116
  );
117
- };
117
+ });
118
118
 
119
119
  const styles = StyleSheet.create({
120
120
  container: {
@@ -0,0 +1,204 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ Animated,
7
+ ScrollView,
8
+ TouchableOpacity,
9
+ } from 'react-native';
10
+ import { Colors } from '../utils/colors';
11
+
12
+ export interface TabItem {
13
+ id: string;
14
+ label: string;
15
+ }
16
+
17
+ interface FeatureTabBarProps {
18
+ tabs: TabItem[];
19
+ activeIndex: number;
20
+ onSelectTab: (index: number) => void;
21
+ }
22
+
23
+ export function FeatureTabBar({ tabs, activeIndex, onSelectTab }: FeatureTabBarProps) {
24
+ const [underlineWidth, setUnderlineWidth] = useState(0);
25
+ const underlineTranslateX = useRef(new Animated.Value(0)).current;
26
+ const tabScrollViewRef = useRef<ScrollView>(null);
27
+ const tabLayouts = useRef<Array<{ x: number; width: number }>>([]);
28
+ const tabScrollOffset = useRef(0);
29
+ const tabViewportWidth = useRef(0);
30
+ const tabContentWidth = useRef(0);
31
+ const underlineInit = useRef(false);
32
+ const isSwitching = useRef(false);
33
+
34
+ const maxTabScroll = useCallback(
35
+ () => Math.max(0, tabContentWidth.current - tabViewportWidth.current),
36
+ [],
37
+ );
38
+ const clampScroll = useCallback(
39
+ (offset: number) => Math.min(Math.max(0, offset), maxTabScroll()),
40
+ [maxTabScroll],
41
+ );
42
+ const getTabScrollTarget = useCallback(
43
+ (index: number) => {
44
+ const layout = tabLayouts.current[index];
45
+ return layout ? clampScroll(layout.x - 60) : clampScroll(tabScrollOffset.current);
46
+ },
47
+ [clampScroll],
48
+ );
49
+
50
+ const animateUnderline = useCallback(
51
+ (index: number, scrollOffset?: number) => {
52
+ const layout = tabLayouts.current[index];
53
+ if (!layout) return;
54
+ const offsetX = clampScroll(scrollOffset ?? tabScrollOffset.current);
55
+ setUnderlineWidth(layout.width);
56
+ Animated.spring(underlineTranslateX, {
57
+ toValue: layout.x - offsetX,
58
+ friction: 7,
59
+ tension: 50,
60
+ useNativeDriver: true,
61
+ }).start();
62
+ },
63
+ [clampScroll, underlineTranslateX],
64
+ );
65
+
66
+ const syncUnderlineToActiveTab = useCallback(() => {
67
+ const layout = tabLayouts.current[activeIndex];
68
+ if (!layout) return;
69
+ const clamped = clampScroll(tabScrollOffset.current);
70
+ if (clamped !== tabScrollOffset.current) tabScrollOffset.current = clamped;
71
+ underlineTranslateX.setValue(layout.x - clamped);
72
+ setUnderlineWidth(layout.width);
73
+ }, [activeIndex, clampScroll, underlineTranslateX]);
74
+
75
+ const tryInit = useCallback(() => {
76
+ if (underlineInit.current || tabViewportWidth.current <= 0 || tabContentWidth.current <= 0)
77
+ return;
78
+ const layout = tabLayouts.current[activeIndex];
79
+ if (!layout) return;
80
+ const target = getTabScrollTarget(activeIndex);
81
+ tabScrollOffset.current = target;
82
+ tabScrollViewRef.current?.scrollTo({ x: target, animated: false });
83
+ underlineTranslateX.setValue(layout.x - target);
84
+ setUnderlineWidth(layout.width);
85
+ underlineInit.current = true;
86
+ }, [activeIndex, getTabScrollTarget, underlineTranslateX]);
87
+
88
+ const handleSelectTab = useCallback(
89
+ (index: number) => {
90
+ if (isSwitching.current || index === activeIndex) return;
91
+ isSwitching.current = true;
92
+
93
+ const layout = tabLayouts.current[index];
94
+ if (layout) {
95
+ const target = getTabScrollTarget(index);
96
+ tabScrollOffset.current = target;
97
+ tabScrollViewRef.current?.scrollTo({ x: target, animated: true });
98
+ animateUnderline(index, target);
99
+ }
100
+
101
+ onSelectTab(index);
102
+ setTimeout(() => {
103
+ isSwitching.current = false;
104
+ }, 350);
105
+ },
106
+ [activeIndex, animateUnderline, getTabScrollTarget, onSelectTab],
107
+ );
108
+
109
+ // React to external activeIndex changes (e.g. swipe-to-switch)
110
+ useEffect(() => {
111
+ if (!underlineInit.current) return;
112
+ const layout = tabLayouts.current[activeIndex];
113
+ if (!layout) return;
114
+ const target = getTabScrollTarget(activeIndex);
115
+ tabScrollOffset.current = target;
116
+ tabScrollViewRef.current?.scrollTo({ x: target, animated: true });
117
+ animateUnderline(activeIndex, target);
118
+ }, [activeIndex, animateUnderline, getTabScrollTarget]);
119
+
120
+ return (
121
+ <View style={styles.container}>
122
+ <ScrollView
123
+ ref={tabScrollViewRef}
124
+ horizontal
125
+ showsHorizontalScrollIndicator={false}
126
+ contentContainerStyle={styles.scrollContent}
127
+ onLayout={(e) => {
128
+ tabViewportWidth.current = e.nativeEvent.layout.width;
129
+ tryInit();
130
+ }}
131
+ onContentSizeChange={(width) => {
132
+ tabContentWidth.current = width;
133
+ tryInit();
134
+ }}
135
+ onScroll={(e) => {
136
+ tabScrollOffset.current = clampScroll(e.nativeEvent.contentOffset.x);
137
+ if (!isSwitching.current) syncUnderlineToActiveTab();
138
+ }}
139
+ scrollEventThrottle={16}
140
+ >
141
+ {tabs.map((tab, index) => (
142
+ <TouchableOpacity
143
+ key={tab.id}
144
+ style={styles.tab}
145
+ onPress={() => handleSelectTab(index)}
146
+ activeOpacity={0.7}
147
+ onLayout={(e) => {
148
+ const { x, width } = e.nativeEvent.layout;
149
+ tabLayouts.current[index] = { x, width };
150
+ if (index === activeIndex) tryInit();
151
+ }}
152
+ >
153
+ <Text
154
+ style={[styles.tabText, activeIndex === index && styles.activeTabText]}
155
+ numberOfLines={1}
156
+ >
157
+ {tab.label}
158
+ </Text>
159
+ </TouchableOpacity>
160
+ ))}
161
+ </ScrollView>
162
+ <Animated.View
163
+ style={[
164
+ styles.underline,
165
+ { width: underlineWidth, transform: [{ translateX: underlineTranslateX }] },
166
+ ]}
167
+ />
168
+ </View>
169
+ );
170
+ }
171
+
172
+ const styles = StyleSheet.create({
173
+ container: {
174
+ backgroundColor: Colors.surface,
175
+ borderBottomWidth: StyleSheet.hairlineWidth,
176
+ borderBottomColor: Colors.border,
177
+ },
178
+ scrollContent: {
179
+ paddingHorizontal: 20,
180
+ flexDirection: 'row',
181
+ },
182
+ tab: {
183
+ paddingVertical: 10,
184
+ paddingHorizontal: 14,
185
+ marginRight: 8,
186
+ },
187
+ tabText: {
188
+ fontSize: 14,
189
+ fontWeight: '500',
190
+ color: Colors.textLight,
191
+ },
192
+ activeTabText: {
193
+ color: Colors.primary,
194
+ fontWeight: '600',
195
+ },
196
+ underline: {
197
+ position: 'absolute',
198
+ bottom: 0,
199
+ left: 0,
200
+ height: 2.5,
201
+ borderRadius: 1.25,
202
+ backgroundColor: Colors.primary,
203
+ },
204
+ });
@@ -0,0 +1,171 @@
1
+ import React, { useRef } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ Animated,
7
+ PanResponder,
8
+ Pressable,
9
+ useWindowDimensions,
10
+ } from 'react-native';
11
+ import { Colors } from '../utils/colors';
12
+ import { ICON_SIZE } from '../utils/layout';
13
+
14
+ interface FloatIconProps {
15
+ visible: boolean;
16
+ onPress: () => void;
17
+ badge: { label: string; color: string } | null;
18
+ }
19
+
20
+ export function FloatIcon({ visible, onPress, badge }: FloatIconProps) {
21
+ const { width: screenWidth, height: screenHeight } = useWindowDimensions();
22
+ const initialX = screenWidth - ICON_SIZE - 16;
23
+ const initialY = screenHeight / 2 - ICON_SIZE / 2;
24
+
25
+ const pan = useRef(
26
+ new Animated.ValueXY({ x: initialX, y: initialY }),
27
+ ).current;
28
+ const scale = useRef(new Animated.Value(1)).current;
29
+ const lastPosition = useRef({ x: initialX, y: initialY });
30
+
31
+ const panResponder = useRef(
32
+ PanResponder.create({
33
+ onStartShouldSetPanResponder: () => true,
34
+ onMoveShouldSetPanResponder: () => true,
35
+ onPanResponderGrant: () => {
36
+ Animated.spring(scale, {
37
+ toValue: 0.9,
38
+ friction: 5,
39
+ useNativeDriver: true,
40
+ }).start();
41
+ },
42
+ onPanResponderMove: (_: unknown, gs: { dx: number; dy: number }) => {
43
+ const x = Math.max(
44
+ 0,
45
+ Math.min(lastPosition.current.x + gs.dx, screenWidth - ICON_SIZE),
46
+ );
47
+ const y = Math.max(
48
+ 0,
49
+ Math.min(lastPosition.current.y + gs.dy, screenHeight - ICON_SIZE),
50
+ );
51
+ pan.setValue({ x, y });
52
+ },
53
+ onPanResponderRelease: (_: unknown, gs: { dx: number; dy: number }) => {
54
+ if (Math.abs(gs.dx) < 5 && Math.abs(gs.dy) < 5) {
55
+ Animated.spring(scale, { toValue: 1, friction: 5, useNativeDriver: true }).start();
56
+ onPress();
57
+ return;
58
+ }
59
+ Animated.spring(scale, { toValue: 1, friction: 5, useNativeDriver: true }).start();
60
+ lastPosition.current = {
61
+ x: Math.max(
62
+ 0,
63
+ Math.min(lastPosition.current.x + gs.dx, screenWidth - ICON_SIZE),
64
+ ),
65
+ y: Math.max(
66
+ 0,
67
+ Math.min(lastPosition.current.y + gs.dy, screenHeight - ICON_SIZE),
68
+ ),
69
+ };
70
+ },
71
+ onPanResponderTerminate: (_: unknown, gs: { dx: number; dy: number }) => {
72
+ lastPosition.current = {
73
+ x: Math.max(
74
+ 0,
75
+ Math.min(lastPosition.current.x + gs.dx, screenWidth - ICON_SIZE),
76
+ ),
77
+ y: Math.max(
78
+ 0,
79
+ Math.min(lastPosition.current.y + gs.dy, screenHeight - ICON_SIZE),
80
+ ),
81
+ };
82
+ },
83
+ }),
84
+ ).current;
85
+
86
+ return (
87
+ <Animated.View
88
+ pointerEvents={visible ? 'auto' : 'none'}
89
+ style={[
90
+ styles.root,
91
+ {
92
+ transform: [{ translateX: pan.x }, { translateY: pan.y }, { scale }],
93
+ opacity: visible ? 1 : 0,
94
+ },
95
+ ]}
96
+ {...panResponder.panHandlers}
97
+ >
98
+ <Pressable onPress={onPress} style={styles.inner}>
99
+ <View style={styles.iconGrid}>
100
+ <View style={styles.iconRow}>
101
+ <View style={[styles.iconCell, { backgroundColor: Colors.primary }]} />
102
+ <View style={[styles.iconCell, { backgroundColor: Colors.success }]} />
103
+ </View>
104
+ <View style={styles.iconRow}>
105
+ <View style={[styles.iconCell, { backgroundColor: Colors.warning }]} />
106
+ <View style={[styles.iconCell, { backgroundColor: Colors.purple }]} />
107
+ </View>
108
+ </View>
109
+ {badge && (
110
+ <View style={[styles.badge, { backgroundColor: badge.color }]}>
111
+ <Text style={styles.badgeText}>{badge.label}</Text>
112
+ </View>
113
+ )}
114
+ </Pressable>
115
+ </Animated.View>
116
+ );
117
+ }
118
+
119
+ const styles = StyleSheet.create({
120
+ root: {
121
+ position: 'absolute',
122
+ width: ICON_SIZE,
123
+ height: ICON_SIZE,
124
+ borderRadius: ICON_SIZE / 2,
125
+ backgroundColor: '#FFFFFF',
126
+ elevation: 6,
127
+ shadowColor: '#000',
128
+ shadowOffset: { width: 0, height: 2 },
129
+ shadowOpacity: 0.12,
130
+ shadowRadius: 8,
131
+ },
132
+ inner: {
133
+ width: '100%',
134
+ height: '100%',
135
+ alignItems: 'center',
136
+ justifyContent: 'center',
137
+ },
138
+ iconGrid: {
139
+ width: 22,
140
+ height: 22,
141
+ justifyContent: 'space-between',
142
+ },
143
+ iconRow: {
144
+ flexDirection: 'row',
145
+ justifyContent: 'space-between',
146
+ },
147
+ iconCell: {
148
+ width: 8,
149
+ height: 8,
150
+ borderRadius: 2.5,
151
+ },
152
+ badge: {
153
+ position: 'absolute',
154
+ top: -4,
155
+ right: -4,
156
+ minWidth: 20,
157
+ height: 18,
158
+ borderRadius: 9,
159
+ paddingHorizontal: 5,
160
+ alignItems: 'center',
161
+ justifyContent: 'center',
162
+ borderWidth: 2,
163
+ borderColor: '#FFF',
164
+ elevation: 4,
165
+ },
166
+ badgeText: {
167
+ color: '#FFF',
168
+ fontSize: 9,
169
+ fontWeight: '700',
170
+ },
171
+ });