@idealyst/navigation 1.0.82 → 1.0.84
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 +23 -10
- package/src/context/NavigatorContext.native.tsx +37 -6
- package/src/context/NavigatorContext.web.tsx +5 -3
- package/src/context/types.ts +2 -0
- package/src/examples/CustomStackLayout.tsx +1 -3
- package/src/examples/CustomTabLayout.tsx +6 -5
- package/src/examples/ExampleNavigationRouter.tsx +111 -0
- package/src/examples/ExampleSearchDialog.tsx +134 -0
- package/src/examples/ExampleSidebar.tsx +134 -0
- package/src/examples/ExampleWebLayout.tsx +107 -0
- package/src/examples/HeaderRight.tsx +27 -0
- package/src/examples/index.ts +3 -5
- package/src/examples/unistyles.ts +6 -12
- package/src/hooks/useParams.web.ts +1 -1
- package/src/index.native.ts +4 -1
- package/src/index.web.ts +2 -2
- package/src/layouts/DefaultStackLayout.tsx +6 -5
- package/src/layouts/DefaultTabLayout.tsx +11 -6
- package/src/router/index.native.ts +17 -0
- package/src/router/index.ts +3 -0
- package/src/router/index.web.ts +6 -0
- package/src/routing/DrawerContentWrapper.native.tsx +25 -0
- package/src/routing/HeaderWrapper.native.tsx +19 -0
- package/src/routing/index.native.tsx +0 -4
- package/src/routing/index.web.tsx +1 -3
- package/src/routing/router.native.tsx +133 -23
- package/src/routing/router.web.tsx +2 -3
- package/src/routing/types.ts +40 -12
- package/CLAUDE.md +0 -417
- package/LLM-ACCESS-GUIDE.md +0 -166
- package/src/examples/ExampleDrawerRouter.tsx +0 -196
- package/src/examples/ExampleHybridRouter.tsx +0 -62
- package/src/examples/ExampleStackRouter.tsx +0 -266
- package/src/examples/ExampleTabRouter.tsx +0 -318
- package/src/examples/README.md +0 -394
- package/src/examples/highContrastThemes.ts +0 -583
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, Text, Button, Divider, ListItem, Icon, Badge } from '@idealyst/components';
|
|
3
|
+
import { useNavigator } from '../context';
|
|
4
|
+
import { Outlet } from '../router';
|
|
5
|
+
import { UnistylesRuntime, useUnistyles } from 'react-native-unistyles';
|
|
6
|
+
import { getNextTheme, getThemeDisplayName, isHighContrastTheme } from './unistyles';
|
|
7
|
+
import ExampleSidebar from './ExampleSidebar';
|
|
8
|
+
import ExampleSearchDialog from './ExampleSearchDialog';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export const ExampleWebLayout: React.FC = () => {
|
|
12
|
+
const navigator = useNavigator();
|
|
13
|
+
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
14
|
+
const [showSearch, setShowSearch] = useState(false);
|
|
15
|
+
const currentTheme = UnistylesRuntime.themeName || 'light';
|
|
16
|
+
const { theme } = useUnistyles();
|
|
17
|
+
|
|
18
|
+
const cycleTheme = () => {
|
|
19
|
+
const nextTheme = getNextTheme(currentTheme);
|
|
20
|
+
UnistylesRuntime.setTheme(nextTheme as any);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const sidebarWidth = sidebarCollapsed ? 0 : 280;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<View background='primary' style={{ height: '100vh', flexDirection: 'column', overflow: 'hidden' }}>
|
|
27
|
+
{/* Header */}
|
|
28
|
+
<View style={{
|
|
29
|
+
paddingRight: 32,
|
|
30
|
+
borderBottomWidth: 1,
|
|
31
|
+
borderBottomStyle: 'solid',
|
|
32
|
+
borderBottomColor: theme.colors.border.primary,
|
|
33
|
+
flexDirection: 'row',
|
|
34
|
+
alignItems: 'center',
|
|
35
|
+
justifyContent: 'space-between',
|
|
36
|
+
}}>
|
|
37
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
38
|
+
<Button
|
|
39
|
+
type="text"
|
|
40
|
+
leftIcon="menu"
|
|
41
|
+
size="lg"
|
|
42
|
+
onPress={() => setSidebarCollapsed(!sidebarCollapsed)}
|
|
43
|
+
style={{ padding: 8 }}
|
|
44
|
+
/>
|
|
45
|
+
<Button type='text' size="lg">
|
|
46
|
+
<Text weight="bold" size="lg">
|
|
47
|
+
Idealyst Components
|
|
48
|
+
</Text>
|
|
49
|
+
</Button>
|
|
50
|
+
<Badge color='green'>
|
|
51
|
+
{getThemeDisplayName(currentTheme)}
|
|
52
|
+
</Badge>
|
|
53
|
+
</View>
|
|
54
|
+
|
|
55
|
+
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
|
|
56
|
+
<Button
|
|
57
|
+
leftIcon="magnify"
|
|
58
|
+
size='lg'
|
|
59
|
+
intent='neutral'
|
|
60
|
+
type='text'
|
|
61
|
+
onPress={() => setShowSearch(true)}
|
|
62
|
+
/>
|
|
63
|
+
<Button
|
|
64
|
+
type="outlined"
|
|
65
|
+
intent="primary"
|
|
66
|
+
size="sm"
|
|
67
|
+
onPress={cycleTheme}
|
|
68
|
+
>
|
|
69
|
+
Cycle Theme
|
|
70
|
+
</Button>
|
|
71
|
+
</View>
|
|
72
|
+
</View>
|
|
73
|
+
|
|
74
|
+
{/* Search Dialog */}
|
|
75
|
+
<ExampleSearchDialog open={showSearch} onOpenChange={setShowSearch} />
|
|
76
|
+
|
|
77
|
+
{/* Main Content Area with Sidebar */}
|
|
78
|
+
<View style={{ flex: 1, flexDirection: 'row', overflow: 'hidden' }}>
|
|
79
|
+
{/* Sidebar */}
|
|
80
|
+
<View style={{
|
|
81
|
+
width: sidebarWidth,
|
|
82
|
+
height: '100%',
|
|
83
|
+
borderRightWidth: sidebarCollapsed ? 0 : 1,
|
|
84
|
+
borderRightStyle: 'solid',
|
|
85
|
+
borderRightColor: theme.colors.border.primary,
|
|
86
|
+
transition: 'width 0.3s ease, border-right-width 0.3s ease',
|
|
87
|
+
overflow: 'hidden',
|
|
88
|
+
flexShrink: 0,
|
|
89
|
+
}}>
|
|
90
|
+
{!sidebarCollapsed && (
|
|
91
|
+
<ExampleSidebar />
|
|
92
|
+
)}
|
|
93
|
+
</View>
|
|
94
|
+
|
|
95
|
+
{/* Content Area */}
|
|
96
|
+
<View style={{
|
|
97
|
+
flex: 1,
|
|
98
|
+
overflowY: 'auto',
|
|
99
|
+
}}>
|
|
100
|
+
<View style={{ padding: 24, maxWidth: 1200, margin: '0 auto', width: '100%' }}>
|
|
101
|
+
<Outlet key={currentTheme} />
|
|
102
|
+
</View>
|
|
103
|
+
</View>
|
|
104
|
+
</View>
|
|
105
|
+
</View>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button, Icon, View } from '@idealyst/components';
|
|
3
|
+
import { UnistylesRuntime } from 'react-native-unistyles';
|
|
4
|
+
import ExampleSearchDialog from './ExampleSearchDialog';
|
|
5
|
+
|
|
6
|
+
export default function HeaderRight() {
|
|
7
|
+
const [isDark, setIsDark] = useState(false);
|
|
8
|
+
const [showDialog, setShowDialog] = useState(false);
|
|
9
|
+
|
|
10
|
+
const toggleTheme = () => {
|
|
11
|
+
const newTheme = isDark ? 'light' : 'dark';
|
|
12
|
+
UnistylesRuntime.setTheme(newTheme);
|
|
13
|
+
setIsDark(!isDark);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, }}>
|
|
18
|
+
<Button style={{ padding: 0, margin: 0, }} type='text' onPress={() => setShowDialog(true)} >
|
|
19
|
+
<Icon name='magnify' intent='neutral' size={'md'} />
|
|
20
|
+
</Button>
|
|
21
|
+
<Button type='text' size='sm' onPress={toggleTheme}>
|
|
22
|
+
<Icon color={isDark ? 'yellow' : 'blue.800'} name={isDark ? 'weather-sunny' : 'moon-waning-crescent'} size='md' />
|
|
23
|
+
</Button>
|
|
24
|
+
<ExampleSearchDialog open={showDialog} onOpenChange={setShowDialog} />
|
|
25
|
+
</View>
|
|
26
|
+
);
|
|
27
|
+
}
|
package/src/examples/index.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export { default as
|
|
2
|
-
export { default as ExampleTabRouter } from './ExampleTabRouter';
|
|
3
|
-
export { default as ExampleDrawerRouter } from './ExampleDrawerRouter';
|
|
4
|
-
export { default as ExampleHybridRouter } from './ExampleHybridRouter';
|
|
1
|
+
export { default as ExampleNavigationRouter } from './ExampleNavigationRouter';
|
|
5
2
|
export { default as CustomStackLayout } from './CustomStackLayout';
|
|
6
|
-
export { default as CustomTabLayout } from './CustomTabLayout';
|
|
3
|
+
export { default as CustomTabLayout } from './CustomTabLayout';
|
|
4
|
+
export { ExampleWebLayout } from './ExampleWebLayout';
|
|
@@ -1,31 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { highContrastThemes } from './highContrastThemes';
|
|
1
|
+
import { lightTheme, darkTheme } from '@idealyst/theme';
|
|
3
2
|
import { StyleSheet } from 'react-native-unistyles';
|
|
4
3
|
|
|
5
4
|
// Extend UnistylesThemes to include high contrast themes
|
|
6
5
|
// This overrides the more limited declaration from the components package
|
|
7
6
|
declare module 'react-native-unistyles' {
|
|
8
7
|
export interface UnistylesThemes {
|
|
9
|
-
light: typeof
|
|
10
|
-
dark: typeof
|
|
11
|
-
lightHighContrast: typeof highContrastThemes.lightHighContrast;
|
|
12
|
-
darkHighContrast: typeof highContrastThemes.darkHighContrast;
|
|
8
|
+
light: typeof lightTheme,
|
|
9
|
+
dark: typeof darkTheme,
|
|
13
10
|
}
|
|
14
11
|
}
|
|
15
|
-
|
|
12
|
+
|
|
16
13
|
// Configure with all themes, including high contrast variants
|
|
17
14
|
// This will override any previous configuration
|
|
18
15
|
StyleSheet.configure({
|
|
19
16
|
themes: {
|
|
20
|
-
light:
|
|
21
|
-
dark:
|
|
22
|
-
lightHighContrast: highContrastThemes.lightHighContrast,
|
|
23
|
-
darkHighContrast: highContrastThemes.darkHighContrast,
|
|
17
|
+
light: lightTheme,
|
|
18
|
+
dark: darkTheme,
|
|
24
19
|
},
|
|
25
20
|
settings: {
|
|
26
21
|
initialTheme: 'light',
|
|
27
22
|
},
|
|
28
|
-
breakpoints,
|
|
29
23
|
});
|
|
30
24
|
|
|
31
25
|
// Export theme names for easy reference
|
package/src/index.native.ts
CHANGED
package/src/index.web.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Web-specific exports
|
|
2
2
|
export * from './index';
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
export {
|
|
4
|
+
// Direct export to fix module resolution
|
|
5
|
+
export { NavigatorProvider, useNavigator } from './context/NavigatorContext.web';
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { View, Text } from '@idealyst/components'
|
|
3
3
|
import { StackLayoutProps } from '../routing/types'
|
|
4
|
-
import { Outlet } from '
|
|
5
|
-
|
|
4
|
+
import { Outlet } from '../router'
|
|
5
|
+
import { useNavigator } from '../context'
|
|
6
6
|
export interface DefaultStackLayoutProps extends StackLayoutProps {
|
|
7
|
-
onNavigate: (path: string) => void
|
|
8
7
|
currentPath: string
|
|
9
8
|
}
|
|
10
9
|
|
|
@@ -15,9 +14,11 @@ export interface DefaultStackLayoutProps extends StackLayoutProps {
|
|
|
15
14
|
export const DefaultStackLayout: React.FC<DefaultStackLayoutProps> = ({
|
|
16
15
|
options,
|
|
17
16
|
routes,
|
|
18
|
-
onNavigate,
|
|
19
17
|
currentPath
|
|
20
18
|
}) => {
|
|
19
|
+
|
|
20
|
+
const navigator = useNavigator()
|
|
21
|
+
|
|
21
22
|
return (
|
|
22
23
|
<View style={{ height: '100vh', flexDirection: 'column' }}>
|
|
23
24
|
{/* Header */}
|
|
@@ -34,7 +35,7 @@ export const DefaultStackLayout: React.FC<DefaultStackLayoutProps> = ({
|
|
|
34
35
|
|
|
35
36
|
{options.headerTitle && (
|
|
36
37
|
typeof options.headerTitle === 'string' ? (
|
|
37
|
-
<Text size="
|
|
38
|
+
<Text size="lg" weight="bold" style={{ marginLeft: options.headerLeft ? 12 : 0 }}>
|
|
38
39
|
{options.headerTitle}
|
|
39
40
|
</Text>
|
|
40
41
|
) : (
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { View, Text, Button, Icon } from '@idealyst/components'
|
|
3
3
|
import { TabLayoutProps } from '../routing/types'
|
|
4
|
-
import { Outlet } from '
|
|
4
|
+
import { Outlet } from '../router'
|
|
5
|
+
import { useNavigator } from '../context'
|
|
5
6
|
|
|
6
7
|
export interface DefaultTabLayoutProps extends TabLayoutProps {}
|
|
7
8
|
|
|
@@ -12,9 +13,11 @@ export interface DefaultTabLayoutProps extends TabLayoutProps {}
|
|
|
12
13
|
export const DefaultTabLayout: React.FC<DefaultTabLayoutProps> = ({
|
|
13
14
|
options,
|
|
14
15
|
routes,
|
|
15
|
-
onNavigate,
|
|
16
16
|
currentPath
|
|
17
17
|
}) => {
|
|
18
|
+
|
|
19
|
+
const navigator = useNavigator()
|
|
20
|
+
|
|
18
21
|
return (
|
|
19
22
|
<View style={{ height: '100vh', flexDirection: 'column' }}>
|
|
20
23
|
{/* Header */}
|
|
@@ -30,7 +33,7 @@ export const DefaultTabLayout: React.FC<DefaultTabLayoutProps> = ({
|
|
|
30
33
|
{options.headerLeft && React.createElement(options.headerLeft as any)}
|
|
31
34
|
|
|
32
35
|
{typeof options.headerTitle === 'string' ? (
|
|
33
|
-
<Text size="
|
|
36
|
+
<Text size="lg" weight="bold" style={{ marginLeft: options.headerLeft ? 12 : 0 }}>
|
|
34
37
|
{options.headerTitle}
|
|
35
38
|
</Text>
|
|
36
39
|
) : (
|
|
@@ -64,8 +67,10 @@ export const DefaultTabLayout: React.FC<DefaultTabLayoutProps> = ({
|
|
|
64
67
|
key={route.path}
|
|
65
68
|
variant={isActive ? 'contained' : 'outlined'}
|
|
66
69
|
intent={isActive ? 'primary' : undefined}
|
|
67
|
-
size="
|
|
68
|
-
onPress={() =>
|
|
70
|
+
size="sm"
|
|
71
|
+
onPress={() => navigator.navigate({
|
|
72
|
+
path: route.fullPath
|
|
73
|
+
})}
|
|
69
74
|
style={{
|
|
70
75
|
flexDirection: 'row',
|
|
71
76
|
alignItems: 'center',
|
|
@@ -80,7 +85,7 @@ export const DefaultTabLayout: React.FC<DefaultTabLayoutProps> = ({
|
|
|
80
85
|
/>
|
|
81
86
|
)}
|
|
82
87
|
<Text
|
|
83
|
-
size="
|
|
88
|
+
size="sm"
|
|
84
89
|
color={isActive ? 'white' : 'primary'}
|
|
85
90
|
weight={isActive ? 'semibold' : 'medium'}
|
|
86
91
|
>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Native platforms don't use React Router
|
|
2
|
+
// This is a stub to maintain cross-platform compatibility
|
|
3
|
+
|
|
4
|
+
export const BrowserRouter = null;
|
|
5
|
+
export const HashRouter = null;
|
|
6
|
+
export const MemoryRouter = null;
|
|
7
|
+
export const Router = null;
|
|
8
|
+
export const useNavigate = () => null;
|
|
9
|
+
export const useLocation = () => null;
|
|
10
|
+
export const useParams = () => null;
|
|
11
|
+
export const useSearchParams = () => null;
|
|
12
|
+
export const Navigate = null;
|
|
13
|
+
export const Outlet = null;
|
|
14
|
+
export const Route = null;
|
|
15
|
+
export const Routes = null;
|
|
16
|
+
export const Link = null;
|
|
17
|
+
export const NavLink = null;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Export all React Router modules from a centralized location
|
|
2
|
+
// This prevents duplication issues when multiple packages need React Router
|
|
3
|
+
|
|
4
|
+
// Re-export everything from react-router-dom
|
|
5
|
+
// This includes all react-router exports plus the DOM-specific ones
|
|
6
|
+
export * from 'react-router-dom';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DrawerContentComponentProps } from '@react-navigation/drawer';
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
|
+
import { DrawerNavigatorProvider } from '../context/index.native';
|
|
5
|
+
import { NavigatorParam, DrawerSidebarProps } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Wrapper that renders the sidebar component and passes safe area insets
|
|
9
|
+
* The NavigatorContext is already provided by NavigatorProvider higher up in the tree,
|
|
10
|
+
* so the sidebar can use useNavigator hook directly
|
|
11
|
+
*/
|
|
12
|
+
export const DrawerContentWrapper: React.FC<{
|
|
13
|
+
content: React.ComponentType<DrawerSidebarProps>;
|
|
14
|
+
route: NavigatorParam;
|
|
15
|
+
drawerProps: DrawerContentComponentProps;
|
|
16
|
+
}> = ({ content: Content, route, drawerProps }) => {
|
|
17
|
+
// Get safe area insets from React Native Safe Area Context
|
|
18
|
+
const insets = useSafeAreaInsets();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<DrawerNavigatorProvider navigation={drawerProps.navigation} route={route}>
|
|
22
|
+
<Content insets={insets} />
|
|
23
|
+
</DrawerNavigatorProvider>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DrawerNavigatorProvider } from '../context/index.native';
|
|
3
|
+
import { NavigatorParam } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Wrapper that renders the header component with navigation context
|
|
7
|
+
* This allows header components to use useNavigator and access route info
|
|
8
|
+
*/
|
|
9
|
+
export const HeaderWrapper: React.FC<{
|
|
10
|
+
content: React.ComponentType<any>;
|
|
11
|
+
route: NavigatorParam;
|
|
12
|
+
navigation: any;
|
|
13
|
+
}> = ({ content: Content, route, navigation }) => {
|
|
14
|
+
return (
|
|
15
|
+
<DrawerNavigatorProvider navigation={navigation} route={route}>
|
|
16
|
+
<Content />
|
|
17
|
+
</DrawerNavigatorProvider>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -1,21 +1,129 @@
|
|
|
1
|
-
import { NavigatorParam, RouteParam } from './types'
|
|
1
|
+
import { NavigatorParam, RouteParam, ScreenOptions } from './types'
|
|
2
2
|
|
|
3
3
|
import { TypedNavigator } from "@react-navigation/native";
|
|
4
4
|
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
|
5
5
|
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
|
6
|
+
import { createDrawerNavigator } from "@react-navigation/drawer";
|
|
7
|
+
import { DrawerContentWrapper } from './DrawerContentWrapper.native';
|
|
8
|
+
import { HeaderWrapper } from './HeaderWrapper.native';
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { useUnistyles } from 'react-native-unistyles';
|
|
11
|
+
import { useIsFocused } from '@react-navigation/native';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Wrapper that makes screen components reactive to theme changes
|
|
15
|
+
* Only updates when the screen is focused
|
|
16
|
+
*/
|
|
17
|
+
const ThemeAwareScreenWrapper: React.FC<{
|
|
18
|
+
Component: React.ComponentType<any>;
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}> = ({ Component, ...props }) => {
|
|
21
|
+
const isFocused = useIsFocused();
|
|
22
|
+
|
|
23
|
+
// Force update mechanism
|
|
24
|
+
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
|
25
|
+
|
|
26
|
+
// Subscribe to theme changes
|
|
27
|
+
const { rt } = useUnistyles();
|
|
28
|
+
|
|
29
|
+
// Force re-render when theme changes (only when focused)
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (isFocused) {
|
|
32
|
+
console.log('[ThemeAwareScreenWrapper] Theme changed, forcing update. New theme:', rt.themeName);
|
|
33
|
+
forceUpdate();
|
|
34
|
+
}
|
|
35
|
+
}, [rt.themeName, isFocused]);
|
|
36
|
+
|
|
37
|
+
// Log when component renders
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
if (isFocused) {
|
|
40
|
+
console.log('[ThemeAwareScreenWrapper] Screen rendered with theme:', rt.themeName);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Only render when focused to optimize performance
|
|
45
|
+
if (!isFocused) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return <Component {...props} />;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Cache for wrapped components to maintain stable references across renders
|
|
54
|
+
*/
|
|
55
|
+
const wrappedComponentCache = new WeakMap<React.ComponentType<any>, React.ComponentType<any>>();
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creates a theme-aware component wrapper with a stable reference
|
|
59
|
+
* This prevents React Navigation warnings about inline components
|
|
60
|
+
*/
|
|
61
|
+
const createThemeAwareComponent = (OriginalComponent: React.ComponentType<any>) => {
|
|
62
|
+
// Check cache first to return the same wrapped component reference
|
|
63
|
+
if (wrappedComponentCache.has(OriginalComponent)) {
|
|
64
|
+
return wrappedComponentCache.get(OriginalComponent)!;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const Wrapped = React.memo((props: any) => (
|
|
68
|
+
<ThemeAwareScreenWrapper Component={OriginalComponent} {...props} />
|
|
69
|
+
));
|
|
70
|
+
Wrapped.displayName = `ThemeAware(${OriginalComponent.displayName || OriginalComponent.name || 'Component'})`;
|
|
71
|
+
|
|
72
|
+
// Store in cache for future lookups
|
|
73
|
+
wrappedComponentCache.set(OriginalComponent, Wrapped);
|
|
74
|
+
|
|
75
|
+
return Wrapped;
|
|
76
|
+
};
|
|
6
77
|
|
|
7
78
|
/**
|
|
8
79
|
* Build the Mobile navigator using React Navigation
|
|
9
|
-
* @param params
|
|
10
|
-
* @param parentPath
|
|
11
|
-
* @returns
|
|
80
|
+
* @param params
|
|
81
|
+
* @param parentPath
|
|
82
|
+
* @returns
|
|
12
83
|
*/
|
|
13
84
|
export const buildNavigator = (params: NavigatorParam, parentPath = '') => {
|
|
14
85
|
const NavigatorType = getNavigatorType(params);
|
|
86
|
+
|
|
87
|
+
// Wrap screenOptions to provide navigation context to headerRight
|
|
88
|
+
const screenOptions = params.options?.headerRight
|
|
89
|
+
? (navProps: any) => {
|
|
90
|
+
const baseOptions = typeof params.options === 'function'
|
|
91
|
+
? params.options(navProps)
|
|
92
|
+
: params.options;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
...baseOptions,
|
|
96
|
+
headerRight: () => (
|
|
97
|
+
<HeaderWrapper
|
|
98
|
+
content={baseOptions.headerRight}
|
|
99
|
+
route={params}
|
|
100
|
+
navigation={navProps.navigation}
|
|
101
|
+
/>
|
|
102
|
+
),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
: params.options;
|
|
106
|
+
|
|
107
|
+
// Special handling for drawer navigator with custom sidebar
|
|
108
|
+
if (params.layout === 'drawer' && params.sidebarComponent) {
|
|
109
|
+
return () => (
|
|
110
|
+
<NavigatorType.Navigator
|
|
111
|
+
screenOptions={screenOptions}
|
|
112
|
+
drawerContent={(drawerProps: any) => (
|
|
113
|
+
<DrawerContentWrapper
|
|
114
|
+
route={params}
|
|
115
|
+
content={params.sidebarComponent!}
|
|
116
|
+
drawerProps={drawerProps}
|
|
117
|
+
/>
|
|
118
|
+
)}
|
|
119
|
+
>
|
|
120
|
+
{params.routes.map((child, index) => buildScreen(child, NavigatorType, parentPath, index))}
|
|
121
|
+
</NavigatorType.Navigator>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
15
125
|
return () => (
|
|
16
|
-
<NavigatorType.Navigator screenOptions={
|
|
17
|
-
headerShown: params.options?.headerShown
|
|
18
|
-
}}>
|
|
126
|
+
<NavigatorType.Navigator screenOptions={screenOptions}>
|
|
19
127
|
{params.routes.map((child, index) => buildScreen(child, NavigatorType, parentPath, index))}
|
|
20
128
|
</NavigatorType.Navigator>
|
|
21
129
|
)
|
|
@@ -23,8 +131,8 @@ export const buildNavigator = (params: NavigatorParam, parentPath = '') => {
|
|
|
23
131
|
|
|
24
132
|
/**
|
|
25
133
|
* Get Navigator Type
|
|
26
|
-
* @param params
|
|
27
|
-
* @returns
|
|
134
|
+
* @param params
|
|
135
|
+
* @returns
|
|
28
136
|
*/
|
|
29
137
|
const getNavigatorType = (params: NavigatorParam) => {
|
|
30
138
|
switch (params.layout) {
|
|
@@ -32,16 +140,18 @@ const getNavigatorType = (params: NavigatorParam) => {
|
|
|
32
140
|
return createNativeStackNavigator();
|
|
33
141
|
case 'tab':
|
|
34
142
|
return createBottomTabNavigator();
|
|
143
|
+
case 'drawer':
|
|
144
|
+
return createDrawerNavigator();
|
|
35
145
|
}
|
|
36
146
|
throw new Error(`Unsupported navigator type: ${params.layout}`);
|
|
37
147
|
}
|
|
38
148
|
|
|
39
149
|
/**
|
|
40
150
|
* Build Screen
|
|
41
|
-
* @param params
|
|
42
|
-
* @param Navigator
|
|
43
|
-
* @param parentPath
|
|
44
|
-
* @returns
|
|
151
|
+
* @param params
|
|
152
|
+
* @param Navigator
|
|
153
|
+
* @param parentPath
|
|
154
|
+
* @returns
|
|
45
155
|
*/
|
|
46
156
|
const buildScreen = (params: RouteParam, Navigator: TypedNavigator, parentPath = '', index: number) => {
|
|
47
157
|
// Build the full path by combining parent path with current route path
|
|
@@ -56,20 +166,20 @@ const buildScreen = (params: RouteParam, Navigator: TypedNavigator, parentPath =
|
|
|
56
166
|
const routePath = params.path.startsWith('/') ? params.path.slice(1) : params.path;
|
|
57
167
|
fullPath = `${parentPath}/${routePath}`;
|
|
58
168
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
169
|
+
|
|
170
|
+
// Determine the component - wrap screens with ThemeAwareScreenWrapper
|
|
171
|
+
let component: React.ComponentType<any>;
|
|
172
|
+
if (params.type === 'screen') {
|
|
173
|
+
component = createThemeAwareComponent(params.component);
|
|
174
|
+
} else {
|
|
175
|
+
component = buildNavigator(params, fullPath);
|
|
176
|
+
}
|
|
177
|
+
|
|
68
178
|
return (
|
|
69
179
|
<Navigator.Screen
|
|
70
180
|
key={`${fullPath}-${index}`}
|
|
71
181
|
name={fullPath}
|
|
72
|
-
component={
|
|
182
|
+
component={component}
|
|
73
183
|
options={params.options}
|
|
74
184
|
/>
|
|
75
185
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { Routes, Route } from '
|
|
2
|
+
import { Routes, Route } from '../router'
|
|
3
3
|
import { DefaultStackLayout } from '../layouts/DefaultStackLayout'
|
|
4
4
|
import { DefaultTabLayout } from '../layouts/DefaultTabLayout'
|
|
5
5
|
import { NavigatorParam, RouteParam } from './types'
|
|
@@ -13,7 +13,7 @@ import { NavigatorParam, RouteParam } from './types'
|
|
|
13
13
|
export const buildNavigator = (params: NavigatorParam, parentPath = '') => {
|
|
14
14
|
return () => (
|
|
15
15
|
<Routes>
|
|
16
|
-
{params
|
|
16
|
+
{buildRoute(params, 0, false)}
|
|
17
17
|
</Routes>
|
|
18
18
|
)
|
|
19
19
|
}
|
|
@@ -63,7 +63,6 @@ const buildRoute = (params: RouteParam, index: number, isNested = false) => {
|
|
|
63
63
|
<LayoutComponent
|
|
64
64
|
options={params.options}
|
|
65
65
|
routes={routesWithFullPaths}
|
|
66
|
-
onNavigate={() => {}} // Layout components can use their own navigation logic
|
|
67
66
|
currentPath=""
|
|
68
67
|
/>
|
|
69
68
|
}
|