@react-navigation/bottom-tabs 8.0.0-alpha.5 → 8.0.0-alpha.51

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 (84) hide show
  1. package/lib/module/index.js +1 -1
  2. package/lib/module/index.js.map +1 -1
  3. package/lib/module/navigators/createBottomTabNavigator.js +11 -34
  4. package/lib/module/navigators/createBottomTabNavigator.js.map +1 -1
  5. package/lib/module/utils/useBottomTabAnimation.js +1 -1
  6. package/lib/module/utils/useBottomTabAnimation.js.map +1 -1
  7. package/lib/module/utils/useBottomTabBarHeight.js +1 -1
  8. package/lib/module/utils/useBottomTabBarHeight.js.map +1 -1
  9. package/lib/module/utils/useIsKeyboardShown.js.map +1 -1
  10. package/lib/module/views/BottomTabBar.js +40 -29
  11. package/lib/module/views/BottomTabBar.js.map +1 -1
  12. package/lib/module/views/BottomTabItem.js +3 -5
  13. package/lib/module/views/BottomTabItem.js.map +1 -1
  14. package/lib/module/views/{BottomTabViewCommon.js → BottomTabView.js} +1 -1
  15. package/lib/module/views/BottomTabView.js.map +1 -0
  16. package/lib/module/views/BottomTabViewCustom.js +97 -81
  17. package/lib/module/views/BottomTabViewCustom.js.map +1 -1
  18. package/lib/module/views/BottomTabViewNativeImpl.js +339 -121
  19. package/lib/module/views/BottomTabViewNativeImpl.js.map +1 -1
  20. package/lib/module/views/Deferred.js +41 -0
  21. package/lib/module/views/Deferred.js.map +1 -0
  22. package/lib/module/views/ScreenContent.js.map +1 -1
  23. package/lib/module/views/TabBarIcon.js +11 -48
  24. package/lib/module/views/TabBarIcon.js.map +1 -1
  25. package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts +1 -1
  26. package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts.map +1 -1
  27. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +1 -1
  28. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
  29. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +1 -1
  30. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
  31. package/lib/typescript/src/index.d.ts +11 -11
  32. package/lib/typescript/src/index.d.ts.map +1 -1
  33. package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts +9 -15
  34. package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts.map +1 -1
  35. package/lib/typescript/src/types.d.ts +66 -104
  36. package/lib/typescript/src/types.d.ts.map +1 -1
  37. package/lib/typescript/src/utils/BottomTabAnimationContext.d.ts +1 -1
  38. package/lib/typescript/src/utils/BottomTabAnimationContext.d.ts.map +1 -1
  39. package/lib/typescript/src/utils/useAnimatedHashMap.d.ts +1 -1
  40. package/lib/typescript/src/utils/useAnimatedHashMap.d.ts.map +1 -1
  41. package/lib/typescript/src/utils/useBottomTabAnimation.d.ts +1 -1
  42. package/lib/typescript/src/utils/useBottomTabAnimation.d.ts.map +1 -1
  43. package/lib/typescript/src/utils/useTabBarPosition.d.ts +1 -1
  44. package/lib/typescript/src/utils/useTabBarPosition.d.ts.map +1 -1
  45. package/lib/typescript/src/views/BottomTabBar.d.ts +3 -2
  46. package/lib/typescript/src/views/BottomTabBar.d.ts.map +1 -1
  47. package/lib/typescript/src/views/BottomTabItem.d.ts +20 -20
  48. package/lib/typescript/src/views/BottomTabItem.d.ts.map +1 -1
  49. package/lib/typescript/src/views/{BottomTabViewCommon.d.ts → BottomTabView.d.ts} +3 -3
  50. package/lib/typescript/src/views/BottomTabView.d.ts.map +1 -0
  51. package/lib/typescript/src/views/BottomTabViewCustom.d.ts +3 -2
  52. package/lib/typescript/src/views/BottomTabViewCustom.d.ts.map +1 -1
  53. package/lib/typescript/src/views/BottomTabViewNative.android.d.ts +1 -1
  54. package/lib/typescript/src/views/BottomTabViewNative.android.d.ts.map +1 -1
  55. package/lib/typescript/src/views/BottomTabViewNative.d.ts +1 -1
  56. package/lib/typescript/src/views/BottomTabViewNative.d.ts.map +1 -1
  57. package/lib/typescript/src/views/BottomTabViewNative.ios.d.ts +1 -1
  58. package/lib/typescript/src/views/BottomTabViewNative.ios.d.ts.map +1 -1
  59. package/lib/typescript/src/views/BottomTabViewNativeImpl.d.ts +3 -2
  60. package/lib/typescript/src/views/BottomTabViewNativeImpl.d.ts.map +1 -1
  61. package/lib/typescript/src/views/Deferred.d.ts +31 -0
  62. package/lib/typescript/src/views/Deferred.d.ts.map +1 -0
  63. package/lib/typescript/src/views/ScreenContent.d.ts +4 -4
  64. package/lib/typescript/src/views/ScreenContent.d.ts.map +1 -1
  65. package/lib/typescript/src/views/TabBarIcon.d.ts +8 -8
  66. package/lib/typescript/src/views/TabBarIcon.d.ts.map +1 -1
  67. package/package.json +22 -22
  68. package/src/index.tsx +2 -2
  69. package/src/navigators/createBottomTabNavigator.tsx +34 -102
  70. package/src/types.tsx +66 -112
  71. package/src/utils/useAnimatedHashMap.tsx +1 -1
  72. package/src/utils/useBottomTabAnimation.tsx +1 -1
  73. package/src/utils/useBottomTabBarHeight.tsx +1 -1
  74. package/src/utils/useIsKeyboardShown.tsx +2 -2
  75. package/src/views/BottomTabBar.tsx +68 -25
  76. package/src/views/BottomTabItem.tsx +19 -21
  77. package/src/views/BottomTabViewCustom.tsx +172 -143
  78. package/src/views/BottomTabViewNativeImpl.tsx +480 -161
  79. package/src/views/Deferred.tsx +57 -0
  80. package/src/views/ScreenContent.tsx +1 -2
  81. package/src/views/TabBarIcon.tsx +13 -56
  82. package/lib/module/views/BottomTabViewCommon.js.map +0 -1
  83. package/lib/typescript/src/views/BottomTabViewCommon.d.ts.map +0 -1
  84. /package/src/views/{BottomTabViewCommon.tsx → BottomTabView.tsx} +0 -0
