@react-navigation/bottom-tabs 7.19.2 → 7.21.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.
Files changed (33) hide show
  1. package/lib/module/unstable/Deferred.js +3 -10
  2. package/lib/module/unstable/Deferred.js.map +1 -1
  3. package/lib/module/unstable/NativeBottomTabView.native.js +309 -198
  4. package/lib/module/unstable/NativeBottomTabView.native.js.map +1 -1
  5. package/lib/module/views/BottomTabBar.js +111 -58
  6. package/lib/module/views/BottomTabBar.js.map +1 -1
  7. package/lib/module/views/BottomTabItem.js +2 -0
  8. package/lib/module/views/BottomTabItem.js.map +1 -1
  9. package/lib/module/views/BottomTabView.js +81 -60
  10. package/lib/module/views/BottomTabView.js.map +1 -1
  11. package/lib/module/views/TabBarIcon.js +6 -2
  12. package/lib/module/views/TabBarIcon.js.map +1 -1
  13. package/lib/typescript/src/types.d.ts +26 -1
  14. package/lib/typescript/src/types.d.ts.map +1 -1
  15. package/lib/typescript/src/unstable/Deferred.d.ts.map +1 -1
  16. package/lib/typescript/src/unstable/NativeBottomTabView.native.d.ts.map +1 -1
  17. package/lib/typescript/src/unstable/types.d.ts +148 -5
  18. package/lib/typescript/src/unstable/types.d.ts.map +1 -1
  19. package/lib/typescript/src/views/BottomTabBar.d.ts.map +1 -1
  20. package/lib/typescript/src/views/BottomTabItem.d.ts +5 -1
  21. package/lib/typescript/src/views/BottomTabItem.d.ts.map +1 -1
  22. package/lib/typescript/src/views/BottomTabView.d.ts.map +1 -1
  23. package/lib/typescript/src/views/TabBarIcon.d.ts +2 -1
  24. package/lib/typescript/src/views/TabBarIcon.d.ts.map +1 -1
  25. package/package.json +6 -6
  26. package/src/types.tsx +36 -2
  27. package/src/unstable/Deferred.tsx +3 -11
  28. package/src/unstable/NativeBottomTabView.native.tsx +376 -217
  29. package/src/unstable/types.tsx +164 -6
  30. package/src/views/BottomTabBar.tsx +153 -72
  31. package/src/views/BottomTabItem.tsx +6 -0
  32. package/src/views/BottomTabView.tsx +101 -69
  33. package/src/views/TabBarIcon.tsx +9 -2
@@ -5,6 +5,7 @@ import {
5
5
  Screen,
6
6
  } from '@react-navigation/elements';
7
7
  import {
8
+ type NavigationRoute,
8
9
  type ParamListBase,
9
10
  StackActions,
10
11
  type TabNavigationState,
@@ -19,6 +20,7 @@ import {
19
20
  } from '../TransitionConfigs/TransitionPresets';
20
21
  import type {
21
22
  BottomTabBarProps,
23
+ BottomTabDescriptor,
22
24
  BottomTabDescriptorMap,
23
25
  BottomTabHeaderProps,
24
26
  BottomTabNavigationConfig,
@@ -187,9 +189,7 @@ export function BottomTabView(props: Props) {
187
189
  // Delay clearing so the previous screen stays attached
188
190
  // This will give time for any native logic to run
189
191
  timer = setTimeout(() => {
190
- setLastUpdate((update) =>
191
- update.animating ? { ...update, animating: false } : update
192
- );
192
+ setLastUpdate({ current: focusedRouteKey, animating: false });
193
193
  }, 32);
194
194
  }
195
195
  });
