@idealyst/navigation 1.0.49 → 1.0.51
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/examples/ExampleTabRouter.tsx +149 -118
- package/src/layouts/GeneralLayout/GeneralLayout.tsx +0 -1
- package/src/layouts/TabBarLayout/TabBarLayout.native.tsx +283 -0
- package/src/layouts/TabBarLayout/TabBarLayout.styles.ts +142 -0
- package/src/layouts/TabBarLayout/TabBarLayout.web.tsx +286 -0
- package/src/layouts/TabBarLayout/index.native.ts +2 -0
- package/src/layouts/TabBarLayout/index.ts +2 -0
- package/src/layouts/TabBarLayout/index.web.ts +2 -0
- package/src/layouts/TabBarLayout/types.ts +176 -0
- package/src/layouts/index.ts +2 -1
- package/src/routing/router.native.tsx +88 -7
- package/src/routing/router.web.tsx +294 -7
- package/src/routing/types.ts +59 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/navigation",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.51",
|
|
4
4
|
"description": "Cross-platform navigation library for React and React Native",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"module": "src/index.ts",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"publish:npm": "npm publish"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@idealyst/components": "^1.0.
|
|
41
|
-
"@idealyst/theme": "^1.0.
|
|
40
|
+
"@idealyst/components": "^1.0.51",
|
|
41
|
+
"@idealyst/theme": "^1.0.51",
|
|
42
42
|
"@react-navigation/bottom-tabs": "^7.0.0",
|
|
43
43
|
"@react-navigation/drawer": "^7.0.0",
|
|
44
44
|
"@react-navigation/native": "^7.0.0",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { DatePickerExamples } from "../../../datepicker/src/examples";
|
|
5
|
-
import { Screen, Text, View, Button } from "../../../components/src";
|
|
2
|
+
import { ButtonExamples, CardExamples, IconExamples, ThemeExtensionExamples } from "../../../components/src/examples";
|
|
3
|
+
import { Screen, Text, View, Button, Icon } from "../../../components/src";
|
|
6
4
|
import { UnistylesRuntime } from 'react-native-unistyles';
|
|
7
5
|
import { RouteParam } from '../routing';
|
|
6
|
+
import { useNavigator } from '../context';
|
|
8
7
|
import { getNextTheme, getThemeDisplayName, isHighContrastTheme } from './unistyles';
|
|
9
8
|
|
|
10
9
|
const HomeTabScreen = () => {
|
|
10
|
+
const navigator = useNavigator();
|
|
11
11
|
const currentTheme = UnistylesRuntime.themeName || 'light';
|
|
12
12
|
|
|
13
13
|
const cycleTheme = () => {
|
|
@@ -21,10 +21,8 @@ const HomeTabScreen = () => {
|
|
|
21
21
|
let newTheme: string;
|
|
22
22
|
|
|
23
23
|
if (isHighContrastTheme(currentTheme)) {
|
|
24
|
-
// Switch to standard variant
|
|
25
24
|
newTheme = currentTheme.includes('dark') ? 'dark' : 'light';
|
|
26
25
|
} else {
|
|
27
|
-
// Switch to high contrast variant
|
|
28
26
|
newTheme = currentTheme.includes('dark') ? 'darkHighContrast' : 'lightHighContrast';
|
|
29
27
|
}
|
|
30
28
|
|
|
@@ -34,32 +32,58 @@ const HomeTabScreen = () => {
|
|
|
34
32
|
|
|
35
33
|
return (
|
|
36
34
|
<Screen>
|
|
37
|
-
<View>
|
|
35
|
+
<View spacing="lg">
|
|
38
36
|
<Text size="large" weight="bold">
|
|
39
|
-
|
|
37
|
+
TabBar Navigation Demo
|
|
40
38
|
</Text>
|
|
41
39
|
<Text size="medium">
|
|
42
|
-
|
|
40
|
+
This demonstrates native tab navigation with screen options
|
|
43
41
|
</Text>
|
|
44
42
|
|
|
45
|
-
<View style={{ marginTop: 24
|
|
46
|
-
<Text size="medium" weight="semibold">
|
|
47
|
-
Theme Controls
|
|
48
|
-
</Text>
|
|
43
|
+
<View spacing="md" style={{ marginTop: 24 }}>
|
|
44
|
+
<Text size="medium" weight="semibold">Navigation Tabs</Text>
|
|
49
45
|
<Text size="small">
|
|
50
|
-
|
|
46
|
+
The tabs below use native React Navigation with screen options for icons and labels.
|
|
47
|
+
On mobile, tabs appear at the bottom. On web, they may adapt based on the platform.
|
|
51
48
|
</Text>
|
|
52
49
|
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
<View spacing="sm">
|
|
51
|
+
<Button size="small" variant="outlined" onPress={() => navigator.navigate({ path: '/components', vars: {} })}>
|
|
52
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
53
|
+
<Icon name="view-dashboard" size="sm" style={{ marginRight: 8 }} />
|
|
54
|
+
<Text>Components Tab</Text>
|
|
55
|
+
</View>
|
|
56
|
+
</Button>
|
|
57
|
+
<Button size="small" variant="outlined" onPress={() => navigator.navigate({ path: '/settings', vars: {} })}>
|
|
58
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
59
|
+
<Icon name="cog" size="sm" style={{ marginRight: 8 }} />
|
|
60
|
+
<Text>Settings Tab</Text>
|
|
61
|
+
</View>
|
|
62
|
+
</Button>
|
|
63
|
+
<Button size="small" variant="outlined" onPress={() => navigator.navigate({ path: '/theme', vars: {} })}>
|
|
64
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
65
|
+
<Icon name="palette" size="sm" style={{ marginRight: 8 }} />
|
|
66
|
+
<Text>Theme Tab</Text>
|
|
67
|
+
</View>
|
|
68
|
+
</Button>
|
|
69
|
+
</View>
|
|
70
|
+
</View>
|
|
71
|
+
|
|
72
|
+
<View spacing="md" style={{ marginTop: 24 }}>
|
|
73
|
+
<Text size="medium" weight="semibold">Theme Controls</Text>
|
|
74
|
+
<Text size="small">Current Theme: {getThemeDisplayName(currentTheme)}</Text>
|
|
56
75
|
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
<View style={{ flexDirection: 'row', marginTop: 12 }}>
|
|
77
|
+
<Button variant="outlined" onPress={cycleTheme} style={{ marginRight: 12 }}>
|
|
78
|
+
Cycle Theme
|
|
79
|
+
</Button>
|
|
80
|
+
<Button variant="outlined" onPress={toggleHighContrast}>
|
|
81
|
+
Toggle High Contrast
|
|
82
|
+
</Button>
|
|
83
|
+
</View>
|
|
60
84
|
|
|
61
85
|
{isHighContrastTheme(currentTheme) && (
|
|
62
|
-
<Text size="small" style={{ fontStyle: 'italic' }}>
|
|
86
|
+
<Text size="small" style={{ fontStyle: 'italic', marginTop: 8 }}>
|
|
63
87
|
♿ High contrast mode is active for better accessibility
|
|
64
88
|
</Text>
|
|
65
89
|
)}
|
|
@@ -69,93 +93,67 @@ const HomeTabScreen = () => {
|
|
|
69
93
|
);
|
|
70
94
|
};
|
|
71
95
|
|
|
72
|
-
const
|
|
73
|
-
<Screen>
|
|
74
|
-
<AvatarExamples />
|
|
75
|
-
</Screen>
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
const BadgeTabScreen = () => (
|
|
79
|
-
<Screen>
|
|
80
|
-
<BadgeExamples />
|
|
81
|
-
</Screen>
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const ButtonTabScreen = () => (
|
|
85
|
-
<Screen>
|
|
86
|
-
<ButtonExamples />
|
|
87
|
-
</Screen>
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
const CardTabScreen = () => (
|
|
91
|
-
<Screen>
|
|
92
|
-
<CardExamples />
|
|
93
|
-
</Screen>
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
const CheckboxTabScreen = () => (
|
|
96
|
+
const ComponentsTabScreen = () => (
|
|
97
97
|
<Screen>
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
</Screen>
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
const ViewTabScreen = () => (
|
|
121
|
-
<Screen>
|
|
122
|
-
<ViewExamples />
|
|
123
|
-
</Screen>
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
const IconTabScreen = () => (
|
|
127
|
-
<Screen>
|
|
128
|
-
<IconExamples />
|
|
129
|
-
</Screen>
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
const DialogTabScreen = () => (
|
|
133
|
-
<Screen>
|
|
134
|
-
<DialogExamples />
|
|
135
|
-
</Screen>
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
const PopoverTabScreen = () => (
|
|
139
|
-
<Screen>
|
|
140
|
-
<PopoverExamples />
|
|
141
|
-
</Screen>
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
const DataGridTabScreen = () => (
|
|
145
|
-
<Screen>
|
|
146
|
-
<DataGridShowcase />
|
|
98
|
+
<View spacing="lg">
|
|
99
|
+
<Text size="large" weight="bold">Components</Text>
|
|
100
|
+
<Text>Explore UI components with native tab navigation</Text>
|
|
101
|
+
|
|
102
|
+
<View spacing="md">
|
|
103
|
+
<Text size="medium" weight="semibold">Button Examples</Text>
|
|
104
|
+
<ButtonExamples />
|
|
105
|
+
</View>
|
|
106
|
+
|
|
107
|
+
<View spacing="md">
|
|
108
|
+
<Text size="medium" weight="semibold">Card Examples</Text>
|
|
109
|
+
<CardExamples />
|
|
110
|
+
</View>
|
|
111
|
+
|
|
112
|
+
<View spacing="md">
|
|
113
|
+
<Text size="medium" weight="semibold">Icon Examples</Text>
|
|
114
|
+
<IconExamples />
|
|
115
|
+
</View>
|
|
116
|
+
</View>
|
|
147
117
|
</Screen>
|
|
148
118
|
);
|
|
149
119
|
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
120
|
+
const SettingsTabScreen = () => {
|
|
121
|
+
const currentTheme = UnistylesRuntime.themeName || 'light';
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<Screen>
|
|
125
|
+
<View spacing="lg">
|
|
126
|
+
<Text size="large" weight="bold">Settings</Text>
|
|
127
|
+
<Text>Configure the TabBar demo settings</Text>
|
|
128
|
+
|
|
129
|
+
<View spacing="md">
|
|
130
|
+
<Text size="medium" weight="semibold">Navigation Info</Text>
|
|
131
|
+
<Text size="small">
|
|
132
|
+
This tab uses native navigation with screen options:
|
|
133
|
+
</Text>
|
|
134
|
+
<Text size="small">• tabBarIcon: "cog" (Material Design Icons)</Text>
|
|
135
|
+
<Text size="small">• tabBarLabel: "Settings"</Text>
|
|
136
|
+
<Text size="small">• title: "Settings"</Text>
|
|
137
|
+
</View>
|
|
138
|
+
|
|
139
|
+
<View spacing="md">
|
|
140
|
+
<Text size="medium" weight="semibold">Current State</Text>
|
|
141
|
+
<Text size="small">Theme: {getThemeDisplayName(currentTheme)}</Text>
|
|
142
|
+
<Text size="small">Platform: React Navigation (Native) / React Router (Web)</Text>
|
|
143
|
+
</View>
|
|
144
|
+
</View>
|
|
145
|
+
</Screen>
|
|
146
|
+
);
|
|
147
|
+
};
|
|
155
148
|
|
|
156
|
-
const
|
|
149
|
+
const ThemeTabScreen = () => (
|
|
157
150
|
<Screen>
|
|
158
|
-
<
|
|
151
|
+
<View spacing="lg">
|
|
152
|
+
<Text size="large" weight="bold">Theme System</Text>
|
|
153
|
+
<Text>Explore the theme extension system</Text>
|
|
154
|
+
|
|
155
|
+
<ThemeExtensionExamples />
|
|
156
|
+
</View>
|
|
159
157
|
</Screen>
|
|
160
158
|
);
|
|
161
159
|
|
|
@@ -165,23 +163,56 @@ const TabRouter: RouteParam = {
|
|
|
165
163
|
layout: {
|
|
166
164
|
type: "tab",
|
|
167
165
|
},
|
|
166
|
+
screenOptions: {
|
|
167
|
+
title: 'Home',
|
|
168
|
+
tabBarLabel: 'Home',
|
|
169
|
+
tabBarIcon: ({ focused, size }) => {
|
|
170
|
+
return <Icon name="home" color={focused ? 'blue' : 'black'} size={size} />
|
|
171
|
+
},
|
|
172
|
+
},
|
|
168
173
|
routes: [
|
|
169
|
-
{
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
{
|
|
174
|
+
{
|
|
175
|
+
path: "components",
|
|
176
|
+
component: ComponentsTabScreen,
|
|
177
|
+
screenOptions: {
|
|
178
|
+
title: 'Components',
|
|
179
|
+
tabBarLabel: 'Components',
|
|
180
|
+
tabBarIcon: (props) => {
|
|
181
|
+
if (props.focused) {
|
|
182
|
+
return <Icon name="view-dashboard" color={props.focused ? 'blue' : 'black'} size={props.size} />;
|
|
183
|
+
}
|
|
184
|
+
return <Icon name="view-dashboard-outline" color={props.color} size={props.size} />;
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
path: "settings",
|
|
190
|
+
component: SettingsTabScreen,
|
|
191
|
+
screenOptions: {
|
|
192
|
+
title: 'Settings',
|
|
193
|
+
tabBarLabel: 'Settings',
|
|
194
|
+
tabBarIcon: ({ focused, size }) => (
|
|
195
|
+
<Icon
|
|
196
|
+
name="cog"
|
|
197
|
+
color={focused ? 'blue' : 'black'}
|
|
198
|
+
size={size}
|
|
199
|
+
style={{ opacity: focused ? 1 : 0.8 }}
|
|
200
|
+
/>
|
|
201
|
+
),
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
path: "theme",
|
|
206
|
+
component: ThemeTabScreen,
|
|
207
|
+
screenOptions: {
|
|
208
|
+
title: 'Theme',
|
|
209
|
+
tabBarLabel: 'Theme',
|
|
210
|
+
tabBarIcon: ({ focused, size }) => (
|
|
211
|
+
<Icon name="palette" color={focused ? 'blue' : 'black'} size={size} />
|
|
212
|
+
),
|
|
213
|
+
},
|
|
214
|
+
},
|
|
184
215
|
],
|
|
185
|
-
}
|
|
216
|
+
};
|
|
186
217
|
|
|
187
218
|
export default TabRouter;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { View, Text, Button, Icon, Pressable } from '@idealyst/components';
|
|
3
|
+
import { TabBarLayoutProps, TabBarConfig, TabButtonProps } from './types';
|
|
4
|
+
import { tabBarLayoutStyles } from './TabBarLayout.styles';
|
|
5
|
+
import { Dimensions } from 'react-native';
|
|
6
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_AUTO_BREAKPOINT = 768;
|
|
9
|
+
|
|
10
|
+
const NativeHeader: React.FC<{
|
|
11
|
+
config: any;
|
|
12
|
+
styles: any;
|
|
13
|
+
showTabsInHeader: boolean;
|
|
14
|
+
tabBarConfig: TabBarConfig;
|
|
15
|
+
insets: any;
|
|
16
|
+
}> = ({ config, styles, showTabsInHeader, tabBarConfig, insets }) => {
|
|
17
|
+
const handleBackPress = () => {
|
|
18
|
+
if (config.onBackPress) {
|
|
19
|
+
config.onBackPress();
|
|
20
|
+
}
|
|
21
|
+
// Could integrate with React Navigation here
|
|
22
|
+
// navigation.goBack();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const headerDynamicStyles = {
|
|
26
|
+
height: config.height,
|
|
27
|
+
minHeight: config.height,
|
|
28
|
+
paddingTop: config.enabled ? insets.top : 0,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<View
|
|
33
|
+
style={[
|
|
34
|
+
styles.headerContainer,
|
|
35
|
+
headerDynamicStyles,
|
|
36
|
+
config.style,
|
|
37
|
+
]}
|
|
38
|
+
>
|
|
39
|
+
<View style={styles.headerContent}>
|
|
40
|
+
{/* Custom content overrides native elements */}
|
|
41
|
+
{config.content ? (
|
|
42
|
+
config.content
|
|
43
|
+
) : (
|
|
44
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
|
|
45
|
+
{/* Back Button */}
|
|
46
|
+
{config.showBackButton && (
|
|
47
|
+
<Button
|
|
48
|
+
variant="text"
|
|
49
|
+
onPress={handleBackPress}
|
|
50
|
+
style={{ marginRight: 8, minWidth: 'auto' }}
|
|
51
|
+
>
|
|
52
|
+
<Icon name="arrow-left" size="lg" color="primary" />
|
|
53
|
+
</Button>
|
|
54
|
+
)}
|
|
55
|
+
|
|
56
|
+
{/* Title */}
|
|
57
|
+
{config.title && (
|
|
58
|
+
<Text
|
|
59
|
+
size="large"
|
|
60
|
+
weight="semibold"
|
|
61
|
+
style={{ flex: 1 }}
|
|
62
|
+
>
|
|
63
|
+
{config.title}
|
|
64
|
+
</Text>
|
|
65
|
+
)}
|
|
66
|
+
</View>
|
|
67
|
+
)}
|
|
68
|
+
</View>
|
|
69
|
+
|
|
70
|
+
<View style={styles.headerRightContent}>
|
|
71
|
+
{/* Tabs in header for wide screens */}
|
|
72
|
+
{showTabsInHeader && tabBarConfig.items.length > 0 && (
|
|
73
|
+
<TabBar config={tabBarConfig} position="header" />
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{/* Additional right content */}
|
|
77
|
+
{config.rightContent}
|
|
78
|
+
</View>
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const TabButton: React.FC<TabButtonProps> = ({ item, isActive, onPress, position }) => {
|
|
84
|
+
const styles = tabBarLayoutStyles;
|
|
85
|
+
const isHeader = position === 'header';
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Pressable
|
|
89
|
+
onPress={onPress}
|
|
90
|
+
disabled={item.disabled}
|
|
91
|
+
style={[
|
|
92
|
+
isHeader ? styles.tabButtonHeader : styles.tabButton,
|
|
93
|
+
isActive && styles.tabButtonActive,
|
|
94
|
+
item.disabled && styles.tabButtonDisabled,
|
|
95
|
+
]}
|
|
96
|
+
>
|
|
97
|
+
<View style={styles.tabIconContainer}>
|
|
98
|
+
{item.icon && (
|
|
99
|
+
typeof item.icon === 'string' ? (
|
|
100
|
+
<Icon name={item.icon as any} size={isHeader ? "lg" : "md"} color={isActive ? 'primary' : 'secondary'} />
|
|
101
|
+
) : (
|
|
102
|
+
item.icon
|
|
103
|
+
)
|
|
104
|
+
)}
|
|
105
|
+
{item.badge !== undefined && (
|
|
106
|
+
<View style={styles.tabBadge}>
|
|
107
|
+
<Text style={styles.tabBadgeText}>
|
|
108
|
+
{typeof item.badge === 'number' && item.badge > 99 ? '99+' : item.badge}
|
|
109
|
+
</Text>
|
|
110
|
+
</View>
|
|
111
|
+
)}
|
|
112
|
+
</View>
|
|
113
|
+
{(!isHeader || (isHeader && item.label)) && (
|
|
114
|
+
<Text
|
|
115
|
+
size={isHeader ? "medium" : "small"}
|
|
116
|
+
color={isActive ? 'primary' : 'secondary'}
|
|
117
|
+
style={isHeader ? { marginLeft: 8 } : { marginTop: 2, textAlign: 'center', fontSize: 10 }}
|
|
118
|
+
>
|
|
119
|
+
{item.label}
|
|
120
|
+
</Text>
|
|
121
|
+
)}
|
|
122
|
+
</Pressable>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const TabBar: React.FC<{
|
|
127
|
+
config: TabBarConfig;
|
|
128
|
+
position: 'bottom' | 'header';
|
|
129
|
+
}> = ({ config, position }) => {
|
|
130
|
+
const styles = tabBarLayoutStyles;
|
|
131
|
+
|
|
132
|
+
if (config.renderTabBar) {
|
|
133
|
+
return (
|
|
134
|
+
<>
|
|
135
|
+
{config.renderTabBar({
|
|
136
|
+
items: config.items,
|
|
137
|
+
activeTab: config.activeTab,
|
|
138
|
+
onTabSelect: config.onTabSelect,
|
|
139
|
+
position,
|
|
140
|
+
})}
|
|
141
|
+
</>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const containerStyle = position === 'header'
|
|
146
|
+
? styles.headerTabs
|
|
147
|
+
: [styles.tabBarBottom, config.style];
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<View style={containerStyle}>
|
|
151
|
+
{config.items.map((item) => {
|
|
152
|
+
if (item.renderTab) {
|
|
153
|
+
return (
|
|
154
|
+
<React.Fragment key={item.id}>
|
|
155
|
+
{item.renderTab({
|
|
156
|
+
item,
|
|
157
|
+
isActive: item.id === config.activeTab,
|
|
158
|
+
onPress: () => config.onTabSelect?.(item.id),
|
|
159
|
+
position,
|
|
160
|
+
})}
|
|
161
|
+
</React.Fragment>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<TabButton
|
|
167
|
+
key={item.id}
|
|
168
|
+
item={item}
|
|
169
|
+
isActive={item.id === config.activeTab}
|
|
170
|
+
onPress={() => config.onTabSelect?.(item.id)}
|
|
171
|
+
position={position}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
})}
|
|
175
|
+
</View>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const TabBarLayout: React.FC<TabBarLayoutProps> = ({
|
|
180
|
+
children,
|
|
181
|
+
tabBar = {},
|
|
182
|
+
header = {},
|
|
183
|
+
style,
|
|
184
|
+
testID,
|
|
185
|
+
}) => {
|
|
186
|
+
const styles = tabBarLayoutStyles;
|
|
187
|
+
const insets = useSafeAreaInsets();
|
|
188
|
+
const [screenWidth, setScreenWidth] = useState(() => Dimensions.get('window').width);
|
|
189
|
+
|
|
190
|
+
// Default tab bar configuration
|
|
191
|
+
const tabBarConfig = {
|
|
192
|
+
items: [],
|
|
193
|
+
activeTab: undefined,
|
|
194
|
+
onTabSelect: undefined,
|
|
195
|
+
position: 'auto' as const,
|
|
196
|
+
autoBreakpoint: DEFAULT_AUTO_BREAKPOINT,
|
|
197
|
+
style: undefined,
|
|
198
|
+
tabStyle: undefined,
|
|
199
|
+
showLabels: true,
|
|
200
|
+
renderTabBar: undefined,
|
|
201
|
+
...tabBar,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Default header configuration
|
|
205
|
+
const headerConfig = {
|
|
206
|
+
enabled: true,
|
|
207
|
+
height: 64,
|
|
208
|
+
title: undefined,
|
|
209
|
+
showBackButton: false,
|
|
210
|
+
onBackPress: undefined,
|
|
211
|
+
content: null,
|
|
212
|
+
rightContent: null,
|
|
213
|
+
style: undefined,
|
|
214
|
+
showTabs: true,
|
|
215
|
+
native: true,
|
|
216
|
+
...header,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Update screen width on orientation change
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
const updateDimensions = () => {
|
|
222
|
+
setScreenWidth(Dimensions.get('window').width);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const subscription = Dimensions.addEventListener('change', updateDimensions);
|
|
226
|
+
return () => subscription?.remove();
|
|
227
|
+
}, []);
|
|
228
|
+
|
|
229
|
+
// Determine actual tab position - native always uses bottom for mobile
|
|
230
|
+
const actualTabPosition = (() => {
|
|
231
|
+
if (tabBarConfig.position === 'auto') {
|
|
232
|
+
return 'bottom'; // Native mobile apps typically use bottom tabs
|
|
233
|
+
}
|
|
234
|
+
return tabBarConfig.position;
|
|
235
|
+
})();
|
|
236
|
+
|
|
237
|
+
const showTabsInHeader = actualTabPosition === 'header' && headerConfig.showTabs;
|
|
238
|
+
const showTabsAtBottom = actualTabPosition === 'bottom';
|
|
239
|
+
|
|
240
|
+
// Create dynamic styles for bottom tab bar
|
|
241
|
+
const tabBarBottomStyles = {
|
|
242
|
+
paddingBottom: showTabsAtBottom ? insets.bottom : 0,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<View
|
|
247
|
+
style={[
|
|
248
|
+
styles.container,
|
|
249
|
+
style,
|
|
250
|
+
]}
|
|
251
|
+
testID={testID}
|
|
252
|
+
>
|
|
253
|
+
{/* Header */}
|
|
254
|
+
{headerConfig.enabled && (
|
|
255
|
+
<NativeHeader
|
|
256
|
+
config={headerConfig}
|
|
257
|
+
styles={styles}
|
|
258
|
+
showTabsInHeader={showTabsInHeader}
|
|
259
|
+
tabBarConfig={tabBarConfig}
|
|
260
|
+
insets={insets}
|
|
261
|
+
/>
|
|
262
|
+
)}
|
|
263
|
+
|
|
264
|
+
{/* Main Content Area */}
|
|
265
|
+
<View style={styles.bodyContainer}>
|
|
266
|
+
<View style={styles.mainContent}>
|
|
267
|
+
<View style={styles.contentArea}>
|
|
268
|
+
{children}
|
|
269
|
+
</View>
|
|
270
|
+
</View>
|
|
271
|
+
</View>
|
|
272
|
+
|
|
273
|
+
{/* Bottom Tab Bar */}
|
|
274
|
+
{showTabsAtBottom && tabBarConfig.items.length > 0 && (
|
|
275
|
+
<View style={tabBarBottomStyles}>
|
|
276
|
+
<TabBar config={tabBarConfig} position="bottom" />
|
|
277
|
+
</View>
|
|
278
|
+
)}
|
|
279
|
+
</View>
|
|
280
|
+
);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
export default TabBarLayout;
|