package/src/types.tsx CHANGED
@@ -1,11 +1,12 @@
1
1
  import type {
2
2
  HeaderOptions,
3
+ Icon,
3
4
  PlatformPressable,
4
5
  } from '@react-navigation/elements';
6
+ import type { Screen } from '@react-navigation/elements/internal';
5
7
  import type {
6
8
  DefaultNavigatorOptions,
7
9
  Descriptor,
8
- MaterialSymbolProps,
9
10
  NavigationHelpers,
10
11
  NavigationProp,
11
12
  ParamListBase,
@@ -20,7 +21,6 @@ import type {
20
21
  Animated,
21
22
  ColorValue,
22
23
  GestureResponderEvent,
23
- ImageSourcePropType,
24
24
  StyleProp,
25
25
  TextStyle,
26
26
  ViewStyle,
@@ -28,9 +28,8 @@ import type {
28
28
  import type {
29
29
  TabBarItemLabelVisibilityMode,
30
30
  TabsScreenBlurEffect,
31
- TabsSystemItem,
31
+ TabsScreenSystemItem,
32
32
  } from 'react-native-screens';
33
- import type { SFSymbol } from 'sf-symbols-typescript';
34
33
 
35
34
  export type Variant = 'uikit' | 'material';
36
35
 
@@ -39,6 +38,7 @@ export type BottomTabNavigationEventMap = {
39
38
  * Event which fires on tapping on the tab in the tab bar.
40
39
  *
41
40
  * Preventing default is only supported with `custom` implementation.
41
+ * Use `tabBarSelectionEnabled: false` option with `native` implementation.
42
42
  */
43
43
  tabPress: { data: undefined; canPreventDefault: true };
44
44
  /**
@@ -114,67 +114,6 @@ export type TabBarVisibilityAnimationConfig =
114
114
 
115
115
  export type TabAnimationName = 'none' | 'fade' | 'shift';
116
116
 
117
- type IconImage = {
118
- /**
119
- * - `image` - Use a local image as the icon.
120
- */
121
- type: 'image';
122
- /**
123
- * Image source to use as the icon.
124
- * e.g., `require('./path/to/image.png')`
125
- */
126
- source: ImageSourcePropType;
127
- /**
128
- * Whether to apply tint color to the icon.
129
- * Defaults to `true`.
130
- *
131
- * @platform ios
132
- */
133
- tinted?: boolean;
134
- };
135
-
136
- type IconSfSymbol = {
137
- /**
138
- * - `sfSymbol` - Use an SF Symbol as the icon on iOS.
139
- */
140
- type: 'sfSymbol';
141
- /**
142
- * Name of the SF Symbol to use as the icon.
143
- */
144
- name: SFSymbol;
145
- };
146
-
147
- type IconMaterialSymbol = {
148
- /**
149
- * - `materialSymbol` - Use a Material Symbol as the icon on Android.
150
- */
151
- type: 'materialSymbol';
152
- } & Pick<MaterialSymbolProps, 'name' | 'variant' | 'weight'>;
153
-
154
- type IconResource = {
155
- /**
156
- * - `resource` - Use a resource from drawables on Android and xcassets asset catalog on iOS as the icon.
157
- */
158
- type: 'resource';
159
- /**
160
- * Name of the drawable resource or xcasset to use as the icon.
161
- */
162
- name: string;
163
- /**
164
- * Whether to apply tint color to the icon.
165
- * Only supported with custom implementation.
166
- *
167
- * Defaults to `true`.
168
- */
169
- tinted?: boolean;
170
- };
171
-
172
- type IconIOS = IconSfSymbol | IconImage;
173
-
174
- type IconAndroid = IconMaterialSymbol | IconResource | IconImage;
175
-
176
- export type BottomTabIcon = IconIOS | IconAndroid;
177
-
178
117
  type BottomTabCustomOptions = {
179
118
  /**
180
119
  * How the screen should animate when switching tabs.
@@ -212,7 +151,7 @@ type BottomTabCustomOptions = {
212
151
  *
213
152
  * Only supported with `custom` implementation.
214
153
  */
215
- tabBarLabelPosition?: LabelPosition;
154
+ tabBarLabelPosition?: LabelPosition | undefined;
216
155
 
217
156
  /**
218
157
  * Whether label font should scale to respect Text Size accessibility settings.
@@ -311,8 +250,6 @@ type BottomTabCustomOptions = {
311
250
  tabBarButton?: (props: BottomTabBarButtonProps) => React.ReactNode;
312
251
  };
313
252
 
314
- type ScrollEdgeEffect = 'auto' | 'hard' | 'soft' | 'hidden';
315
-
316
253
  type BottomTabNativeOptions = {
317
254
  /**
318
255
  * Uses iOS built-in tab bar items with standard iOS styling and localized titles.
@@ -324,7 +261,7 @@ type BottomTabNativeOptions = {
324
261
  *
325
262
  * @platform ios
326
263
  */
327
- tabBarSystemItem?: TabsSystemItem;
264
+ tabBarSystemItem?: TabsScreenSystemItem;
328
265
 
329
266
  /**
330
267
  * Blur effect applied to the tab bar when tab screen is selected.
@@ -410,36 +347,25 @@ type BottomTabNativeOptions = {
410
347
  }) => React.ReactNode;
411
348
 
412
349
  /**
413
- * Configures the scroll edge effect for the _content ScrollView_ (the ScrollView that is present in first descendants chain of the Screen).
414
- * Depending on values set, it will blur the scrolling content below certain UI elements (header items, search bar)
415
- * for the specified edge of the ScrollView.
416
- *
417
- * When set in nested containers, i.e. Native Stack inside Native Bottom Tabs, or the other way around,
418
- * the ScrollView will use only the innermost one's config.
419
- *
420
- * **Note:** Using both `headerBlurEffect` and `scrollEdgeEffects` (>= iOS 26) simultaneously may cause overlapping effects.
350
+ * Specifies whether `contentInsetAdjustmentBehavior` of the `ScrollView`
351
+ * in the screen is automatically adjusted.
421
352
  *
422
- * Edge effects can be configured for each edge separately. The following values are currently supported:
353
+ * `react-native`'s `ScrollView`s has `contentInsetAdjustmentBehavior` set to `never` by default.
354
+ * To provide a better UX, it is overridden to `automatic` - so it respects the tab bar.
355
+ * Otherwise, content can get hidden behind the tab bar as it's translucent on iOS 26+.
423
356
  *
424
- * - `auto` - the automatic scroll edge effect style,
425
- * - `hard` - a scroll edge effect with a hard cutoff and dividing line,
426
- * - `soft` - a soft-edged scroll edge effect,
427
- * - `hidden` - no scroll edge effect.
357
+ * Note that this only affects the `ScrollView` in first descendant chain of the screen,
358
+ * i.e., first view of each nested view is checked until a `ScrollView` is found.
428
359
  *
429
- * Defaults to `automatic` for each edge.
430
- *
431
- * Available starting from iOS 26.
360
+ * To disable this behavior for specific screens, set this option to `false`.
432
361
  *
433
362
  * Only supported with `native` implementation.
434
363
  *
364
+ * Defaults to `true`.
365
+ *
435
366
  * @platform ios
436
367
  */
437
- scrollEdgeEffects?: {
438
- bottom?: ScrollEdgeEffect;
439
- left?: ScrollEdgeEffect;
440
- right?: ScrollEdgeEffect;
441
- top?: ScrollEdgeEffect;
442
- };
368
+ overrideScrollViewContentInsetAdjustmentBehavior?: boolean;
443
369
  };
444
370
 
445
371
  export type BottomTabNavigationOptions = {
@@ -481,7 +407,7 @@ export type BottomTabNavigationOptions = {
481
407
  */
482
408
  tabBarLabelStyle?: Pick<
483
409
  TextStyle,
484
- 'fontFamily' | 'fontSize' | 'fontWeight' | 'fontStyle'
410
+ 'fontFamily' | 'fontSize' | 'fontWeight' | 'fontStyle' | 'color'
485
411
  >;
486
412
 
487
413
  /**
@@ -493,26 +419,30 @@ export type BottomTabNavigationOptions = {
493
419
  * - on all platforms with `custom` implementation
494
420
  * - on iOS with `native` implementation
495
421
  *
422
+ * With `native` implementation, `tinted` for `image` can only be disabled on iOS.
423
+ *
496
424
  * A React element is only supported with `custom` implementation.
497
425
  */
498
426
  tabBarIcon?:
499
- | BottomTabIcon
427
+ | Icon
500
428
  | ((props: {
501
429
  focused: boolean;
502
430
  color: ColorValue;
503
431
  size: number;
504
- }) => BottomTabIcon | React.ReactNode);
432
+ }) => Icon | React.ReactNode);
505
433
 
506
434
  /**
507
435
  * Text to show in a badge on the tab icon.
508
436
  */
509
- tabBarBadge?: number | string;
437
+ tabBarBadge?: number | string | undefined;
510
438
 
511
439
  /**
512
440
  * Custom style for the tab bar badge.
513
441
  * You can specify a background color or text color here.
514
442
  *
515
- * @platform android
443
+ * With `native` implementation, limited customization is supported:
444
+ * - on iOS, you can only set the background color.
445
+ * - on Android, you can set both background and text colors.
516
446
  */
517
447
  tabBarBadgeStyle?: {
518
448
  backgroundColor?: ColorValue;
@@ -522,7 +452,7 @@ export type BottomTabNavigationOptions = {
522
452
  /**
523
453
  * Color for the icon and label in the active tab.
524
454
  */
525
- tabBarActiveTintColor?: ColorValue;
455
+ tabBarActiveTintColor?: ColorValue | undefined;
526
456
 
527
457
  /**
528
458
  * Color for the icon and label in the inactive tabs.
@@ -582,11 +512,31 @@ export type BottomTabNavigationOptions = {
582
512
  * Only supported on iOS 18 and below.
583
513
  */
584
514
  shadowColor?: ColorValue;
515
+ /**
516
+ * Whether the tab bar is visible.
517
+ * Setting this to `'none'` hides the tab bar.
518
+ */
519
+ display?: 'flex' | 'none';
585
520
  };
586
521
 
587
522
  /**
588
- * Whether this screens should render the first time it's accessed. Defaults to `true`.
589
- * Set it to `false` if you want to render the screen on initial render.
523
+ * Whether this tab can be selected.
524
+ * When `false`, tapping on the tab in tab bar won't select the tab.
525
+ * Custom behavior can be implemented by listening to `tabPress` event.
526
+ *
527
+ * Defaults to `true`.
528
+ */
529
+ tabBarSelectionEnabled?: boolean;
530
+
531
+ /**
532
+ * Whether this screen should render only when first accessed.
533
+ *
534
+ * Defaults to:
535
+ * - `true` with custom implementation.
536
+ * - `false` with native implementation.
537
+ *
538
+ * Enabling in native implementation may result in flicker
539
+ * as React renders after native transition is already finished.
590
540
  */
591
541
  lazy?: boolean;
592
542
 
@@ -599,7 +549,7 @@ export type BottomTabNavigationOptions = {
599
549
  /**
600
550
  * Style object for the component wrapping the screen content.
601
551
  */
602
- sceneStyle?: StyleProp<ViewStyle>;
552
+ sceneStyle?: React.ComponentProps<typeof Screen>['style'];
603
553
 
604
554
  /**
605
555
  * Function that returns a React Element to display as a header.
@@ -612,6 +562,19 @@ export type BottomTabNavigationOptions = {
612
562
  * Defaults to `false` unless a header is provided.
613
563
  */
614
564
  headerShown?: boolean;
565
+
566
+ /**
567
+ * What should happen when screens become inactive.
568
+ * - `pause`: Effects are cleaned up.
569
+ * - `none`: Screen renders normally
570
+ *
571
+ * Defaults to `pause`.
572
+ *
573
+ * If you set `lazy: false` or preload a screen,
574
+ * It won't be paused until after the first time it becomes focused.
575
+ * This makes sure that effects are run to initialize the screen.
576
+ */
577
+ inactiveBehavior?: 'pause' | 'none';
615
578
  } & HeaderOptions &
616
579
  BottomTabNativeOptions &
617
580
  BottomTabCustomOptions;
@@ -655,14 +618,14 @@ export type TransitionSpec =
655
618
  animation: 'timing';
656
619
  config: Omit<
657
620
  Animated.TimingAnimationConfig,
658
- 'toValue' | keyof Animated.AnimationConfig
621
+ 'toValue' | 'useNativeDriver'
659
622
  >;
660
623
  }
661
624
  | {
662
625
  animation: 'spring';
663
626
  config: Omit<
664
627
  Animated.SpringAnimationConfig,
665
- 'toValue' | keyof Animated.AnimationConfig
628
+ 'toValue' | 'useNativeDriver'
666
629
  >;
667
630
  };
668
631
 
@@ -702,15 +665,6 @@ export type BottomTabNavigationConfig = {
702
665
  * Function that returns a React element to display as the tab bar.
703
666
  */
704
667
  tabBar?: (props: BottomTabBarProps) => React.ReactNode;
705
-
706
- /**
707
- * Whether inactive screens should be detached from the view hierarchy to save memory.
708
- *
709
- * Defaults to `true`.
710
- *
711
- * Only supported with `custom` implementation.
712
- */
713
- detachInactiveScreens?: boolean;
714
668
  };
715
669
 
716
670
  export type BottomTabHeaderProps = {
@@ -738,7 +692,7 @@ export type BottomTabBarButtonProps = Omit<
738
692
  React.ComponentProps<typeof PlatformPressable>,
739
693
  'style'
740
694
  > & {
741
- href?: string;
695
+ href?: string | undefined;
742
696
  children: React.ReactNode;
743
697
  style?: StyleProp<ViewStyle>;
744
698
  onPress?: (
@@ -1,4 +1,4 @@
1
- import type { NavigationState } from '@react-navigation/routers';
1
+ import type { NavigationState } from '@react-navigation/native';
2
2
  import * as React from 'react';
3
3
  import { Animated } from 'react-native';
4
4
 
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { BottomTabAnimationContext } from './BottomTabAnimationContext';
4
4
 
5
5
  export function useBottomTabAnimation() {
6
- const animation = React.useContext(BottomTabAnimationContext);
6
+ const animation = React.use(BottomTabAnimationContext);
7
7
 
8
8
  if (animation === undefined) {
9
9
  throw new Error(
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { BottomTabBarHeightContext } from './BottomTabBarHeightContext';
4
4
 
5
5
  export function useBottomTabBarHeight() {
6
- const height = React.useContext(BottomTabBarHeightContext);
6
+ const height = React.use(BottomTabBarHeightContext);
7
7
 
8
8
  if (height === undefined) {
9
9
  throw new Error(
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type EmitterSubscription, Keyboard, Platform } from 'react-native';
2
+ import { Keyboard, Platform } from 'react-native';
3
3
 
4
4
  export function useIsKeyboardShown() {
5
5
  const [isKeyboardShown, setIsKeyboardShown] = React.useState(false);
@@ -8,7 +8,7 @@ export function useIsKeyboardShown() {
8
8
  const handleKeyboardShow = () => setIsKeyboardShown(true);
9
9
  const handleKeyboardHide = () => setIsKeyboardShown(false);
10
10
 
11
- let subscriptions: EmitterSubscription[];
11
+ let subscriptions: ReturnType<typeof Keyboard.addListener>[];
12
12
 
13
13
  if (Platform.OS === 'ios') {
14
14
  subscriptions = [
@@ -12,7 +12,7 @@ import {
12
12
  useLocale,
13
13
  useTheme,
14
14
  } from '@react-navigation/native';
15
- import React from 'react';
15
+ import * as React from 'react';
16
16
  import {
17
17
  Animated,
18
18
  type LayoutChangeEvent,
@@ -57,8 +57,11 @@ const shouldUseHorizontalLabels = ({
57
57
  }: Options & {
58
58
  dimensions: { height: number; width: number };
59
59
  }) => {
60
- const { tabBarLabelPosition } =
61
- descriptors[state.routes[state.index].key].options;
60
+ const focusedRoute = state.routes[state.index];
61
+ const tabBarLabelPosition =
62
+ focusedRoute == null
63
+ ? undefined
64
+ : descriptors[focusedRoute.key]?.options.tabBarLabelPosition;
62
65
 
63
66
  if (tabBarLabelPosition) {
64
67
  switch (tabBarLabelPosition) {
@@ -72,8 +75,9 @@ const shouldUseHorizontalLabels = ({
72
75
  if (dimensions.width >= 768) {
73
76
  // Screen size matches a tablet
74
77
  const maxTabWidth = state.routes.reduce((acc, route) => {
75
- const { tabBarItemStyle } = descriptors[route.key].options;
76
- const flattenedStyle = StyleSheet.flatten(tabBarItemStyle);
78
+ const flattenedStyle = StyleSheet.flatten(
79
+ descriptors[route.key]?.options.tabBarItemStyle
80
+ );
77
81
 
78
82
  if (flattenedStyle) {
79
83
  if (typeof flattenedStyle.width === 'number') {
@@ -97,8 +101,9 @@ const isCompact = ({
97
101
  descriptors,
98
102
  dimensions,
99
103
  }: Options & { dimensions: { height: number; width: number } }): boolean => {
104
+ const focusedRoute = state.routes[state.index];
100
105
  const { tabBarPosition, tabBarVariant } =
101
- descriptors[state.routes[state.index].key].options;
106
+ focusedRoute == null ? {} : (descriptors[focusedRoute.key]?.options ?? {});
102
107
 
103
108
  if (
104
109
  tabBarPosition === 'left' ||
@@ -138,7 +143,11 @@ export const getTabBarHeight = ({
138
143
  dimensions: { height: number; width: number };
139
144
  style: Animated.WithAnimatedValue<StyleProp<ViewStyle>> | undefined;
140
145
  }) => {
141
- const { tabBarPosition } = descriptors[state.routes[state.index].key].options;
146
+ const focusedRoute = state.routes[state.index];
147
+ const tabBarPosition =
148
+ focusedRoute == null
149
+ ? undefined
150
+ : descriptors[focusedRoute.key]?.options.tabBarPosition;
142
151
 
143
152
  const flattenedStyle = StyleSheet.flatten(style);
144
153
  const customHeight =
@@ -165,7 +174,19 @@ export function BottomTabBar({ state, navigation, descriptors, style }: Props) {
165
174
  const { buildHref } = useLinkBuilder();
166
175
 
167
176
  const focusedRoute = state.routes[state.index];
177
+
178
+ if (focusedRoute == null) {
179
+ throw new Error(`Couldn't find a route at index ${state.index}.`);
180
+ }
181
+
168
182
  const focusedDescriptor = descriptors[focusedRoute.key];
183
+
184
+ if (focusedDescriptor == null) {
185
+ throw new Error(
186
+ `Couldn't find a descriptor for route '${focusedRoute.key}'.`
187
+ );
188
+ }
189
+
169
190
  const focusedOptions = focusedDescriptor.options;
170
191
 
171
192
  const {
@@ -207,7 +228,7 @@ export function BottomTabBar({ state, navigation, descriptors, style }: Props) {
207
228
  const insets = useSafeAreaInsets();
208
229
  const isKeyboardShown = useIsKeyboardShown();
209
230
 
210
- const onHeightChange = React.useContext(BottomTabBarHeightCallbackContext);
231
+ const onHeightChange = React.use(BottomTabBarHeightCallbackContext);
211
232
 
212
233
  const shouldShowTabBar = !(tabBarHideOnKeyboard && isKeyboardShown);
213
234
 
@@ -228,18 +249,22 @@ export function BottomTabBar({ state, navigation, descriptors, style }: Props) {
228
249
  React.useEffect(() => {
229
250
  const visibilityAnimationConfig = visibilityAnimationConfigRef.current;
230
251
 
252
+ let animation;
253
+
231
254
  if (shouldShowTabBar) {
232
- const animation =
255
+ const animate =
233
256
  visibilityAnimationConfig?.show?.animation === 'spring'
234
257
  ? Animated.spring
235
258
  : Animated.timing;
236
259
 
237
- animation(visible, {
260
+ animation = animate(visible, {
238
261
  toValue: 1,
239
262
  useNativeDriver,
240
263
  duration: 250,
241
264
  ...visibilityAnimationConfig?.show?.config,
242
- }).start(({ finished }) => {
265
+ });
266
+
267
+ animation.start(({ finished }) => {
243
268
  if (finished) {
244
269
  setIsTabBarHidden(false);
245
270
  }
@@ -248,20 +273,22 @@ export function BottomTabBar({ state, navigation, descriptors, style }: Props) {
248
273
  // eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect
249
274
  setIsTabBarHidden(true);
250
275
 
251
- const animation =
276
+ const animate =
252
277
  visibilityAnimationConfig?.hide?.animation === 'spring'
253
278
  ? Animated.spring
254
279
  : Animated.timing;
255
280
 
256
- animation(visible, {
281
+ animation = animate(visible, {
257
282
  toValue: 0,
258
283
  useNativeDriver,
259
284
  duration: 200,
260
285
  ...visibilityAnimationConfig?.hide?.config,
261
- }).start();
286
+ });
287
+
288
+ animation.start();
262
289
  }
263
290
 
264
- return () => visible.stopAnimation();
291
+ return () => animation.stop();
265
292
  }, [visible, shouldShowTabBar]);
266
293
 
267
294
  const [layout, setLayout] = React.useState({
@@ -343,7 +370,7 @@ export function BottomTabBar({ state, navigation, descriptors, style }: Props) {
343
370
  : { borderTopWidth: StyleSheet.hairlineWidth },
344
371
  {
345
372
  backgroundColor:
346
- tabBarBackgroundElement != null ? 'transparent' : colors.card,
373
+ tabBarBackgroundElement != null ? 'transparent' : colors.background,
347
374
  borderColor: colors.border,
348
375
  },
349
376
  sidebar
@@ -400,7 +427,15 @@ export function BottomTabBar({ state, navigation, descriptors, style }: Props) {
400
427
  >
401
428
  {routes.map((route, index) => {
402
429
  const focused = index === state.index;
403
- const { options } = descriptors[route.key];
430
+ const descriptor = descriptors[route.key];
431
+
432
+ if (descriptor == null) {
433
+ throw new Error(
434
+ `Couldn't find a descriptor for route '${route.key}'.`
435
+ );
436
+ }
437
+
438
+ const { options } = descriptor;
404
439
 
405
440
  const onPress = () => {
406
441
  const event = navigation.emit({
@@ -409,10 +444,16 @@ export function BottomTabBar({ state, navigation, descriptors, style }: Props) {
409
444
  canPreventDefault: true,
410
445
  });
411
446
 
412
- if (!focused && !event.defaultPrevented) {
413
- navigation.dispatch({
414
- ...CommonActions.navigate(route),
415
- target: state.key,
447
+ if (
448
+ !focused &&
449
+ !event.defaultPrevented &&
450
+ options.tabBarSelectionEnabled !== false
451
+ ) {
452
+ React.startTransition(() => {
453
+ navigation.dispatch({
454
+ ...CommonActions.navigate(route.name, route.params),
455
+ target: state.key,
456
+ });
416
457
  });
417
458
  }
418
459
  };
@@ -435,20 +476,22 @@ export function BottomTabBar({ state, navigation, descriptors, style }: Props) {
435
476
  const accessibilityLabel =
436
477
  options.tabBarAccessibilityLabel !== undefined
437
478
  ? options.tabBarAccessibilityLabel
438
- : typeof label === 'string' && Platform.OS === 'ios'
439
- ? `${label}, tab, ${index + 1} of ${routes.length}`
479
+ : typeof label === 'string'
480
+ ? Platform.OS === 'android'
481
+ ? `${label}, ${index + 1} of ${routes.length}`
482
+ : `${label}, ${focused ? 'selected' : 'unselected'}`
440
483
  : undefined;
441
484
 
442
485
  return (
443
486
  <NavigationProvider
444
487
  key={route.key}
445
- navigation={descriptors[route.key].navigation}
488
+ navigation={descriptor.navigation}
446
489
  route={route}
447
490
  >
448
491
  <BottomTabItem
449
492
  href={buildHref(route.name, route.params)}
450
493
  route={route}
451
- descriptor={descriptors[route.key]}
494
+ descriptor={descriptor}
452
495
  focused={focused}
453
496
  horizontal={hasHorizontalLabels}
454
497
  compact={compact}