@@ -285,12 +285,7 @@ export function BottomTabView(props: Props) {
285
285
  >
286
286
  {routes.map((route, index) => {
287
287
  const descriptor = descriptors[route.key];
288
- const {
289
- lazy = true,
290
- animation = 'none',
291
- sceneStyleInterpolator = NAMED_TRANSITIONS_PRESETS[animation]
292
- .sceneStyleInterpolator,
293
- } = descriptor.options;
288
+ const { lazy = true } = descriptor.options;
294
289
  const isFocused = state.index === index;
295
290
  const isPreloaded = state.preloadedRouteKeys.includes(route.key);
296
291
 
@@ -304,73 +299,23 @@ export function BottomTabView(props: Props) {
304
299
  return null;
305
300
  }
306
301
 
307
- const {
308
- freezeOnBlur,
309
- header = ({ layout, options }: BottomTabHeaderProps) => (
310
- <Header
311
- {...options}
312
- layout={layout}
313
- title={getHeaderTitle(options, route.name)}
314
- />
315
- ),
316
- headerShown,
317
- headerStatusBarHeight,
318
- headerTransparent,
319
- sceneStyle: customSceneStyle,
320
- } = descriptor.options;
321
-
322
- const { sceneStyle } =
323
- sceneStyleInterpolator?.({
324
- current: {
325
- progress: tabAnims[route.key],
326
- },
327
- }) ?? {};
328
-
329
- const animationEnabled = hasAnimation(descriptor.options);
330
302
  const isAnimatingRoute =
331
303
  lastUpdate.animating &&
332
304
  (lastUpdate.previous === route.key ||
333
305
  lastUpdate.current === route.key);
334
306
 
335
- const activityState = isFocused
336
- ? STATE_ON_TOP // the screen is on top after the transition
337
- : animationEnabled && isAnimatingRoute
338
- ? STATE_TRANSITIONING_OR_BELOW_TOP // screen visible during transition
339
- : STATE_INACTIVE;
340
-
341
307
  return (
342
- <MaybeScreen
308
+ <TabScene
343
309
  key={route.key}
344
- style={[StyleSheet.absoluteFill, { zIndex: isFocused ? 0 : -1 }]}
345
- active={activityState}
346
- enabled={detachInactiveScreens}
347
- freezeOnBlur={freezeOnBlur}
348
- shouldFreeze={activityState === STATE_INACTIVE && !isPreloaded}
349
- pointerEvents={isFocused ? 'box-none' : 'none'}
350
- >
351
- <BottomTabBarHeightContext.Provider
352
- value={tabBarPosition === 'bottom' ? tabBarHeight : 0}
353
- >
354
- <Screen
355
- focused={isFocused}
356
- route={descriptor.route}
357
- navigation={descriptor.navigation}
358
- headerShown={headerShown}
359
- headerStatusBarHeight={headerStatusBarHeight}
360
- headerTransparent={headerTransparent}
361
- header={header({
362
- layout: dimensions,
363
- route: descriptor.route,
364
- navigation:
365
- descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
366
- options: descriptor.options,
367
- })}
368
- style={[customSceneStyle, animationEnabled && sceneStyle]}
369
- >
370
- {descriptor.render()}
371
- </Screen>
372
- </BottomTabBarHeightContext.Provider>
373
- </MaybeScreen>
310
+ route={route}
311
+ descriptor={descriptor}
312
+ isFocused={isFocused}
313
+ isPreloaded={isPreloaded}
314
+ isAnimatingRoute={isAnimatingRoute}
315
+ tabAnimation={tabAnims[route.key]}
316
+ tabBarHeight={tabBarPosition === 'bottom' ? tabBarHeight : 0}
317
+ detachInactiveScreens={detachInactiveScreens}
318
+ />
374
319
  );
375
320
  })}
376
321
  </MaybeScreenContainer>
@@ -381,6 +326,93 @@ export function BottomTabView(props: Props) {
381
326
  );
382
327
  }
383
328
 
