@idealyst/navigation 1.0.60 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/navigation",
3
- "version": "1.0.60",
3
+ "version": "1.0.62",
4
4
  "description": "Cross-platform navigation library for React and React Native",
5
5
  "readme": "README.md",
6
6
  "main": "src/index.ts",
@@ -38,8 +38,8 @@
38
38
  "publish:npm": "npm publish"
39
39
  },
40
40
  "peerDependencies": {
41
- "@idealyst/components": "^1.0.60",
42
- "@idealyst/theme": "^1.0.60",
41
+ "@idealyst/components": "^1.0.62",
42
+ "@idealyst/theme": "^1.0.62",
43
43
  "@react-navigation/bottom-tabs": "^7.0.0",
44
44
  "@react-navigation/drawer": "^7.0.0",
45
45
  "@react-navigation/native": "^7.0.0",
@@ -1,7 +1,7 @@
1
1
  import React, { createContext, memo, useContext, useMemo } from 'react';
2
2
  import { NavigateParams, NavigatorProviderProps } from './types';
3
3
  import { useNavigation, useNavigationState, DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
4
- import { buildRouter } from '../routing';
4
+ import { buildNavigator } from '../routing';
5
5
  import { useUnistyles } from 'react-native-unistyles';
6
6
 
7
7
  const NavigatorContext = createContext<{
@@ -20,9 +20,11 @@ const UnwrappedNavigatorProvider = ({ route }: NavigatorProviderProps) => {
20
20
  };
21
21
 
22
22
  const RouteComponent = useMemo(() => {
23
- // Memoize the router to prevent unnecessary re-renders
24
- return memo(buildRouter(route));
23
+ // Memoize the navigator to prevent unnecessary re-renders
24
+ return memo(buildNavigator(route));
25
25
  }, [route]);
26
+
27
+ console.log('UnwrappedNavigatorProvider render', RouteComponent);
26
28
 
27
29
  return (
28
30
  <NavigatorContext.Provider value={{ navigate }}>
@@ -1,7 +1,6 @@
1
1
  import React, { createContext, memo, useContext, useMemo } from 'react';
2
2
  import { NavigateParams, NavigatorProviderProps } from './types';
3
- import { useNavigate } from "react-router-dom";
4
- import { buildRouter } from '../routing';
3
+ import { buildNavigator } from '../routing';
5
4
 
6
5
  const NavigatorContext = createContext<{
7
6
  navigate: (params: NavigateParams) => void;
@@ -12,17 +11,26 @@ const NavigatorContext = createContext<{
12
11
  export const NavigatorProvider = ({
13
12
  route,
14
13
  }: NavigatorProviderProps) => {
15
- const routerNavigate = useNavigate();
16
-
17
14
  const navigateFunction = (params: NavigateParams) => {
18
15
  if (params.path) {
19
- routerNavigate(params.path);
16
+ // Normalize path - convert empty string to '/'
17
+ let path = params.path
18
+ if (path === '' || path === '/') {
19
+ path = '/'
20
+ } else if (!path.startsWith('/')) {
21
+ path = `/${path}`
22
+ }
23
+
24
+ // Use HTML5 history API for proper navigation without hash
25
+ window.history.pushState({}, '', path);
26
+ // Trigger a popstate event to update any listening components
27
+ window.dispatchEvent(new PopStateEvent('popstate'));
20
28
  }
21
29
  };
22
30
 
23
31
  const RouteComponent = useMemo(() => {
24
32
  // Memoize the router to prevent unnecessary re-renders
25
- return memo(buildRouter(route));
33
+ return memo(buildNavigator(route));
26
34
  }, [route]);
27
35
 
28
36
  return (
@@ -1,4 +1,4 @@
1
- import { RouteParam } from "../routing";
1
+ import { NavigatorParam } from "../routing";
2
2
 
3
3
  /**
4
4
  * When navigating to a new route, specify the path and the variables to be used in the route.
@@ -9,5 +9,5 @@ export type NavigateParams = {
9
9
  };
10
10
 
11
11
  export type NavigatorProviderProps = {
12
- route: RouteParam;
12
+ route: NavigatorParam;
13
13
  };
@@ -0,0 +1,63 @@
1
+ import { Button, Text, View } from "@idealyst/components";
2
+ import { StackLayoutProps } from "src";
3
+ import React, { useMemo } from "react";
4
+
5
+ export default function CustomStackLayout({
6
+ routes,
7
+ options,
8
+ ContentComponent,
9
+ onNavigate,
10
+ currentPath
11
+ }: StackLayoutProps) {
12
+
13
+ const headerTitle = useMemo(() => {
14
+ if (!options?.headerTitle) return <Text>{currentPath}</Text>;
15
+ if (typeof options.headerTitle === 'string') {
16
+ return <Text>{options.headerTitle}</Text>;
17
+ }
18
+ const HeaderComponent = options.headerTitle as React.ComponentType;
19
+ return <HeaderComponent />;
20
+ }, [options?.headerTitle, currentPath]);
21
+
22
+ return (
23
+ <View style={{ height: '100vh' }}>
24
+ {/* Header */}
25
+ <View style={{ padding: 16, borderBottom: '1px solid #e0e0e0' }}>
26
+ {headerTitle}
27
+ </View>
28
+
29
+ {/* Main content area with sidebar and content */}
30
+ <View style={{ display: 'flex', flexDirection: 'row', flex: 1 }}>
31
+ {/* Left sidebar with routes */}
32
+ <View style={{
33
+ width: 250,
34
+ padding: 16,
35
+ borderRight: '1px solid #e0e0e0',
36
+ backgroundColor: '#f5f5f5',
37
+ display: 'flex',
38
+ flexDirection: 'column',
39
+ gap: 8
40
+ }}>
41
+ {routes.map(route => (
42
+ <Button
43
+ variant={currentPath === route.fullPath ? 'contained' : 'outlined'}
44
+ key={route.path}
45
+ onPress={() => onNavigate(route.path)}
46
+ style={{
47
+ justifyContent: 'flex-start',
48
+ textAlign: 'left'
49
+ }}
50
+ >
51
+ {route.fullPath === '/' ? 'Home' : route.fullPath}
52
+ </Button>
53
+ ))}
54
+ </View>
55
+
56
+ {/* Main content area */}
57
+ <View style={{ flex: 1, padding: 16 }}>
58
+ <ContentComponent />
59
+ </View>
60
+ </View>
61
+ </View>
62
+ )
63
+ }
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { Button, View, Text } from "@idealyst/components";
3
+ import { TabLayoutProps } from "src/routing";
4
+
5
+ export default function CustomTabLayout({
6
+ routes,
7
+ options,
8
+ ContentComponent,
9
+ onNavigate,
10
+ currentPath
11
+ }: TabLayoutProps) {
12
+
13
+ return (
14
+ <View>
15
+ <View>
16
+ {options?.headerTitle ? (
17
+ typeof options.headerTitle === 'string' ? (
18
+ <Text>{options.headerTitle}</Text>
19
+ ) : (
20
+ React.createElement(options.headerTitle as React.ComponentType)
21
+ )
22
+ ) : (
23
+ <Text>Custom Tab Layout</Text>
24
+ )}
25
+ </View>
26
+ <View style={{ flexDirection: 'row' }}>
27
+ {routes.map(route => (
28
+ <Button
29
+ variant={currentPath === route.fullPath ? 'contained' : 'outlined'}
30
+ key={route.path}
31
+ onPress={() => onNavigate(route.path)}
32
+ style={{ margin: 4 }}
33
+ >
34
+ {route.fullPath === '/' ? 'Home' : route.fullPath}
35
+ </Button>
36
+ ))}
37
+ </View>
38
+ <View>
39
+ <ContentComponent />
40
+ </View>
41
+ </View>
42
+ )
43
+
44
+ }
@@ -0,0 +1,62 @@
1
+ import { NavigatorParam, RouteParam } from '../routing';
2
+ import React from 'react';
3
+ import { Button, Screen, Text } from '../../../components/src';
4
+ import { useNavigator } from '../context';
5
+ import CustomTabLayout from './CustomTabLayout';
6
+ import CustomStackLayout from './CustomStackLayout';
7
+
8
+ const RootScreen = () => {
9
+
10
+ const navigator = useNavigator();
11
+
12
+ return <Screen>
13
+ <Text>Root Screen</Text>
14
+ <Button title="Go to Tab" onPress={() => navigator.navigate({
15
+ path: '/tab',
16
+ vars: {},
17
+ })} />
18
+ </Screen>
19
+ }
20
+
21
+ const ExampleHybridRouter: NavigatorParam = {
22
+ path: '/',
23
+ type: 'navigator',
24
+ layout: 'stack',
25
+ layoutComponent: CustomStackLayout,
26
+ routes: [
27
+ {
28
+ type: 'screen',
29
+ path: '/',
30
+ component: RootScreen,
31
+ options: {
32
+ title: 'Example',
33
+ },
34
+ },
35
+ {
36
+ type: 'navigator',
37
+ path: '/tab',
38
+ layout: 'tab',
39
+ layoutComponent: CustomTabLayout,
40
+ routes: [
41
+ {
42
+ type: 'screen',
43
+ path: '/a',
44
+ component: () => <Text>Tab A Example</Text>,
45
+ options: {
46
+ title: 'Tab Example',
47
+ },
48
+ },
49
+ {
50
+ type: 'screen',
51
+ path: '/b',
52
+ component: () => <Text>Tab B Example</Text>,
53
+ options: {
54
+ title: 'B',
55
+ },
56
+ },
57
+ ]
58
+ },
59
+ ]
60
+ }
61
+
62
+ export default ExampleHybridRouter;
@@ -6,7 +6,7 @@ import { Button, Divider, Screen, Text, View } from "../../../components/src";
6
6
  import { useNavigator } from "../context";
7
7
  import { UnistylesRuntime } from 'react-native-unistyles';
8
8
  import { GeneralLayout } from '../layouts/GeneralLayout';
9
- import { RouteParam } from '../routing';
9
+ import { NavigatorParam, RouteParam } from '../routing';
10
10
  import { getNextTheme, getThemeDisplayName, isHighContrastTheme } from './unistyles';
11
11
 
12
12
  const HomeScreen = () => {
@@ -264,31 +264,29 @@ const WrappedGeneralLayout = (props: any) => {
264
264
  )
265
265
  }
266
266
 
267
- const StackRouter: RouteParam = {
267
+ const StackRouter: NavigatorParam = {
268
268
  path: "/",
269
- component: HomeScreen,
270
- layout: {
271
- type: "stack",
272
- component: WrappedGeneralLayout,
273
- },
269
+ type: 'navigator',
270
+ layout: 'stack',
274
271
  routes: [
275
- { path: "avatar", component: AvatarExamples},
276
- { path: "badge", component: BadgeExamples},
277
- { path: "button", component: ButtonExamples},
278
- { path: "card", component: CardExamples},
279
- { path: "checkbox", component: CheckboxExamples},
280
- { path: "divider", component: DividerExamples},
281
- { path: "input", component: InputExamples},
282
- { path: "text", component: TextExamples},
283
- { path: "view", component: ViewExamples},
284
- { path: "screen", component: ScreenExamples},
285
- { path: "icon", component: IconExamples},
286
- { path: "svg-image", component: SVGImageExamples},
287
- { path: "dialog", component: DialogExamples},
288
- { path: "popover", component: PopoverExamples},
289
- { path: "datagrid", component: DataGridShowcase},
290
- { path: "datepicker", component: DatePickerExamples},
291
- { path: "theme-extension", component: ThemeExtensionExamples},
272
+ { path: "/", type: 'screen', component: HomeScreen },
273
+ { path: "avatar", type: 'screen', component: AvatarExamples},
274
+ { path: "badge", type: 'screen', component: BadgeExamples},
275
+ { path: "button", type: 'screen', component: ButtonExamples},
276
+ { path: "card", type: 'screen', component: CardExamples},
277
+ { path: "checkbox", type: 'screen', component: CheckboxExamples},
278
+ { path: "divider", type: 'screen', component: DividerExamples},
279
+ { path: "input", type: 'screen', component: InputExamples},
280
+ { path: "text", type: 'screen', component: TextExamples},
281
+ { path: "view", type: 'screen', component: ViewExamples},
282
+ { path: "screen", type: 'screen', component: ScreenExamples},
283
+ { path: "icon", type: 'screen', component: IconExamples},
284
+ { path: "svg-image", type: 'screen', component: SVGImageExamples},
285
+ { path: "dialog", type: 'screen', component: DialogExamples},
286
+ { path: "popover", type: 'screen', component: PopoverExamples},
287
+ { path: "datagrid", type: 'screen', component: DataGridShowcase},
288
+ { path: "datepicker", type: 'screen', component: DatePickerExamples},
289
+ { path: "theme-extension", type: 'screen', component: ThemeExtensionExamples},
292
290
  ],
293
291
  };
294
292
 
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { ButtonExamples, CardExamples, IconExamples, SVGImageExamples, ThemeExtensionExamples } from "../../../components/src/examples";
3
3
  import { Screen, Text, View, Button, Icon } from "../../../components/src";
4
4
  import { UnistylesRuntime } from 'react-native-unistyles';
5
- import { RouteParam } from '../routing';
5
+ import { NavigatorParam, RouteParam } from '../routing';
6
6
  import { useNavigator } from '../context';
7
7
  import { getNextTheme, getThemeDisplayName, isHighContrastTheme } from './unistyles';
8
8
 
@@ -160,12 +160,7 @@ const SettingsTabScreen = () => {
160
160
  <View spacing="md">
161
161
  <Text size="medium" weight="semibold">Screen Options Used</Text>
162
162
  <Text size="small" style={{ fontFamily: 'monospace', backgroundColor: 'rgba(0,0,0,0.05)', padding: 8 }}>
163
- screenOptions: {{
164
- title: 'Settings',{"\n"}
165
- headerTitle: 'App Settings',{"\n"}
166
- headerLeft: () => &lt;BackButton /&gt;,{"\n"}
167
- headerRight: () => &lt;ActionButtons /&gt;
168
- }}
163
+ Test
169
164
  </Text>
170
165
  </View>
171
166
 
@@ -197,45 +192,48 @@ const ThemeTabScreen = () => (
197
192
  </Screen>
198
193
  );
199
194
 
200
- const TabRouter: RouteParam = {
195
+ const TabRouter: NavigatorParam = {
201
196
  path: "/",
202
- component: HomeTabScreen,
203
- layout: {
204
- type: "tab",
205
- },
206
- screenOptions: {
207
- title: 'Home',
208
- headerTitle: () => (
209
- <View style={{ flexDirection: 'row', alignItems: 'center' }}>
210
- <Icon name="home" size="md" style={{ marginRight: 8 }} />
211
- <Text size="large" weight="bold">Tab Demo</Text>
212
- </View>
213
- ),
214
- headerLeft: () => (
215
- <Button variant="text" size="small">
216
- <Icon name="menu" size="md" />
217
- </Button>
218
- ),
219
- headerRight: () => (
220
- <View style={{ flexDirection: 'row', alignItems: 'center' }}>
221
- <Button variant="text" size="small" style={{ marginRight: 8 }}>
222
- <Icon name="bell" size="md" />
223
- </Button>
224
- <Button variant="text" size="small">
225
- <Icon name="account" size="md" />
226
- </Button>
227
- </View>
228
- ),
229
- tabBarLabel: 'Home',
230
- tabBarIcon: ({ focused, size }) => {
231
- return <Icon name="home" color={focused ? 'blue' : 'black'} size={size} />
232
- },
233
- },
197
+ layout: 'tab',
234
198
  routes: [
199
+ {
200
+ path: '/',
201
+ type: 'screen',
202
+ component: HomeTabScreen,
203
+ options: {
204
+ title: 'Home',
205
+ headerTitle: () => (
206
+ <View style={{ flexDirection: 'row', alignItems: 'center' }}>
207
+ <Icon name="home" size="md" style={{ marginRight: 8 }} />
208
+ <Text size="large" weight="bold">Tab Demo</Text>
209
+ </View>
210
+ ),
211
+ headerLeft: () => (
212
+ <Button variant="text" size="small">
213
+ <Icon name="menu" size="md" />
214
+ </Button>
215
+ ),
216
+ headerRight: () => (
217
+ <View style={{ flexDirection: 'row', alignItems: 'center' }}>
218
+ <Button variant="text" size="small" style={{ marginRight: 8 }}>
219
+ <Icon name="bell" size="md" />
220
+ </Button>
221
+ <Button variant="text" size="small">
222
+ <Icon name="account" size="md" />
223
+ </Button>
224
+ </View>
225
+ ),
226
+ tabBarLabel: 'Home',
227
+ tabBarIcon: ({ focused, size }) => {
228
+ return <Icon name="home" color={focused ? 'blue' : 'black'} size={size} />
229
+ },
230
+ },
231
+ },
235
232
  {
236
233
  path: "components",
237
234
  component: ComponentsTabScreen,
238
- screenOptions: {
235
+ type: 'screen',
236
+ options: {
239
237
  title: 'Components',
240
238
  headerLeft: () => (
241
239
  <Button variant="text" size="small">
@@ -259,7 +257,8 @@ const TabRouter: RouteParam = {
259
257
  {
260
258
  path: "settings",
261
259
  component: SettingsTabScreen,
262
- screenOptions: {
260
+ type: 'screen',
261
+ options: {
263
262
  title: 'Settings',
264
263
  headerTitle: 'App Settings',
265
264
  headerLeft: () => (
@@ -291,7 +290,8 @@ const TabRouter: RouteParam = {
291
290
  {
292
291
  path: "theme",
293
292
  component: ThemeTabScreen,
294
- screenOptions: {
293
+ type: 'screen',
294
+ options: {
295
295
  title: 'Theme',
296
296
  headerTitle: () => (
297
297
  <View style={{ flexDirection: 'row', alignItems: 'center' }}>
@@ -1,3 +1,4 @@
1
1
  export { default as ExampleStackRouter } from './ExampleStackRouter';
2
2
  export { default as ExampleTabRouter } from './ExampleTabRouter';
3
- export { default as ExampleDrawerRouter } from './ExampleDrawerRouter';
3
+ export { default as ExampleDrawerRouter } from './ExampleDrawerRouter';
4
+ export { default as ExampleHybridRouter } from './ExampleHybridRouter';
@@ -0,0 +1,57 @@
1
+ import React from 'react'
2
+ import { View, Text } from '@idealyst/components'
3
+ import { StackLayoutProps } from '../routing/types'
4
+
5
+ export interface DefaultStackLayoutProps extends StackLayoutProps {
6
+ onNavigate: (path: string) => void
7
+ currentPath: string
8
+ }
9
+
10
+ /**
11
+ * Default Stack Layout Component for Web
12
+ * Provides a simple stack navigation interface using @idealyst/components
13
+ */
14
+ export const DefaultStackLayout: React.FC<DefaultStackLayoutProps> = ({
15
+ options,
16
+ routes,
17
+ ContentComponent,
18
+ onNavigate,
19
+ currentPath
20
+ }) => {
21
+ return (
22
+ <View style={{ height: '100vh', flexDirection: 'column' }}>
23
+ {/* Header */}
24
+ {(options?.headerTitle || options?.headerLeft || options?.headerRight) && (
25
+ <View style={{
26
+ padding: 16,
27
+ borderBottomWidth: 1,
28
+ borderBottomColor: '#e0e0e0',
29
+ backgroundColor: '#f8f9fa'
30
+ }}>
31
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
32
+ <View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
33
+ {options.headerLeft && React.createElement(options.headerLeft as any)}
34
+
35
+ {options.headerTitle && (
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
+ )}
44
+ </View>
45
+
46
+ {options.headerRight && React.createElement(options.headerRight as any)}
47
+ </View>
48
+ </View>
49
+ )}
50
+
51
+ {/* Content Area */}
52
+ <View style={{ flex: 1, padding: 20 }}>
53
+ <ContentComponent />
54
+ </View>
55
+ </View>
56
+ )
57
+ }
@@ -0,0 +1,101 @@
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
+
7
+ /**
8
+ * Default Tab Layout Component for Web
9
+ * Provides a simple tab navigation interface using @idealyst/components
10
+ */
11
+ export const DefaultTabLayout: React.FC<DefaultTabLayoutProps> = ({
12
+ options,
13
+ routes,
14
+ ContentComponent,
15
+ onNavigate,
16
+ currentPath
17
+ }) => {
18
+ return (
19
+ <View style={{ height: '100vh', flexDirection: 'column' }}>
20
+ {/* Header */}
21
+ {options?.headerTitle && (
22
+ <View style={{
23
+ padding: 16,
24
+ borderBottomWidth: 1,
25
+ borderBottomColor: '#e0e0e0',
26
+ backgroundColor: '#f8f9fa'
27
+ }}>
28
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
29
+ <View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
30
+ {options.headerLeft && React.createElement(options.headerLeft as any)}
31
+
32
+ {typeof options.headerTitle === 'string' ? (
33
+ <Text size="large" weight="bold" style={{ marginLeft: options.headerLeft ? 12 : 0 }}>
34
+ {options.headerTitle}
35
+ </Text>
36
+ ) : (
37
+ React.createElement(options.headerTitle as any)
38
+ )}
39
+ </View>
40
+
41
+ {options.headerRight && React.createElement(options.headerRight as any)}
42
+ </View>
43
+ </View>
44
+ )}
45
+
46
+ {/* Tab Navigation */}
47
+ <View style={{
48
+ padding: 12,
49
+ borderBottomWidth: 1,
50
+ borderBottomColor: '#e0e0e0',
51
+ backgroundColor: '#ffffff'
52
+ }}>
53
+ <View style={{ flexDirection: 'row', gap: 8 }}>
54
+ {routes.map((route) => {
55
+ if (route.type !== 'screen') return null
56
+
57
+ const isActive = currentPath === route.fullPath
58
+ const screenRoute = route as any
59
+ const label = screenRoute.options?.tabBarLabel || screenRoute.options?.title || (route.fullPath === '/' ? 'Home' : route.fullPath)
60
+ const icon = screenRoute.options?.tabBarIcon
61
+
62
+ return (
63
+ <Button
64
+ key={route.path}
65
+ variant={isActive ? 'contained' : 'outlined'}
66
+ intent={isActive ? 'primary' : undefined}
67
+ size="small"
68
+ onPress={() => onNavigate(route.path)}
69
+ style={{
70
+ flexDirection: 'row',
71
+ alignItems: 'center',
72
+ gap: 6
73
+ }}
74
+ >
75
+ {icon && typeof icon === 'string' && (
76
+ <Icon
77
+ name={icon as any}
78
+ size="sm"
79
+ color={isActive ? 'white' : 'primary'}
80
+ />
81
+ )}
82
+ <Text
83
+ size="small"
84
+ color={isActive ? 'white' : 'primary'}
85
+ weight={isActive ? 'semibold' : 'medium'}
86
+ >
87
+ {label}
88
+ </Text>
89
+ </Button>
90
+ )
91
+ })}
92
+ </View>
93
+ </View>
94
+
95
+ {/* Content Area */}
96
+ <View style={{ flex: 1, padding: 20 }}>
97
+ <ContentComponent />
98
+ </View>
99
+ </View>
100
+ )
101
+ }