@idealyst/navigation 1.0.61 → 1.0.63
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/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 +64 -17
- package/src/layouts/GeneralLayout/GeneralLayout.styles.tsx +0 -55
- package/src/layouts/GeneralLayout/GeneralLayout.tsx +0 -143
- package/src/layouts/GeneralLayout/README.md +0 -498
- package/src/layouts/GeneralLayout/index.ts +0 -2
- package/src/layouts/GeneralLayout/types.ts +0 -99
- package/src/layouts/TabBarLayout/TabBarLayout.native.tsx +0 -283
- package/src/layouts/TabBarLayout/TabBarLayout.styles.tsx +0 -142
- package/src/layouts/TabBarLayout/TabBarLayout.web.tsx +0 -286
- package/src/layouts/TabBarLayout/index.native.ts +0 -2
- package/src/layouts/TabBarLayout/index.ts +0 -2
- package/src/layouts/TabBarLayout/index.web.ts +0 -2
- package/src/layouts/TabBarLayout/types.ts +0 -176
- package/src/routing/README.md +0 -421
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/navigation",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.63",
|
|
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.63",
|
|
42
|
+
"@idealyst/theme": "^1.0.63",
|
|
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
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { RouteParam } from '../routing';
|
|
1
|
+
import { NavigatorParam, RouteParam } from '../routing';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Button, Screen, Text } from '../../../components/src';
|
|
4
4
|
import { useNavigator } from '../context';
|
|
5
|
+
import CustomTabLayout from './CustomTabLayout';
|
|
6
|
+
import CustomStackLayout from './CustomStackLayout';
|
|
5
7
|
|
|
6
8
|
const RootScreen = () => {
|
|
7
9
|
|
|
@@ -10,50 +12,50 @@ const RootScreen = () => {
|
|
|
10
12
|
return <Screen>
|
|
11
13
|
<Text>Root Screen</Text>
|
|
12
14
|
<Button title="Go to Tab" onPress={() => navigator.navigate({
|
|
13
|
-
path: '/
|
|
15
|
+
path: '/tab',
|
|
14
16
|
vars: {},
|
|
15
17
|
})} />
|
|
16
18
|
</Screen>
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const ExampleHybridRouter:
|
|
21
|
+
const ExampleHybridRouter: NavigatorParam = {
|
|
20
22
|
path: '/',
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
component: RootScreen,
|
|
25
|
-
screenOptions: {
|
|
26
|
-
title: 'Example',
|
|
27
|
-
headerTitle: 'Example Header',
|
|
28
|
-
tabBarLabel: 'Example',
|
|
29
|
-
tabBarIcon: 'example-icon',
|
|
30
|
-
},
|
|
23
|
+
type: 'navigator',
|
|
24
|
+
layout: 'stack',
|
|
25
|
+
layoutComponent: CustomStackLayout,
|
|
31
26
|
routes: [
|
|
32
27
|
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
screenOptions: {
|
|
39
|
-
title: 'Tab Example',
|
|
40
|
-
headerTitle: 'Tab Header',
|
|
41
|
-
tabBarLabel: 'Tab',
|
|
42
|
-
tabBarIcon: 'tab-icon',
|
|
28
|
+
type: 'screen',
|
|
29
|
+
path: '/',
|
|
30
|
+
component: RootScreen,
|
|
31
|
+
options: {
|
|
32
|
+
title: 'Example',
|
|
43
33
|
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'navigator',
|
|
37
|
+
path: '/tab',
|
|
38
|
+
layout: 'tab',
|
|
39
|
+
layoutComponent: CustomTabLayout,
|
|
44
40
|
routes: [
|
|
45
41
|
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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',
|
|
53
55
|
},
|
|
54
|
-
}
|
|
56
|
+
},
|
|
55
57
|
]
|
|
56
|
-
}
|
|
58
|
+
},
|
|
57
59
|
]
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -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
|
|
|
@@ -192,45 +192,48 @@ const ThemeTabScreen = () => (
|
|
|
192
192
|
</Screen>
|
|
193
193
|
);
|
|
194
194
|
|
|
195
|
-
const TabRouter:
|
|
195
|
+
const TabRouter: NavigatorParam = {
|
|
196
196
|
path: "/",
|
|
197
|
-
|
|
198
|
-
layout: {
|
|
199
|
-
type: "tab",
|
|
200
|
-
},
|
|
201
|
-
screenOptions: {
|
|
202
|
-
title: 'Home',
|
|
203
|
-
headerTitle: () => (
|
|
204
|
-
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
205
|
-
<Icon name="home" size="md" style={{ marginRight: 8 }} />
|
|
206
|
-
<Text size="large" weight="bold">Tab Demo</Text>
|
|
207
|
-
</View>
|
|
208
|
-
),
|
|
209
|
-
headerLeft: () => (
|
|
210
|
-
<Button variant="text" size="small">
|
|
211
|
-
<Icon name="menu" size="md" />
|
|
212
|
-
</Button>
|
|
213
|
-
),
|
|
214
|
-
headerRight: () => (
|
|
215
|
-
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
216
|
-
<Button variant="text" size="small" style={{ marginRight: 8 }}>
|
|
217
|
-
<Icon name="bell" size="md" />
|
|
218
|
-
</Button>
|
|
219
|
-
<Button variant="text" size="small">
|
|
220
|
-
<Icon name="account" size="md" />
|
|
221
|
-
</Button>
|
|
222
|
-
</View>
|
|
223
|
-
),
|
|
224
|
-
tabBarLabel: 'Home',
|
|
225
|
-
tabBarIcon: ({ focused, size }) => {
|
|
226
|
-
return <Icon name="home" color={focused ? 'blue' : 'black'} size={size} />
|
|
227
|
-
},
|
|
228
|
-
},
|
|
197
|
+
layout: 'tab',
|
|
229
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
|
+
},
|
|
230
232
|
{
|
|
231
233
|
path: "components",
|
|
232
234
|
component: ComponentsTabScreen,
|
|
233
|
-
|
|
235
|
+
type: 'screen',
|
|
236
|
+
options: {
|
|
234
237
|
title: 'Components',
|
|
235
238
|
headerLeft: () => (
|
|
236
239
|
<Button variant="text" size="small">
|
|
@@ -254,7 +257,8 @@ const TabRouter: RouteParam = {
|
|
|
254
257
|
{
|
|
255
258
|
path: "settings",
|
|
256
259
|
component: SettingsTabScreen,
|
|
257
|
-
|
|
260
|
+
type: 'screen',
|
|
261
|
+
options: {
|
|
258
262
|
title: 'Settings',
|
|
259
263
|
headerTitle: 'App Settings',
|
|
260
264
|
headerLeft: () => (
|
|
@@ -286,7 +290,8 @@ const TabRouter: RouteParam = {
|
|
|
286
290
|
{
|
|
287
291
|
path: "theme",
|
|
288
292
|
component: ThemeTabScreen,
|
|
289
|
-
|
|
293
|
+
type: 'screen',
|
|
294
|
+
options: {
|
|
290
295
|
title: 'Theme',
|
|
291
296
|
headerTitle: () => (
|
|
292
297
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
@@ -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
|
+
}
|
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'
|