329
+ type TabSceneProps = {
330
+ route: NavigationRoute<ParamListBase, string>;
331
+ descriptor: BottomTabDescriptor;
332
+ isFocused: boolean;
333
+ isPreloaded: boolean;
334
+ isAnimatingRoute: boolean;
335
+ tabAnimation: Animated.Value;
336
+ tabBarHeight: number;
337
+ detachInactiveScreens: boolean;
338
+ };
339
+
340
+ const TabScene = React.memo(function TabScene({
341
+ route,
342
+ descriptor,
343
+ isFocused,
344
+ isPreloaded,
345
+ isAnimatingRoute,
346
+ tabAnimation,
347
+ tabBarHeight,
348
+ detachInactiveScreens,
349
+ }: TabSceneProps) {
350
+ const {
351
+ animation = 'none',
352
+ sceneStyleInterpolator = NAMED_TRANSITIONS_PRESETS[animation]
353
+ .sceneStyleInterpolator,
354
+ freezeOnBlur,
355
+ header = ({ layout, options }: BottomTabHeaderProps) => (
356
+ <Header
357
+ {...options}
358
+ layout={layout}
359
+ title={getHeaderTitle(options, route.name)}
360
+ />
361
+ ),
362
+ headerShown,
363
+ headerStatusBarHeight,
364
+ headerTransparent,
365
+ sceneStyle: customSceneStyle,
366
+ } = descriptor.options;
367
+
368
+ const { sceneStyle } =
369
+ sceneStyleInterpolator?.({
370
+ current: {
371
+ progress: tabAnimation,
372
+ },
373
+ }) ?? {};
374
+
375
+ const animationEnabled = hasAnimation(descriptor.options);
376
+
377
+ const activityState = isFocused
378
+ ? STATE_ON_TOP // the screen is on top after the transition
379
+ : animationEnabled && isAnimatingRoute
380
+ ? STATE_TRANSITIONING_OR_BELOW_TOP // screen visible during transition
381
+ : STATE_INACTIVE;
382
+
383
+ return (
384
+ <MaybeScreen
385
+ style={[StyleSheet.absoluteFill, { zIndex: isFocused ? 0 : -1 }]}
386
+ active={activityState}
387
+ enabled={detachInactiveScreens}
388
+ freezeOnBlur={freezeOnBlur}
389
+ shouldFreeze={activityState === STATE_INACTIVE && !isPreloaded}
390
+ pointerEvents={isFocused ? 'box-none' : 'none'}
391
+ >
392
+ <BottomTabBarHeightContext.Provider value={tabBarHeight}>
393
+ <Screen
394
+ focused={isFocused}
395
+ route={descriptor.route}
396
+ navigation={descriptor.navigation}
397
+ headerShown={headerShown}
398
+ headerStatusBarHeight={headerStatusBarHeight}
399
+ headerTransparent={headerTransparent}
400
+ header={header({
401
+ layout: SafeAreaProviderCompat.initialMetrics.frame,
402
+ route: descriptor.route,
403
+ navigation:
404
+ descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
405
+ options: descriptor.options,
406
+ })}
407
+ style={[customSceneStyle, animationEnabled && sceneStyle]}
408
+ >
409
+ {descriptor.render()}
410
+ </Screen>
411
+ </BottomTabBarHeightContext.Provider>
412
+ </MaybeScreen>
413
+ );
414
+ });
415
+
384
416
  const styles = StyleSheet.create({
385
417
  screens: {
386
418
  flex: 1,
@@ -13,6 +13,7 @@ type Props = {
13
13
  route: Route<string>;
14
14
  variant: 'uikit' | 'material';
15
15
  size: 'compact' | 'regular';
16
+ iconSize?: number;
16
17
  badge?: string | number;
17
18
  badgeStyle?: StyleProp<TextStyle>;
18
19
  activeOpacity: number;
@@ -44,6 +45,7 @@ export function TabBarIcon({
44
45
  route: _,
45
46
  variant,
46
47
  size,
48
+ iconSize: iconSizeOverride,
47
49
  badge,
48
50
  badgeStyle,
49
51
  activeOpacity,
@@ -55,11 +57,12 @@ export function TabBarIcon({
55
57
  style,
56
58
  }: Props) {
57
59
  const iconSize =
58
- variant === 'material'
60
+ iconSizeOverride ??
61
+ (variant === 'material'
59
62
  ? ICON_SIZE_MATERIAL
60
63
  : size === 'compact'
61
64
  ? ICON_SIZE_ROUND_COMPACT
62
- : ICON_SIZE_ROUND;
65
+ : ICON_SIZE_ROUND);
63
66
 
64
67
  // We render the icon twice at the same position on top of each other:
65
68
  // active and inactive one, so we can fade between them.
@@ -71,6 +74,10 @@ export function TabBarIcon({
71
74
  : size === 'compact'
72
75
  ? styles.wrapperUikitCompact
73
76
  : styles.wrapperUikit,
77
+ iconSizeOverride != null && {
78
+ width: iconSizeOverride,
79
+ height: iconSizeOverride,
80
+ },
74
81
  style,
75
82
  ]}
76
83
  >