react-native-debug-toolkit 0.3.0 → 0.3.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.
package/index.js CHANGED
@@ -9,6 +9,8 @@ import { createNetworkFeature } from './lib/features/NetworkFeature'
9
9
  import { createPerformanceFeature } from './lib/features/PerformanceFeature'
10
10
  import { createConsoleLogFeature } from './lib/features/ConsoleLogFeature'
11
11
  import { createZustandLogFeature, addZustandLog } from './lib/features/ZustandLogFeature'
12
+ import { createNavigationLogFeature, addNavigationLog } from './lib/features/NavigationLogFeature'
13
+ import { useNavigationLogger } from './lib/hooks/useNavigationLogger'
12
14
 
13
15
  export {
14
16
  DebugToolKit,
@@ -17,7 +19,10 @@ export {
17
19
  createPerformanceFeature,
18
20
  createConsoleLogFeature,
19
21
  createZustandLogFeature,
20
- addZustandLog
22
+ addZustandLog,
23
+ createNavigationLogFeature,
24
+ addNavigationLog,
25
+ useNavigationLogger
21
26
  }
22
27
 
23
28
  export default DebugToolKit
@@ -0,0 +1,47 @@
1
+ const MAX_LOGS = 200; // Max number of navigation logs to store
2
+ const logs = [];
3
+ const originalNavigationMethods = {}; // Store original navigation methods
4
+
5
+ const setup = () => {
6
+ if (!__DEV__) {
7
+ return;
8
+ }
9
+ // Note: Actual navigation interception will be done in the main app code
10
+ // This is just a setup function for compatibility with the feature API
11
+ };
12
+
13
+ // Function to add navigation log
14
+ export const addNavigationLog = (action, from, to, startTime, duration) => {
15
+ // Store log data
16
+ logs.push({
17
+ timestamp: new Date(),
18
+ action: action, // push, pop, replace, etc.
19
+ from: from, // source route info
20
+ to: to, // destination route info
21
+ startTime: startTime,
22
+ duration: duration,
23
+ });
24
+
25
+ // Trim logs if they exceed the maximum limit
26
+ if (logs.length > MAX_LOGS) {
27
+ logs.splice(0, logs.length - MAX_LOGS);
28
+ }
29
+ };
30
+
31
+ const getData = () => {
32
+ return logs;
33
+ };
34
+
35
+ const cleanup = () => {
36
+ logs.length = 0; // Clear array
37
+ };
38
+
39
+ export const createNavigationLogFeature = () => {
40
+ return {
41
+ name: 'navigation',
42
+ label: 'Navigation Logs',
43
+ setup: setup,
44
+ getData: getData,
45
+ cleanup: cleanup,
46
+ };
47
+ };
@@ -0,0 +1,92 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { addNavigationLog } from '../features/NavigationLogFeature';
3
+
4
+ /**
5
+ * Custom hook to log React Navigation events for debugging
6
+ *
7
+ * Usage:
8
+ *
9
+ * // In your navigation container
10
+ * import { useNavigationLogger } from 'react-native-debug-toolkit';
11
+ *
12
+ * function App() {
13
+ * const navigationRef = useRef();
14
+ *
15
+ * // Enable navigation logging
16
+ * useNavigationLogger(navigationRef);
17
+ *
18
+ * return (
19
+ * <NavigationContainer ref={navigationRef}>
20
+ * // ... your navigation structure
21
+ * </NavigationContainer>
22
+ * );
23
+ * }
24
+ */
25
+ export function useNavigationLogger(navigationRef) {
26
+ // Track previous state
27
+ const routeRef = useRef(null);
28
+
29
+ useEffect(() => {
30
+ if (!__DEV__ || !navigationRef.current) {
31
+ return;
32
+ }
33
+
34
+ // Create listeners for navigation events
35
+ const unsubscribeReady = navigationRef.current?.addListener('state', (e) => {
36
+ if (!navigationRef.current) return;
37
+
38
+ const state = navigationRef.current.getRootState();
39
+ if (!state) return;
40
+
41
+ const currentRoute = navigationRef.current.getCurrentRoute();
42
+
43
+ if (!currentRoute) return;
44
+
45
+ // First render won't have a previous state
46
+ if (!routeRef.current) {
47
+ routeRef.current = currentRoute;
48
+ return;
49
+ }
50
+
51
+ // Determine the navigation action
52
+ let action = 'navigate';
53
+
54
+ // Check if this was a push (adding to history) or replace
55
+ const routes = state.routes;
56
+ if (routes.length > 0) {
57
+ const prevIndex = routes.findIndex(route => route.key === routeRef.current.key);
58
+ const currentIndex = routes.findIndex(route => route.key === currentRoute.key);
59
+
60
+ if (currentIndex > prevIndex) {
61
+ action = 'push';
62
+ } else if (currentIndex < prevIndex) {
63
+ action = 'pop';
64
+ } else if (currentIndex === prevIndex) {
65
+ action = 'replace';
66
+ }
67
+ }
68
+
69
+ // Log the navigation
70
+ addNavigationLog(
71
+ action,
72
+ {
73
+ name: routeRef.current.name,
74
+ params: routeRef.current.params,
75
+ },
76
+ {
77
+ name: currentRoute.name,
78
+ params: currentRoute.params,
79
+ },
80
+ performance.now(), // Approximate time
81
+ 0 // No duration measurement available for this event type
82
+ );
83
+
84
+ // Update the reference
85
+ routeRef.current = currentRoute;
86
+ });
87
+
88
+ return () => {
89
+ unsubscribeReady && unsubscribeReady();
90
+ };
91
+ }, [navigationRef]);
92
+ }
package/lib/index.js CHANGED
@@ -3,6 +3,7 @@ import { createNetworkFeature } from './features/NetworkFeature'
3
3
  import { createPerformanceFeature} from './features/PerformanceFeature'
4
4
  import { createConsoleLogFeature } from './features/ConsoleLogFeature'
5
5
  import { createZustandLogFeature, zustandLogMiddleware } from './features/ZustandLogFeature'
6
+ import { createNavigationLogFeature, addNavigationLog } from './features/NavigationLogFeature'
6
7
 
7
8
  // Registry mapping feature names (strings) to their creator functions
8
9
  const featureRegistry = {
@@ -10,12 +11,13 @@ const featureRegistry = {
10
11
  console: createConsoleLogFeature,
11
12
  performance: createPerformanceFeature,
12
13
  zustand: createZustandLogFeature,
14
+ navigation: createNavigationLogFeature,
13
15
  // Add other built-in features here
14
16
  // 'storage': createStorageFeature,
15
17
  }
16
18
 
17
19
  // List of default features (can use strings now)
18
- const defaultFeatures = ['network', 'console', 'zustand']
20
+ const defaultFeatures = ['network', 'console', 'zustand', 'navigation']
19
21
 
20
22
  /**
21
23
  * Initializes and shows the Debug ToolKit panel with specified features.
@@ -81,6 +83,8 @@ export {
81
83
  createPerformanceFeature,
82
84
  createConsoleLogFeature,
83
85
  createZustandLogFeature,
86
+ createNavigationLogFeature,
87
+ addNavigationLog,
84
88
  zustandLogMiddleware, // Export middleware for use in Zustand stores
85
89
  featureRegistry
86
90
  };
@@ -0,0 +1 @@
1
+
@@ -19,15 +19,15 @@ export const DebugColors = {
19
19
  export const getLogLevelColor = (level) => {
20
20
  switch (level?.toLowerCase()) {
21
21
  case 'log':
22
- return '#333';
22
+ return '#333'; // Dark gray for standard log
23
23
  case 'info':
24
- return '#0D96F2';
24
+ return '#0D96F2'; // Blue for info
25
25
  case 'warn':
26
- return '#FCA130';
26
+ return '#FCA130'; // Orange for warning
27
27
  case 'error':
28
- return '#F93E3E';
28
+ return '#F93E3E'; // Red for error
29
29
  default:
30
- return '#666666';
30
+ return '#666666'; // Default gray
31
31
  }
32
32
  };
33
33
  export const DebugImgs = {
@@ -47,3 +47,21 @@ export const getZustandActionColor = (action) => {
47
47
 
48
48
  return '#0D96F2'; // Default blue for other actions
49
49
  };
50
+
51
+ // Navigation action colors
52
+ export const getNavigationActionColor = (action) => {
53
+ switch (action?.toLowerCase()) {
54
+ case 'push':
55
+ return '#0D96F2'; // Blue for push
56
+ case 'pop':
57
+ return '#6E7072'; // Gray for pop
58
+ case 'replace':
59
+ return '#4CAF50'; // Green for replace
60
+ case 'reset':
61
+ return '#FCA130'; // Orange for reset
62
+ case 'goback':
63
+ return '#9C27B0'; // Purple for go back
64
+ default:
65
+ return '#0D96F2'; // Default blue for navigation
66
+ }
67
+ };
@@ -12,6 +12,7 @@ const STORAGE_KEYS = {
12
12
  NETWORK_LOGS: 'network_logs',
13
13
  PERFORMANCE_DATA: 'performance_data',
14
14
  ZUSTAND_LOGS: 'zustand_logs',
15
+ NAVIGATION_LOGS: 'navigation_logs',
15
16
  };
16
17
 
17
18
  /**
@@ -8,15 +8,18 @@ import {
8
8
  Dimensions,
9
9
  Pressable,
10
10
  SafeAreaView,
11
+ ScrollView,
12
+ TouchableOpacity,
13
+ Modal,
14
+ FlatList,
11
15
  } from 'react-native'
12
16
  // import AsyncStorage from '@react-native-async-storage/async-storage'
13
17
  import { IconRadius, DebugColors } from '../utils/DebugConst'
14
18
  import SubViewHTTPLogs from './SubViewHTTPLogs'
15
19
  import SubViewPerformance from './SubViewPerformance'
16
- import TabView from './TabView'
17
20
  import SubViewConsoleLogs from './SubViewConsoleLogs'
18
21
  import SubViewZustandLogs from './SubViewZustandLogs'
19
-
22
+ import SubViewNavigationLogs from './SubViewNavigationLogs'
20
23
  const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
21
24
 
22
25
  export default class FloatPanelView extends Component {
@@ -38,8 +41,12 @@ export default class FloatPanelView extends Component {
38
41
  activeTab: 0,
39
42
  panelTranslateY: new Animated.Value(screenHeight),
40
43
  backdropOpacity: new Animated.Value(0),
44
+ isTabMenuVisible: false,
45
+ panelLayout: { width: 0, height: 0 },
41
46
  }
42
47
 
48
+ this.tabScrollViewRef = React.createRef()
49
+
43
50
  this.gestureResponder = PanResponder.create({
44
51
  onStartShouldSetPanResponder: () => true,
45
52
  onMoveShouldSetPanResponder: () => true,
@@ -197,6 +204,8 @@ export default class FloatPanelView extends Component {
197
204
  }
198
205
 
199
206
  _closePanel = () => {
207
+ this.setState({ isTabMenuVisible: false })
208
+
200
209
  Animated.parallel([
201
210
  Animated.spring(this.state.panelTranslateY, {
202
211
  toValue: screenHeight,
@@ -214,6 +223,26 @@ export default class FloatPanelView extends Component {
214
223
  })
215
224
  }
216
225
 
226
+ _toggleTabMenu = () => {
227
+ this.setState(prevState => ({ isTabMenuVisible: !prevState.isTabMenuVisible }))
228
+ }
229
+
230
+ _handleTabChange = (index) => {
231
+ this.setState({
232
+ activeTab: index,
233
+ isTabMenuVisible: false
234
+ }, () => {
235
+ // Scroll to make active tab visible
236
+ if (this.tabScrollViewRef.current) {
237
+ const tabWidth = 80; // Approximate tab width
238
+ this.tabScrollViewRef.current.scrollTo({
239
+ x: Math.max(0, index * tabWidth - 50), // Center the tab
240
+ animated: true
241
+ });
242
+ }
243
+ })
244
+ }
245
+
217
246
  renderFloatBtn() {
218
247
  const { isOpen, toFloat } = this.state
219
248
 
@@ -287,6 +316,10 @@ export default class FloatPanelView extends Component {
287
316
  return <SubViewZustandLogs logs={data} />
288
317
  }
289
318
 
319
+ if (feature.name === 'navigation') {
320
+ return <SubViewNavigationLogs logs={data} />
321
+ }
322
+
290
323
  // Generic fallback for other feature types
291
324
  return (
292
325
  <View style={styles.genericContent}>
@@ -309,9 +342,46 @@ export default class FloatPanelView extends Component {
309
342
  return this.renderFeatureContent(tabs[activeTab].id)
310
343
  }
311
344
 
345
+ renderTabBar() {
346
+ const tabs = this.getFeatureTabs()
347
+ const { activeTab } = this.state
348
+
349
+ if (!tabs.length) return null
350
+
351
+ return (
352
+ <View style={styles.tabBarContainer}>
353
+ <View style={styles.tabsRow}>
354
+ <ScrollView
355
+ ref={this.tabScrollViewRef}
356
+ horizontal
357
+ showsHorizontalScrollIndicator={false}
358
+ contentContainerStyle={styles.tabsScrollContainer}>
359
+ {tabs.map((tab, index) => {
360
+ const isActive = index === activeTab
361
+
362
+ return (
363
+ <TouchableOpacity
364
+ key={`tab-${tab.id}`}
365
+ style={[styles.tab, isActive && styles.activeTab]}
366
+ onPress={() => this._handleTabChange(index)}>
367
+ <Text
368
+ style={[styles.tabText, isActive && styles.activeTabText]}
369
+ numberOfLines={1}
370
+ ellipsizeMode="tail">
371
+ {tab.label}
372
+ </Text>
373
+ </TouchableOpacity>
374
+ )
375
+ })}
376
+ </ScrollView>
377
+
378
+ </View>
379
+ </View>
380
+ )
381
+ }
382
+
312
383
  renderPanel() {
313
384
  const { isOpen, panelTranslateY, backdropOpacity } = this.state
314
- const tabs = this.getFeatureTabs()
315
385
 
316
386
  if (!isOpen) {
317
387
  return null
@@ -329,7 +399,11 @@ export default class FloatPanelView extends Component {
329
399
  style={[
330
400
  styles.panel,
331
401
  { transform: [{ translateY: panelTranslateY }] },
332
- ]}>
402
+ ]}
403
+ onLayout={(e) => {
404
+ const { width, height } = e.nativeEvent.layout
405
+ this.setState({ panelLayout: { width, height } })
406
+ }}>
333
407
  <View {...this.panelResponder.panHandlers} style={styles.dragHandle}>
334
408
  <View style={styles.dragIndicator} />
335
409
  </View>
@@ -340,12 +414,12 @@ export default class FloatPanelView extends Component {
340
414
  <Text style={styles.closeButtonText}>×</Text>
341
415
  </Pressable>
342
416
  </View>
343
- <TabView
344
- tabs={tabs}
345
- activeTab={this.state.activeTab}
346
- onTabChange={(index) => this.setState({ activeTab: index })}>
417
+
418
+ {this.renderTabBar()}
419
+
420
+ <View style={styles.contentContainer}>
347
421
  {this.renderContent()}
348
- </TabView>
422
+ </View>
349
423
  </SafeAreaView>
350
424
  </Animated.View>
351
425
  </View>
@@ -445,6 +519,9 @@ const styles = StyleSheet.create({
445
519
  panelContent: {
446
520
  flex: 1,
447
521
  },
522
+ contentContainer: {
523
+ flex: 1,
524
+ },
448
525
  header: {
449
526
  flexDirection: 'row',
450
527
  justifyContent: 'space-between',
@@ -467,6 +544,41 @@ const styles = StyleSheet.create({
467
544
  color: DebugColors.text,
468
545
  fontWeight: 'bold',
469
546
  },
547
+ tabBarContainer: {
548
+ borderBottomWidth: 1,
549
+ borderBottomColor: DebugColors.border,
550
+ backgroundColor: DebugColors.background,
551
+ },
552
+ tabsRow: {
553
+ flexDirection: 'row',
554
+ alignItems: 'center',
555
+ },
556
+ tabsScrollContainer: {
557
+ paddingHorizontal: 15,
558
+ flexDirection: 'row',
559
+ alignItems: 'center',
560
+ },
561
+ tab: {
562
+ paddingVertical: 12,
563
+ paddingHorizontal: 8,
564
+ marginRight: 4,
565
+ alignItems: 'center',
566
+ justifyContent: 'center',
567
+ minWidth: 50,
568
+ },
569
+ activeTab: {
570
+ borderBottomWidth: 2,
571
+ borderBottomColor: DebugColors.blue,
572
+ },
573
+ tabText: {
574
+ fontSize: 13,
575
+ color: DebugColors.textLight,
576
+ textAlign: 'center',
577
+ },
578
+ activeTabText: {
579
+ color: DebugColors.blue,
580
+ fontWeight: '600',
581
+ },
470
582
  emptyText: {
471
583
  padding: 20,
472
584
  textAlign: 'center',
@@ -501,5 +613,5 @@ const styles = StyleSheet.create({
501
613
  fontFamily: 'monospace',
502
614
  fontSize: 12,
503
615
  color: DebugColors.text,
504
- },
616
+ }
505
617
  })
@@ -0,0 +1,294 @@
1
+ import React from 'react'
2
+ import { View, Text, StyleSheet, Clipboard } from 'react-native'
3
+ import { ScrollView, Pressable } from 'react-native'
4
+ import JSONTree from 'react-native-json-tree'
5
+ import { getNavigationActionColor } from '../utils/DebugConst'
6
+
7
+ // Re-using the theme from ConsoleLogDetails for consistency
8
+ const theme = {
9
+ scheme: 'monokai',
10
+ author: 'wimer hazenberg (http://www.monokai.nl)',
11
+ base00: '#272822',
12
+ base01: '#383830',
13
+ base02: '#49483e',
14
+ base03: '#75715e',
15
+ base04: '#a59f85',
16
+ base05: '#f8f8f2',
17
+ base06: '#f5f4f1',
18
+ base07: '#f9f8f5',
19
+ base08: '#f92672',
20
+ base09: '#fd971f',
21
+ base0A: '#f4bf75',
22
+ base0B: '#a6e22e',
23
+ base0C: '#a1efe4',
24
+ base0D: '#66d9ef',
25
+ base0E: '#ae81ff',
26
+ base0F: '#cc6633'
27
+ };
28
+
29
+ const CopyButton = ({ text, style }) => {
30
+ const [copied, setCopied] = React.useState(false)
31
+
32
+ const handleCopy = async () => {
33
+ await Clipboard.setString(text)
34
+ setCopied(true)
35
+ setTimeout(() => setCopied(false), 2000)
36
+ }
37
+
38
+ return (
39
+ <Pressable style={[styles.copyButton, style]} onPress={handleCopy}>
40
+ <Text style={styles.copyButtonText}>{copied ? 'Copied!' : 'Copy'}</Text>
41
+ </Pressable>
42
+ )
43
+ }
44
+
45
+ const CollapsibleSection = ({ title, children, initiallyExpanded = false }) => {
46
+ const [expanded, setExpanded] = React.useState(initiallyExpanded)
47
+
48
+ return (
49
+ <View style={styles.collapsibleSection}>
50
+ <Pressable
51
+ style={styles.sectionHeader}
52
+ onPress={() => setExpanded(!expanded)}>
53
+ <Text style={styles.sectionTitle}>{title}</Text>
54
+ <Text style={styles.expandIcon}>{expanded ? '▼' : '▶'}</Text>
55
+ </Pressable>
56
+ {expanded && children}
57
+ </View>
58
+ )
59
+ }
60
+
61
+ // Basic JSONValue renderer
62
+ const JSONValue = ({ value }) => {
63
+ if (value === null) {
64
+ return <Text style={styles.jsonNull}>null</Text>
65
+ }
66
+
67
+ if (value === undefined) {
68
+ return <Text style={styles.jsonNull}>undefined</Text>
69
+ }
70
+
71
+ if (typeof value === 'boolean') {
72
+ return <Text style={styles.jsonBoolean}>{value.toString()}</Text>
73
+ }
74
+
75
+ if (typeof value === 'number') {
76
+ return <Text style={styles.jsonNumber}>{value}</Text>
77
+ }
78
+
79
+ if (typeof value === 'string') {
80
+ return (
81
+ <Text style={styles.jsonString} selectable={true}>
82
+ "{value}"
83
+ </Text>
84
+ )
85
+ }
86
+
87
+ if (typeof value === 'object') {
88
+ return (
89
+ <JSONTree
90
+ data={value}
91
+ theme={theme}
92
+ invertTheme={true}
93
+ hideRoot={true}
94
+ shouldExpandNode={(keyPath, nodeData, currentLevel) => currentLevel < 1}
95
+ />
96
+ )
97
+ }
98
+
99
+ return <Text style={styles.jsonOther}>{String(value)}</Text>
100
+ }
101
+
102
+ const NavigationLogDetails = ({ log }) => {
103
+ if (!log) {
104
+ return (
105
+ <View style={styles.errorContainer}>
106
+ <Text style={styles.errorText}>Navigation log data is missing</Text>
107
+ </View>
108
+ )
109
+ }
110
+
111
+ const { action, from, to, timestamp, startTime, duration } = log
112
+ const actionColor = getNavigationActionColor(action)
113
+
114
+ // Format the log data for text display and copying
115
+ const formatLogForCopy = () => {
116
+ try {
117
+ return `Navigation: ${action}
118
+ From: ${JSON.stringify(from, null, 2)}
119
+ To: ${JSON.stringify(to, null, 2)}
120
+ Time: ${timestamp ? new Date(timestamp).toLocaleString() : 'Unknown'}
121
+ Duration: ${duration || 'N/A'} ms`;
122
+ } catch (e) {
123
+ return 'Failed to format navigation log';
124
+ }
125
+ };
126
+
127
+ return (
128
+ <ScrollView
129
+ style={styles.container}
130
+ contentContainerStyle={styles.contentContainer}
131
+ showsVerticalScrollIndicator={true}
132
+ scrollEventThrottle={16}
133
+ keyboardShouldPersistTaps='handled'>
134
+
135
+ <View style={styles.header}>
136
+ <View style={styles.headerInfo}>
137
+ <Text style={[styles.actionIndicator, { color: actionColor }]}>
138
+ {action?.toUpperCase() || 'UNKNOWN'}
139
+ </Text>
140
+ <Text style={styles.timestamp}>
141
+ {timestamp
142
+ ? new Date(timestamp).toLocaleString()
143
+ : 'Unknown time'}
144
+ </Text>
145
+ {duration && (
146
+ <Text style={styles.duration}>
147
+ {`${duration} ms`}
148
+ </Text>
149
+ )}
150
+ </View>
151
+ <CopyButton text={formatLogForCopy()} />
152
+ </View>
153
+
154
+ <CollapsibleSection title='From Route' initiallyExpanded={true}>
155
+ <View style={styles.dataContentWrapper}>
156
+ <View style={styles.dataContent}>
157
+ <JSONValue value={from} />
158
+ </View>
159
+ </View>
160
+ </CollapsibleSection>
161
+
162
+ <CollapsibleSection title='To Route' initiallyExpanded={true}>
163
+ <View style={styles.dataContentWrapper}>
164
+ <View style={styles.dataContent}>
165
+ <JSONValue value={to} />
166
+ </View>
167
+ </View>
168
+ </CollapsibleSection>
169
+
170
+ </ScrollView>
171
+ )
172
+ }
173
+
174
+ // Styles adapted from ConsoleLogDetails
175
+ const styles = StyleSheet.create({
176
+ container: {
177
+ flex: 1,
178
+ backgroundColor: '#fff',
179
+ },
180
+ contentContainer: {
181
+ paddingBottom: 20,
182
+ },
183
+ header: {
184
+ flexDirection: 'row',
185
+ padding: 15,
186
+ borderBottomWidth: 1,
187
+ borderBottomColor: '#eee',
188
+ alignItems: 'center',
189
+ justifyContent: 'space-between',
190
+ },
191
+ headerInfo: {
192
+ flexShrink: 1,
193
+ marginRight: 10,
194
+ },
195
+ actionIndicator: {
196
+ fontSize: 14,
197
+ fontWeight: 'bold',
198
+ marginBottom: 4,
199
+ },
200
+ timestamp: {
201
+ fontSize: 13,
202
+ color: '#666',
203
+ marginBottom: 2,
204
+ },
205
+ duration: {
206
+ fontSize: 12,
207
+ color: '#888',
208
+ },
209
+ collapsibleSection: {
210
+ marginBottom: 1,
211
+ backgroundColor: '#fff',
212
+ },
213
+ sectionHeader: {
214
+ flexDirection: 'row',
215
+ justifyContent: 'space-between',
216
+ alignItems: 'center',
217
+ padding: 15,
218
+ backgroundColor: '#f5f5f5',
219
+ },
220
+ sectionTitle: {
221
+ fontSize: 15,
222
+ fontWeight: 'bold',
223
+ color: '#333',
224
+ },
225
+ expandIcon: {
226
+ fontSize: 14,
227
+ color: '#666',
228
+ },
229
+ content: {
230
+ padding: 15,
231
+ },
232
+ dataContentWrapper: {
233
+ flex: 1,
234
+ padding: 10,
235
+ },
236
+ dataContent: {
237
+ backgroundColor: '#f8f9fa',
238
+ padding: 10,
239
+ borderRadius: 4,
240
+ borderWidth: 1,
241
+ borderColor: '#e9ecef',
242
+ },
243
+ errorContainer: {
244
+ flex: 1,
245
+ justifyContent: 'center',
246
+ alignItems: 'center',
247
+ padding: 20,
248
+ },
249
+ errorText: {
250
+ color: '#ff4444',
251
+ fontSize: 16,
252
+ },
253
+ copyButton: {
254
+ backgroundColor: '#e9ecef',
255
+ paddingHorizontal: 10,
256
+ paddingVertical: 5,
257
+ borderRadius: 4,
258
+ flexShrink: 0,
259
+ },
260
+ copyButtonText: {
261
+ fontSize: 12,
262
+ color: '#666',
263
+ },
264
+ // JSON Value Styles (reused from ConsoleLogDetails)
265
+ jsonString: {
266
+ color: '#CB772F',
267
+ fontFamily: 'monospace',
268
+ fontSize: 13,
269
+ },
270
+ jsonNumber: {
271
+ color: '#AE81FF',
272
+ fontFamily: 'monospace',
273
+ fontSize: 13,
274
+ },
275
+ jsonBoolean: {
276
+ color: '#66D9EF',
277
+ fontWeight: 'bold',
278
+ fontFamily: 'monospace',
279
+ fontSize: 13,
280
+ },
281
+ jsonNull: {
282
+ color: '#F92672',
283
+ fontStyle: 'italic',
284
+ fontFamily: 'monospace',
285
+ fontSize: 13,
286
+ },
287
+ jsonOther: {
288
+ color: '#75715e',
289
+ fontFamily: 'monospace',
290
+ fontSize: 13,
291
+ },
292
+ });
293
+
294
+ export default NavigationLogDetails
@@ -0,0 +1,199 @@
1
+ import React, { useState, useMemo } from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ FlatList,
7
+ TouchableOpacity,
8
+ } from 'react-native'
9
+ import NavigationLogDetails from './NavigationLogDetails'
10
+ import { getNavigationActionColor } from '../utils/DebugConst'
11
+
12
+ const SubViewNavigationLogs = ({ logs = [] }) => {
13
+ const [selectedLog, setSelectedLog] = useState(null)
14
+
15
+ // Memoize the sorted logs
16
+ const sortedLogs = useMemo(() => {
17
+ // Create a stable copy before sorting if FlatList relies on reference equality
18
+ return [...logs].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
19
+ }, [logs]);
20
+
21
+ // Helper to format navigation data for preview
22
+ const formatNavigationPreview = (action, from, to) => {
23
+ const fromName = from?.name || 'Unknown';
24
+ const toName = to?.name || 'Unknown';
25
+
26
+ let preview = `${action || 'Navigate'}: ${fromName} → ${toName}`;
27
+
28
+ // Add params summary if available
29
+ if (to?.params) {
30
+ try {
31
+ const paramsStr = JSON.stringify(to.params);
32
+ const shortParams = paramsStr.length > 30 ? paramsStr.substring(0, 27) + '...' : paramsStr;
33
+ preview += ` ${shortParams}`;
34
+ } catch (e) {
35
+ preview += ' [with params]';
36
+ }
37
+ }
38
+
39
+ return preview;
40
+ };
41
+
42
+ const renderLogItem = ({ item }) => {
43
+ const action = item.action || 'navigate';
44
+ const actionColor = getNavigationActionColor(action);
45
+ const previewText = formatNavigationPreview(action, item.from, item.to);
46
+ const timestamp = item.timestamp
47
+ ? new Date(item.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })
48
+ : '';
49
+
50
+ return (
51
+ <TouchableOpacity
52
+ style={styles.logItem}
53
+ onPress={() => setSelectedLog(item)}>
54
+ <View style={styles.logItemContainer}>
55
+ <View
56
+ style={[styles.actionIndicator, { backgroundColor: actionColor }]}
57
+ />
58
+
59
+ <View style={styles.logContent}>
60
+ <View style={styles.logHeader}>
61
+ <Text style={[styles.actionText, { color: actionColor }]}>
62
+ {action.toUpperCase()}
63
+ </Text>
64
+ <Text style={styles.time}>{timestamp}</Text>
65
+ </View>
66
+
67
+ <Text style={styles.logMessage} numberOfLines={2}>
68
+ {previewText}
69
+ </Text>
70
+
71
+ {item.duration && (
72
+ <Text style={styles.duration}>{`${item.duration} ms`}</Text>
73
+ )}
74
+ </View>
75
+ </View>
76
+ </TouchableOpacity>
77
+ )
78
+ }
79
+
80
+ // If a log is selected, show the details view
81
+ if (selectedLog) {
82
+ return (
83
+ <View style={styles.container}>
84
+ <View style={styles.detailsHeader}>
85
+ <TouchableOpacity
86
+ style={styles.backButton}
87
+ onPress={() => setSelectedLog(null)}>
88
+ <Text style={styles.backButtonText}>← Back</Text>
89
+ </TouchableOpacity>
90
+ <Text style={styles.headerTitle}>Navigation Details</Text>
91
+ </View>
92
+ <NavigationLogDetails log={selectedLog} />
93
+ </View>
94
+ )
95
+ }
96
+
97
+ // Otherwise show the list view
98
+ return (
99
+ <View style={styles.container}>
100
+ {sortedLogs.length === 0 ? (
101
+ <Text style={styles.emptyText}>No navigation events logged yet</Text>
102
+ ) : (
103
+ <FlatList
104
+ data={sortedLogs}
105
+ renderItem={renderLogItem}
106
+ keyExtractor={(item, index) => `${item.timestamp}-${index}`}
107
+ style={styles.list}
108
+ />
109
+ )}
110
+ </View>
111
+ )
112
+ }
113
+
114
+ // Adapted styles from SubViewConsoleLogs
115
+ const styles = StyleSheet.create({
116
+ container: {
117
+ flex: 1,
118
+ backgroundColor: '#fff',
119
+ },
120
+ list: {
121
+ flex: 1,
122
+ },
123
+ emptyText: {
124
+ textAlign: 'center',
125
+ color: '#999',
126
+ marginTop: 20,
127
+ },
128
+ logItem: {
129
+ borderBottomWidth: 1,
130
+ borderBottomColor: '#eee',
131
+ },
132
+ logItemContainer: {
133
+ flexDirection: 'row',
134
+ paddingVertical: 10,
135
+ paddingHorizontal: 12,
136
+ },
137
+ actionIndicator: {
138
+ width: 4,
139
+ borderRadius: 2,
140
+ marginRight: 10,
141
+ },
142
+ logContent: {
143
+ flex: 1,
144
+ },
145
+ logHeader: {
146
+ flexDirection: 'row',
147
+ justifyContent: 'space-between',
148
+ alignItems: 'center',
149
+ marginBottom: 5,
150
+ },
151
+ actionText: {
152
+ fontSize: 13,
153
+ fontWeight: 'bold',
154
+ },
155
+ time: {
156
+ fontSize: 12,
157
+ color: '#888',
158
+ },
159
+ logMessage: {
160
+ fontSize: 14,
161
+ color: '#333',
162
+ lineHeight: 18,
163
+ fontFamily: 'monospace',
164
+ },
165
+ duration: {
166
+ fontSize: 12,
167
+ color: '#888',
168
+ marginTop: 4,
169
+ },
170
+ // Details Header Styles
171
+ detailsHeader: {
172
+ flexDirection: 'row',
173
+ alignItems: 'center',
174
+ paddingVertical: 10,
175
+ paddingHorizontal: 15,
176
+ borderBottomWidth: 1,
177
+ borderBottomColor: '#eee',
178
+ backgroundColor: '#f8f9fa',
179
+ },
180
+ backButton: {
181
+ paddingHorizontal: 10,
182
+ paddingVertical: 5,
183
+ borderRadius: 4,
184
+ backgroundColor: '#eee',
185
+ marginRight: 15,
186
+ },
187
+ backButtonText: {
188
+ color: '#333',
189
+ fontWeight: '500',
190
+ fontSize: 14,
191
+ },
192
+ headerTitle: {
193
+ fontSize: 16,
194
+ fontWeight: 'bold',
195
+ color: '#333',
196
+ },
197
+ })
198
+
199
+ export default SubViewNavigationLogs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-debug-toolkit",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A simple yet powerful debugging toolkit for React Native with a convenient floating UI for development",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",