@idealyst/navigation 1.0.99 → 1.1.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/package.json +6 -6
- package/src/context/NavigatorContext.native.tsx +19 -1
- package/src/context/NavigatorContext.web.tsx +47 -1
- package/src/context/types.ts +12 -0
- package/src/examples/ExampleNavigationRouter.tsx +83 -0
- package/src/index.web.ts +6 -0
- package/src/routing/router.native.tsx +13 -1
- package/src/routing/router.web.tsx +79 -16
- package/src/routing/types.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/navigation",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Cross-platform navigation library for React and React Native",
|
|
5
5
|
"readme": "README.md",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"publish:npm": "npm publish"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@idealyst/components": "^1.
|
|
47
|
-
"@idealyst/theme": "^1.
|
|
46
|
+
"@idealyst/components": "^1.1.1",
|
|
47
|
+
"@idealyst/theme": "^1.1.1",
|
|
48
48
|
"@react-navigation/bottom-tabs": ">=7.0.0",
|
|
49
49
|
"@react-navigation/drawer": ">=7.0.0",
|
|
50
50
|
"@react-navigation/native": ">=7.0.0",
|
|
@@ -60,10 +60,10 @@
|
|
|
60
60
|
"react-router-dom": ">=6.0.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@idealyst/components": "^1.
|
|
63
|
+
"@idealyst/components": "^1.1.1",
|
|
64
64
|
"@idealyst/datagrid": "^1.0.93",
|
|
65
65
|
"@idealyst/datepicker": "^1.0.93",
|
|
66
|
-
"@idealyst/theme": "^1.
|
|
66
|
+
"@idealyst/theme": "^1.1.1",
|
|
67
67
|
"@types/react": "^19.1.8",
|
|
68
68
|
"@types/react-dom": "^19.1.6",
|
|
69
69
|
"react": "^19.1.0",
|
|
@@ -86,4 +86,4 @@
|
|
|
86
86
|
"cross-platform",
|
|
87
87
|
"router"
|
|
88
88
|
]
|
|
89
|
-
}
|
|
89
|
+
}
|
|
@@ -217,10 +217,20 @@ const UnwrappedNavigatorProvider = ({ route }: NavigatorProviderProps) => {
|
|
|
217
217
|
return memo(buildNavigator(route));
|
|
218
218
|
}, [route]);
|
|
219
219
|
|
|
220
|
+
const canGoBack = () => navigation.canGoBack();
|
|
221
|
+
|
|
222
|
+
const goBack = () => {
|
|
223
|
+
if (navigation.canGoBack()) {
|
|
224
|
+
navigation.goBack();
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
220
228
|
return (
|
|
221
229
|
<NavigatorContext.Provider value={{
|
|
222
230
|
route,
|
|
223
231
|
navigate,
|
|
232
|
+
canGoBack,
|
|
233
|
+
goBack,
|
|
224
234
|
}}>
|
|
225
235
|
<RouteComponent />
|
|
226
236
|
</NavigatorContext.Provider>
|
|
@@ -294,8 +304,16 @@ const DrawerNavigatorProvider = ({ navigation, route, children }: { navigation:
|
|
|
294
304
|
}
|
|
295
305
|
};
|
|
296
306
|
|
|
307
|
+
const canGoBack = () => navigation.canGoBack();
|
|
308
|
+
|
|
309
|
+
const goBack = () => {
|
|
310
|
+
if (navigation.canGoBack()) {
|
|
311
|
+
navigation.goBack();
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
297
315
|
return (
|
|
298
|
-
<DrawerNavigatorContext.Provider value={{ navigate, route }}>
|
|
316
|
+
<DrawerNavigatorContext.Provider value={{ navigate, route, canGoBack, goBack }}>
|
|
299
317
|
{children}
|
|
300
318
|
</DrawerNavigatorContext.Provider>
|
|
301
319
|
);
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React, { createContext, memo, useContext, useMemo } from 'react';
|
|
2
|
-
import { useNavigate, useParams } from '../router';
|
|
2
|
+
import { useNavigate, useParams, useLocation } from '../router';
|
|
3
3
|
import { NavigateParams, NavigatorProviderProps, NavigatorContextValue } from './types';
|
|
4
4
|
import { buildNavigator, NavigatorParam } from '../routing';
|
|
5
5
|
|
|
6
6
|
const NavigatorContext = createContext<NavigatorContextValue>({
|
|
7
7
|
navigate: () => {},
|
|
8
8
|
route: undefined,
|
|
9
|
+
canGoBack: () => false,
|
|
10
|
+
goBack: () => {},
|
|
9
11
|
});
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -171,10 +173,37 @@ function findInvalidRouteHandler(
|
|
|
171
173
|
return handlers.length > 0 ? handlers[handlers.length - 1] : undefined;
|
|
172
174
|
}
|
|
173
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Get the parent path from a given path.
|
|
178
|
+
* e.g., "/users/123/edit" -> "/users/123"
|
|
179
|
+
* "/users" -> "/"
|
|
180
|
+
* "/" -> null (no parent)
|
|
181
|
+
*/
|
|
182
|
+
function getParentPath(path: string): string | null {
|
|
183
|
+
const normalizedPath = path === '' ? '/' : path;
|
|
184
|
+
|
|
185
|
+
if (normalizedPath === '/') {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const segments = normalizedPath.split('/').filter(Boolean);
|
|
190
|
+
|
|
191
|
+
if (segments.length === 0) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (segments.length === 1) {
|
|
196
|
+
return '/';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return '/' + segments.slice(0, -1).join('/');
|
|
200
|
+
}
|
|
201
|
+
|
|
174
202
|
export const NavigatorProvider = ({
|
|
175
203
|
route,
|
|
176
204
|
}: NavigatorProviderProps) => {
|
|
177
205
|
const reactRouterNavigate = useNavigate();
|
|
206
|
+
const location = useLocation();
|
|
178
207
|
|
|
179
208
|
// Memoize the list of valid route patterns
|
|
180
209
|
const validPatterns = useMemo(() => buildValidPatterns(route), [route]);
|
|
@@ -219,10 +248,27 @@ export const NavigatorProvider = ({
|
|
|
219
248
|
return memo(buildNavigator(route));
|
|
220
249
|
}, [route]);
|
|
221
250
|
|
|
251
|
+
const canGoBack = () => {
|
|
252
|
+
const parentPath = getParentPath(location.pathname);
|
|
253
|
+
if (!parentPath) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
return isValidRoute(parentPath, validPatterns);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const goBack = () => {
|
|
260
|
+
const parentPath = getParentPath(location.pathname);
|
|
261
|
+
if (parentPath && isValidRoute(parentPath, validPatterns)) {
|
|
262
|
+
reactRouterNavigate(parentPath);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
222
266
|
return (
|
|
223
267
|
<NavigatorContext.Provider value={{
|
|
224
268
|
route,
|
|
225
269
|
navigate: navigateFunction,
|
|
270
|
+
canGoBack,
|
|
271
|
+
goBack,
|
|
226
272
|
}}>
|
|
227
273
|
<RouteComponent />
|
|
228
274
|
</NavigatorContext.Provider>
|
package/src/context/types.ts
CHANGED
|
@@ -24,4 +24,16 @@ export type NavigatorProviderProps = {
|
|
|
24
24
|
export type NavigatorContextValue = {
|
|
25
25
|
route: NavigatorParam | undefined;
|
|
26
26
|
navigate: (params: NavigateParams) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Returns true if there is a parent route in the route hierarchy to navigate back to.
|
|
29
|
+
* On web, this checks for parent routes (not browser history).
|
|
30
|
+
* On native, this uses react-navigation's canGoBack().
|
|
31
|
+
*/
|
|
32
|
+
canGoBack: () => boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Navigate back to the parent route in the route hierarchy.
|
|
35
|
+
* On web, this navigates to the parent path (e.g., /users/123 -> /users).
|
|
36
|
+
* On native, this uses react-navigation's goBack().
|
|
37
|
+
*/
|
|
38
|
+
goBack: () => void;
|
|
27
39
|
};
|
|
@@ -84,6 +84,66 @@ const SettingsNotFoundScreen = ({ path, params }: NotFoundComponentProps) => {
|
|
|
84
84
|
);
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* FullScreen Example - demonstrates a screen that renders without parent layout
|
|
89
|
+
* This screen will NOT have the sidebar, header, or any parent navigator chrome
|
|
90
|
+
*/
|
|
91
|
+
const FullScreenExample = () => {
|
|
92
|
+
const { navigate } = useNavigator();
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Screen>
|
|
96
|
+
<View
|
|
97
|
+
spacing="lg"
|
|
98
|
+
padding={12}
|
|
99
|
+
style={{
|
|
100
|
+
flex: 1,
|
|
101
|
+
alignItems: 'center',
|
|
102
|
+
justifyContent: 'center',
|
|
103
|
+
backgroundColor: '#1a1a2e',
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
<Icon name="fullscreen" size={80} color="white" />
|
|
107
|
+
<Text size="xxl" weight="bold" style={{ color: 'white', marginTop: 24 }}>
|
|
108
|
+
Full Screen Mode
|
|
109
|
+
</Text>
|
|
110
|
+
<Text size="md" style={{ color: '#aaa', textAlign: 'center', maxWidth: 400, marginTop: 8 }}>
|
|
111
|
+
This screen renders outside of the parent layout. Notice there's no sidebar,
|
|
112
|
+
header, or any navigation chrome from the parent navigator.
|
|
113
|
+
</Text>
|
|
114
|
+
<Card style={{ marginTop: 32, padding: 24, maxWidth: 500 }}>
|
|
115
|
+
<View spacing="md">
|
|
116
|
+
<Text size="lg" weight="semibold">How it works</Text>
|
|
117
|
+
<Text size="sm" color="secondary">
|
|
118
|
+
Add <Text weight="semibold">fullScreen: true</Text> to the screen's options:
|
|
119
|
+
</Text>
|
|
120
|
+
<View style={{ backgroundColor: '#f5f5f5', padding: 12, borderRadius: 8, marginTop: 8 }}>
|
|
121
|
+
<Text size="sm" style={{ fontFamily: 'monospace' }}>
|
|
122
|
+
{`{
|
|
123
|
+
path: "fullscreen-demo",
|
|
124
|
+
type: 'screen',
|
|
125
|
+
component: FullScreenExample,
|
|
126
|
+
options: { fullScreen: true }
|
|
127
|
+
}`}
|
|
128
|
+
</Text>
|
|
129
|
+
</View>
|
|
130
|
+
<Text size="sm" color="secondary" style={{ marginTop: 8 }}>
|
|
131
|
+
Use cases: Onboarding flows, media viewers, immersive experiences, modal-like screens.
|
|
132
|
+
</Text>
|
|
133
|
+
</View>
|
|
134
|
+
</Card>
|
|
135
|
+
<Button
|
|
136
|
+
style={{ marginTop: 32 }}
|
|
137
|
+
intent="primary"
|
|
138
|
+
onPress={() => navigate({ path: '/', replace: false })}
|
|
139
|
+
>
|
|
140
|
+
Back to Home
|
|
141
|
+
</Button>
|
|
142
|
+
</View>
|
|
143
|
+
</Screen>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
87
147
|
// Settings section screens
|
|
88
148
|
const SettingsHomeScreen = () => (
|
|
89
149
|
<Screen>
|
|
@@ -149,6 +209,8 @@ const SettingsNavigator: NavigatorParam = {
|
|
|
149
209
|
};
|
|
150
210
|
|
|
151
211
|
const HomeScreen = () => {
|
|
212
|
+
const { navigate } = useNavigator();
|
|
213
|
+
|
|
152
214
|
return (
|
|
153
215
|
<Screen>
|
|
154
216
|
<View spacing='lg' padding={12}>
|
|
@@ -194,6 +256,25 @@ const HomeScreen = () => {
|
|
|
194
256
|
</Text>
|
|
195
257
|
</View>
|
|
196
258
|
</Card>
|
|
259
|
+
|
|
260
|
+
<Card>
|
|
261
|
+
<View spacing="md" style={{ padding: 16 }}>
|
|
262
|
+
<Text size="lg" weight="semibold">
|
|
263
|
+
Full Screen Mode
|
|
264
|
+
</Text>
|
|
265
|
+
<Text>
|
|
266
|
+
Screens can opt-out of parent layout inheritance using the fullScreen option.
|
|
267
|
+
This is useful for onboarding flows, media viewers, or immersive experiences.
|
|
268
|
+
</Text>
|
|
269
|
+
<Button
|
|
270
|
+
style={{ marginTop: 12 }}
|
|
271
|
+
type="outlined"
|
|
272
|
+
onPress={() => navigate({ path: '/fullscreen-demo' })}
|
|
273
|
+
>
|
|
274
|
+
Try Full Screen Demo
|
|
275
|
+
</Button>
|
|
276
|
+
</View>
|
|
277
|
+
</Card>
|
|
197
278
|
</View>
|
|
198
279
|
</Screen>
|
|
199
280
|
)
|
|
@@ -211,6 +292,8 @@ const ExampleNavigationRouter: NavigatorParam = {
|
|
|
211
292
|
},
|
|
212
293
|
routes: [
|
|
213
294
|
{ path: "/", type: 'screen', component: HomeScreen, options: { title: "Home" } },
|
|
295
|
+
// FullScreen demo - renders without parent layout (no sidebar/header)
|
|
296
|
+
{ path: "fullscreen-demo", type: 'screen', component: FullScreenExample, options: { title: "Full Screen Demo", fullScreen: true } },
|
|
214
297
|
// Nested settings navigator with its own 404 handling
|
|
215
298
|
SettingsNavigator,
|
|
216
299
|
{ path: "avatar", type: 'screen', component: AvatarExamples, options: { title: "Avatar" } },
|
package/src/index.web.ts
CHANGED
|
@@ -3,3 +3,9 @@ export * from './index';
|
|
|
3
3
|
|
|
4
4
|
// Direct export to fix module resolution
|
|
5
5
|
export { NavigatorProvider, useNavigator } from './context/NavigatorContext.web';
|
|
6
|
+
|
|
7
|
+
// [DEVCONTAINER FIX] Explicit hook exports to fix Vite re-export resolution
|
|
8
|
+
// When Vite processes `export * from './index'` -> `export * from './hooks'`,
|
|
9
|
+
// it doesn't apply .web extension priority for nested re-exports.
|
|
10
|
+
// This explicit export ensures useParams is available from @idealyst/navigation.
|
|
11
|
+
export { useParams } from './hooks/useParams.web';
|
|
@@ -213,12 +213,24 @@ const buildScreen = (params: RouteParam, Navigator: TypedNavigator, parentPath =
|
|
|
213
213
|
component = buildNavigator(params, fullPath);
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
// Build screen options, adding fullScreen presentation if specified
|
|
217
|
+
let screenOptions = params.options;
|
|
218
|
+
if (params.type === 'screen' && params.options?.fullScreen) {
|
|
219
|
+
screenOptions = {
|
|
220
|
+
...params.options,
|
|
221
|
+
// Use fullScreenModal presentation to bypass parent navigator chrome
|
|
222
|
+
presentation: 'fullScreenModal',
|
|
223
|
+
// Hide header for true fullScreen experience
|
|
224
|
+
headerShown: params.options.headerShown ?? false,
|
|
225
|
+
} as ScreenOptions;
|
|
226
|
+
}
|
|
227
|
+
|
|
216
228
|
return (
|
|
217
229
|
<Navigator.Screen
|
|
218
230
|
key={`${fullPath}-${index}`}
|
|
219
231
|
name={fullPath}
|
|
220
232
|
component={component}
|
|
221
|
-
options={
|
|
233
|
+
options={screenOptions}
|
|
222
234
|
/>
|
|
223
235
|
)
|
|
224
236
|
}
|
|
@@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react'
|
|
|
2
2
|
import { Routes, Route, useLocation, useParams } from '../router'
|
|
3
3
|
import { DefaultStackLayout } from '../layouts/DefaultStackLayout'
|
|
4
4
|
import { DefaultTabLayout } from '../layouts/DefaultTabLayout'
|
|
5
|
-
import { NavigatorParam, RouteParam, NotFoundComponentProps } from './types'
|
|
5
|
+
import { NavigatorParam, RouteParam, ScreenParam, NotFoundComponentProps } from './types'
|
|
6
6
|
import { NavigateParams } from '../context/types'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -98,30 +98,55 @@ const NotFoundWrapper = ({
|
|
|
98
98
|
export const buildNavigator = (params: NavigatorParam, parentPath = '') => {
|
|
99
99
|
return () => (
|
|
100
100
|
<Routes>
|
|
101
|
-
{buildRoute(params, 0, false)}
|
|
101
|
+
{buildRoute(params, 0, false, parentPath)}
|
|
102
102
|
</Routes>
|
|
103
103
|
)
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Helper to compute full path by joining parent and child paths
|
|
109
|
+
*/
|
|
110
|
+
const joinPaths = (parentPath: string, childPath: string): string => {
|
|
111
|
+
// Remove trailing slash from parent
|
|
112
|
+
const normalizedParent = parentPath.endsWith('/') ? parentPath.slice(0, -1) : parentPath;
|
|
113
|
+
// Remove leading slash from child
|
|
114
|
+
const normalizedChild = childPath.startsWith('/') ? childPath.slice(1) : childPath;
|
|
115
|
+
|
|
116
|
+
if (!normalizedParent || normalizedParent === '') {
|
|
117
|
+
return normalizedChild ? `/${normalizedChild}` : '/';
|
|
118
|
+
}
|
|
119
|
+
if (!normalizedChild || normalizedChild === '') {
|
|
120
|
+
return normalizedParent;
|
|
121
|
+
}
|
|
122
|
+
return `${normalizedParent}/${normalizedChild}`;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if a screen has fullScreen option enabled
|
|
127
|
+
*/
|
|
128
|
+
const isFullScreenRoute = (route: RouteParam): route is ScreenParam => {
|
|
129
|
+
return route.type === 'screen' && route.options?.fullScreen === true;
|
|
130
|
+
};
|
|
131
|
+
|
|
107
132
|
/**
|
|
108
133
|
* Build Route - handles both screens and nested navigators
|
|
109
134
|
* @param params Route configuration
|
|
110
135
|
* @param index Route index for key generation
|
|
111
136
|
* @param isNested Whether this is a nested route (should strip leading slash)
|
|
112
|
-
* @
|
|
137
|
+
* @param parentPath Full parent path for computing fullScreen route paths
|
|
138
|
+
* @returns React Router Route element or array of elements
|
|
113
139
|
*/
|
|
114
|
-
const buildRoute = (params: RouteParam, index: number, isNested = false) => {
|
|
140
|
+
const buildRoute = (params: RouteParam, index: number, isNested = false, parentPath = ''): React.ReactNode => {
|
|
115
141
|
// For nested routes, strip leading slash to make path relative
|
|
116
|
-
const routePath = isNested && params.path.startsWith('/')
|
|
117
|
-
? params.path.slice(1)
|
|
142
|
+
const routePath = isNested && params.path.startsWith('/')
|
|
143
|
+
? params.path.slice(1)
|
|
118
144
|
: params.path;
|
|
119
|
-
|
|
145
|
+
|
|
120
146
|
if (params.type === 'screen') {
|
|
121
|
-
|
|
122
147
|
// If the route path is empty (root route in navigator), make it an index route
|
|
123
148
|
const routeProps = routePath === '' ? { index: true } : { path: routePath };
|
|
124
|
-
|
|
149
|
+
|
|
125
150
|
return (
|
|
126
151
|
<Route
|
|
127
152
|
key={`${params.path}-${index}`}
|
|
@@ -134,19 +159,28 @@ const buildRoute = (params: RouteParam, index: number, isNested = false) => {
|
|
|
134
159
|
const LayoutComponent = params.layoutComponent ||
|
|
135
160
|
(params.layout === 'tab' ? DefaultTabLayout : DefaultStackLayout);
|
|
136
161
|
|
|
137
|
-
//
|
|
138
|
-
const
|
|
162
|
+
// Compute the full path for this navigator
|
|
163
|
+
const navigatorFullPath = joinPaths(parentPath, params.path);
|
|
164
|
+
|
|
165
|
+
// Separate fullScreen routes from regular routes
|
|
166
|
+
const regularRoutes = params.routes.filter(route => !isFullScreenRoute(route));
|
|
167
|
+
const fullScreenRoutes = params.routes.filter(isFullScreenRoute);
|
|
168
|
+
|
|
169
|
+
// Transform routes to include full paths for layout component (only non-fullScreen)
|
|
170
|
+
const routesWithFullPaths = regularRoutes.map(route => ({
|
|
139
171
|
...route,
|
|
140
172
|
fullPath: route.path
|
|
141
173
|
}));
|
|
142
174
|
|
|
143
|
-
// Build child routes
|
|
144
|
-
const childRoutes =
|
|
175
|
+
// Build child routes for regular (non-fullScreen) screens
|
|
176
|
+
const childRoutes = regularRoutes.map((child, childIndex) =>
|
|
177
|
+
buildRoute(child, childIndex, true, navigatorFullPath)
|
|
178
|
+
);
|
|
145
179
|
|
|
146
180
|
// Add catch-all and index routes if notFoundComponent or onInvalidRoute is configured
|
|
147
181
|
if (params.notFoundComponent || params.onInvalidRoute) {
|
|
148
182
|
// Check if any route handles the index (empty path or "/" for this navigator)
|
|
149
|
-
const hasIndexRoute =
|
|
183
|
+
const hasIndexRoute = regularRoutes.some(route => {
|
|
150
184
|
const childPath = route.path.startsWith('/') ? route.path.slice(1) : route.path;
|
|
151
185
|
return childPath === '' || childPath === '/';
|
|
152
186
|
});
|
|
@@ -183,7 +217,8 @@ const buildRoute = (params: RouteParam, index: number, isNested = false) => {
|
|
|
183
217
|
);
|
|
184
218
|
}
|
|
185
219
|
|
|
186
|
-
|
|
220
|
+
// Build the main navigator route with layout
|
|
221
|
+
const navigatorRoute = (
|
|
187
222
|
<Route
|
|
188
223
|
key={`${params.path}-${index}`}
|
|
189
224
|
path={routePath}
|
|
@@ -198,8 +233,36 @@ const buildRoute = (params: RouteParam, index: number, isNested = false) => {
|
|
|
198
233
|
{childRoutes}
|
|
199
234
|
</Route>
|
|
200
235
|
);
|
|
236
|
+
|
|
237
|
+
// If there are fullScreen routes, return them as siblings to the navigator route
|
|
238
|
+
if (fullScreenRoutes.length > 0) {
|
|
239
|
+
const fullScreenElements = fullScreenRoutes.map((route, fsIndex) => {
|
|
240
|
+
// Compute full absolute path for the fullScreen route
|
|
241
|
+
const fullPath = joinPaths(navigatorFullPath, route.path);
|
|
242
|
+
// Remove leading slash for React Router path (it will be relative to root)
|
|
243
|
+
const routerPath = fullPath.startsWith('/') ? fullPath.slice(1) : fullPath;
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<Route
|
|
247
|
+
key={`fullscreen-${route.path}-${fsIndex}`}
|
|
248
|
+
path={routerPath || '/'}
|
|
249
|
+
element={React.createElement(route.component)}
|
|
250
|
+
/>
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Return array of routes: navigator + fullScreen siblings
|
|
255
|
+
return (
|
|
256
|
+
<React.Fragment key={`navigator-group-${index}`}>
|
|
257
|
+
{navigatorRoute}
|
|
258
|
+
{fullScreenElements}
|
|
259
|
+
</React.Fragment>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return navigatorRoute;
|
|
201
264
|
}
|
|
202
|
-
|
|
265
|
+
|
|
203
266
|
return null;
|
|
204
267
|
}
|
|
205
268
|
|
package/src/routing/types.ts
CHANGED
|
@@ -61,6 +61,15 @@ export type ScreenOptions = {
|
|
|
61
61
|
*/
|
|
62
62
|
title?: string;
|
|
63
63
|
headerShown?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* When true, renders the screen outside of parent layout wrappers.
|
|
66
|
+
* Useful for fullscreen modals, onboarding flows, or any screen that
|
|
67
|
+
* should not inherit the parent navigator's layout (header, sidebar, tabs, etc.)
|
|
68
|
+
*
|
|
69
|
+
* Web: Screen renders as a sibling route without the parent LayoutComponent
|
|
70
|
+
* Native: Screen uses fullScreenModal presentation
|
|
71
|
+
*/
|
|
72
|
+
fullScreen?: boolean;
|
|
64
73
|
|
|
65
74
|
} & NavigatorOptions;
|
|
66
75
|
|