@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 +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 +62 -0
- package/src/examples/ExampleStackRouter.tsx +22 -24
- package/src/examples/ExampleTabRouter.tsx +43 -43
- package/src/examples/index.ts +2 -1
- 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 -166
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/navigation",
|
|
3
|
-
"version": "1.0.
|
|
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.
|
|
42
|
-
"@idealyst/theme": "^1.0.
|
|
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 {
|
|
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
|
|
24
|
-
return memo(
|
|
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 {
|
|
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
|
-
|
|
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(
|
|
33
|
+
return memo(buildNavigator(route));
|
|
26
34
|
}, [route]);
|
|
27
35
|
|
|
28
36
|
return (
|
package/src/context/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
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:
|
|
267
|
+
const StackRouter: NavigatorParam = {
|
|
268
268
|
path: "/",
|
|
269
|
-
|
|
270
|
-
layout:
|
|
271
|
-
type: "stack",
|
|
272
|
-
component: WrappedGeneralLayout,
|
|
273
|
-
},
|
|
269
|
+
type: 'navigator',
|
|
270
|
+
layout: 'stack',
|
|
274
271
|
routes: [
|
|
275
|
-
{ path: "
|
|
276
|
-
{ path: "
|
|
277
|
-
{ path: "
|
|
278
|
-
{ path: "
|
|
279
|
-
{ path: "
|
|
280
|
-
{ path: "
|
|
281
|
-
{ path: "
|
|
282
|
-
{ path: "
|
|
283
|
-
{ path: "
|
|
284
|
-
{ path: "
|
|
285
|
-
{ path: "
|
|
286
|
-
{ path: "
|
|
287
|
-
{ path: "
|
|
288
|
-
{ path: "
|
|
289
|
-
{ path: "
|
|
290
|
-
{ path: "
|
|
291
|
-
{ path: "
|
|
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
|
-
|
|
164
|
-
title: 'Settings',{"\n"}
|
|
165
|
-
headerTitle: 'App Settings',{"\n"}
|
|
166
|
-
headerLeft: () => <BackButton />,{"\n"}
|
|
167
|
-
headerRight: () => <ActionButtons />
|
|
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:
|
|
195
|
+
const TabRouter: NavigatorParam = {
|
|
201
196
|
path: "/",
|
|
202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
+
type: 'screen',
|
|
294
|
+
options: {
|
|
295
295
|
title: 'Theme',
|
|
296
296
|
headerTitle: () => (
|
|
297
297
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
package/src/examples/index.ts
CHANGED
|
@@ -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
|
+
}
|