@buoy-gg/route-events 1.7.2

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 (79) hide show
  1. package/README.md +654 -0
  2. package/lib/commonjs/RouteObserver.js +54 -0
  3. package/lib/commonjs/RouteParser.js +310 -0
  4. package/lib/commonjs/RouteTracker.js +39 -0
  5. package/lib/commonjs/components/NavigationStack.js +584 -0
  6. package/lib/commonjs/components/RouteEventDetailContent.js +492 -0
  7. package/lib/commonjs/components/RouteEventExpandedContent.js +187 -0
  8. package/lib/commonjs/components/RouteEventItemCompact.js +175 -0
  9. package/lib/commonjs/components/RouteEventsModalWithTabs.js +560 -0
  10. package/lib/commonjs/components/RouteEventsTimeline.js +82 -0
  11. package/lib/commonjs/components/RouteFilterViewV2.js +42 -0
  12. package/lib/commonjs/components/RoutesSitemap.js +948 -0
  13. package/lib/commonjs/expoRouterStore.js +104 -0
  14. package/lib/commonjs/index.js +99 -0
  15. package/lib/commonjs/package.json +1 -0
  16. package/lib/commonjs/preset.js +83 -0
  17. package/lib/commonjs/useNavigationStack.js +241 -0
  18. package/lib/commonjs/useRouteObserver.js +73 -0
  19. package/lib/commonjs/useRouteSitemap.js +234 -0
  20. package/lib/commonjs/utils/safeExpoRouter.js +129 -0
  21. package/lib/commonjs/utils/safeReactNavigation.js +104 -0
  22. package/lib/module/RouteObserver.js +49 -0
  23. package/lib/module/RouteParser.js +305 -0
  24. package/lib/module/RouteTracker.js +35 -0
  25. package/lib/module/components/NavigationStack.js +580 -0
  26. package/lib/module/components/RouteEventDetailContent.js +487 -0
  27. package/lib/module/components/RouteEventExpandedContent.js +183 -0
  28. package/lib/module/components/RouteEventItemCompact.js +171 -0
  29. package/lib/module/components/RouteEventsModalWithTabs.js +557 -0
  30. package/lib/module/components/RouteEventsTimeline.js +78 -0
  31. package/lib/module/components/RouteFilterViewV2.js +38 -0
  32. package/lib/module/components/RoutesSitemap.js +944 -0
  33. package/lib/module/expoRouterStore.js +98 -0
  34. package/lib/module/index.js +23 -0
  35. package/lib/module/preset.js +79 -0
  36. package/lib/module/useNavigationStack.js +238 -0
  37. package/lib/module/useRouteObserver.js +70 -0
  38. package/lib/module/useRouteSitemap.js +229 -0
  39. package/lib/module/utils/safeExpoRouter.js +120 -0
  40. package/lib/module/utils/safeReactNavigation.js +98 -0
  41. package/lib/typescript/RouteObserver.d.ts +37 -0
  42. package/lib/typescript/RouteObserver.d.ts.map +1 -0
  43. package/lib/typescript/RouteParser.d.ts +129 -0
  44. package/lib/typescript/RouteParser.d.ts.map +1 -0
  45. package/lib/typescript/RouteTracker.d.ts +29 -0
  46. package/lib/typescript/RouteTracker.d.ts.map +1 -0
  47. package/lib/typescript/components/NavigationStack.d.ts +11 -0
  48. package/lib/typescript/components/NavigationStack.d.ts.map +1 -0
  49. package/lib/typescript/components/RouteEventDetailContent.d.ts +21 -0
  50. package/lib/typescript/components/RouteEventDetailContent.d.ts.map +1 -0
  51. package/lib/typescript/components/RouteEventExpandedContent.d.ts +16 -0
  52. package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +1 -0
  53. package/lib/typescript/components/RouteEventItemCompact.d.ts +15 -0
  54. package/lib/typescript/components/RouteEventItemCompact.d.ts.map +1 -0
  55. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts +15 -0
  56. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -0
  57. package/lib/typescript/components/RouteEventsTimeline.d.ts +17 -0
  58. package/lib/typescript/components/RouteEventsTimeline.d.ts.map +1 -0
  59. package/lib/typescript/components/RouteFilterViewV2.d.ts +9 -0
  60. package/lib/typescript/components/RouteFilterViewV2.d.ts.map +1 -0
  61. package/lib/typescript/components/RoutesSitemap.d.ts +15 -0
  62. package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -0
  63. package/lib/typescript/expoRouterStore.d.ts +28 -0
  64. package/lib/typescript/expoRouterStore.d.ts.map +1 -0
  65. package/lib/typescript/index.d.ts +18 -0
  66. package/lib/typescript/index.d.ts.map +1 -0
  67. package/lib/typescript/preset.d.ts +76 -0
  68. package/lib/typescript/preset.d.ts.map +1 -0
  69. package/lib/typescript/useNavigationStack.d.ts +48 -0
  70. package/lib/typescript/useNavigationStack.d.ts.map +1 -0
  71. package/lib/typescript/useRouteObserver.d.ts +27 -0
  72. package/lib/typescript/useRouteObserver.d.ts.map +1 -0
  73. package/lib/typescript/useRouteSitemap.d.ts +102 -0
  74. package/lib/typescript/useRouteSitemap.d.ts.map +1 -0
  75. package/lib/typescript/utils/safeExpoRouter.d.ts +13 -0
  76. package/lib/typescript/utils/safeExpoRouter.d.ts.map +1 -0
  77. package/lib/typescript/utils/safeReactNavigation.d.ts +10 -0
  78. package/lib/typescript/utils/safeReactNavigation.d.ts.map +1 -0
  79. package/package.json +72 -0
