@react-navigation/bottom-tabs 7.0.0-alpha.8 → 7.0.0-rc.0
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 +11 -13
- package/lib/commonjs/TransitionConfigs/SceneStyleInterpolators.js.map +1 -1
- package/lib/commonjs/TransitionConfigs/TransitionPresets.js +6 -6
- package/lib/commonjs/TransitionConfigs/TransitionPresets.js.map +1 -1
- package/lib/commonjs/TransitionConfigs/TransitionSpecs.js +3 -3
- package/lib/commonjs/TransitionConfigs/TransitionSpecs.js.map +1 -1
- package/lib/commonjs/index.js +4 -4
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/navigators/createBottomTabNavigator.js +20 -17
- package/lib/commonjs/navigators/createBottomTabNavigator.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/utils/BottomTabBarHeightCallbackContext.js +1 -1
- package/lib/commonjs/utils/BottomTabBarHeightCallbackContext.js.map +1 -1
- package/lib/commonjs/utils/BottomTabBarHeightContext.js +1 -1
- package/lib/commonjs/utils/BottomTabBarHeightContext.js.map +1 -1
- package/lib/commonjs/utils/useAnimatedHashMap.js +8 -10
- package/lib/commonjs/utils/useAnimatedHashMap.js.map +1 -1
- package/lib/commonjs/utils/useBottomTabBarHeight.js +1 -1
- package/lib/commonjs/utils/useBottomTabBarHeight.js.map +1 -1
- package/lib/commonjs/utils/useIsKeyboardShown.js +1 -1
- package/lib/commonjs/utils/useIsKeyboardShown.js.map +1 -1
- package/lib/commonjs/views/Badge.js +13 -15
- package/lib/commonjs/views/Badge.js.map +1 -1
- package/lib/commonjs/views/BottomTabBar.js +81 -82
- package/lib/commonjs/views/BottomTabBar.js.map +1 -1
- package/lib/commonjs/views/BottomTabItem.js +69 -59
- package/lib/commonjs/views/BottomTabItem.js.map +1 -1
- package/lib/commonjs/views/BottomTabView.js +76 -50
- package/lib/commonjs/views/BottomTabView.js.map +1 -1
- package/lib/commonjs/views/ScreenFallback.js +11 -13
- package/lib/commonjs/views/ScreenFallback.js.map +1 -1
- package/lib/commonjs/views/TabBarIcon.js +14 -15
- package/lib/commonjs/views/TabBarIcon.js.map +1 -1
- package/lib/module/TransitionConfigs/SceneStyleInterpolators.js +9 -11
- package/lib/module/TransitionConfigs/SceneStyleInterpolators.js.map +1 -1
- package/lib/module/TransitionConfigs/TransitionPresets.js +7 -7
- package/lib/module/TransitionConfigs/TransitionPresets.js.map +1 -1
- package/lib/module/TransitionConfigs/TransitionSpecs.js +2 -2
- package/lib/module/TransitionConfigs/TransitionSpecs.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/navigators/createBottomTabNavigator.js +18 -15
- 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 +7 -9
- package/lib/module/utils/useAnimatedHashMap.js.map +1 -1
- package/lib/module/utils/useBottomTabBarHeight.js.map +1 -1
- package/lib/module/utils/useIsKeyboardShown.js.map +1 -1
- package/lib/module/views/Badge.js +11 -13
- package/lib/module/views/Badge.js.map +1 -1
- package/lib/module/views/BottomTabBar.js +82 -83
- package/lib/module/views/BottomTabBar.js.map +1 -1
- package/lib/module/views/BottomTabItem.js +68 -58
- package/lib/module/views/BottomTabItem.js.map +1 -1
- package/lib/module/views/BottomTabView.js +75 -49
- package/lib/module/views/BottomTabView.js.map +1 -1
- package/lib/module/views/ScreenFallback.js +10 -12
- package/lib/module/views/ScreenFallback.js.map +1 -1
- package/lib/module/views/TabBarIcon.js +13 -14
- package/lib/module/views/TabBarIcon.js.map +1 -1
- package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts +2 -2
- package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +2 -2
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts +14 -9
- package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +25 -12
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabBar.d.ts +1 -5
- package/lib/typescript/src/views/BottomTabBar.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabItem.d.ts +7 -1
- package/lib/typescript/src/views/BottomTabItem.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabView.d.ts +1 -1
- package/lib/typescript/src/views/BottomTabView.d.ts.map +1 -1
- package/lib/typescript/src/views/ScreenFallback.d.ts.map +1 -1
- package/lib/typescript/src/views/TabBarIcon.d.ts +1 -1
- package/lib/typescript/src/views/TabBarIcon.d.ts.map +1 -1
- package/package.json +17 -17
- package/src/TransitionConfigs/SceneStyleInterpolators.tsx +5 -5
- package/src/TransitionConfigs/TransitionPresets.tsx +7 -7
- package/src/TransitionConfigs/TransitionSpecs.tsx +2 -2
- package/src/index.tsx +3 -2
- package/src/navigators/createBottomTabNavigator.tsx +33 -7
- package/src/types.tsx +31 -11
- package/src/views/BottomTabBar.tsx +74 -58
- package/src/views/BottomTabItem.tsx +27 -3
- package/src/views/BottomTabView.tsx +116 -55
- package/src/views/TabBarIcon.tsx +1 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type ParamListBase,
|
|
11
11
|
type TabNavigationState,
|
|
12
12
|
useLinkBuilder,
|
|
13
|
+
useLocale,
|
|
13
14
|
useTheme,
|
|
14
15
|
} from '@react-navigation/native';
|
|
15
16
|
import Color from 'color';
|
|
@@ -20,11 +21,13 @@ import {
|
|
|
20
21
|
Platform,
|
|
21
22
|
type StyleProp,
|
|
22
23
|
StyleSheet,
|
|
23
|
-
useWindowDimensions,
|
|
24
24
|
View,
|
|
25
25
|
type ViewStyle,
|
|
26
26
|
} from 'react-native';
|
|
27
|
-
import
|
|
27
|
+
import {
|
|
28
|
+
type EdgeInsets,
|
|
29
|
+
useSafeAreaFrame,
|
|
30
|
+
} from 'react-native-safe-area-context';
|
|
28
31
|
|
|
29
32
|
import type { BottomTabBarProps, BottomTabDescriptorMap } from '../types';
|
|
30
33
|
import { BottomTabBarHeightCallbackContext } from '../utils/BottomTabBarHeightCallbackContext';
|
|
@@ -45,14 +48,12 @@ const useNativeDriver = Platform.OS !== 'web';
|
|
|
45
48
|
type Options = {
|
|
46
49
|
state: TabNavigationState<ParamListBase>;
|
|
47
50
|
descriptors: BottomTabDescriptorMap;
|
|
48
|
-
layout: { height: number; width: number };
|
|
49
51
|
dimensions: { height: number; width: number };
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
const shouldUseHorizontalLabels = ({
|
|
53
55
|
state,
|
|
54
56
|
descriptors,
|
|
55
|
-
layout,
|
|
56
57
|
dimensions,
|
|
57
58
|
}: Options) => {
|
|
58
59
|
const { tabBarLabelPosition } =
|
|
@@ -67,7 +68,7 @@ const shouldUseHorizontalLabels = ({
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
if (
|
|
71
|
+
if (dimensions.width >= 768) {
|
|
71
72
|
// Screen size matches a tablet
|
|
72
73
|
const maxTabWidth = state.routes.reduce((acc, route) => {
|
|
73
74
|
const { tabBarItemStyle } = descriptors[route.key].options;
|
|
@@ -84,7 +85,7 @@ const shouldUseHorizontalLabels = ({
|
|
|
84
85
|
return acc + DEFAULT_MAX_TAB_ITEM_WIDTH;
|
|
85
86
|
}, 0);
|
|
86
87
|
|
|
87
|
-
return maxTabWidth <=
|
|
88
|
+
return maxTabWidth <= dimensions.width;
|
|
88
89
|
} else {
|
|
89
90
|
return dimensions.width > dimensions.height;
|
|
90
91
|
}
|
|
@@ -143,6 +144,7 @@ export function BottomTabBar({
|
|
|
143
144
|
style,
|
|
144
145
|
}: Props) {
|
|
145
146
|
const { colors } = useTheme();
|
|
147
|
+
const { direction } = useLocale();
|
|
146
148
|
const { buildHref } = useLinkBuilder();
|
|
147
149
|
|
|
148
150
|
const focusedRoute = state.routes[state.index];
|
|
@@ -158,7 +160,8 @@ export function BottomTabBar({
|
|
|
158
160
|
tabBarBackground,
|
|
159
161
|
tabBarActiveTintColor,
|
|
160
162
|
tabBarInactiveTintColor,
|
|
161
|
-
tabBarActiveBackgroundColor = tabBarPosition !== 'bottom'
|
|
163
|
+
tabBarActiveBackgroundColor = tabBarPosition !== 'bottom' &&
|
|
164
|
+
tabBarPosition !== 'top'
|
|
162
165
|
? Color(tabBarActiveTintColor ?? colors.primary)
|
|
163
166
|
.alpha(0.12)
|
|
164
167
|
.rgb()
|
|
@@ -167,8 +170,7 @@ export function BottomTabBar({
|
|
|
167
170
|
tabBarInactiveBackgroundColor,
|
|
168
171
|
} = focusedOptions;
|
|
169
172
|
|
|
170
|
-
|
|
171
|
-
const dimensions = useWindowDimensions();
|
|
173
|
+
const dimensions = useSafeAreaFrame();
|
|
172
174
|
const isKeyboardShown = useIsKeyboardShown();
|
|
173
175
|
|
|
174
176
|
const onHeightChange = React.useContext(BottomTabBarHeightCallbackContext);
|
|
@@ -229,22 +231,18 @@ export function BottomTabBar({
|
|
|
229
231
|
|
|
230
232
|
const [layout, setLayout] = React.useState({
|
|
231
233
|
height: 0,
|
|
232
|
-
width: dimensions.width,
|
|
233
234
|
});
|
|
234
235
|
|
|
235
236
|
const handleLayout = (e: LayoutChangeEvent) => {
|
|
236
|
-
const { height
|
|
237
|
+
const { height } = e.nativeEvent.layout;
|
|
237
238
|
|
|
238
239
|
onHeightChange?.(height);
|
|
239
240
|
|
|
240
241
|
setLayout((layout) => {
|
|
241
|
-
if (height === layout.height
|
|
242
|
+
if (height === layout.height) {
|
|
242
243
|
return layout;
|
|
243
244
|
} else {
|
|
244
|
-
return {
|
|
245
|
-
height,
|
|
246
|
-
width,
|
|
247
|
-
};
|
|
245
|
+
return { height };
|
|
248
246
|
}
|
|
249
247
|
});
|
|
250
248
|
};
|
|
@@ -257,7 +255,6 @@ export function BottomTabBar({
|
|
|
257
255
|
descriptors,
|
|
258
256
|
insets,
|
|
259
257
|
dimensions,
|
|
260
|
-
layout,
|
|
261
258
|
style: [tabBarStyle, style],
|
|
262
259
|
});
|
|
263
260
|
|
|
@@ -265,26 +262,55 @@ export function BottomTabBar({
|
|
|
265
262
|
state,
|
|
266
263
|
descriptors,
|
|
267
264
|
dimensions,
|
|
268
|
-
layout,
|
|
269
265
|
});
|
|
270
266
|
|
|
267
|
+
const isSidebar = tabBarPosition === 'left' || tabBarPosition === 'right';
|
|
268
|
+
|
|
271
269
|
const tabBarBackgroundElement = tabBarBackground?.();
|
|
272
270
|
|
|
273
271
|
return (
|
|
274
272
|
<Animated.View
|
|
275
273
|
style={[
|
|
276
274
|
tabBarPosition === 'left'
|
|
277
|
-
? styles.
|
|
275
|
+
? styles.start
|
|
278
276
|
: tabBarPosition === 'right'
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
? styles.end
|
|
278
|
+
: styles.bottom,
|
|
279
|
+
(
|
|
280
|
+
Platform.OS === 'web'
|
|
281
|
+
? tabBarPosition === 'right'
|
|
282
|
+
: (direction === 'rtl' && tabBarPosition === 'left') ||
|
|
283
|
+
(direction !== 'rtl' && tabBarPosition === 'right')
|
|
284
|
+
)
|
|
285
|
+
? { borderLeftWidth: StyleSheet.hairlineWidth }
|
|
286
|
+
: (
|
|
287
|
+
Platform.OS === 'web'
|
|
288
|
+
? tabBarPosition === 'left'
|
|
289
|
+
: (direction === 'rtl' && tabBarPosition === 'right') ||
|
|
290
|
+
(direction !== 'rtl' && tabBarPosition === 'left')
|
|
291
|
+
)
|
|
292
|
+
? { borderRightWidth: StyleSheet.hairlineWidth }
|
|
293
|
+
: tabBarPosition === 'top'
|
|
294
|
+
? { borderBottomWidth: StyleSheet.hairlineWidth }
|
|
295
|
+
: { borderTopWidth: StyleSheet.hairlineWidth },
|
|
281
296
|
{
|
|
282
297
|
backgroundColor:
|
|
283
298
|
tabBarBackgroundElement != null ? 'transparent' : colors.card,
|
|
284
299
|
borderColor: colors.border,
|
|
285
300
|
},
|
|
286
|
-
|
|
287
|
-
?
|
|
301
|
+
isSidebar
|
|
302
|
+
? {
|
|
303
|
+
paddingTop: SPACING + insets.top,
|
|
304
|
+
paddingBottom: SPACING + insets.bottom,
|
|
305
|
+
paddingStart:
|
|
306
|
+
SPACING + (tabBarPosition === 'left' ? insets.left : 0),
|
|
307
|
+
paddingEnd:
|
|
308
|
+
SPACING + (tabBarPosition === 'right' ? insets.right : 0),
|
|
309
|
+
minWidth: hasHorizontalLabels
|
|
310
|
+
? getDefaultSidebarWidth(dimensions)
|
|
311
|
+
: 0,
|
|
312
|
+
}
|
|
313
|
+
: [
|
|
288
314
|
{
|
|
289
315
|
transform: [
|
|
290
316
|
{
|
|
@@ -308,31 +334,18 @@ export function BottomTabBar({
|
|
|
308
334
|
paddingBottom,
|
|
309
335
|
paddingHorizontal: Math.max(insets.left, insets.right),
|
|
310
336
|
},
|
|
311
|
-
]
|
|
312
|
-
: {
|
|
313
|
-
paddingTop: insets.top,
|
|
314
|
-
paddingBottom: insets.bottom,
|
|
315
|
-
paddingLeft: tabBarPosition === 'left' ? insets.left : 0,
|
|
316
|
-
paddingRight: tabBarPosition === 'right' ? insets.right : 0,
|
|
317
|
-
minWidth: hasHorizontalLabels
|
|
318
|
-
? getDefaultSidebarWidth(dimensions)
|
|
319
|
-
: 0,
|
|
320
|
-
},
|
|
337
|
+
],
|
|
321
338
|
tabBarStyle,
|
|
322
339
|
]}
|
|
323
340
|
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
|
|
324
|
-
onLayout={
|
|
341
|
+
onLayout={isSidebar ? undefined : handleLayout}
|
|
325
342
|
>
|
|
326
343
|
<View pointerEvents="none" style={StyleSheet.absoluteFill}>
|
|
327
344
|
{tabBarBackgroundElement}
|
|
328
345
|
</View>
|
|
329
346
|
<View
|
|
330
347
|
accessibilityRole="tablist"
|
|
331
|
-
style={
|
|
332
|
-
tabBarPosition === 'bottom'
|
|
333
|
-
? styles.bottomContent
|
|
334
|
-
: styles.sideContent
|
|
335
|
-
}
|
|
348
|
+
style={isSidebar ? styles.sideContent : styles.bottomContent}
|
|
336
349
|
>
|
|
337
350
|
{routes.map((route, index) => {
|
|
338
351
|
const focused = index === state.index;
|
|
@@ -372,8 +385,8 @@ export function BottomTabBar({
|
|
|
372
385
|
options.tabBarAccessibilityLabel !== undefined
|
|
373
386
|
? options.tabBarAccessibilityLabel
|
|
374
387
|
: typeof label === 'string' && Platform.OS === 'ios'
|
|
375
|
-
|
|
376
|
-
|
|
388
|
+
? `${label}, tab, ${index + 1} of ${routes.length}`
|
|
389
|
+
: undefined;
|
|
377
390
|
|
|
378
391
|
return (
|
|
379
392
|
<NavigationContext.Provider
|
|
@@ -387,6 +400,7 @@ export function BottomTabBar({
|
|
|
387
400
|
descriptor={descriptors[route.key]}
|
|
388
401
|
focused={focused}
|
|
389
402
|
horizontal={hasHorizontalLabels}
|
|
403
|
+
variant={isSidebar ? 'material' : 'uikit'}
|
|
390
404
|
onPress={onPress}
|
|
391
405
|
onLongPress={onLongPress}
|
|
392
406
|
accessibilityLabel={accessibilityLabel}
|
|
@@ -410,14 +424,14 @@ export function BottomTabBar({
|
|
|
410
424
|
labelStyle={options.tabBarLabelStyle}
|
|
411
425
|
iconStyle={options.tabBarIconStyle}
|
|
412
426
|
style={[
|
|
413
|
-
|
|
414
|
-
?
|
|
415
|
-
: [
|
|
427
|
+
isSidebar
|
|
428
|
+
? [
|
|
416
429
|
styles.sideItem,
|
|
417
430
|
hasHorizontalLabels
|
|
418
|
-
?
|
|
419
|
-
:
|
|
420
|
-
]
|
|
431
|
+
? styles.sideItemHorizontal
|
|
432
|
+
: styles.sideItemVertical,
|
|
433
|
+
]
|
|
434
|
+
: styles.bottomItem,
|
|
421
435
|
options.tabBarItemStyle,
|
|
422
436
|
]}
|
|
423
437
|
/>
|
|
@@ -431,23 +445,20 @@ export function BottomTabBar({
|
|
|
431
445
|
}
|
|
432
446
|
|
|
433
447
|
const styles = StyleSheet.create({
|
|
434
|
-
|
|
448
|
+
start: {
|
|
435
449
|
top: 0,
|
|
436
450
|
bottom: 0,
|
|
437
|
-
|
|
438
|
-
borderRightWidth: StyleSheet.hairlineWidth,
|
|
451
|
+
start: 0,
|
|
439
452
|
},
|
|
440
|
-
|
|
453
|
+
end: {
|
|
441
454
|
top: 0,
|
|
442
455
|
bottom: 0,
|
|
443
|
-
|
|
444
|
-
borderLeftWidth: StyleSheet.hairlineWidth,
|
|
456
|
+
end: 0,
|
|
445
457
|
},
|
|
446
458
|
bottom: {
|
|
447
|
-
|
|
448
|
-
|
|
459
|
+
start: 0,
|
|
460
|
+
end: 0,
|
|
449
461
|
bottom: 0,
|
|
450
|
-
borderTopWidth: StyleSheet.hairlineWidth,
|
|
451
462
|
elevation: 8,
|
|
452
463
|
},
|
|
453
464
|
bottomContent: {
|
|
@@ -457,7 +468,6 @@ const styles = StyleSheet.create({
|
|
|
457
468
|
sideContent: {
|
|
458
469
|
flex: 1,
|
|
459
470
|
flexDirection: 'column',
|
|
460
|
-
padding: SPACING,
|
|
461
471
|
},
|
|
462
472
|
bottomItem: {
|
|
463
473
|
flex: 1,
|
|
@@ -465,6 +475,12 @@ const styles = StyleSheet.create({
|
|
|
465
475
|
sideItem: {
|
|
466
476
|
margin: SPACING,
|
|
467
477
|
padding: SPACING * 2,
|
|
468
|
-
|
|
478
|
+
},
|
|
479
|
+
sideItemHorizontal: {
|
|
480
|
+
borderRadius: 56,
|
|
481
|
+
justifyContent: 'flex-start',
|
|
482
|
+
},
|
|
483
|
+
sideItemVertical: {
|
|
484
|
+
borderRadius: 16,
|
|
469
485
|
},
|
|
470
486
|
});
|
|
@@ -89,6 +89,12 @@ type Props = {
|
|
|
89
89
|
* Whether the label should be aligned with the icon horizontally.
|
|
90
90
|
*/
|
|
91
91
|
horizontal: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Variant of navigation bar styling
|
|
94
|
+
* - `uikit`: iOS UIKit style
|
|
95
|
+
* - `material`: Material Design style
|
|
96
|
+
*/
|
|
97
|
+
variant: 'uikit' | 'material';
|
|
92
98
|
/**
|
|
93
99
|
* Color for the icon and label when the item is active.
|
|
94
100
|
*/
|
|
@@ -163,6 +169,7 @@ export function BottomTabItem({
|
|
|
163
169
|
onPress,
|
|
164
170
|
onLongPress,
|
|
165
171
|
horizontal,
|
|
172
|
+
variant,
|
|
166
173
|
activeTintColor: customActiveTintColor,
|
|
167
174
|
inactiveTintColor: customInactiveTintColor,
|
|
168
175
|
activeBackgroundColor = 'transparent',
|
|
@@ -173,7 +180,7 @@ export function BottomTabItem({
|
|
|
173
180
|
iconStyle,
|
|
174
181
|
style,
|
|
175
182
|
}: Props) {
|
|
176
|
-
const { colors } = useTheme();
|
|
183
|
+
const { colors, fonts } = useTheme();
|
|
177
184
|
|
|
178
185
|
const activeTintColor =
|
|
179
186
|
customActiveTintColor === undefined
|
|
@@ -216,7 +223,14 @@ export function BottomTabItem({
|
|
|
216
223
|
return (
|
|
217
224
|
<Label
|
|
218
225
|
style={[
|
|
219
|
-
horizontal
|
|
226
|
+
horizontal
|
|
227
|
+
? [
|
|
228
|
+
styles.labelBeside,
|
|
229
|
+
{ marginStart: icon !== undefined ? 16 : 0 },
|
|
230
|
+
variant === 'uikit' && styles.labelBesideUikit,
|
|
231
|
+
]
|
|
232
|
+
: styles.labelBeneath,
|
|
233
|
+
variant === 'material' && fonts.medium,
|
|
220
234
|
labelStyle,
|
|
221
235
|
]}
|
|
222
236
|
allowFontScaling={allowFontScaling}
|
|
@@ -286,6 +300,8 @@ export function BottomTabItem({
|
|
|
286
300
|
const styles = StyleSheet.create({
|
|
287
301
|
tab: {
|
|
288
302
|
alignItems: 'center',
|
|
303
|
+
// Roundness for iPad hover effect
|
|
304
|
+
borderRadius: 10,
|
|
289
305
|
},
|
|
290
306
|
tabPortrait: {
|
|
291
307
|
justifyContent: 'flex-end',
|
|
@@ -294,12 +310,20 @@ const styles = StyleSheet.create({
|
|
|
294
310
|
tabLandscape: {
|
|
295
311
|
justifyContent: 'center',
|
|
296
312
|
flexDirection: 'row',
|
|
313
|
+
paddingVertical: 12,
|
|
314
|
+
paddingStart: 16,
|
|
315
|
+
paddingEnd: 24,
|
|
297
316
|
},
|
|
298
317
|
labelBeneath: {
|
|
299
318
|
fontSize: 10,
|
|
300
319
|
},
|
|
301
320
|
labelBeside: {
|
|
321
|
+
marginEnd: 12,
|
|
322
|
+
marginVertical: 4,
|
|
323
|
+
lineHeight: 24,
|
|
324
|
+
marginStart: 20,
|
|
325
|
+
},
|
|
326
|
+
labelBesideUikit: {
|
|
302
327
|
fontSize: 13,
|
|
303
|
-
marginLeft: 20,
|
|
304
328
|
},
|
|
305
329
|
});
|
|
@@ -4,14 +4,21 @@ import {
|
|
|
4
4
|
SafeAreaProviderCompat,
|
|
5
5
|
Screen,
|
|
6
6
|
} from '@react-navigation/elements';
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import {
|
|
8
|
+
type NavigationAction,
|
|
9
|
+
type ParamListBase,
|
|
10
|
+
StackActions,
|
|
11
|
+
type TabNavigationState,
|
|
12
|
+
useLocale,
|
|
10
13
|
} from '@react-navigation/native';
|
|
11
14
|
import * as React from 'react';
|
|
12
15
|
import { Animated, Platform, StyleSheet } from 'react-native';
|
|
13
16
|
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
|
|
14
17
|
|
|
18
|
+
import {
|
|
19
|
+
FadeTransition,
|
|
20
|
+
ShiftTransition,
|
|
21
|
+
} from '../TransitionConfigs/TransitionPresets';
|
|
15
22
|
import type {
|
|
16
23
|
BottomTabBarProps,
|
|
17
24
|
BottomTabDescriptorMap,
|
|
@@ -38,14 +45,26 @@ const STATE_INACTIVE = 0;
|
|
|
38
45
|
const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
|
|
39
46
|
const STATE_ON_TOP = 2;
|
|
40
47
|
|
|
48
|
+
const NAMED_TRANSITIONS_PRESETS = {
|
|
49
|
+
fade: FadeTransition,
|
|
50
|
+
shift: ShiftTransition,
|
|
51
|
+
none: {
|
|
52
|
+
sceneStyleInterpolator: undefined,
|
|
53
|
+
transitionSpec: {
|
|
54
|
+
animation: 'timing',
|
|
55
|
+
config: { duration: 0 },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
41
60
|
const hasAnimation = (options: BottomTabNavigationOptions) => {
|
|
42
|
-
const {
|
|
61
|
+
const { animation, transitionSpec } = options;
|
|
43
62
|
|
|
44
|
-
if (
|
|
45
|
-
return
|
|
63
|
+
if (animation) {
|
|
64
|
+
return animation !== 'none';
|
|
46
65
|
}
|
|
47
66
|
|
|
48
|
-
return
|
|
67
|
+
return !transitionSpec;
|
|
49
68
|
};
|
|
50
69
|
|
|
51
70
|
export function BottomTabView(props: Props) {
|
|
@@ -63,6 +82,8 @@ export function BottomTabView(props: Props) {
|
|
|
63
82
|
|
|
64
83
|
const focusedRouteKey = state.routes[state.index].key;
|
|
65
84
|
|
|
85
|
+
const { direction } = useLocale();
|
|
86
|
+
|
|
66
87
|
/**
|
|
67
88
|
* List of loaded tabs, tabs will be loaded when navigated to.
|
|
68
89
|
*/
|
|
@@ -73,41 +94,82 @@ export function BottomTabView(props: Props) {
|
|
|
73
94
|
setLoaded([...loaded, focusedRouteKey]);
|
|
74
95
|
}
|
|
75
96
|
|
|
97
|
+
const previousRouteKeyRef = React.useRef(focusedRouteKey);
|
|
76
98
|
const tabAnims = useAnimatedHashMap(state);
|
|
77
99
|
|
|
78
100
|
React.useEffect(() => {
|
|
101
|
+
const previousRouteKey = previousRouteKeyRef.current;
|
|
102
|
+
|
|
103
|
+
let popToTopAction: NavigationAction | undefined;
|
|
104
|
+
|
|
105
|
+
if (
|
|
106
|
+
previousRouteKey !== focusedRouteKey &&
|
|
107
|
+
descriptors[previousRouteKey]?.options.popToTopOnBlur
|
|
108
|
+
) {
|
|
109
|
+
const prevRoute = state.routes.find(
|
|
110
|
+
(route) => route.key === previousRouteKey
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (prevRoute?.state?.type === 'stack' && prevRoute.state.key) {
|
|
114
|
+
popToTopAction = {
|
|
115
|
+
...StackActions.popToTop(),
|
|
116
|
+
target: prevRoute.state.key,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
79
121
|
const animateToIndex = () => {
|
|
80
122
|
Animated.parallel(
|
|
81
123
|
state.routes
|
|
82
124
|
.map((route, index) => {
|
|
83
125
|
const { options } = descriptors[route.key];
|
|
84
|
-
const {
|
|
126
|
+
const {
|
|
127
|
+
animation = 'none',
|
|
128
|
+
transitionSpec = NAMED_TRANSITIONS_PRESETS[animation]
|
|
129
|
+
.transitionSpec,
|
|
130
|
+
} = options;
|
|
131
|
+
|
|
132
|
+
let spec = transitionSpec;
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
route.key !== previousRouteKey &&
|
|
136
|
+
route.key !== focusedRouteKey
|
|
137
|
+
) {
|
|
138
|
+
// Don't animate if the screen is not previous one or new one
|
|
139
|
+
// This will avoid flicker for screens not involved in the transition
|
|
140
|
+
spec = NAMED_TRANSITIONS_PRESETS.none.transitionSpec;
|
|
141
|
+
}
|
|
85
142
|
|
|
86
|
-
|
|
143
|
+
spec = spec ?? NAMED_TRANSITIONS_PRESETS.none.transitionSpec;
|
|
87
144
|
|
|
88
145
|
const toValue =
|
|
89
146
|
index === state.index ? 0 : index >= state.index ? 1 : -1;
|
|
90
147
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
toValue,
|
|
94
|
-
duration: 0,
|
|
95
|
-
useNativeDriver: true,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return Animated[transitionSpec.animation](tabAnims[route.key], {
|
|
100
|
-
...transitionSpec.config,
|
|
148
|
+
return Animated[spec.animation](tabAnims[route.key], {
|
|
149
|
+
...spec.config,
|
|
101
150
|
toValue,
|
|
102
151
|
useNativeDriver: true,
|
|
103
152
|
});
|
|
104
153
|
})
|
|
105
154
|
.filter(Boolean) as Animated.CompositeAnimation[]
|
|
106
|
-
).start()
|
|
155
|
+
).start(({ finished }) => {
|
|
156
|
+
if (finished && popToTopAction) {
|
|
157
|
+
navigation.dispatch(popToTopAction);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
107
160
|
};
|
|
108
161
|
|
|
109
162
|
animateToIndex();
|
|
110
|
-
|
|
163
|
+
|
|
164
|
+
previousRouteKeyRef.current = focusedRouteKey;
|
|
165
|
+
}, [
|
|
166
|
+
descriptors,
|
|
167
|
+
focusedRouteKey,
|
|
168
|
+
navigation,
|
|
169
|
+
state.index,
|
|
170
|
+
state.routes,
|
|
171
|
+
tabAnims,
|
|
172
|
+
]);
|
|
111
173
|
|
|
112
174
|
const dimensions = SafeAreaProviderCompat.initialMetrics.frame;
|
|
113
175
|
const [tabBarHeight, setTabBarHeight] = React.useState(() =>
|
|
@@ -115,7 +177,6 @@ export function BottomTabView(props: Props) {
|
|
|
115
177
|
state,
|
|
116
178
|
descriptors,
|
|
117
179
|
dimensions,
|
|
118
|
-
layout: { width: dimensions.width, height: 0 },
|
|
119
180
|
insets: {
|
|
120
181
|
...SafeAreaProviderCompat.initialMetrics.insets,
|
|
121
182
|
...props.safeAreaInsets,
|
|
@@ -155,14 +216,19 @@ export function BottomTabView(props: Props) {
|
|
|
155
216
|
|
|
156
217
|
return (
|
|
157
218
|
<SafeAreaProviderCompat
|
|
158
|
-
style={
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
219
|
+
style={{
|
|
220
|
+
flexDirection:
|
|
221
|
+
(tabBarPosition === 'left' && direction === 'ltr') ||
|
|
222
|
+
(tabBarPosition === 'right' && direction === 'rtl')
|
|
223
|
+
? 'row-reverse'
|
|
224
|
+
: 'row',
|
|
225
|
+
}}
|
|
165
226
|
>
|
|
227
|
+
{tabBarPosition === 'top' ? (
|
|
228
|
+
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
|
|
229
|
+
{renderTabBar()}
|
|
230
|
+
</BottomTabBarHeightCallbackContext.Provider>
|
|
231
|
+
) : null}
|
|
166
232
|
<MaybeScreenContainer
|
|
167
233
|
enabled={detachInactiveScreens}
|
|
168
234
|
hasTwoStates={hasTwoStates}
|
|
@@ -172,15 +238,12 @@ export function BottomTabView(props: Props) {
|
|
|
172
238
|
const descriptor = descriptors[route.key];
|
|
173
239
|
const {
|
|
174
240
|
lazy = true,
|
|
175
|
-
|
|
176
|
-
sceneStyleInterpolator
|
|
241
|
+
animation = 'none',
|
|
242
|
+
sceneStyleInterpolator = NAMED_TRANSITIONS_PRESETS[animation]
|
|
243
|
+
.sceneStyleInterpolator,
|
|
177
244
|
} = descriptor.options;
|
|
178
245
|
const isFocused = state.index === index;
|
|
179
246
|
|
|
180
|
-
if (unmountOnBlur && !isFocused) {
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
247
|
if (
|
|
185
248
|
lazy &&
|
|
186
249
|
!loaded.includes(route.key) &&
|
|
@@ -207,23 +270,25 @@ export function BottomTabView(props: Props) {
|
|
|
207
270
|
|
|
208
271
|
const { sceneStyle } =
|
|
209
272
|
sceneStyleInterpolator?.({
|
|
210
|
-
current:
|
|
273
|
+
current: {
|
|
274
|
+
progress: tabAnims[route.key],
|
|
275
|
+
},
|
|
211
276
|
}) ?? {};
|
|
212
277
|
|
|
213
278
|
const animationEnabled = hasAnimation(descriptor.options);
|
|
214
279
|
const activityState = isFocused
|
|
215
280
|
? STATE_ON_TOP // the screen is on top after the transition
|
|
216
281
|
: animationEnabled // is animation is not enabled, immediately move to inactive state
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
282
|
+
? tabAnims[route.key].interpolate({
|
|
283
|
+
inputRange: [0, 1 - EPSILON, 1],
|
|
284
|
+
outputRange: [
|
|
285
|
+
STATE_TRANSITIONING_OR_BELOW_TOP, // screen visible during transition
|
|
286
|
+
STATE_TRANSITIONING_OR_BELOW_TOP,
|
|
287
|
+
STATE_INACTIVE, // the screen is detached after transition
|
|
288
|
+
],
|
|
289
|
+
extrapolate: 'extend',
|
|
290
|
+
})
|
|
291
|
+
: STATE_INACTIVE;
|
|
227
292
|
|
|
228
293
|
return (
|
|
229
294
|
<MaybeScreen
|
|
@@ -259,20 +324,16 @@ export function BottomTabView(props: Props) {
|
|
|
259
324
|
);
|
|
260
325
|
})}
|
|
261
326
|
</MaybeScreenContainer>
|
|
262
|
-
|
|
263
|
-
{
|
|
264
|
-
|
|
327
|
+
{tabBarPosition !== 'top' ? (
|
|
328
|
+
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
|
|
329
|
+
{renderTabBar()}
|
|
330
|
+
</BottomTabBarHeightCallbackContext.Provider>
|
|
331
|
+
) : null}
|
|
265
332
|
</SafeAreaProviderCompat>
|
|
266
333
|
);
|
|
267
334
|
}
|
|
268
335
|
|
|
269
336
|
const styles = StyleSheet.create({
|
|
270
|
-
left: {
|
|
271
|
-
flexDirection: 'row-reverse',
|
|
272
|
-
},
|
|
273
|
-
right: {
|
|
274
|
-
flexDirection: 'row',
|
|
275
|
-
},
|
|
276
337
|
screens: {
|
|
277
338
|
flex: 1,
|
|
278
339
|
overflow: 'hidden',
|