@idealyst/navigation 1.0.49 → 1.0.50
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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
2
|
+
|
|
3
|
+
export const tabBarLayoutStyles = StyleSheet.create(theme => ({
|
|
4
|
+
container: {
|
|
5
|
+
flex: 1,
|
|
6
|
+
backgroundColor: theme.colors.background,
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
headerContainer: {
|
|
10
|
+
flexDirection: 'row',
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
justifyContent: 'space-between',
|
|
13
|
+
backgroundColor: theme.colors.surface,
|
|
14
|
+
borderBottomWidth: 1,
|
|
15
|
+
borderBottomColor: theme.colors.border,
|
|
16
|
+
paddingHorizontal: theme.spacing?.md,
|
|
17
|
+
zIndex: 10,
|
|
18
|
+
web: {
|
|
19
|
+
backgroundColor: '#ffffff', // Light background for web
|
|
20
|
+
borderBottomColor: '#e0e0e0', // Light border for web
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
headerContent: {
|
|
25
|
+
flex: 1,
|
|
26
|
+
flexDirection: 'row',
|
|
27
|
+
alignItems: 'center',
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
headerRightContent: {
|
|
31
|
+
flexDirection: 'row',
|
|
32
|
+
alignItems: 'center',
|
|
33
|
+
gap: theme.spacing?.sm,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
headerTabs: {
|
|
37
|
+
flexDirection: 'row',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
gap: theme.spacing?.xs,
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
bodyContainer: {
|
|
43
|
+
flex: 1,
|
|
44
|
+
position: 'relative',
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
mainContent: {
|
|
48
|
+
flex: 1,
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
contentArea: {
|
|
52
|
+
flex: 1,
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
tabBarBottom: {
|
|
56
|
+
flexDirection: 'row',
|
|
57
|
+
backgroundColor: theme.colors.surface,
|
|
58
|
+
borderTopWidth: 1,
|
|
59
|
+
borderTopColor: theme.colors.border,
|
|
60
|
+
paddingTop: 6,
|
|
61
|
+
paddingHorizontal: 0,
|
|
62
|
+
web: {
|
|
63
|
+
backgroundColor: '#ffffff', // Light background for web
|
|
64
|
+
borderTopColor: '#e0e0e0', // Light border for web
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
tabButton: {
|
|
69
|
+
flex: 1,
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
justifyContent: 'center',
|
|
72
|
+
paddingVertical: 6,
|
|
73
|
+
paddingHorizontal: 4,
|
|
74
|
+
minHeight: 49,
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
tabButtonHeader: {
|
|
78
|
+
flex: 0,
|
|
79
|
+
flexDirection: 'row',
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
justifyContent: 'center',
|
|
82
|
+
paddingVertical: theme.spacing?.xs,
|
|
83
|
+
paddingHorizontal: theme.spacing?.md,
|
|
84
|
+
borderRadius: theme.radius?.md,
|
|
85
|
+
minHeight: 36,
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
tabButtonActive: {
|
|
89
|
+
// Let native platform handle active state styling
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
tabButtonDisabled: {
|
|
93
|
+
opacity: 0.5,
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
tabIconContainer: {
|
|
97
|
+
alignItems: 'center',
|
|
98
|
+
justifyContent: 'center',
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
tabIcon: {
|
|
102
|
+
width: 24,
|
|
103
|
+
height: 24,
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
tabLabel: {
|
|
107
|
+
fontSize: 10,
|
|
108
|
+
marginTop: 2,
|
|
109
|
+
color: theme.colors.onSurface,
|
|
110
|
+
textAlign: 'center',
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
tabLabelHeader: {
|
|
114
|
+
fontSize: 14,
|
|
115
|
+
marginLeft: theme.spacing?.xs,
|
|
116
|
+
color: theme.colors.onSurface,
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
tabLabelActive: {
|
|
120
|
+
color: theme.colors.primary,
|
|
121
|
+
fontWeight: '600',
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
tabBadge: {
|
|
125
|
+
position: 'absolute',
|
|
126
|
+
top: -4,
|
|
127
|
+
right: -8,
|
|
128
|
+
backgroundColor: theme.colors.error,
|
|
129
|
+
borderRadius: 10,
|
|
130
|
+
minWidth: 16,
|
|
131
|
+
height: 16,
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
justifyContent: 'center',
|
|
134
|
+
paddingHorizontal: 4,
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
tabBadgeText: {
|
|
138
|
+
color: theme.colors.onError,
|
|
139
|
+
fontSize: 10,
|
|
140
|
+
fontWeight: 'bold',
|
|
141
|
+
},
|
|
142
|
+
}));
|
|
@@ -0,0 +1,286 @@
|
|
|
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
|
+
|
|
6
|
+
const DEFAULT_AUTO_BREAKPOINT = 768;
|
|
7
|
+
|
|
8
|
+
// Web doesn't need safe area insets
|
|
9
|
+
const useSafeAreaInsets = () => ({ top: 0, bottom: 0, left: 0, right: 0 });
|
|
10
|
+
|
|
11
|
+
const WebHeader: React.FC<{
|
|
12
|
+
config: any;
|
|
13
|
+
styles: any;
|
|
14
|
+
showTabsInHeader: boolean;
|
|
15
|
+
tabBarConfig: TabBarConfig;
|
|
16
|
+
insets: any;
|
|
17
|
+
}> = ({ config, styles, showTabsInHeader, tabBarConfig, insets }) => {
|
|
18
|
+
const handleBackPress = () => {
|
|
19
|
+
if (config.onBackPress) {
|
|
20
|
+
config.onBackPress();
|
|
21
|
+
} else {
|
|
22
|
+
// Web history back
|
|
23
|
+
window.history.back();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const headerDynamicStyles = {
|
|
28
|
+
height: config.height,
|
|
29
|
+
minHeight: config.height,
|
|
30
|
+
paddingTop: config.enabled ? insets.top : 0,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View
|
|
35
|
+
style={[
|
|
36
|
+
styles.headerContainer,
|
|
37
|
+
headerDynamicStyles,
|
|
38
|
+
config.style,
|
|
39
|
+
]}
|
|
40
|
+
>
|
|
41
|
+
<View style={styles.headerContent}>
|
|
42
|
+
{/* Custom content overrides native elements */}
|
|
43
|
+
{config.content ? (
|
|
44
|
+
config.content
|
|
45
|
+
) : (
|
|
46
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
|
|
47
|
+
{/* Back Button */}
|
|
48
|
+
{config.showBackButton && (
|
|
49
|
+
<Button
|
|
50
|
+
variant="text"
|
|
51
|
+
onPress={handleBackPress}
|
|
52
|
+
style={{ marginRight: 8, minWidth: 'auto' }}
|
|
53
|
+
>
|
|
54
|
+
<Icon name="arrow-left" size="lg" color="primary" />
|
|
55
|
+
</Button>
|
|
56
|
+
)}
|
|
57
|
+
|
|
58
|
+
{/* Title */}
|
|
59
|
+
{config.title && (
|
|
60
|
+
<Text
|
|
61
|
+
size="large"
|
|
62
|
+
weight="semibold"
|
|
63
|
+
style={{ flex: 1 }}
|
|
64
|
+
>
|
|
65
|
+
{config.title}
|
|
66
|
+
</Text>
|
|
67
|
+
)}
|
|
68
|
+
</View>
|
|
69
|
+
)}
|
|
70
|
+
</View>
|
|
71
|
+
|
|
72
|
+
<View style={styles.headerRightContent}>
|
|
73
|
+
{/* Tabs in header for wide screens */}
|
|
74
|
+
{showTabsInHeader && tabBarConfig.items.length > 0 && (
|
|
75
|
+
<TabBar config={tabBarConfig} position="header" />
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
{/* Additional right content */}
|
|
79
|
+
{config.rightContent}
|
|
80
|
+
</View>
|
|
81
|
+
</View>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const TabButton: React.FC<TabButtonProps> = ({ item, isActive, onPress, position }) => {
|
|
86
|
+
const styles = tabBarLayoutStyles;
|
|
87
|
+
const isHeader = position === 'header';
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Pressable
|
|
91
|
+
onPress={onPress}
|
|
92
|
+
disabled={item.disabled}
|
|
93
|
+
style={[
|
|
94
|
+
isHeader ? styles.tabButtonHeader : styles.tabButton,
|
|
95
|
+
isActive && styles.tabButtonActive,
|
|
96
|
+
item.disabled && styles.tabButtonDisabled,
|
|
97
|
+
]}
|
|
98
|
+
>
|
|
99
|
+
<View style={styles.tabIconContainer}>
|
|
100
|
+
{item.icon && (
|
|
101
|
+
typeof item.icon === 'string' ? (
|
|
102
|
+
<Icon name={item.icon as any} size={isHeader ? "lg" : "md"} color={isActive ? 'primary' : 'secondary'} />
|
|
103
|
+
) : (
|
|
104
|
+
item.icon
|
|
105
|
+
)
|
|
106
|
+
)}
|
|
107
|
+
{item.badge !== undefined && (
|
|
108
|
+
<View style={styles.tabBadge}>
|
|
109
|
+
<Text style={styles.tabBadgeText}>
|
|
110
|
+
{typeof item.badge === 'number' && item.badge > 99 ? '99+' : item.badge}
|
|
111
|
+
</Text>
|
|
112
|
+
</View>
|
|
113
|
+
)}
|
|
114
|
+
</View>
|
|
115
|
+
{(!isHeader || (isHeader && item.label)) && (
|
|
116
|
+
<Text
|
|
117
|
+
size={isHeader ? "medium" : "small"}
|
|
118
|
+
color={isActive ? 'primary' : 'secondary'}
|
|
119
|
+
style={isHeader ? { marginLeft: 8 } : { marginTop: 2, textAlign: 'center', fontSize: 10 }}
|
|
120
|
+
>
|
|
121
|
+
{item.label}
|
|
122
|
+
</Text>
|
|
123
|
+
)}
|
|
124
|
+
</Pressable>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const TabBar: React.FC<{
|
|
129
|
+
config: TabBarConfig;
|
|
130
|
+
position: 'bottom' | 'header';
|
|
131
|
+
}> = ({ config, position }) => {
|
|
132
|
+
const styles = tabBarLayoutStyles;
|
|
133
|
+
|
|
134
|
+
if (config.renderTabBar) {
|
|
135
|
+
return (
|
|
136
|
+
<>
|
|
137
|
+
{config.renderTabBar({
|
|
138
|
+
items: config.items,
|
|
139
|
+
activeTab: config.activeTab,
|
|
140
|
+
onTabSelect: config.onTabSelect,
|
|
141
|
+
position,
|
|
142
|
+
})}
|
|
143
|
+
</>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const containerStyle = position === 'header'
|
|
148
|
+
? styles.headerTabs
|
|
149
|
+
: [styles.tabBarBottom, config.style];
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<View style={containerStyle}>
|
|
153
|
+
{config.items.map((item) => {
|
|
154
|
+
if (item.renderTab) {
|
|
155
|
+
return (
|
|
156
|
+
<React.Fragment key={item.id}>
|
|
157
|
+
{item.renderTab({
|
|
158
|
+
item,
|
|
159
|
+
isActive: item.id === config.activeTab,
|
|
160
|
+
onPress: () => config.onTabSelect?.(item.id),
|
|
161
|
+
position,
|
|
162
|
+
})}
|
|
163
|
+
</React.Fragment>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<TabButton
|
|
169
|
+
key={item.id}
|
|
170
|
+
item={item}
|
|
171
|
+
isActive={item.id === config.activeTab}
|
|
172
|
+
onPress={() => config.onTabSelect?.(item.id)}
|
|
173
|
+
position={position}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
})}
|
|
177
|
+
</View>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const TabBarLayout: React.FC<TabBarLayoutProps> = ({
|
|
182
|
+
children,
|
|
183
|
+
tabBar = {},
|
|
184
|
+
header = {},
|
|
185
|
+
style,
|
|
186
|
+
testID,
|
|
187
|
+
}) => {
|
|
188
|
+
const styles = tabBarLayoutStyles;
|
|
189
|
+
const insets = useSafeAreaInsets();
|
|
190
|
+
const [screenWidth, setScreenWidth] = useState(() => window.innerWidth);
|
|
191
|
+
|
|
192
|
+
// Default tab bar configuration
|
|
193
|
+
const tabBarConfig = {
|
|
194
|
+
items: [],
|
|
195
|
+
activeTab: undefined,
|
|
196
|
+
onTabSelect: undefined,
|
|
197
|
+
position: 'auto' as const,
|
|
198
|
+
autoBreakpoint: DEFAULT_AUTO_BREAKPOINT,
|
|
199
|
+
style: undefined,
|
|
200
|
+
tabStyle: undefined,
|
|
201
|
+
showLabels: true,
|
|
202
|
+
renderTabBar: undefined,
|
|
203
|
+
...tabBar,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Default header configuration
|
|
207
|
+
const headerConfig = {
|
|
208
|
+
enabled: true,
|
|
209
|
+
height: 64,
|
|
210
|
+
title: undefined,
|
|
211
|
+
showBackButton: false,
|
|
212
|
+
onBackPress: undefined,
|
|
213
|
+
content: null,
|
|
214
|
+
rightContent: null,
|
|
215
|
+
style: undefined,
|
|
216
|
+
showTabs: true,
|
|
217
|
+
native: true,
|
|
218
|
+
...header,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Update screen width on window resize
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
const updateDimensions = () => {
|
|
224
|
+
setScreenWidth(window.innerWidth);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
window.addEventListener('resize', updateDimensions);
|
|
228
|
+
return () => window.removeEventListener('resize', updateDimensions);
|
|
229
|
+
}, []);
|
|
230
|
+
|
|
231
|
+
// Determine actual tab position
|
|
232
|
+
const actualTabPosition = (() => {
|
|
233
|
+
if (tabBarConfig.position === 'auto') {
|
|
234
|
+
// On web, use header tabs for wide screens
|
|
235
|
+
return screenWidth >= tabBarConfig.autoBreakpoint ? 'header' : 'bottom';
|
|
236
|
+
}
|
|
237
|
+
return tabBarConfig.position;
|
|
238
|
+
})();
|
|
239
|
+
|
|
240
|
+
const showTabsInHeader = actualTabPosition === 'header' && headerConfig.showTabs;
|
|
241
|
+
const showTabsAtBottom = actualTabPosition === 'bottom';
|
|
242
|
+
|
|
243
|
+
// Create dynamic styles for bottom tab bar (no safe area needed on web)
|
|
244
|
+
const tabBarBottomStyles = {
|
|
245
|
+
paddingBottom: 0,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<View
|
|
250
|
+
style={[
|
|
251
|
+
styles.container,
|
|
252
|
+
style,
|
|
253
|
+
]}
|
|
254
|
+
testID={testID}
|
|
255
|
+
>
|
|
256
|
+
{/* Header */}
|
|
257
|
+
{headerConfig.enabled && (
|
|
258
|
+
<WebHeader
|
|
259
|
+
config={headerConfig}
|
|
260
|
+
styles={styles}
|
|
261
|
+
showTabsInHeader={showTabsInHeader}
|
|
262
|
+
tabBarConfig={tabBarConfig}
|
|
263
|
+
insets={insets}
|
|
264
|
+
/>
|
|
265
|
+
)}
|
|
266
|
+
|
|
267
|
+
{/* Main Content Area */}
|
|
268
|
+
<View style={styles.bodyContainer}>
|
|
269
|
+
<View style={styles.mainContent}>
|
|
270
|
+
<View style={styles.contentArea}>
|
|
271
|
+
{children}
|
|
272
|
+
</View>
|
|
273
|
+
</View>
|
|
274
|
+
</View>
|
|
275
|
+
|
|
276
|
+
{/* Bottom Tab Bar */}
|
|
277
|
+
{showTabsAtBottom && tabBarConfig.items.length > 0 && (
|
|
278
|
+
<View style={tabBarBottomStyles}>
|
|
279
|
+
<TabBar config={tabBarConfig} position="bottom" />
|
|
280
|
+
</View>
|
|
281
|
+
)}
|
|
282
|
+
</View>
|
|
283
|
+
);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
export default TabBarLayout;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface TabBarItem {
|
|
4
|
+
/**
|
|
5
|
+
* Unique identifier for the tab
|
|
6
|
+
*/
|
|
7
|
+
id: string;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Display label for the tab
|
|
11
|
+
*/
|
|
12
|
+
label: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Icon for the tab (can be a component or icon name)
|
|
16
|
+
*/
|
|
17
|
+
icon?: ReactNode | string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Badge content to display on the tab
|
|
21
|
+
*/
|
|
22
|
+
badge?: string | number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether the tab is disabled
|
|
26
|
+
*/
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Custom render function for the tab button
|
|
31
|
+
*/
|
|
32
|
+
renderTab?: (props: TabButtonProps) => ReactNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TabButtonProps {
|
|
36
|
+
item: TabBarItem;
|
|
37
|
+
isActive: boolean;
|
|
38
|
+
onPress: () => void;
|
|
39
|
+
position: 'bottom' | 'header';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TabBarConfig {
|
|
43
|
+
/**
|
|
44
|
+
* Array of tab items
|
|
45
|
+
*/
|
|
46
|
+
items: TabBarItem[];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Currently active tab ID
|
|
50
|
+
*/
|
|
51
|
+
activeTab?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Callback when tab is selected
|
|
55
|
+
*/
|
|
56
|
+
onTabSelect?: (tabId: string) => void;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Position of tab bar
|
|
60
|
+
* - 'bottom': Traditional mobile tab bar at bottom
|
|
61
|
+
* - 'header': Tabs integrated into header (for wider screens)
|
|
62
|
+
* - 'auto': Automatically choose based on screen width
|
|
63
|
+
*/
|
|
64
|
+
position?: 'bottom' | 'header' | 'auto';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Breakpoint for auto position switching (default: 768px)
|
|
68
|
+
*/
|
|
69
|
+
autoBreakpoint?: number;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Custom styles for tab bar container
|
|
73
|
+
*/
|
|
74
|
+
style?: any;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Custom styles for tab buttons
|
|
78
|
+
*/
|
|
79
|
+
tabStyle?: any;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether to show labels with icons
|
|
83
|
+
*/
|
|
84
|
+
showLabels?: boolean;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Custom component for rendering tab bar
|
|
88
|
+
*/
|
|
89
|
+
renderTabBar?: (props: TabBarRenderProps) => ReactNode;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface TabBarRenderProps {
|
|
93
|
+
items: TabBarItem[];
|
|
94
|
+
activeTab?: string;
|
|
95
|
+
onTabSelect?: (tabId: string) => void;
|
|
96
|
+
position: 'bottom' | 'header';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface TabBarHeaderConfig {
|
|
100
|
+
/**
|
|
101
|
+
* Whether the header is enabled
|
|
102
|
+
*/
|
|
103
|
+
enabled?: boolean;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Height of the header
|
|
107
|
+
*/
|
|
108
|
+
height?: number;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Header title (native-style)
|
|
112
|
+
*/
|
|
113
|
+
title?: string;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Whether to show back button (auto-detected from navigation stack)
|
|
117
|
+
*/
|
|
118
|
+
showBackButton?: boolean;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Custom back button handler (overrides native navigation)
|
|
122
|
+
*/
|
|
123
|
+
onBackPress?: () => void;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Content to display in the header (left side) - overrides native elements
|
|
127
|
+
*/
|
|
128
|
+
content?: ReactNode;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Content to display on the right side of header
|
|
132
|
+
*/
|
|
133
|
+
rightContent?: ReactNode;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Custom styles for the header
|
|
137
|
+
*/
|
|
138
|
+
style?: any;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Whether to show tabs in header (when position is 'header')
|
|
142
|
+
*/
|
|
143
|
+
showTabs?: boolean;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Use native header styling (platform-specific)
|
|
147
|
+
*/
|
|
148
|
+
native?: boolean;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface TabBarLayoutProps {
|
|
152
|
+
/**
|
|
153
|
+
* The main content to display
|
|
154
|
+
*/
|
|
155
|
+
children?: ReactNode;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Tab bar configuration
|
|
159
|
+
*/
|
|
160
|
+
tabBar?: TabBarConfig;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Header configuration
|
|
164
|
+
*/
|
|
165
|
+
header?: TabBarHeaderConfig;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Additional styles for the layout container
|
|
169
|
+
*/
|
|
170
|
+
style?: any;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Test ID for testing
|
|
174
|
+
*/
|
|
175
|
+
testID?: string;
|
|
176
|
+
}
|
package/src/layouts/index.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from './GeneralLayout';
|
|
1
|
+
export * from './GeneralLayout';
|
|
2
|
+
export * from './TabBarLayout';
|