@react-navigation/bottom-tabs 7.0.0-alpha.1 → 7.0.0-alpha.10
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/lib/commonjs/TransitionConfigs/SceneStyleInterpolators.js +47 -0
- package/lib/commonjs/TransitionConfigs/SceneStyleInterpolators.js.map +1 -0
- package/lib/commonjs/TransitionConfigs/TransitionPresets.js +17 -0
- package/lib/commonjs/TransitionConfigs/TransitionPresets.js.map +1 -0
- package/lib/commonjs/TransitionConfigs/TransitionSpecs.js +22 -0
- package/lib/commonjs/TransitionConfigs/TransitionSpecs.js.map +1 -0
- package/lib/commonjs/index.js +9 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/navigators/createBottomTabNavigator.js +5 -4
- package/lib/commonjs/navigators/createBottomTabNavigator.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/utils/BottomTabBarHeightCallbackContext.js +3 -4
- package/lib/commonjs/utils/BottomTabBarHeightCallbackContext.js.map +1 -1
- package/lib/commonjs/utils/BottomTabBarHeightContext.js +3 -4
- package/lib/commonjs/utils/BottomTabBarHeightContext.js.map +1 -1
- package/lib/commonjs/utils/useAnimatedHashMap.js +31 -0
- package/lib/commonjs/utils/useAnimatedHashMap.js.map +1 -0
- package/lib/commonjs/utils/useBottomTabBarHeight.js +2 -2
- package/lib/commonjs/utils/useBottomTabBarHeight.js.map +1 -1
- package/lib/commonjs/utils/useIsKeyboardShown.js +2 -2
- package/lib/commonjs/utils/useIsKeyboardShown.js.map +1 -1
- package/lib/commonjs/views/Badge.js +2 -2
- package/lib/commonjs/views/Badge.js.map +1 -1
- package/lib/commonjs/views/BottomTabBar.js +63 -25
- package/lib/commonjs/views/BottomTabBar.js.map +1 -1
- package/lib/commonjs/views/BottomTabItem.js +32 -54
- package/lib/commonjs/views/BottomTabItem.js.map +1 -1
- package/lib/commonjs/views/BottomTabView.js +100 -18
- package/lib/commonjs/views/BottomTabView.js.map +1 -1
- package/lib/commonjs/views/ScreenFallback.js +10 -14
- package/lib/commonjs/views/ScreenFallback.js.map +1 -1
- package/lib/commonjs/views/TabBarIcon.js +12 -17
- package/lib/commonjs/views/TabBarIcon.js.map +1 -1
- package/lib/module/TransitionConfigs/SceneStyleInterpolators.js +40 -0
- package/lib/module/TransitionConfigs/SceneStyleInterpolators.js.map +1 -0
- package/lib/module/TransitionConfigs/TransitionPresets.js +11 -0
- package/lib/module/TransitionConfigs/TransitionPresets.js.map +1 -0
- package/lib/module/TransitionConfigs/TransitionSpecs.js +16 -0
- package/lib/module/TransitionConfigs/TransitionSpecs.js.map +1 -0
- package/lib/module/index.js +9 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/navigators/createBottomTabNavigator.js +2 -0
- package/lib/module/navigators/createBottomTabNavigator.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/BottomTabBarHeightCallbackContext.js.map +1 -1
- package/lib/module/utils/BottomTabBarHeightContext.js.map +1 -1
- package/lib/module/utils/useAnimatedHashMap.js +23 -0
- package/lib/module/utils/useAnimatedHashMap.js.map +1 -0
- package/lib/module/utils/useBottomTabBarHeight.js.map +1 -1
- package/lib/module/utils/useIsKeyboardShown.js.map +1 -1
- package/lib/module/views/Badge.js.map +1 -1
- package/lib/module/views/BottomTabBar.js +66 -28
- package/lib/module/views/BottomTabBar.js.map +1 -1
- package/lib/module/views/BottomTabItem.js +34 -56
- package/lib/module/views/BottomTabItem.js.map +1 -1
- package/lib/module/views/BottomTabView.js +99 -17
- package/lib/module/views/BottomTabView.js.map +1 -1
- package/lib/module/views/ScreenFallback.js +8 -12
- package/lib/module/views/ScreenFallback.js.map +1 -1
- package/lib/module/views/TabBarIcon.js +12 -17
- package/lib/module/views/TabBarIcon.js.map +1 -1
- package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts +10 -0
- package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts.map +1 -0
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +4 -0
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -0
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +4 -0
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +7 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts +4 -4
- package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +57 -3
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/useAnimatedHashMap.d.ts +4 -0
- package/lib/typescript/src/utils/useAnimatedHashMap.d.ts.map +1 -0
- package/lib/typescript/src/views/Badge.d.ts +3 -3
- package/lib/typescript/src/views/Badge.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabBar.d.ts +5 -5
- package/lib/typescript/src/views/BottomTabBar.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabItem.d.ts +3 -3
- package/lib/typescript/src/views/BottomTabItem.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabView.d.ts +2 -2
- package/lib/typescript/src/views/BottomTabView.d.ts.map +1 -1
- package/lib/typescript/src/views/ScreenFallback.d.ts +5 -5
- package/lib/typescript/src/views/ScreenFallback.d.ts.map +1 -1
- package/lib/typescript/src/views/TabBarIcon.d.ts +2 -2
- package/lib/typescript/src/views/TabBarIcon.d.ts.map +1 -1
- package/package.json +18 -19
- package/src/TransitionConfigs/SceneStyleInterpolators.tsx +44 -0
- package/src/TransitionConfigs/TransitionPresets.tsx +13 -0
- package/src/TransitionConfigs/TransitionSpecs.tsx +19 -0
- package/src/index.tsx +9 -0
- package/src/navigators/createBottomTabNavigator.tsx +7 -5
- package/src/types.tsx +82 -5
- package/src/utils/useAnimatedHashMap.tsx +25 -0
- package/src/utils/useIsKeyboardShown.tsx +1 -1
- package/src/views/Badge.tsx +6 -1
- package/src/views/BottomTabBar.tsx +123 -45
- package/src/views/BottomTabItem.tsx +51 -81
- package/src/views/BottomTabView.tsx +131 -14
- package/src/views/ScreenFallback.tsx +12 -13
- package/src/views/TabBarIcon.tsx +16 -24
|
@@ -1,24 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getDefaultSidebarWidth,
|
|
3
|
+
getLabel,
|
|
4
|
+
MissingIcon,
|
|
5
|
+
} from '@react-navigation/elements';
|
|
2
6
|
import {
|
|
3
7
|
CommonActions,
|
|
4
8
|
NavigationContext,
|
|
5
9
|
NavigationRouteContext,
|
|
6
|
-
ParamListBase,
|
|
7
|
-
TabNavigationState,
|
|
8
|
-
|
|
10
|
+
type ParamListBase,
|
|
11
|
+
type TabNavigationState,
|
|
12
|
+
useLinkBuilder,
|
|
9
13
|
useTheme,
|
|
10
14
|
} from '@react-navigation/native';
|
|
15
|
+
import Color from 'color';
|
|
11
16
|
import React from 'react';
|
|
12
17
|
import {
|
|
13
18
|
Animated,
|
|
14
|
-
LayoutChangeEvent,
|
|
19
|
+
type LayoutChangeEvent,
|
|
15
20
|
Platform,
|
|
16
|
-
StyleProp,
|
|
21
|
+
type StyleProp,
|
|
17
22
|
StyleSheet,
|
|
23
|
+
useWindowDimensions,
|
|
18
24
|
View,
|
|
19
|
-
ViewStyle,
|
|
25
|
+
type ViewStyle,
|
|
20
26
|
} from 'react-native';
|
|
21
|
-
import { EdgeInsets
|
|
27
|
+
import type { EdgeInsets } from 'react-native-safe-area-context';
|
|
22
28
|
|
|
23
29
|
import type { BottomTabBarProps, BottomTabDescriptorMap } from '../types';
|
|
24
30
|
import { BottomTabBarHeightCallbackContext } from '../utils/BottomTabBarHeightCallbackContext';
|
|
@@ -32,6 +38,7 @@ type Props = BottomTabBarProps & {
|
|
|
32
38
|
const DEFAULT_TABBAR_HEIGHT = 49;
|
|
33
39
|
const COMPACT_TABBAR_HEIGHT = 32;
|
|
34
40
|
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
|
|
41
|
+
const SPACING = 5;
|
|
35
42
|
|
|
36
43
|
const useNativeDriver = Platform.OS !== 'web';
|
|
37
44
|
|
|
@@ -97,8 +104,11 @@ export const getTabBarHeight = ({
|
|
|
97
104
|
insets: EdgeInsets;
|
|
98
105
|
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>> | undefined;
|
|
99
106
|
}) => {
|
|
100
|
-
|
|
101
|
-
const customHeight =
|
|
107
|
+
const flattenedStyle = StyleSheet.flatten(style);
|
|
108
|
+
const customHeight =
|
|
109
|
+
flattenedStyle && 'height' in flattenedStyle
|
|
110
|
+
? flattenedStyle.height
|
|
111
|
+
: undefined;
|
|
102
112
|
|
|
103
113
|
if (typeof customHeight === 'number') {
|
|
104
114
|
return customHeight;
|
|
@@ -133,13 +143,14 @@ export function BottomTabBar({
|
|
|
133
143
|
style,
|
|
134
144
|
}: Props) {
|
|
135
145
|
const { colors } = useTheme();
|
|
136
|
-
const { buildHref } =
|
|
146
|
+
const { buildHref } = useLinkBuilder();
|
|
137
147
|
|
|
138
148
|
const focusedRoute = state.routes[state.index];
|
|
139
149
|
const focusedDescriptor = descriptors[focusedRoute.key];
|
|
140
150
|
const focusedOptions = focusedDescriptor.options;
|
|
141
151
|
|
|
142
152
|
const {
|
|
153
|
+
tabBarPosition = 'bottom',
|
|
143
154
|
tabBarShowLabel,
|
|
144
155
|
tabBarHideOnKeyboard = false,
|
|
145
156
|
tabBarVisibilityAnimationConfig,
|
|
@@ -147,11 +158,18 @@ export function BottomTabBar({
|
|
|
147
158
|
tabBarBackground,
|
|
148
159
|
tabBarActiveTintColor,
|
|
149
160
|
tabBarInactiveTintColor,
|
|
150
|
-
tabBarActiveBackgroundColor
|
|
161
|
+
tabBarActiveBackgroundColor = tabBarPosition !== 'bottom' &&
|
|
162
|
+
tabBarPosition !== 'top'
|
|
163
|
+
? Color(tabBarActiveTintColor ?? colors.primary)
|
|
164
|
+
.alpha(0.12)
|
|
165
|
+
.rgb()
|
|
166
|
+
.string()
|
|
167
|
+
: undefined,
|
|
151
168
|
tabBarInactiveBackgroundColor,
|
|
152
169
|
} = focusedOptions;
|
|
153
170
|
|
|
154
|
-
|
|
171
|
+
// FIXME: useSafeAreaFrame doesn't update values when window is resized on Web
|
|
172
|
+
const dimensions = useWindowDimensions();
|
|
155
173
|
const isKeyboardShown = useIsKeyboardShown();
|
|
156
174
|
|
|
157
175
|
const onHeightChange = React.useContext(BottomTabBarHeightCallbackContext);
|
|
@@ -253,45 +271,69 @@ export function BottomTabBar({
|
|
|
253
271
|
|
|
254
272
|
const tabBarBackgroundElement = tabBarBackground?.();
|
|
255
273
|
|
|
274
|
+
const tabBarIsHorizontal =
|
|
275
|
+
tabBarPosition === 'bottom' || tabBarPosition === 'top';
|
|
276
|
+
|
|
256
277
|
return (
|
|
257
278
|
<Animated.View
|
|
258
279
|
style={[
|
|
259
|
-
|
|
280
|
+
tabBarPosition === 'left'
|
|
281
|
+
? styles.left
|
|
282
|
+
: tabBarPosition === 'right'
|
|
283
|
+
? styles.right
|
|
284
|
+
: styles.bottom,
|
|
260
285
|
{
|
|
261
286
|
backgroundColor:
|
|
262
287
|
tabBarBackgroundElement != null ? 'transparent' : colors.card,
|
|
263
|
-
|
|
288
|
+
borderColor: colors.border,
|
|
264
289
|
},
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
290
|
+
tabBarIsHorizontal
|
|
291
|
+
? [
|
|
292
|
+
{
|
|
293
|
+
transform: [
|
|
294
|
+
{
|
|
295
|
+
translateY: visible.interpolate({
|
|
296
|
+
inputRange: [0, 1],
|
|
297
|
+
outputRange: [
|
|
298
|
+
layout.height +
|
|
299
|
+
paddingBottom +
|
|
300
|
+
StyleSheet.hairlineWidth,
|
|
301
|
+
0,
|
|
302
|
+
],
|
|
303
|
+
}),
|
|
304
|
+
},
|
|
273
305
|
],
|
|
274
|
-
|
|
306
|
+
// Absolutely position the tab bar so that the content is below it
|
|
307
|
+
// This is needed to avoid gap at bottom when the tab bar is hidden
|
|
308
|
+
position: isTabBarHidden ? 'absolute' : undefined,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
height: tabBarHeight,
|
|
312
|
+
paddingBottom,
|
|
313
|
+
paddingHorizontal: Math.max(insets.left, insets.right),
|
|
314
|
+
},
|
|
315
|
+
]
|
|
316
|
+
: {
|
|
317
|
+
paddingTop: insets.top,
|
|
318
|
+
paddingBottom: insets.bottom,
|
|
319
|
+
paddingLeft: tabBarPosition === 'left' ? insets.left : 0,
|
|
320
|
+
paddingRight: tabBarPosition === 'right' ? insets.right : 0,
|
|
321
|
+
minWidth: hasHorizontalLabels
|
|
322
|
+
? getDefaultSidebarWidth(dimensions)
|
|
323
|
+
: 0,
|
|
275
324
|
},
|
|
276
|
-
],
|
|
277
|
-
// Absolutely position the tab bar so that the content is below it
|
|
278
|
-
// This is needed to avoid gap at bottom when the tab bar is hidden
|
|
279
|
-
position: isTabBarHidden ? 'absolute' : (null as any),
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
height: tabBarHeight,
|
|
283
|
-
paddingBottom,
|
|
284
|
-
paddingHorizontal: Math.max(insets.left, insets.right),
|
|
285
|
-
},
|
|
286
325
|
tabBarStyle,
|
|
287
326
|
]}
|
|
288
327
|
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
|
|
289
|
-
onLayout={handleLayout}
|
|
328
|
+
onLayout={tabBarIsHorizontal ? handleLayout : undefined}
|
|
290
329
|
>
|
|
291
330
|
<View pointerEvents="none" style={StyleSheet.absoluteFill}>
|
|
292
331
|
{tabBarBackgroundElement}
|
|
293
332
|
</View>
|
|
294
|
-
<View
|
|
333
|
+
<View
|
|
334
|
+
accessibilityRole="tablist"
|
|
335
|
+
style={tabBarIsHorizontal ? styles.bottomContent : styles.sideContent}
|
|
336
|
+
>
|
|
295
337
|
{routes.map((route, index) => {
|
|
296
338
|
const focused = index === state.index;
|
|
297
339
|
const { options } = descriptors[route.key];
|
|
@@ -319,18 +361,19 @@ export function BottomTabBar({
|
|
|
319
361
|
};
|
|
320
362
|
|
|
321
363
|
const label =
|
|
322
|
-
options.tabBarLabel
|
|
364
|
+
typeof options.tabBarLabel === 'function'
|
|
323
365
|
? options.tabBarLabel
|
|
324
|
-
:
|
|
325
|
-
|
|
326
|
-
|
|
366
|
+
: getLabel(
|
|
367
|
+
{ label: options.tabBarLabel, title: options.title },
|
|
368
|
+
route.name
|
|
369
|
+
);
|
|
327
370
|
|
|
328
371
|
const accessibilityLabel =
|
|
329
372
|
options.tabBarAccessibilityLabel !== undefined
|
|
330
373
|
? options.tabBarAccessibilityLabel
|
|
331
374
|
: typeof label === 'string' && Platform.OS === 'ios'
|
|
332
|
-
|
|
333
|
-
|
|
375
|
+
? `${label}, tab, ${index + 1} of ${routes.length}`
|
|
376
|
+
: undefined;
|
|
334
377
|
|
|
335
378
|
return (
|
|
336
379
|
<NavigationContext.Provider
|
|
@@ -366,7 +409,17 @@ export function BottomTabBar({
|
|
|
366
409
|
showLabel={tabBarShowLabel}
|
|
367
410
|
labelStyle={options.tabBarLabelStyle}
|
|
368
411
|
iconStyle={options.tabBarIconStyle}
|
|
369
|
-
style={
|
|
412
|
+
style={[
|
|
413
|
+
tabBarIsHorizontal
|
|
414
|
+
? styles.bottomItem
|
|
415
|
+
: [
|
|
416
|
+
styles.sideItem,
|
|
417
|
+
hasHorizontalLabels
|
|
418
|
+
? { justifyContent: 'flex-start' }
|
|
419
|
+
: null,
|
|
420
|
+
],
|
|
421
|
+
options.tabBarItemStyle,
|
|
422
|
+
]}
|
|
370
423
|
/>
|
|
371
424
|
</NavigationRouteContext.Provider>
|
|
372
425
|
</NavigationContext.Provider>
|
|
@@ -378,15 +431,40 @@ export function BottomTabBar({
|
|
|
378
431
|
}
|
|
379
432
|
|
|
380
433
|
const styles = StyleSheet.create({
|
|
381
|
-
|
|
434
|
+
left: {
|
|
435
|
+
top: 0,
|
|
436
|
+
bottom: 0,
|
|
437
|
+
left: 0,
|
|
438
|
+
borderRightWidth: StyleSheet.hairlineWidth,
|
|
439
|
+
},
|
|
440
|
+
right: {
|
|
441
|
+
top: 0,
|
|
442
|
+
bottom: 0,
|
|
443
|
+
right: 0,
|
|
444
|
+
borderLeftWidth: StyleSheet.hairlineWidth,
|
|
445
|
+
},
|
|
446
|
+
bottom: {
|
|
382
447
|
left: 0,
|
|
383
448
|
right: 0,
|
|
384
449
|
bottom: 0,
|
|
385
450
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
386
451
|
elevation: 8,
|
|
387
452
|
},
|
|
388
|
-
|
|
453
|
+
bottomContent: {
|
|
389
454
|
flex: 1,
|
|
390
455
|
flexDirection: 'row',
|
|
391
456
|
},
|
|
457
|
+
sideContent: {
|
|
458
|
+
flex: 1,
|
|
459
|
+
flexDirection: 'column',
|
|
460
|
+
padding: SPACING,
|
|
461
|
+
},
|
|
462
|
+
bottomItem: {
|
|
463
|
+
flex: 1,
|
|
464
|
+
},
|
|
465
|
+
sideItem: {
|
|
466
|
+
margin: SPACING,
|
|
467
|
+
padding: SPACING * 2,
|
|
468
|
+
borderRadius: 4,
|
|
469
|
+
},
|
|
392
470
|
});
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getLabel, Label, PlatformPressable } from '@react-navigation/elements';
|
|
2
|
+
import { type Route, useTheme } from '@react-navigation/native';
|
|
2
3
|
import Color from 'color';
|
|
3
4
|
import React from 'react';
|
|
4
5
|
import {
|
|
5
|
-
GestureResponderEvent,
|
|
6
|
+
type GestureResponderEvent,
|
|
6
7
|
Platform,
|
|
7
|
-
|
|
8
|
-
StyleProp,
|
|
8
|
+
type StyleProp,
|
|
9
9
|
StyleSheet,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
ViewStyle,
|
|
10
|
+
type TextStyle,
|
|
11
|
+
type ViewStyle,
|
|
13
12
|
} from 'react-native';
|
|
14
13
|
|
|
15
14
|
import type {
|
|
@@ -64,7 +63,7 @@ type Props = {
|
|
|
64
63
|
*/
|
|
65
64
|
badgeStyle?: StyleProp<TextStyle>;
|
|
66
65
|
/**
|
|
67
|
-
* The button for the tab. Uses a `
|
|
66
|
+
* The button for the tab. Uses a `Pressable` by default.
|
|
68
67
|
*/
|
|
69
68
|
button?: (props: BottomTabBarButtonProps) => React.ReactNode;
|
|
70
69
|
/**
|
|
@@ -145,40 +144,19 @@ export function BottomTabItem({
|
|
|
145
144
|
accessibilityRole,
|
|
146
145
|
...rest
|
|
147
146
|
}: BottomTabBarButtonProps) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
) {
|
|
162
|
-
e.preventDefault();
|
|
163
|
-
onPress?.(e);
|
|
164
|
-
}
|
|
165
|
-
}}
|
|
166
|
-
>
|
|
167
|
-
{children}
|
|
168
|
-
</Link>
|
|
169
|
-
);
|
|
170
|
-
} else {
|
|
171
|
-
return (
|
|
172
|
-
<Pressable
|
|
173
|
-
{...rest}
|
|
174
|
-
accessibilityRole={accessibilityRole}
|
|
175
|
-
onPress={onPress}
|
|
176
|
-
style={style}
|
|
177
|
-
>
|
|
178
|
-
{children}
|
|
179
|
-
</Pressable>
|
|
180
|
-
);
|
|
181
|
-
}
|
|
147
|
+
return (
|
|
148
|
+
<PlatformPressable
|
|
149
|
+
{...rest}
|
|
150
|
+
android_ripple={{ borderless: true }}
|
|
151
|
+
pressOpacity={1}
|
|
152
|
+
href={href}
|
|
153
|
+
accessibilityRole={accessibilityRole}
|
|
154
|
+
onPress={onPress}
|
|
155
|
+
style={style}
|
|
156
|
+
>
|
|
157
|
+
{children}
|
|
158
|
+
</PlatformPressable>
|
|
159
|
+
);
|
|
182
160
|
},
|
|
183
161
|
accessibilityLabel,
|
|
184
162
|
testID,
|
|
@@ -195,7 +173,7 @@ export function BottomTabItem({
|
|
|
195
173
|
iconStyle,
|
|
196
174
|
style,
|
|
197
175
|
}: Props) {
|
|
198
|
-
const { colors
|
|
176
|
+
const { colors } = useTheme();
|
|
199
177
|
|
|
200
178
|
const activeTintColor =
|
|
201
179
|
customActiveTintColor === undefined
|
|
@@ -214,38 +192,39 @@ export function BottomTabItem({
|
|
|
214
192
|
|
|
215
193
|
const color = focused ? activeTintColor : inactiveTintColor;
|
|
216
194
|
|
|
217
|
-
if (typeof label
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
allowFontScaling={allowFontScaling}
|
|
229
|
-
>
|
|
230
|
-
{label}
|
|
231
|
-
</Text>
|
|
195
|
+
if (typeof label !== 'string') {
|
|
196
|
+
const { options } = descriptor;
|
|
197
|
+
const children = getLabel(
|
|
198
|
+
{
|
|
199
|
+
label:
|
|
200
|
+
typeof options.tabBarLabel === 'string'
|
|
201
|
+
? options.tabBarLabel
|
|
202
|
+
: undefined,
|
|
203
|
+
title: options.title,
|
|
204
|
+
},
|
|
205
|
+
route.name
|
|
232
206
|
);
|
|
233
|
-
}
|
|
234
207
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
?
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
208
|
+
return label({
|
|
209
|
+
focused,
|
|
210
|
+
color,
|
|
211
|
+
position: horizontal ? 'beside-icon' : 'below-icon',
|
|
212
|
+
children,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
242
215
|
|
|
243
|
-
return
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
216
|
+
return (
|
|
217
|
+
<Label
|
|
218
|
+
style={[
|
|
219
|
+
horizontal ? styles.labelBeside : styles.labelBeneath,
|
|
220
|
+
labelStyle,
|
|
221
|
+
]}
|
|
222
|
+
allowFontScaling={allowFontScaling}
|
|
223
|
+
tintColor={color}
|
|
224
|
+
>
|
|
225
|
+
{label}
|
|
226
|
+
</Label>
|
|
227
|
+
);
|
|
249
228
|
};
|
|
250
229
|
|
|
251
230
|
const renderIcon = ({ focused }: { focused: boolean }) => {
|
|
@@ -306,7 +285,6 @@ export function BottomTabItem({
|
|
|
306
285
|
|
|
307
286
|
const styles = StyleSheet.create({
|
|
308
287
|
tab: {
|
|
309
|
-
flex: 1,
|
|
310
288
|
alignItems: 'center',
|
|
311
289
|
},
|
|
312
290
|
tabPortrait: {
|
|
@@ -317,19 +295,11 @@ const styles = StyleSheet.create({
|
|
|
317
295
|
justifyContent: 'center',
|
|
318
296
|
flexDirection: 'row',
|
|
319
297
|
},
|
|
320
|
-
label: {
|
|
321
|
-
textAlign: 'center',
|
|
322
|
-
backgroundColor: 'transparent',
|
|
323
|
-
},
|
|
324
298
|
labelBeneath: {
|
|
325
299
|
fontSize: 10,
|
|
326
300
|
},
|
|
327
301
|
labelBeside: {
|
|
328
302
|
fontSize: 13,
|
|
329
303
|
marginLeft: 20,
|
|
330
|
-
marginTop: 3,
|
|
331
|
-
},
|
|
332
|
-
button: {
|
|
333
|
-
display: 'flex',
|
|
334
304
|
},
|
|
335
305
|
});
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
TabNavigationState,
|
|
10
10
|
} from '@react-navigation/native';
|
|
11
11
|
import * as React from 'react';
|
|
12
|
-
import { Platform, StyleSheet } from 'react-native';
|
|
12
|
+
import { Animated, Platform, StyleSheet } from 'react-native';
|
|
13
13
|
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
|
|
14
14
|
|
|
15
15
|
import type {
|
|
@@ -18,10 +18,12 @@ import type {
|
|
|
18
18
|
BottomTabHeaderProps,
|
|
19
19
|
BottomTabNavigationConfig,
|
|
20
20
|
BottomTabNavigationHelpers,
|
|
21
|
+
BottomTabNavigationOptions,
|
|
21
22
|
BottomTabNavigationProp,
|
|
22
23
|
} from '../types';
|
|
23
24
|
import { BottomTabBarHeightCallbackContext } from '../utils/BottomTabBarHeightCallbackContext';
|
|
24
25
|
import { BottomTabBarHeightContext } from '../utils/BottomTabBarHeightContext';
|
|
26
|
+
import { useAnimatedHashMap } from '../utils/useAnimatedHashMap';
|
|
25
27
|
import { BottomTabBar, getTabBarHeight } from './BottomTabBar';
|
|
26
28
|
import { MaybeScreen, MaybeScreenContainer } from './ScreenFallback';
|
|
27
29
|
|
|
@@ -31,6 +33,21 @@ type Props = BottomTabNavigationConfig & {
|
|
|
31
33
|
descriptors: BottomTabDescriptorMap;
|
|
32
34
|
};
|
|
33
35
|
|
|
36
|
+
const EPSILON = 1e-5;
|
|
37
|
+
const STATE_INACTIVE = 0;
|
|
38
|
+
const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
|
|
39
|
+
const STATE_ON_TOP = 2;
|
|
40
|
+
|
|
41
|
+
const hasAnimation = (options: BottomTabNavigationOptions) => {
|
|
42
|
+
const { animationEnabled, transitionSpec } = options;
|
|
43
|
+
|
|
44
|
+
if (animationEnabled === false || !transitionSpec) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
|
|
34
51
|
export function BottomTabView(props: Props) {
|
|
35
52
|
const {
|
|
36
53
|
tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
|
|
@@ -45,12 +62,53 @@ export function BottomTabView(props: Props) {
|
|
|
45
62
|
} = props;
|
|
46
63
|
|
|
47
64
|
const focusedRouteKey = state.routes[state.index].key;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List of loaded tabs, tabs will be loaded when navigated to.
|
|
68
|
+
*/
|
|
48
69
|
const [loaded, setLoaded] = React.useState([focusedRouteKey]);
|
|
49
70
|
|
|
50
71
|
if (!loaded.includes(focusedRouteKey)) {
|
|
72
|
+
// Set the current tab to be loaded if it was not loaded before
|
|
51
73
|
setLoaded([...loaded, focusedRouteKey]);
|
|
52
74
|
}
|
|
53
75
|
|
|
76
|
+
const tabAnims = useAnimatedHashMap(state);
|
|
77
|
+
|
|
78
|
+
React.useEffect(() => {
|
|
79
|
+
const animateToIndex = () => {
|
|
80
|
+
Animated.parallel(
|
|
81
|
+
state.routes
|
|
82
|
+
.map((route, index) => {
|
|
83
|
+
const { options } = descriptors[route.key];
|
|
84
|
+
const { transitionSpec } = options;
|
|
85
|
+
|
|
86
|
+
const animationEnabled = hasAnimation(options);
|
|
87
|
+
|
|
88
|
+
const toValue =
|
|
89
|
+
index === state.index ? 0 : index >= state.index ? 1 : -1;
|
|
90
|
+
|
|
91
|
+
if (!animationEnabled || !transitionSpec) {
|
|
92
|
+
return Animated.timing(tabAnims[route.key], {
|
|
93
|
+
toValue,
|
|
94
|
+
duration: 0,
|
|
95
|
+
useNativeDriver: true,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Animated[transitionSpec.animation](tabAnims[route.key], {
|
|
100
|
+
...transitionSpec.config,
|
|
101
|
+
toValue,
|
|
102
|
+
useNativeDriver: true,
|
|
103
|
+
});
|
|
104
|
+
})
|
|
105
|
+
.filter(Boolean) as Animated.CompositeAnimation[]
|
|
106
|
+
).start();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
animateToIndex();
|
|
110
|
+
}, [descriptors, state.index, state.routes, tabAnims]);
|
|
111
|
+
|
|
54
112
|
const dimensions = SafeAreaProviderCompat.initialMetrics.frame;
|
|
55
113
|
const [tabBarHeight, setTabBarHeight] = React.useState(() =>
|
|
56
114
|
getTabBarHeight({
|
|
@@ -88,24 +146,53 @@ export function BottomTabView(props: Props) {
|
|
|
88
146
|
|
|
89
147
|
const { routes } = state;
|
|
90
148
|
|
|
149
|
+
// If there is no animation, we only have 2 states: visible and invisible
|
|
150
|
+
const hasTwoStates = !routes.some((route) =>
|
|
151
|
+
hasAnimation(descriptors[route.key].options)
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const { tabBarPosition = 'bottom' } = descriptors[focusedRouteKey].options;
|
|
155
|
+
|
|
91
156
|
return (
|
|
92
|
-
<SafeAreaProviderCompat
|
|
157
|
+
<SafeAreaProviderCompat
|
|
158
|
+
style={
|
|
159
|
+
tabBarPosition === 'left'
|
|
160
|
+
? styles.left
|
|
161
|
+
: tabBarPosition === 'right'
|
|
162
|
+
? styles.right
|
|
163
|
+
: null
|
|
164
|
+
}
|
|
165
|
+
>
|
|
166
|
+
{tabBarPosition === 'top' ? (
|
|
167
|
+
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
|
|
168
|
+
{renderTabBar()}
|
|
169
|
+
</BottomTabBarHeightCallbackContext.Provider>
|
|
170
|
+
) : null}
|
|
93
171
|
<MaybeScreenContainer
|
|
94
172
|
enabled={detachInactiveScreens}
|
|
95
|
-
hasTwoStates
|
|
96
|
-
style={styles.
|
|
173
|
+
hasTwoStates={hasTwoStates}
|
|
174
|
+
style={styles.screens}
|
|
97
175
|
>
|
|
98
176
|
{routes.map((route, index) => {
|
|
99
177
|
const descriptor = descriptors[route.key];
|
|
100
|
-
const {
|
|
178
|
+
const {
|
|
179
|
+
lazy = true,
|
|
180
|
+
unmountOnBlur,
|
|
181
|
+
sceneStyleInterpolator,
|
|
182
|
+
} = descriptor.options;
|
|
101
183
|
const isFocused = state.index === index;
|
|
102
184
|
|
|
103
185
|
if (unmountOnBlur && !isFocused) {
|
|
104
186
|
return null;
|
|
105
187
|
}
|
|
106
188
|
|
|
107
|
-
if (
|
|
108
|
-
|
|
189
|
+
if (
|
|
190
|
+
lazy &&
|
|
191
|
+
!loaded.includes(route.key) &&
|
|
192
|
+
!isFocused &&
|
|
193
|
+
!state.preloadedRouteKeys.includes(route.key)
|
|
194
|
+
) {
|
|
195
|
+
// Don't render a lazy screen if we've never navigated to it or it wasn't preloaded
|
|
109
196
|
return null;
|
|
110
197
|
}
|
|
111
198
|
|
|
@@ -123,15 +210,37 @@ export function BottomTabView(props: Props) {
|
|
|
123
210
|
headerTransparent,
|
|
124
211
|
} = descriptor.options;
|
|
125
212
|
|
|
213
|
+
const { sceneStyle } =
|
|
214
|
+
sceneStyleInterpolator?.({
|
|
215
|
+
current: tabAnims[route.key],
|
|
216
|
+
}) ?? {};
|
|
217
|
+
|
|
218
|
+
const animationEnabled = hasAnimation(descriptor.options);
|
|
219
|
+
const activityState = isFocused
|
|
220
|
+
? STATE_ON_TOP // the screen is on top after the transition
|
|
221
|
+
: animationEnabled // is animation is not enabled, immediately move to inactive state
|
|
222
|
+
? tabAnims[route.key].interpolate({
|
|
223
|
+
inputRange: [0, 1 - EPSILON, 1],
|
|
224
|
+
outputRange: [
|
|
225
|
+
STATE_TRANSITIONING_OR_BELOW_TOP, // screen visible during transition
|
|
226
|
+
STATE_TRANSITIONING_OR_BELOW_TOP,
|
|
227
|
+
STATE_INACTIVE, // the screen is detached after transition
|
|
228
|
+
],
|
|
229
|
+
extrapolate: 'extend',
|
|
230
|
+
})
|
|
231
|
+
: STATE_INACTIVE;
|
|
232
|
+
|
|
126
233
|
return (
|
|
127
234
|
<MaybeScreen
|
|
128
235
|
key={route.key}
|
|
129
236
|
style={[StyleSheet.absoluteFill, { zIndex: isFocused ? 0 : -1 }]}
|
|
130
|
-
|
|
237
|
+
active={activityState}
|
|
131
238
|
enabled={detachInactiveScreens}
|
|
132
239
|
freezeOnBlur={freezeOnBlur}
|
|
133
240
|
>
|
|
134
|
-
<BottomTabBarHeightContext.Provider
|
|
241
|
+
<BottomTabBarHeightContext.Provider
|
|
242
|
+
value={tabBarPosition === 'bottom' ? tabBarHeight : 0}
|
|
243
|
+
>
|
|
135
244
|
<Screen
|
|
136
245
|
focused={isFocused}
|
|
137
246
|
route={descriptor.route}
|
|
@@ -146,7 +255,7 @@ export function BottomTabView(props: Props) {
|
|
|
146
255
|
descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
|
|
147
256
|
options: descriptor.options,
|
|
148
257
|
})}
|
|
149
|
-
style={sceneContainerStyle}
|
|
258
|
+
style={[sceneContainerStyle, animationEnabled && sceneStyle]}
|
|
150
259
|
>
|
|
151
260
|
{descriptor.render()}
|
|
152
261
|
</Screen>
|
|
@@ -155,15 +264,23 @@ export function BottomTabView(props: Props) {
|
|
|
155
264
|
);
|
|
156
265
|
})}
|
|
157
266
|
</MaybeScreenContainer>
|
|
158
|
-
|
|
159
|
-
{
|
|
160
|
-
|
|
267
|
+
{tabBarPosition !== 'top' ? (
|
|
268
|
+
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
|
|
269
|
+
{renderTabBar()}
|
|
270
|
+
</BottomTabBarHeightCallbackContext.Provider>
|
|
271
|
+
) : null}
|
|
161
272
|
</SafeAreaProviderCompat>
|
|
162
273
|
);
|
|
163
274
|
}
|
|
164
275
|
|
|
165
276
|
const styles = StyleSheet.create({
|
|
166
|
-
|
|
277
|
+
left: {
|
|
278
|
+
flexDirection: 'row-reverse',
|
|
279
|
+
},
|
|
280
|
+
right: {
|
|
281
|
+
flexDirection: 'row',
|
|
282
|
+
},
|
|
283
|
+
screens: {
|
|
167
284
|
flex: 1,
|
|
168
285
|
overflow: 'hidden',
|
|
169
286
|
},
|