@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.
@@ -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
+ }
@@ -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 React from "react";
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
- export const buildRouter = (routeParam: RouteParam, path: string = '') => {
12
- return () => buildNativeRouter(routeParam, path)
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
- * Convert ScreenOptions to React Navigation screen options
23
+ * Get Navigator Type
24
+ * @param params
25
+ * @returns
17
26
  */
18
- const convertScreenOptions = (screenOptions?: ScreenOptions) => {
19
- if (!screenOptions) return {};
20
-
21
- const options: any = {};
22
-
23
- if (screenOptions.title) {
24
- options.title = screenOptions.title;
25
- // Set default headerTitle to title, but allow headerTitle to override
26
- if (!screenOptions.headerTitle) {
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
- * Create the router supporting React Navigation
84
- * @param routeParam
85
- * @param path
86
- * @param LastNavigator
38
+ * Build Screen
39
+ * @param params
40
+ * @param Navigator
41
+ * @param parentPath
87
42
  * @returns
88
43
  */
89
- const buildNativeRouter = (routeParam: RouteParam, path: string = '', LastNavigator?: TypedNavigator<any>): React.ReactElement => {
90
- const nextPath = (routeParam.path ? path + routeParam.path : path) || '';
91
- const type = routeParam.layout?.type;
92
- const screenOptions = convertScreenOptions(routeParam.screenOptions);
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
- }