@@ -0,0 +1,944 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * RoutesSitemap - Visual sitemap of all app routes
5
+ *
6
+ * Displays parsed route information from Expo Router with:
7
+ * - Search/filter
8
+ * - Organized groups
9
+ * - Route details
10
+ * - Copy to clipboard
11
+ * - Navigation
12
+ */
13
+
14
+ import { useState, useCallback, useMemo } from "react";
15
+ import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, Alert } from "react-native";
16
+ import { useRouter } from "expo-router";
17
+ import { Search, ChevronDown, ChevronRight, InlineCopyButton, ToolbarCopyButton, RefreshCw, formatRelativeTime, ProUpgradeModal, buoyColors } from "@buoy-gg/shared-ui";
18
+
19
+ // Lazy load the license hooks to avoid circular dependencies
20
+ let _useIsPro = null;
21
+ let _licenseLoadAttempted = false;
22
+ function loadLicenseModule() {
23
+ if (_licenseLoadAttempted) return;
24
+ _licenseLoadAttempted = true;
25
+ try {
26
+ const mod = require("@buoy-gg/license");
27
+ if (mod) {
28
+ _useIsPro = mod.useIsPro ?? null;
29
+ }
30
+ } catch {
31
+ // License package not available
32
+ }
33
+ }
34
+ function getUseIsPro() {
35
+ loadLicenseModule();
36
+ return _useIsPro ?? (() => false);
37
+ }
38
+ import { useRouteSitemap } from "../useRouteSitemap";
39
+
40
+ // ============================================================================
41
+ // Types
42
+ // ============================================================================
43
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
44
+ // ============================================================================
45
+ // Main Component
46
+ // ============================================================================
47
+
48
+ export function RoutesSitemap({
49
+ style
50
+ }) {
51
+ const [searchQuery, setSearchQuery] = useState("");
52
+ const [isSearching, setIsSearching] = useState(false);
53
+ const [expandedGroups, setExpandedGroups] = useState(new Set(["Root Routes", "Dynamic Routes"]));
54
+ const [isRefreshing, setIsRefreshing] = useState(false);
55
+ const [showUpgradeModal, setShowUpgradeModal] = useState(false);
56
+
57
+ // Check Pro status internally
58
+ const useIsPro = getUseIsPro();
59
+ const isPro = useIsPro();
60
+ const router = useRouter();
61
+ const {
62
+ groups,
63
+ stats,
64
+ isLoaded,
65
+ filteredRoutes,
66
+ routes,
67
+ refresh,
68
+ lastUpdatedAt,
69
+ source
70
+ } = useRouteSitemap({
71
+ searchQuery,
72
+ sortBy: "path"
73
+ });
74
+
75
+ // Prepare copy data - memoized so it only rebuilds when dependencies change
76
+ const copyAllData = useMemo(() => {
77
+ return {
78
+ summary: {
79
+ total: stats.total,
80
+ static: stats.static,
81
+ dynamic: stats.dynamic,
82
+ layouts: stats.layouts,
83
+ groups: stats.groups,
84
+ timestamp: new Date().toISOString()
85
+ },
86
+ groups: groups.map(group => ({
87
+ title: group.title,
88
+ description: group.description,
89
+ count: group.routes.length,
90
+ routes: group.routes.map(route => ({
91
+ path: route.path,
92
+ name: route.name,
93
+ type: route.type,
94
+ params: route.params,
95
+ depth: route.depth,
96
+ hasChildren: route.children.length > 0,
97
+ childrenCount: route.children.length
98
+ }))
99
+ })),
100
+ allRoutes: routes.map(route => route.path)
101
+ };
102
+ }, [groups, stats, routes]);
103
+ const handleToggleGroup = useCallback(groupTitle => {
104
+ setExpandedGroups(prev => {
105
+ const next = new Set(prev);
106
+ if (next.has(groupTitle)) {
107
+ next.delete(groupTitle);
108
+ } else {
109
+ next.add(groupTitle);
110
+ }
111
+ return next;
112
+ });
113
+ }, []);
114
+ const promptForParams = useCallback((route, paramIndex = 0, collectedParams = {}) => {
115
+ const params = route.params;
116
+ if (paramIndex >= params.length) {
117
+ // All parameters collected, build path and navigate
118
+ let finalPath = route.path;
119
+
120
+ // Replace each parameter in the path
121
+ Object.entries(collectedParams).forEach(([param, value]) => {
122
+ if (route.type === "catch-all") {
123
+ // For catch-all routes, replace [...param] with the value
124
+ finalPath = finalPath.replace(`[...${param}]`, value);
125
+ } else {
126
+ // For regular dynamic routes, replace [param] with the value
127
+ finalPath = finalPath.replace(`[${param}]`, value);
128
+ }
129
+ });
130
+
131
+ // Navigate
132
+ try {
133
+ router.push(finalPath);
134
+ } catch (error) {
135
+ Alert.alert("Navigation Error", String(error));
136
+ }
137
+ return;
138
+ }
139
+ const currentParam = params[paramIndex];
140
+ const paramDisplay = route.type === "catch-all" ? `[...${currentParam}]` : `[${currentParam}]`;
141
+ Alert.prompt("Enter Parameter Value", `Enter value for ${paramDisplay}:\n\nRoute: ${route.path}`, [{
142
+ text: "Cancel",
143
+ style: "cancel"
144
+ }, {
145
+ text: paramIndex < params.length - 1 ? "Next" : "Navigate",
146
+ onPress: value => {
147
+ if (value && value.trim()) {
148
+ const newParams = {
149
+ ...collectedParams,
150
+ [currentParam]: value.trim()
151
+ };
152
+ promptForParams(route, paramIndex + 1, newParams);
153
+ } else {
154
+ Alert.alert("Invalid Value", "Please enter a value for the parameter");
155
+ }
156
+ }
157
+ }], "plain-text");
158
+ }, [router]);
159
+ const handleNavigate = useCallback(route => {
160
+ // Gate behind Pro
161
+ if (!isPro) {
162
+ setShowUpgradeModal(true);
163
+ return;
164
+ }
165
+
166
+ // Don't navigate to layouts or groups
167
+ if (route.type === "layout" || route.type === "group") {
168
+ Alert.alert("Cannot Navigate", `${route.type === "layout" ? "Layouts" : "Route groups"} are not navigable routes`);
169
+ return;
170
+ }
171
+
172
+ // For dynamic routes, prompt for parameters
173
+ if (route.type === "dynamic" || route.type === "catch-all") {
174
+ if (route.params.length === 0) {
175
+ // No parameters despite being dynamic? Just navigate
176
+ try {
177
+ router.push(route.path);
178
+ } catch (error) {
179
+ Alert.alert("Navigation Error", String(error));
180
+ }
181
+ return;
182
+ }
183
+
184
+ // Start the parameter prompting flow
185
+ promptForParams(route);
186
+ return;
187
+ }
188
+
189
+ // Navigate to static route
190
+ try {
191
+ router.push(route.path);
192
+ } catch (error) {
193
+ Alert.alert("Navigation Error", String(error));
194
+ }
195
+ }, [router, promptForParams, isPro]);
196
+ const handleManualRefresh = useCallback(() => {
197
+ if (isRefreshing) return;
198
+ setIsRefreshing(true);
199
+ refresh();
200
+ setTimeout(() => setIsRefreshing(false), 400);
201
+ }, [isRefreshing, refresh]);
202
+ const lastRefreshLabel = useMemo(() => {
203
+ if (!lastUpdatedAt) return "Awaiting route data";
204
+ return `Updated ${formatRelativeTime(lastUpdatedAt)}`;
205
+ }, [lastUpdatedAt]);
206
+ const sourceLabel = useMemo(() => {
207
+ if (!source) return "Source: unknown";
208
+ return `Source: ${source}`;
209
+ }, [source]);
210
+
211
+ // When searching, show filtered routes, otherwise show groups
212
+ const displayGroups = useMemo(() => {
213
+ if (searchQuery && filteredRoutes.length > 0) {
214
+ // Create a single "Search Results" group
215
+ return [{
216
+ title: "Search Results",
217
+ icon: "",
218
+ routes: filteredRoutes
219
+ }];
220
+ }
221
+ return groups;
222
+ }, [searchQuery, filteredRoutes, groups]);
223
+ if (!isLoaded) {
224
+ return /*#__PURE__*/_jsx(View, {
225
+ style: [styles.container, style],
226
+ children: /*#__PURE__*/_jsx(View, {
227
+ style: styles.loadingContainer,
228
+ children: /*#__PURE__*/_jsx(Text, {
229
+ style: styles.loadingText,
230
+ children: "Loading routes..."
231
+ })
232
+ })
233
+ });
234
+ }
235
+ return /*#__PURE__*/_jsxs(View, {
236
+ style: [styles.container, style],
237
+ children: [!isSearching ? /*#__PURE__*/_jsxs(View, {
238
+ style: styles.header,
239
+ children: [/*#__PURE__*/_jsxs(View, {
240
+ style: styles.actionsRow,
241
+ children: [/*#__PURE__*/_jsxs(View, {
242
+ style: styles.actionWrapper,
243
+ children: [/*#__PURE__*/_jsx(ToolbarCopyButton, {
244
+ value: copyAllData,
245
+ buttonStyle: styles.actionButtonHeader
246
+ }), /*#__PURE__*/_jsx(Text, {
247
+ style: styles.actionLabel,
248
+ children: "Copy"
249
+ })]
250
+ }), /*#__PURE__*/_jsxs(View, {
251
+ style: styles.actionWrapper,
252
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
253
+ style: [styles.iconButton, isRefreshing && styles.refreshButtonDisabled],
254
+ onPress: handleManualRefresh,
255
+ disabled: isRefreshing,
256
+ children: /*#__PURE__*/_jsx(RefreshCw, {
257
+ size: 16,
258
+ color: isRefreshing ? buoyColors.textMuted : buoyColors.textSecondary
259
+ })
260
+ }), /*#__PURE__*/_jsx(Text, {
261
+ style: styles.actionLabel,
262
+ children: "Refresh"
263
+ })]
264
+ }), /*#__PURE__*/_jsxs(View, {
265
+ style: styles.actionWrapper,
266
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
267
+ style: styles.iconButton,
268
+ onPress: () => setIsSearching(true),
269
+ children: /*#__PURE__*/_jsx(Search, {
270
+ size: 16,
271
+ color: buoyColors.textSecondary
272
+ })
273
+ }), /*#__PURE__*/_jsx(Text, {
274
+ style: styles.actionLabel,
275
+ children: "Search"
276
+ })]
277
+ })]
278
+ }), /*#__PURE__*/_jsxs(View, {
279
+ style: styles.statsGrid,
280
+ children: [/*#__PURE__*/_jsx(StatItem, {
281
+ value: stats.total,
282
+ label: "Total"
283
+ }), /*#__PURE__*/_jsx(StatItem, {
284
+ value: stats.static,
285
+ label: "Static"
286
+ }), /*#__PURE__*/_jsx(StatItem, {
287
+ value: stats.dynamic,
288
+ label: "Dynamic"
289
+ }), /*#__PURE__*/_jsx(StatItem, {
290
+ value: stats.catchAll,
291
+ label: "Catch-All"
292
+ }), /*#__PURE__*/_jsx(StatItem, {
293
+ value: stats.layouts,
294
+ label: "Layouts"
295
+ }), /*#__PURE__*/_jsx(StatItem, {
296
+ value: stats.groups,
297
+ label: "Groups"
298
+ })]
299
+ })]
300
+ }) :
301
+ /*#__PURE__*/
302
+ /* Search mode - full width search bar */
303
+ _jsxs(View, {
304
+ style: styles.searchContainer,
305
+ children: [/*#__PURE__*/_jsx(Search, {
306
+ size: 16,
307
+ color: buoyColors.textMuted
308
+ }), /*#__PURE__*/_jsx(TextInput, {
309
+ style: styles.searchInput,
310
+ placeholder: "Search routes...",
311
+ placeholderTextColor: buoyColors.textMuted,
312
+ value: searchQuery,
313
+ onChangeText: setSearchQuery,
314
+ autoCapitalize: "none",
315
+ autoCorrect: false,
316
+ autoFocus: true
317
+ }), searchQuery.length > 0 && /*#__PURE__*/_jsx(TouchableOpacity, {
318
+ onPress: () => setSearchQuery(""),
319
+ style: styles.clearButton,
320
+ children: /*#__PURE__*/_jsx(Text, {
321
+ style: styles.clearButtonText,
322
+ children: "\u2715"
323
+ })
324
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
325
+ style: styles.closeSearchButton,
326
+ onPress: () => {
327
+ setIsSearching(false);
328
+ setSearchQuery("");
329
+ },
330
+ children: /*#__PURE__*/_jsx(Text, {
331
+ style: styles.closeSearchText,
332
+ children: "Done"
333
+ })
334
+ })]
335
+ }), /*#__PURE__*/_jsxs(ScrollView, {
336
+ style: styles.scrollView,
337
+ contentContainerStyle: styles.scrollContent,
338
+ children: [displayGroups.length === 0 ? /*#__PURE__*/_jsx(View, {
339
+ style: styles.emptyState,
340
+ children: /*#__PURE__*/_jsx(Text, {
341
+ style: styles.emptyText,
342
+ children: "No routes found"
343
+ })
344
+ }) : displayGroups.map(group => /*#__PURE__*/_jsx(RouteGroupView, {
345
+ group: group,
346
+ isExpanded: searchQuery.length > 0 || expandedGroups.has(group.title),
347
+ onToggleExpand: () => handleToggleGroup(group.title),
348
+ onNavigate: handleNavigate
349
+ }, group.title)), /*#__PURE__*/_jsxs(View, {
350
+ style: styles.metaFooter,
351
+ children: [/*#__PURE__*/_jsx(Text, {
352
+ style: styles.metaText,
353
+ children: lastRefreshLabel
354
+ }), /*#__PURE__*/_jsx(View, {
355
+ style: styles.sourceBadge,
356
+ children: /*#__PURE__*/_jsx(Text, {
357
+ style: styles.sourceText,
358
+ children: sourceLabel
359
+ })
360
+ })]
361
+ })]
362
+ }), /*#__PURE__*/_jsx(ProUpgradeModal, {
363
+ visible: showUpgradeModal,
364
+ onClose: () => setShowUpgradeModal(false),
365
+ featureName: "Route Navigation"
366
+ })]
367
+ });
368
+ }
369
+
370
+ // ============================================================================
371
+ // Sub-components
372
+ // ============================================================================
373
+
374
+ function StatItem({
375
+ value,
376
+ label
377
+ }) {
378
+ return /*#__PURE__*/_jsxs(View, {
379
+ style: styles.statItem,
380
+ children: [/*#__PURE__*/_jsx(Text, {
381
+ style: styles.statValue,
382
+ children: value
383
+ }), /*#__PURE__*/_jsx(Text, {
384
+ style: styles.statLabel,
385
+ children: label
386
+ })]
387
+ });
388
+ }
389
+ function RouteGroupView({
390
+ group,
391
+ isExpanded,
392
+ onToggleExpand,
393
+ onNavigate
394
+ }) {
395
+ return /*#__PURE__*/_jsxs(View, {
396
+ style: styles.groupContainer,
397
+ children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
398
+ style: styles.groupHeader,
399
+ onPress: onToggleExpand,
400
+ activeOpacity: 0.7,
401
+ children: [/*#__PURE__*/_jsxs(View, {
402
+ style: styles.groupHeaderLeft,
403
+ children: [isExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
404
+ size: 14,
405
+ color: buoyColors.textSecondary
406
+ }) : /*#__PURE__*/_jsx(ChevronRight, {
407
+ size: 14,
408
+ color: buoyColors.textSecondary
409
+ }), /*#__PURE__*/_jsx(Text, {
410
+ style: styles.groupTitle,
411
+ children: group.title
412
+ })]
413
+ }), /*#__PURE__*/_jsx(View, {
414
+ style: styles.groupBadge,
415
+ children: /*#__PURE__*/_jsx(Text, {
416
+ style: styles.groupCount,
417
+ children: group.routes.length
418
+ })
419
+ })]
420
+ }), isExpanded && /*#__PURE__*/_jsxs(View, {
421
+ style: styles.routesList,
422
+ children: [group.description && /*#__PURE__*/_jsx(View, {
423
+ style: styles.groupDescription,
424
+ children: /*#__PURE__*/_jsx(Text, {
425
+ style: styles.groupDescriptionText,
426
+ children: group.description
427
+ })
428
+ }), group.routes.map((route, index) => /*#__PURE__*/_jsx(RouteItemView, {
429
+ route: route,
430
+ onNavigate: onNavigate
431
+ }, `${route.path}-${index}`))]
432
+ })]
433
+ });
434
+ }
435
+ function RouteItemView({
436
+ route,
437
+ depth = 0,
438
+ onNavigate
439
+ }) {
440
+ const [isExpanded, setIsExpanded] = useState(false);
441
+ const hasChildren = route.children.length > 0;
442
+ const hasParams = route.params.length > 0;
443
+ const typeColor = getRouteTypeColor(route.type);
444
+ const canNavigate = route.type !== "layout" && route.type !== "group";
445
+ return /*#__PURE__*/_jsxs(View, {
446
+ style: [styles.routeItem, {
447
+ marginLeft: depth * 12
448
+ }],
449
+ children: [/*#__PURE__*/_jsxs(View, {
450
+ style: styles.routeCard,
451
+ children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
452
+ style: styles.routeHeaderLeft,
453
+ onPress: () => setIsExpanded(!isExpanded),
454
+ activeOpacity: 0.7,
455
+ children: [/*#__PURE__*/_jsx(View, {
456
+ style: styles.expandIndicator,
457
+ children: isExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
458
+ size: 14,
459
+ color: buoyColors.textSecondary
460
+ }) : /*#__PURE__*/_jsx(ChevronRight, {
461
+ size: 14,
462
+ color: buoyColors.textSecondary
463
+ })
464
+ }), /*#__PURE__*/_jsx(Text, {
465
+ style: styles.routePath,
466
+ numberOfLines: 1,
467
+ children: route.path
468
+ })]
469
+ }), /*#__PURE__*/_jsxs(View, {
470
+ style: styles.routeHeaderActions,
471
+ children: [hasChildren && /*#__PURE__*/_jsx(View, {
472
+ style: styles.childCountBadge,
473
+ children: /*#__PURE__*/_jsx(Text, {
474
+ style: styles.childCountText,
475
+ children: route.children.length
476
+ })
477
+ }), /*#__PURE__*/_jsx(View, {
478
+ style: [styles.typeTag, {
479
+ backgroundColor: `${typeColor}15`,
480
+ borderColor: `${typeColor}40`
481
+ }],
482
+ children: /*#__PURE__*/_jsx(Text, {
483
+ style: [styles.typeText, {
484
+ color: typeColor
485
+ }],
486
+ children: route.type.toUpperCase()
487
+ })
488
+ })]
489
+ })]
490
+ }), isExpanded && /*#__PURE__*/_jsxs(View, {
491
+ style: styles.routeDetails,
492
+ children: [/*#__PURE__*/_jsxs(View, {
493
+ style: styles.routeButtons,
494
+ children: [/*#__PURE__*/_jsx(InlineCopyButton, {
495
+ value: route.path,
496
+ buttonStyle: styles.actionButton
497
+ }), canNavigate && /*#__PURE__*/_jsx(TouchableOpacity, {
498
+ style: [styles.actionButton, styles.navigateButton],
499
+ onPress: () => onNavigate(route),
500
+ children: /*#__PURE__*/_jsx(Text, {
501
+ style: styles.navigateButtonText,
502
+ children: "Go"
503
+ })
504
+ })]
505
+ }), hasParams && /*#__PURE__*/_jsxs(View, {
506
+ style: styles.paramsContainer,
507
+ children: [/*#__PURE__*/_jsx(Text, {
508
+ style: styles.paramsLabel,
509
+ children: "Parameters:"
510
+ }), /*#__PURE__*/_jsx(View, {
511
+ style: styles.paramsRow,
512
+ children: route.params.map(param => /*#__PURE__*/_jsx(View, {
513
+ style: styles.paramTag,
514
+ children: /*#__PURE__*/_jsx(Text, {
515
+ style: styles.paramText,
516
+ children: param
517
+ })
518
+ }, param))
519
+ })]
520
+ }), hasChildren && /*#__PURE__*/_jsx(View, {
521
+ style: styles.childrenContainer,
522
+ children: route.children.map((child, index) => /*#__PURE__*/_jsx(RouteItemView, {
523
+ route: child,
524
+ depth: depth + 1,
525
+ onNavigate: onNavigate
526
+ }, `${child.path}-${index}`))
527
+ })]
528
+ })]
529
+ });
530
+ }
531
+
532
+ // ============================================================================
533
+ // Helpers
534
+ // ============================================================================
535
+
536
+ function getRouteTypeColor(type) {
537
+ switch (type) {
538
+ case "static":
539
+ return "#3B82F6";
540
+ // Blue
541
+ case "dynamic":
542
+ return "#F59E0B";
543
+ // Orange
544
+ case "catch-all":
545
+ return "#EC4899";
546
+ // Pink
547
+ case "index":
548
+ return "#10B981";
549
+ // Green
550
+ case "layout":
551
+ return "#8B5CF6";
552
+ // Purple
553
+ case "group":
554
+ return "#6366F1";
555
+ // Indigo
556
+ case "not-found":
557
+ return "#EF4444";
558
+ // Red
559
+ default:
560
+ return "#6B7280";
561
+ // Gray
562
+ }
563
+ }
564
+
565
+ // ============================================================================
566
+ // Styles
567
+ // ============================================================================
568
+
569
+ const styles = StyleSheet.create({
570
+ container: {
571
+ flex: 1,
572
+ backgroundColor: buoyColors.base
573
+ },
574
+ loadingContainer: {
575
+ flex: 1,
576
+ justifyContent: "center",
577
+ alignItems: "center"
578
+ },
579
+ loadingText: {
580
+ color: buoyColors.textSecondary,
581
+ fontSize: 14,
582
+ fontFamily: "monospace"
583
+ },
584
+ header: {
585
+ flexDirection: "column",
586
+ padding: 8,
587
+ borderBottomWidth: 1,
588
+ borderBottomColor: buoyColors.border,
589
+ gap: 12
590
+ },
591
+ actionsRow: {
592
+ flexDirection: "row",
593
+ alignItems: "center",
594
+ justifyContent: "flex-end",
595
+ gap: 12,
596
+ paddingBottom: 4
597
+ },
598
+ statsGrid: {
599
+ flexDirection: "row",
600
+ flexWrap: "wrap",
601
+ gap: 8
602
+ },
603
+ statItem: {
604
+ flexGrow: 1,
605
+ flexShrink: 1,
606
+ flexBasis: "30%",
607
+ minWidth: 90,
608
+ backgroundColor: buoyColors.card,
609
+ borderRadius: 8,
610
+ paddingVertical: 10,
611
+ paddingHorizontal: 12,
612
+ alignItems: "center"
613
+ },
614
+ statValue: {
615
+ fontSize: 18,
616
+ fontWeight: "800",
617
+ color: buoyColors.text,
618
+ fontFamily: "monospace"
619
+ },
620
+ statLabel: {
621
+ fontSize: 10,
622
+ color: buoyColors.textMuted,
623
+ marginTop: 4,
624
+ fontFamily: "monospace",
625
+ textTransform: "uppercase",
626
+ letterSpacing: 0.5
627
+ },
628
+ actionWrapper: {
629
+ alignItems: "center",
630
+ justifyContent: "center",
631
+ gap: 4,
632
+ minWidth: 48
633
+ },
634
+ actionButtonHeader: {
635
+ padding: 6,
636
+ borderRadius: 4,
637
+ backgroundColor: buoyColors.input
638
+ },
639
+ iconButton: {
640
+ padding: 6,
641
+ borderRadius: 4,
642
+ alignItems: "center",
643
+ justifyContent: "center"
644
+ },
645
+ actionLabel: {
646
+ fontSize: 8,
647
+ color: buoyColors.textMuted,
648
+ fontFamily: "monospace",
649
+ textTransform: "uppercase",
650
+ letterSpacing: 0.5
651
+ },
652
+ refreshButtonDisabled: {
653
+ opacity: 0.5
654
+ },
655
+ metaFooter: {
656
+ flexDirection: "row",
657
+ alignItems: "center",
658
+ justifyContent: "space-between",
659
+ gap: 8,
660
+ paddingHorizontal: 16,
661
+ paddingVertical: 16,
662
+ marginTop: 16,
663
+ borderTopWidth: 1,
664
+ borderTopColor: buoyColors.border
665
+ },
666
+ metaText: {
667
+ fontSize: 12,
668
+ color: buoyColors.textSecondary,
669
+ fontFamily: "monospace"
670
+ },
671
+ sourceBadge: {
672
+ paddingHorizontal: 8,
673
+ paddingVertical: 2,
674
+ borderRadius: 999,
675
+ borderWidth: 1,
676
+ borderColor: buoyColors.border,
677
+ backgroundColor: buoyColors.card
678
+ },
679
+ sourceText: {
680
+ fontSize: 11,
681
+ color: buoyColors.textSecondary,
682
+ textTransform: "capitalize",
683
+ fontFamily: "monospace"
684
+ },
685
+ searchContainer: {
686
+ flexDirection: "row",
687
+ alignItems: "center",
688
+ padding: 8,
689
+ gap: 8,
690
+ borderBottomWidth: 1,
691
+ borderBottomColor: buoyColors.border
692
+ },
693
+ searchInput: {
694
+ flex: 1,
695
+ color: buoyColors.text,
696
+ fontSize: 14,
697
+ fontFamily: "monospace"
698
+ },
699
+ clearButton: {
700
+ padding: 4
701
+ },
702
+ clearButtonText: {
703
+ color: buoyColors.textMuted,
704
+ fontSize: 18
705
+ },
706
+ closeSearchButton: {
707
+ paddingHorizontal: 8,
708
+ paddingVertical: 4
709
+ },
710
+ closeSearchText: {
711
+ color: buoyColors.primary,
712
+ fontSize: 14,
713
+ fontWeight: "600",
714
+ fontFamily: "monospace"
715
+ },
716
+ scrollView: {
717
+ flex: 1
718
+ },
719
+ scrollContent: {
720
+ paddingVertical: 8
721
+ },
722
+ emptyState: {
723
+ padding: 32,
724
+ alignItems: "center"
725
+ },
726
+ emptyText: {
727
+ color: buoyColors.textSecondary,
728
+ fontSize: 14,
729
+ fontFamily: "monospace"
730
+ },
731
+ groupContainer: {
732
+ marginVertical: 4
733
+ },
734
+ groupHeader: {
735
+ flexDirection: "row",
736
+ alignItems: "center",
737
+ justifyContent: "space-between",
738
+ padding: 12,
739
+ paddingHorizontal: 16,
740
+ backgroundColor: buoyColors.card,
741
+ borderLeftWidth: 3,
742
+ borderLeftColor: buoyColors.textSecondary
743
+ },
744
+ groupHeaderLeft: {
745
+ flexDirection: "row",
746
+ alignItems: "center",
747
+ gap: 8,
748
+ flex: 1
749
+ },
750
+ groupTitle: {
751
+ fontSize: 12,
752
+ fontWeight: "700",
753
+ color: buoyColors.text,
754
+ fontFamily: "monospace",
755
+ textTransform: "uppercase",
756
+ letterSpacing: 0.8
757
+ },
758
+ groupBadge: {
759
+ backgroundColor: "#3B82F615",
760
+ borderColor: "#3B82F640",
761
+ borderWidth: 1,
762
+ borderRadius: 12,
763
+ paddingHorizontal: 8,
764
+ paddingVertical: 3,
765
+ minWidth: 24,
766
+ alignItems: "center",
767
+ justifyContent: "center"
768
+ },
769
+ groupCount: {
770
+ fontSize: 11,
771
+ fontWeight: "600",
772
+ color: "#3B82F6",
773
+ fontFamily: "monospace"
774
+ },
775
+ routesList: {
776
+ backgroundColor: buoyColors.base
777
+ },
778
+ groupDescription: {
779
+ paddingHorizontal: 16,
780
+ paddingVertical: 8,
781
+ marginBottom: 8,
782
+ backgroundColor: buoyColors.input,
783
+ borderLeftWidth: 3,
784
+ borderLeftColor: buoyColors.textSecondary
785
+ },
786
+ groupDescriptionText: {
787
+ fontSize: 11,
788
+ color: buoyColors.textSecondary,
789
+ fontFamily: "monospace",
790
+ lineHeight: 16
791
+ },
792
+ routeItem: {
793
+ marginBottom: 6
794
+ },
795
+ routeCard: {
796
+ flexDirection: "row",
797
+ alignItems: "center",
798
+ justifyContent: "space-between",
799
+ paddingVertical: 10,
800
+ paddingHorizontal: 12,
801
+ backgroundColor: buoyColors.card,
802
+ borderRadius: 6,
803
+ borderWidth: 1,
804
+ borderColor: buoyColors.border,
805
+ shadowColor: "#000",
806
+ shadowOffset: {
807
+ width: 0,
808
+ height: 1
809
+ },
810
+ shadowOpacity: 0.05,
811
+ shadowRadius: 2,
812
+ elevation: 1
813
+ },
814
+ expandIndicator: {
815
+ width: 20,
816
+ alignItems: "center"
817
+ },
818
+ routeHeaderLeft: {
819
+ flexDirection: "row",
820
+ alignItems: "center",
821
+ gap: 8,
822
+ flex: 1
823
+ },
824
+ routePath: {
825
+ fontSize: 13,
826
+ fontWeight: "600",
827
+ color: buoyColors.text,
828
+ fontFamily: "monospace",
829
+ flex: 1
830
+ },
831
+ routeHeaderActions: {
832
+ flexDirection: "row",
833
+ alignItems: "center",
834
+ gap: 6
835
+ },
836
+ childCountBadge: {
837
+ backgroundColor: "#6B728015",
838
+ borderColor: "#6B728040",
839
+ borderWidth: 1,
840
+ borderRadius: 12,
841
+ paddingHorizontal: 7,
842
+ paddingVertical: 2,
843
+ minWidth: 22,
844
+ alignItems: "center",
845
+ justifyContent: "center"
846
+ },
847
+ childCountText: {
848
+ fontSize: 11,
849
+ fontWeight: "600",
850
+ color: "#6B7280",
851
+ fontFamily: "monospace"
852
+ },
853
+ typeTag: {
854
+ borderRadius: 4,
855
+ paddingHorizontal: 6,
856
+ paddingVertical: 2,
857
+ borderWidth: 1
858
+ },
859
+ typeText: {
860
+ fontSize: 10,
861
+ fontWeight: "600",
862
+ fontFamily: "monospace"
863
+ },
864
+ routeDetails: {
865
+ paddingHorizontal: 12,
866
+ paddingTop: 8,
867
+ paddingBottom: 12,
868
+ gap: 12,
869
+ borderTopWidth: 1,
870
+ borderTopColor: buoyColors.border,
871
+ backgroundColor: buoyColors.card,
872
+ borderRadius: 6,
873
+ borderWidth: 1,
874
+ borderColor: buoyColors.border,
875
+ borderTopLeftRadius: 0,
876
+ borderTopRightRadius: 0,
877
+ marginTop: -6
878
+ },
879
+ paramsContainer: {
880
+ gap: 6
881
+ },
882
+ paramsLabel: {
883
+ fontSize: 10,
884
+ color: buoyColors.textSecondary,
885
+ fontFamily: "monospace",
886
+ marginBottom: 4
887
+ },
888
+ paramsRow: {
889
+ flexDirection: "row",
890
+ flexWrap: "wrap",
891
+ gap: 6
892
+ },
893
+ paramTag: {
894
+ backgroundColor: "#F59E0B15",
895
+ borderColor: "#F59E0B40",
896
+ borderWidth: 1,
897
+ borderRadius: 4,
898
+ paddingHorizontal: 6,
899
+ paddingVertical: 2
900
+ },
901
+ paramText: {
902
+ fontSize: 10,
903
+ color: "#F59E0B",
904
+ fontFamily: "monospace",
905
+ fontWeight: "600"
906
+ },
907
+ routeButtons: {
908
+ flexDirection: "row",
909
+ gap: 6,
910
+ marginBottom: 12
911
+ },
912
+ actionButton: {
913
+ flexDirection: "row",
914
+ alignItems: "center",
915
+ gap: 4,
916
+ backgroundColor: buoyColors.input,
917
+ borderRadius: 4,
918
+ paddingHorizontal: 12,
919
+ paddingVertical: 6,
920
+ borderWidth: 1,
921
+ borderColor: buoyColors.border
922
+ },
923
+ actionButtonText: {
924
+ fontSize: 12,
925
+ color: buoyColors.textSecondary,
926
+ fontFamily: "monospace",
927
+ fontWeight: "600"
928
+ },
929
+ navigateButton: {
930
+ backgroundColor: buoyColors.primary + "15",
931
+ borderColor: buoyColors.primary + "40"
932
+ },
933
+ navigateButtonText: {
934
+ fontSize: 12,
935
+ color: buoyColors.primary,
936
+ fontFamily: "monospace",
937
+ fontWeight: "600"
938
+ },
939
+ childrenContainer: {
940
+ borderLeftWidth: 2,
941
+ borderLeftColor: buoyColors.border,
942
+ marginLeft: 16
943
+ }
944
+ });