@idealyst/navigation 1.0.61 → 1.0.62
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 +3 -3
- package/src/context/NavigatorContext.native.tsx +5 -3
- package/src/context/NavigatorContext.web.tsx +14 -6
- package/src/context/types.ts +2 -2
- package/src/examples/CustomStackLayout.tsx +63 -0
- package/src/examples/CustomTabLayout.tsx +44 -0
- package/src/examples/ExampleHybridRouter.tsx +34 -32
- package/src/examples/ExampleStackRouter.tsx +22 -24
- package/src/examples/ExampleTabRouter.tsx +42 -37
- package/src/layouts/DefaultStackLayout.tsx +57 -0
- package/src/layouts/DefaultTabLayout.tsx +101 -0
- package/src/layouts/DefaultTabLayout.web.tsx +105 -0
- package/src/layouts/index.ts +4 -1
- package/src/routing/router.native.tsx +40 -179
- package/src/routing/router.web.tsx +192 -335
- package/src/routing/types.ts +62 -20
- package/src/routing_old/index.native.tsx +2 -0
- package/src/routing_old/index.ts +2 -0
- package/src/routing_old/index.web.tsx +2 -0
- package/src/routing_old/router.native.tsx +192 -0
- package/src/routing_old/router.web.tsx +366 -0
- package/src/routing_old/types.ts +74 -0
- /package/src/{routing → routing_old}/README.md +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, Button, Icon } from '@idealyst/components'
|
|
3
|
+
import { TabLayoutProps } from '../routing/types'
|
|
4
|
+
|
|
5
|
+
export interface DefaultTabLayoutProps extends TabLayoutProps {
|
|
6
|
+
currentRoute: React.ComponentType
|
|
7
|
+
onNavigate: (path: string) => void
|
|
8
|
+
currentPath: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default Tab Layout Component for Web
|
|
13
|
+
* Provides a simple tab navigation interface using @idealyst/components
|
|
14
|
+
*/
|
|
15
|
+
export const DefaultTabLayout: React.FC<DefaultTabLayoutProps> = ({
|
|
16
|
+
options,
|
|
17
|
+
routes,
|
|
18
|
+
currentRoute: CurrentRoute,
|
|
19
|
+
onNavigate,
|
|
20
|
+
currentPath
|
|
21
|
+
}) => {
|
|
22
|
+
return (
|
|
23
|
+
<View style={{ height: '100vh', flexDirection: 'column' }}>
|
|
24
|
+
{/* Header */}
|
|
25
|
+
{options?.headerTitle && (
|
|
26
|
+
<View style={{
|
|
27
|
+
padding: 16,
|
|
28
|
+
borderBottomWidth: 1,
|
|
29
|
+
borderBottomColor: '#e0e0e0',
|
|
30
|
+
backgroundColor: '#f8f9fa'
|
|
31
|
+
}}>
|
|
32
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
33
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
|
|
34
|
+
{options.headerLeft && React.createElement(options.headerLeft as any)}
|
|
35
|
+
|
|
36
|
+
{typeof options.headerTitle === 'string' ? (
|
|
37
|
+
<Text size="large" weight="bold" style={{ marginLeft: options.headerLeft ? 12 : 0 }}>
|
|
38
|
+
{options.headerTitle}
|
|
39
|
+
</Text>
|
|
40
|
+
) : (
|
|
41
|
+
React.createElement(options.headerTitle as any)
|
|
42
|
+
)}
|
|
43
|
+
</View>
|
|
44
|
+
|
|
45
|
+
{options.headerRight && React.createElement(options.headerRight as any)}
|
|
46
|
+
</View>
|
|
47
|
+
</View>
|
|
48
|
+
)}
|
|
49
|
+
|
|
50
|
+
{/* Tab Navigation */}
|
|
51
|
+
<View style={{
|
|
52
|
+
padding: 12,
|
|
53
|
+
borderBottomWidth: 1,
|
|
54
|
+
borderBottomColor: '#e0e0e0',
|
|
55
|
+
backgroundColor: '#ffffff'
|
|
56
|
+
}}>
|
|
57
|
+
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
58
|
+
{routes.map((route) => {
|
|
59
|
+
if (route.type !== 'screen') return null
|
|
60
|
+
|
|
61
|
+
const isActive = currentPath === (route.path === '/' ? '/' : `/${route.path.replace(/^\//, '')}`)
|
|
62
|
+
const screenRoute = route as any
|
|
63
|
+
const label = screenRoute.options?.tabBarLabel || screenRoute.options?.title || (route.path === '/' ? 'Home' : route.path)
|
|
64
|
+
const icon = screenRoute.options?.tabBarIcon
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Button
|
|
68
|
+
key={route.path}
|
|
69
|
+
variant={isActive ? 'contained' : 'outlined'}
|
|
70
|
+
intent={isActive ? 'primary' : undefined}
|
|
71
|
+
size="small"
|
|
72
|
+
onPress={() => onNavigate(route.path)}
|
|
73
|
+
style={{
|
|
74
|
+
flexDirection: 'row',
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
gap: 6
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
{icon && typeof icon === 'string' && (
|
|
80
|
+
<Icon
|
|
81
|
+
name={icon as any}
|
|
82
|
+
size="sm"
|
|
83
|
+
color={isActive ? 'white' : 'primary'}
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
<Text
|
|
87
|
+
size="small"
|
|
88
|
+
color={isActive ? 'white' : 'primary'}
|
|
89
|
+
weight={isActive ? 'semibold' : 'medium'}
|
|
90
|
+
>
|
|
91
|
+
{label}
|
|
92
|
+
</Text>
|
|
93
|
+
</Button>
|
|
94
|
+
)
|
|
95
|
+
})}
|
|
96
|
+
</View>
|
|
97
|
+
</View>
|
|
98
|
+
|
|
99
|
+
{/* Content Area */}
|
|
100
|
+
<View style={{ flex: 1, padding: 20 }}>
|
|
101
|
+
<CurrentRoute />
|
|
102
|
+
</View>
|
|
103
|
+
</View>
|
|
104
|
+
)
|
|
105
|
+
}
|
package/src/layouts/index.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export * from './GeneralLayout';
|
|
2
|
-
export * from './TabBarLayout';
|
|
2
|
+
export * from './TabBarLayout';
|
|
3
|
+
export { DefaultTabLayout } from './DefaultTabLayout'
|
|
4
|
+
export { DefaultStackLayout } from './DefaultStackLayout'
|
|
5
|
+
export type { TabLayoutProps, StackLayoutProps, TabLayoutComponent, StackLayoutComponent } from '../routing/types'
|
|
@@ -1,192 +1,53 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { NavigatorParam, RouteParam } from './types'
|
|
2
2
|
|
|
3
|
+
import { TypedNavigator } from "@react-navigation/native";
|
|
3
4
|
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
|
4
5
|
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
|
5
|
-
import { createDrawerNavigator } from "@react-navigation/drawer";
|
|
6
|
-
|
|
7
|
-
import { RouteParam, ScreenOptions } from "./types"
|
|
8
|
-
import { TypedNavigator } from "@react-navigation/native";
|
|
9
|
-
import { Icon } from '@idealyst/components';
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Build the Mobile navigator using React Navigation
|
|
9
|
+
* @param params
|
|
10
|
+
* @param parentPath
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
export const buildNavigator = (params: NavigatorParam, parentPath = '') => {
|
|
14
|
+
const NavigatorType = getNavigatorType(params);
|
|
15
|
+
return () => (
|
|
16
|
+
<NavigatorType.Navigator>
|
|
17
|
+
{params.routes.map((child) => buildScreen(child, NavigatorType))}
|
|
18
|
+
</NavigatorType.Navigator>
|
|
19
|
+
)
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
/**
|
|
16
|
-
*
|
|
23
|
+
* Get Navigator Type
|
|
24
|
+
* @param params
|
|
25
|
+
* @returns
|
|
17
26
|
*/
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
options.headerTitle = screenOptions.title;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (screenOptions.tabBarLabel) {
|
|
32
|
-
options.tabBarLabel = screenOptions.tabBarLabel;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (screenOptions.tabBarIcon) {
|
|
36
|
-
if (typeof screenOptions.tabBarIcon === 'string') {
|
|
37
|
-
options.tabBarIcon = ({ focused }: { focused: boolean; color: string; size: number }) => (
|
|
38
|
-
<Icon
|
|
39
|
-
name={screenOptions.tabBarIcon as any}
|
|
40
|
-
color={focused ? 'primary' : 'secondary'}
|
|
41
|
-
/>
|
|
42
|
-
);
|
|
43
|
-
} else if (typeof screenOptions.tabBarIcon === 'function') {
|
|
44
|
-
options.tabBarIcon = screenOptions.tabBarIcon
|
|
45
|
-
} else {
|
|
46
|
-
options.tabBarIcon = screenOptions.tabBarIcon;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (screenOptions.tabBarBadge !== undefined) {
|
|
51
|
-
options.tabBarBadge = screenOptions.tabBarBadge;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (screenOptions.tabBarVisible !== undefined) {
|
|
55
|
-
options.tabBarStyle = screenOptions.tabBarVisible ? {} : { display: 'none' };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// headerTitle should override the default title in the header
|
|
59
|
-
if (screenOptions.headerTitle) {
|
|
60
|
-
options.headerTitle = screenOptions.headerTitle;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (screenOptions.headerBackVisible !== undefined) {
|
|
64
|
-
options.headerBackVisible = screenOptions.headerBackVisible;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (screenOptions.headerLeft) {
|
|
68
|
-
options.headerLeft = screenOptions.headerLeft;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (screenOptions.headerRight) {
|
|
72
|
-
options.headerRight = screenOptions.headerRight;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (screenOptions.platformOptions?.native) {
|
|
76
|
-
Object.assign(options, screenOptions.platformOptions.native);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return options;
|
|
80
|
-
};
|
|
27
|
+
const getNavigatorType = (params: NavigatorParam) => {
|
|
28
|
+
switch (params.layout) {
|
|
29
|
+
case 'stack':
|
|
30
|
+
return createNativeStackNavigator();
|
|
31
|
+
case 'tab':
|
|
32
|
+
return createBottomTabNavigator();
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Unsupported navigator type: ${params.layout}`);
|
|
35
|
+
}
|
|
81
36
|
|
|
82
37
|
/**
|
|
83
|
-
*
|
|
84
|
-
* @param
|
|
85
|
-
* @param
|
|
86
|
-
* @param
|
|
38
|
+
* Build Screen
|
|
39
|
+
* @param params
|
|
40
|
+
* @param Navigator
|
|
41
|
+
* @param parentPath
|
|
87
42
|
* @returns
|
|
88
43
|
*/
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
44
|
+
const buildScreen = (params: RouteParam, Navigator: TypedNavigator, parentPath = '') => {
|
|
45
|
+
return (
|
|
46
|
+
<Navigator.Screen
|
|
47
|
+
name={params.path}
|
|
48
|
+
component={params.type === 'screen' ? params.component : buildNavigator(params, parentPath + params.path)}
|
|
49
|
+
options={params.options}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
93
53
|
|
|
94
|
-
function buildComponent() {
|
|
95
|
-
switch (type) {
|
|
96
|
-
case 'stack':
|
|
97
|
-
const Stack = createNativeStackNavigator();
|
|
98
|
-
return (
|
|
99
|
-
<Stack.Navigator
|
|
100
|
-
screenOptions={{
|
|
101
|
-
// Disable screen optimization to ensure theme updates
|
|
102
|
-
freezeOnBlur: false,
|
|
103
|
-
}}
|
|
104
|
-
>
|
|
105
|
-
<Stack.Screen
|
|
106
|
-
name={nextPath}
|
|
107
|
-
component={routeParam.component}
|
|
108
|
-
options={screenOptions}
|
|
109
|
-
/>
|
|
110
|
-
{routeParam.routes?.map((route) => buildNativeRouter(route, nextPath, Stack))}
|
|
111
|
-
</Stack.Navigator>
|
|
112
|
-
)
|
|
113
|
-
case 'tab':
|
|
114
|
-
const Tab = createBottomTabNavigator();
|
|
115
|
-
return (
|
|
116
|
-
<Tab.Navigator
|
|
117
|
-
screenOptions={{
|
|
118
|
-
// Disable screen optimization to ensure theme updates
|
|
119
|
-
lazy: false,
|
|
120
|
-
freezeOnBlur: false,
|
|
121
|
-
}}
|
|
122
|
-
>
|
|
123
|
-
<Tab.Screen
|
|
124
|
-
name={nextPath}
|
|
125
|
-
component={routeParam.component}
|
|
126
|
-
options={screenOptions}
|
|
127
|
-
/>
|
|
128
|
-
{routeParam.routes?.map((route) => buildNativeRouter(route, nextPath, Tab))}
|
|
129
|
-
</Tab.Navigator>
|
|
130
|
-
)
|
|
131
|
-
case 'drawer':
|
|
132
|
-
const Drawer = createDrawerNavigator();
|
|
133
|
-
return (
|
|
134
|
-
<Drawer.Navigator
|
|
135
|
-
screenOptions={{
|
|
136
|
-
// Disable screen optimization to ensure theme updates
|
|
137
|
-
lazy: false,
|
|
138
|
-
freezeOnBlur: false,
|
|
139
|
-
}}
|
|
140
|
-
>
|
|
141
|
-
<Drawer.Screen
|
|
142
|
-
name={nextPath}
|
|
143
|
-
component={routeParam.component}
|
|
144
|
-
options={screenOptions}
|
|
145
|
-
/>
|
|
146
|
-
{routeParam.routes?.map((route) => buildNativeRouter(route, nextPath, Drawer))}
|
|
147
|
-
</Drawer.Navigator>
|
|
148
|
-
)
|
|
149
|
-
case 'modal':
|
|
150
|
-
if (!LastNavigator) {
|
|
151
|
-
throw new Error('LastNavigator is required for modal layout');
|
|
152
|
-
}
|
|
153
|
-
return (
|
|
154
|
-
<>
|
|
155
|
-
<LastNavigator.Screen
|
|
156
|
-
options={{ headerShown: false, presentation: 'modal', ...screenOptions }}
|
|
157
|
-
name={nextPath}
|
|
158
|
-
component={routeParam.component}
|
|
159
|
-
/>
|
|
160
|
-
{routeParam.routes?.map((route) => buildNativeRouter(route, nextPath, LastNavigator))}
|
|
161
|
-
</>
|
|
162
|
-
)
|
|
163
|
-
case undefined:
|
|
164
|
-
if (!LastNavigator) {
|
|
165
|
-
throw new Error('LastNavigator is required for undefined layout - ' + routeParam.path);
|
|
166
|
-
}
|
|
167
|
-
return (
|
|
168
|
-
<>
|
|
169
|
-
<LastNavigator.Screen
|
|
170
|
-
name={nextPath}
|
|
171
|
-
component={routeParam.component}
|
|
172
|
-
options={screenOptions}
|
|
173
|
-
/>
|
|
174
|
-
{routeParam.routes?.map((route) => buildNativeRouter(route, nextPath, LastNavigator))}
|
|
175
|
-
</>
|
|
176
|
-
)
|
|
177
|
-
default:
|
|
178
|
-
throw new Error(`Unknown layout type: ${type}`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const Component = buildComponent();
|
|
182
|
-
if (LastNavigator) {
|
|
183
|
-
return (
|
|
184
|
-
<LastNavigator.Screen
|
|
185
|
-
name={nextPath}
|
|
186
|
-
component={() => Component}
|
|
187
|
-
options={screenOptions}
|
|
188
|
-
/>
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
return Component;
|
|
192
|
-
}
|