@react-navigation/bottom-tabs 7.0.0-alpha.2 → 7.0.0-alpha.21

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.
Files changed (102) hide show
  1. package/lib/commonjs/TransitionConfigs/SceneStyleInterpolators.js +47 -0
  2. package/lib/commonjs/TransitionConfigs/SceneStyleInterpolators.js.map +1 -0
  3. package/lib/commonjs/TransitionConfigs/TransitionPresets.js +17 -0
  4. package/lib/commonjs/TransitionConfigs/TransitionPresets.js.map +1 -0
  5. package/lib/commonjs/TransitionConfigs/TransitionSpecs.js +22 -0
  6. package/lib/commonjs/TransitionConfigs/TransitionSpecs.js.map +1 -0
  7. package/lib/commonjs/index.js +9 -0
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/navigators/createBottomTabNavigator.js +11 -6
  10. package/lib/commonjs/navigators/createBottomTabNavigator.js.map +1 -1
  11. package/lib/commonjs/types.js.map +1 -1
  12. package/lib/commonjs/utils/BottomTabBarHeightCallbackContext.js +3 -4
  13. package/lib/commonjs/utils/BottomTabBarHeightCallbackContext.js.map +1 -1
  14. package/lib/commonjs/utils/BottomTabBarHeightContext.js +3 -4
  15. package/lib/commonjs/utils/BottomTabBarHeightContext.js.map +1 -1
  16. package/lib/commonjs/utils/useAnimatedHashMap.js +31 -0
  17. package/lib/commonjs/utils/useAnimatedHashMap.js.map +1 -0
  18. package/lib/commonjs/utils/useBottomTabBarHeight.js +2 -2
  19. package/lib/commonjs/utils/useBottomTabBarHeight.js.map +1 -1
  20. package/lib/commonjs/utils/useIsKeyboardShown.js +2 -2
  21. package/lib/commonjs/utils/useIsKeyboardShown.js.map +1 -1
  22. package/lib/commonjs/views/Badge.js +2 -2
  23. package/lib/commonjs/views/Badge.js.map +1 -1
  24. package/lib/commonjs/views/BottomTabBar.js +65 -27
  25. package/lib/commonjs/views/BottomTabBar.js.map +1 -1
  26. package/lib/commonjs/views/BottomTabItem.js +32 -54
  27. package/lib/commonjs/views/BottomTabItem.js.map +1 -1
  28. package/lib/commonjs/views/BottomTabView.js +120 -18
  29. package/lib/commonjs/views/BottomTabView.js.map +1 -1
  30. package/lib/commonjs/views/ScreenFallback.js +10 -14
  31. package/lib/commonjs/views/ScreenFallback.js.map +1 -1
  32. package/lib/commonjs/views/TabBarIcon.js +12 -17
  33. package/lib/commonjs/views/TabBarIcon.js.map +1 -1
  34. package/lib/module/TransitionConfigs/SceneStyleInterpolators.js +40 -0
  35. package/lib/module/TransitionConfigs/SceneStyleInterpolators.js.map +1 -0
  36. package/lib/module/TransitionConfigs/TransitionPresets.js +11 -0
  37. package/lib/module/TransitionConfigs/TransitionPresets.js.map +1 -0
  38. package/lib/module/TransitionConfigs/TransitionSpecs.js +16 -0
  39. package/lib/module/TransitionConfigs/TransitionSpecs.js.map +1 -0
  40. package/lib/module/index.js +9 -0
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/module/navigators/createBottomTabNavigator.js +8 -2
  43. package/lib/module/navigators/createBottomTabNavigator.js.map +1 -1
  44. package/lib/module/types.js.map +1 -1
  45. package/lib/module/utils/BottomTabBarHeightCallbackContext.js.map +1 -1
  46. package/lib/module/utils/BottomTabBarHeightContext.js.map +1 -1
  47. package/lib/module/utils/useAnimatedHashMap.js +23 -0
  48. package/lib/module/utils/useAnimatedHashMap.js.map +1 -0
  49. package/lib/module/utils/useBottomTabBarHeight.js.map +1 -1
  50. package/lib/module/utils/useIsKeyboardShown.js.map +1 -1
  51. package/lib/module/views/Badge.js.map +1 -1
  52. package/lib/module/views/BottomTabBar.js +68 -30
  53. package/lib/module/views/BottomTabBar.js.map +1 -1
  54. package/lib/module/views/BottomTabItem.js +34 -56
  55. package/lib/module/views/BottomTabItem.js.map +1 -1
  56. package/lib/module/views/BottomTabView.js +119 -17
  57. package/lib/module/views/BottomTabView.js.map +1 -1
  58. package/lib/module/views/ScreenFallback.js +8 -12
  59. package/lib/module/views/ScreenFallback.js.map +1 -1
  60. package/lib/module/views/TabBarIcon.js +12 -17
  61. package/lib/module/views/TabBarIcon.js.map +1 -1
  62. package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts +10 -0
  63. package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts.map +1 -0
  64. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +4 -0
  65. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -0
  66. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +4 -0
  67. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -0
  68. package/lib/typescript/src/index.d.ts +8 -1
  69. package/lib/typescript/src/index.d.ts.map +1 -1
  70. package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts +15 -9
  71. package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts.map +1 -1
  72. package/lib/typescript/src/types.d.ts +71 -4
  73. package/lib/typescript/src/types.d.ts.map +1 -1
  74. package/lib/typescript/src/utils/useAnimatedHashMap.d.ts +4 -0
  75. package/lib/typescript/src/utils/useAnimatedHashMap.d.ts.map +1 -0
  76. package/lib/typescript/src/views/Badge.d.ts +3 -2
  77. package/lib/typescript/src/views/Badge.d.ts.map +1 -1
  78. package/lib/typescript/src/views/BottomTabBar.d.ts +5 -4
  79. package/lib/typescript/src/views/BottomTabBar.d.ts.map +1 -1
  80. package/lib/typescript/src/views/BottomTabItem.d.ts +3 -3
  81. package/lib/typescript/src/views/BottomTabItem.d.ts.map +1 -1
  82. package/lib/typescript/src/views/BottomTabView.d.ts +2 -1
  83. package/lib/typescript/src/views/BottomTabView.d.ts.map +1 -1
  84. package/lib/typescript/src/views/ScreenFallback.d.ts +5 -5
  85. package/lib/typescript/src/views/ScreenFallback.d.ts.map +1 -1
  86. package/lib/typescript/src/views/TabBarIcon.d.ts +2 -2
  87. package/lib/typescript/src/views/TabBarIcon.d.ts.map +1 -1
  88. package/package.json +17 -18
  89. package/src/TransitionConfigs/SceneStyleInterpolators.tsx +44 -0
  90. package/src/TransitionConfigs/TransitionPresets.tsx +13 -0
  91. package/src/TransitionConfigs/TransitionSpecs.tsx +19 -0
  92. package/src/index.tsx +10 -0
  93. package/src/navigators/createBottomTabNavigator.tsx +40 -12
  94. package/src/types.tsx +102 -5
  95. package/src/utils/useAnimatedHashMap.tsx +25 -0
  96. package/src/utils/useIsKeyboardShown.tsx +1 -1
  97. package/src/views/Badge.tsx +6 -1
  98. package/src/views/BottomTabBar.tsx +125 -47
  99. package/src/views/BottomTabItem.tsx +52 -82
  100. package/src/views/BottomTabView.tsx +163 -14
  101. package/src/views/ScreenFallback.tsx +12 -13
  102. package/src/views/TabBarIcon.tsx +16 -24
@@ -9,19 +9,25 @@ 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
+ import {
16
+ FadeTransition,
17
+ ShiftTransition,
18
+ } from '../TransitionConfigs/TransitionPresets';
15
19
  import type {
16
20
  BottomTabBarProps,
17
21
  BottomTabDescriptorMap,
18
22
  BottomTabHeaderProps,
19
23
  BottomTabNavigationConfig,
20
24
  BottomTabNavigationHelpers,
25
+ BottomTabNavigationOptions,
21
26
  BottomTabNavigationProp,
22
27
  } from '../types';
23
28
  import { BottomTabBarHeightCallbackContext } from '../utils/BottomTabBarHeightCallbackContext';
24
29
  import { BottomTabBarHeightContext } from '../utils/BottomTabBarHeightContext';
30
+ import { useAnimatedHashMap } from '../utils/useAnimatedHashMap';
25
31
  import { BottomTabBar, getTabBarHeight } from './BottomTabBar';
26
32
  import { MaybeScreen, MaybeScreenContainer } from './ScreenFallback';
27
33
 
@@ -31,6 +37,33 @@ type Props = BottomTabNavigationConfig & {
31
37
  descriptors: BottomTabDescriptorMap;
32
38
  };
33
39
 
40
+ const EPSILON = 1e-5;
41
+ const STATE_INACTIVE = 0;
42
+ const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
43
+ const STATE_ON_TOP = 2;
44
+
45
+ const NAMED_TRANSITIONS_PRESETS = {
46
+ fade: FadeTransition,
47
+ shift: ShiftTransition,
48
+ none: {
49
+ sceneStyleInterpolator: undefined,
50
+ transitionSpec: {
51
+ animation: 'timing',
52
+ config: { duration: 0 },
53
+ },
54
+ },
55
+ } as const;
56
+
57
+ const hasAnimation = (options: BottomTabNavigationOptions) => {
58
+ const { animation, transitionSpec } = options;
59
+
60
+ if (animation) {
61
+ return animation !== 'none';
62
+ }
63
+
64
+ return !transitionSpec;
65
+ };
66
+
34
67
  export function BottomTabView(props: Props) {
35
68
  const {
36
69
  tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
@@ -45,12 +78,65 @@ export function BottomTabView(props: Props) {
45
78
  } = props;
46
79
 
47
80
  const focusedRouteKey = state.routes[state.index].key;
81
+
82
+ /**
83
+ * List of loaded tabs, tabs will be loaded when navigated to.
84
+ */
48
85
  const [loaded, setLoaded] = React.useState([focusedRouteKey]);
49
86
 
50
87
  if (!loaded.includes(focusedRouteKey)) {
88
+ // Set the current tab to be loaded if it was not loaded before
51
89
  setLoaded([...loaded, focusedRouteKey]);
52
90
  }
53
91
 
92
+ const previousRouteKeyRef = React.useRef(focusedRouteKey);
93
+ const tabAnims = useAnimatedHashMap(state);
94
+
95
+ React.useEffect(() => {
96
+ const previousRouteKey = previousRouteKeyRef.current;
97
+
98
+ previousRouteKeyRef.current = focusedRouteKey;
99
+
100
+ const animateToIndex = () => {
101
+ Animated.parallel(
102
+ state.routes
103
+ .map((route, index) => {
104
+ const { options } = descriptors[route.key];
105
+ const {
106
+ animation = 'none',
107
+ transitionSpec = NAMED_TRANSITIONS_PRESETS[animation]
108
+ .transitionSpec,
109
+ } = options;
110
+
111
+ let spec = transitionSpec;
112
+
113
+ if (
114
+ route.key !== previousRouteKey &&
115
+ route.key !== focusedRouteKey
116
+ ) {
117
+ // Don't animate if the screen is not previous one or new one
118
+ // This will avoid flicker for screens not involved in the transition
119
+ spec = NAMED_TRANSITIONS_PRESETS.none.transitionSpec;
120
+ }
121
+
122
+ spec = spec ?? NAMED_TRANSITIONS_PRESETS.none.transitionSpec;
123
+
124
+ const toValue =
125
+ index === state.index ? 0 : index >= state.index ? 1 : -1;
126
+
127
+ return Animated[spec.animation](tabAnims[route.key], {
128
+ ...spec.config,
129
+ toValue,
130
+ useNativeDriver: true,
131
+ });
132
+ })
133
+ .filter(Boolean) as Animated.CompositeAnimation[]
134
+ ).start();
135
+ };
136
+
137
+ animateToIndex();
138
+ }, [descriptors, focusedRouteKey, state.index, state.routes, tabAnims]);
139
+
54
140
  const dimensions = SafeAreaProviderCompat.initialMetrics.frame;
55
141
  const [tabBarHeight, setTabBarHeight] = React.useState(() =>
56
142
  getTabBarHeight({
@@ -88,24 +174,55 @@ export function BottomTabView(props: Props) {
88
174
 
89
175
  const { routes } = state;
90
176
 
177
+ // If there is no animation, we only have 2 states: visible and invisible
178
+ const hasTwoStates = !routes.some((route) =>
179
+ hasAnimation(descriptors[route.key].options)
180
+ );
181
+
182
+ const { tabBarPosition = 'bottom' } = descriptors[focusedRouteKey].options;
183
+
91
184
  return (
92
- <SafeAreaProviderCompat>
185
+ <SafeAreaProviderCompat
186
+ style={
187
+ tabBarPosition === 'left'
188
+ ? styles.start
189
+ : tabBarPosition === 'right'
190
+ ? styles.end
191
+ : null
192
+ }
193
+ >
194
+ {tabBarPosition === 'top' ? (
195
+ <BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
196
+ {renderTabBar()}
197
+ </BottomTabBarHeightCallbackContext.Provider>
198
+ ) : null}
93
199
  <MaybeScreenContainer
94
200
  enabled={detachInactiveScreens}
95
- hasTwoStates
96
- style={styles.container}
201
+ hasTwoStates={hasTwoStates}
202
+ style={styles.screens}
97
203
  >
98
204
  {routes.map((route, index) => {
99
205
  const descriptor = descriptors[route.key];
100
- const { lazy = true, unmountOnBlur } = descriptor.options;
206
+ const {
207
+ lazy = true,
208
+ unmountOnBlur,
209
+ animation = 'none',
210
+ sceneStyleInterpolator = NAMED_TRANSITIONS_PRESETS[animation]
211
+ .sceneStyleInterpolator,
212
+ } = descriptor.options;
101
213
  const isFocused = state.index === index;
102
214
 
103
215
  if (unmountOnBlur && !isFocused) {
104
216
  return null;
105
217
  }
106
218
 
107
- if (lazy && !loaded.includes(route.key) && !isFocused) {
108
- // Don't render a lazy screen if we've never navigated to it
219
+ if (
220
+ lazy &&
221
+ !loaded.includes(route.key) &&
222
+ !isFocused &&
223
+ !state.preloadedRouteKeys.includes(route.key)
224
+ ) {
225
+ // Don't render a lazy screen if we've never navigated to it or it wasn't preloaded
109
226
  return null;
110
227
  }
111
228
 
@@ -123,15 +240,39 @@ export function BottomTabView(props: Props) {
123
240
  headerTransparent,
124
241
  } = descriptor.options;
125
242
 
243
+ const { sceneStyle } =
244
+ sceneStyleInterpolator?.({
245
+ current: {
246
+ progress: tabAnims[route.key],
247
+ },
248
+ }) ?? {};
249
+
250
+ const animationEnabled = hasAnimation(descriptor.options);
251
+ const activityState = isFocused
252
+ ? STATE_ON_TOP // the screen is on top after the transition
253
+ : animationEnabled // is animation is not enabled, immediately move to inactive state
254
+ ? tabAnims[route.key].interpolate({
255
+ inputRange: [0, 1 - EPSILON, 1],
256
+ outputRange: [
257
+ STATE_TRANSITIONING_OR_BELOW_TOP, // screen visible during transition
258
+ STATE_TRANSITIONING_OR_BELOW_TOP,
259
+ STATE_INACTIVE, // the screen is detached after transition
260
+ ],
261
+ extrapolate: 'extend',
262
+ })
263
+ : STATE_INACTIVE;
264
+
126
265
  return (
127
266
  <MaybeScreen
128
267
  key={route.key}
129
268
  style={[StyleSheet.absoluteFill, { zIndex: isFocused ? 0 : -1 }]}
130
- visible={isFocused}
269
+ active={activityState}
131
270
  enabled={detachInactiveScreens}
132
271
  freezeOnBlur={freezeOnBlur}
133
272
  >
134
- <BottomTabBarHeightContext.Provider value={tabBarHeight}>
273
+ <BottomTabBarHeightContext.Provider
274
+ value={tabBarPosition === 'bottom' ? tabBarHeight : 0}
275
+ >
135
276
  <Screen
136
277
  focused={isFocused}
137
278
  route={descriptor.route}
@@ -146,7 +287,7 @@ export function BottomTabView(props: Props) {
146
287
  descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
147
288
  options: descriptor.options,
148
289
  })}
149
- style={sceneContainerStyle}
290
+ style={[sceneContainerStyle, animationEnabled && sceneStyle]}
150
291
  >
151
292
  {descriptor.render()}
152
293
  </Screen>
@@ -155,15 +296,23 @@ export function BottomTabView(props: Props) {
155
296
  );
156
297
  })}
157
298
  </MaybeScreenContainer>
158
- <BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
159
- {renderTabBar()}
160
- </BottomTabBarHeightCallbackContext.Provider>
299
+ {tabBarPosition !== 'top' ? (
300
+ <BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
301
+ {renderTabBar()}
302
+ </BottomTabBarHeightCallbackContext.Provider>
303
+ ) : null}
161
304
  </SafeAreaProviderCompat>
162
305
  );
163
306
  }
164
307
 
165
308
  const styles = StyleSheet.create({
166
- container: {
309
+ start: {
310
+ flexDirection: 'row-reverse',
311
+ },
312
+ end: {
313
+ flexDirection: 'row',
314
+ },
315
+ screens: {
167
316
  flex: 1,
168
317
  overflow: 'hidden',
169
318
  },
@@ -1,11 +1,16 @@
1
- import { ResourceSavingView } from '@react-navigation/elements';
2
1
  import * as React from 'react';
3
- import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
2
+ import {
3
+ Animated,
4
+ type StyleProp,
5
+ View,
6
+ type ViewProps,
7
+ type ViewStyle,
8
+ } from 'react-native';
4
9
 
5
10
  type Props = {
6
- visible: boolean;
7
- children: React.ReactNode;
8
11
  enabled: boolean;
12
+ active: 0 | 1 | 2 | Animated.AnimatedInterpolation<0 | 1>;
13
+ children: React.ReactNode;
9
14
  freezeOnBlur?: boolean;
10
15
  style?: StyleProp<ViewStyle>;
11
16
  };
@@ -33,18 +38,12 @@ export const MaybeScreenContainer = ({
33
38
  return <View {...rest} />;
34
39
  };
35
40
 
36
- export function MaybeScreen({ visible, children, ...rest }: Props) {
41
+ export function MaybeScreen({ enabled, active, ...rest }: ViewProps & Props) {
37
42
  if (Screens?.screensEnabled?.()) {
38
43
  return (
39
- <Screens.Screen activityState={visible ? 2 : 0} {...rest}>
40
- {children}
41
- </Screens.Screen>
44
+ <Screens.Screen enabled={enabled} activityState={active} {...rest} />
42
45
  );
43
46
  }
44
47
 
45
- return (
46
- <ResourceSavingView visible={visible} {...rest}>
47
- {children}
48
- </ResourceSavingView>
49
- );
48
+ return <View {...rest} />;
50
49
  }
@@ -1,11 +1,11 @@
1
1
  import type { Route } from '@react-navigation/native';
2
2
  import React from 'react';
3
3
  import {
4
- StyleProp,
4
+ type StyleProp,
5
5
  StyleSheet,
6
- TextStyle,
6
+ type TextStyle,
7
7
  View,
8
- ViewStyle,
8
+ type ViewStyle,
9
9
  } from 'react-native';
10
10
 
11
11
  import { Badge } from './Badge';
@@ -27,6 +27,8 @@ type Props = {
27
27
  style: StyleProp<ViewStyle>;
28
28
  };
29
29
 
30
+ const ICON_SIZE = 25;
31
+
30
32
  export function TabBarIcon({
31
33
  route: _,
32
34
  horizontal,
@@ -39,8 +41,6 @@ export function TabBarIcon({
39
41
  renderIcon,
40
42
  style,
41
43
  }: Props) {
42
- const size = 25;
43
-
44
44
  // We render the icon twice at the same position on top of each other:
45
45
  // active and inactive one, so we can fade between them.
46
46
  return (
@@ -50,25 +50,21 @@ export function TabBarIcon({
50
50
  <View style={[styles.icon, { opacity: activeOpacity }]}>
51
51
  {renderIcon({
52
52
  focused: true,
53
- size,
53
+ size: ICON_SIZE,
54
54
  color: activeTintColor,
55
55
  })}
56
56
  </View>
57
57
  <View style={[styles.icon, { opacity: inactiveOpacity }]}>
58
58
  {renderIcon({
59
59
  focused: false,
60
- size,
60
+ size: ICON_SIZE,
61
61
  color: inactiveTintColor,
62
62
  })}
63
63
  </View>
64
64
  <Badge
65
65
  visible={badge != null}
66
- style={[
67
- styles.badge,
68
- horizontal ? styles.badgeHorizontal : styles.badgeVertical,
69
- badgeStyle,
70
- ]}
71
- size={(size * 3) / 4}
66
+ style={[styles.badge, badgeStyle]}
67
+ size={ICON_SIZE * 0.75}
72
68
  >
73
69
  {badge}
74
70
  </Badge>
@@ -88,23 +84,19 @@ const styles = StyleSheet.create({
88
84
  height: '100%',
89
85
  width: '100%',
90
86
  // Workaround for react-native >= 0.54 layout bug
91
- minWidth: 25,
87
+ minWidth: ICON_SIZE,
92
88
  },
93
89
  iconVertical: {
94
- flex: 1,
90
+ width: ICON_SIZE,
91
+ height: ICON_SIZE,
95
92
  },
96
93
  iconHorizontal: {
97
- height: '100%',
98
- marginTop: 3,
94
+ width: ICON_SIZE,
95
+ height: ICON_SIZE,
99
96
  },
100
97
  badge: {
101
98
  position: 'absolute',
102
- left: 3,
103
- },
104
- badgeVertical: {
105
- top: 3,
106
- },
107
- badgeHorizontal: {
108
- top: 7,
99
+ end: -5,
100
+ top: -5,
109
101
  },
110
102
  });