@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
@@ -28,8 +28,22 @@ export type NativeBottomTabNavigationEventMap = {
28
28
  * Event which fires on tapping on the tab in the tab bar.
29
29
  *
30
30
  * Use the `tabBarSelectionEnabled: false` option to prevent the tab from being selected.
31
- */
32
- tabPress: { data: undefined; canPreventDefault: false };
31
+ * The `data` object contains the following properties:
32
+ * - `origin`: always `native`.
33
+ * - `behavior`: specifies how consumers should handle the tab press:
34
+ * - `scrollToTop`: whether the content should scroll to top.
35
+ * - `popToTop`: whether a nested stack should pop to top.
36
+ */
37
+ tabPress: {
38
+ data: {
39
+ origin: 'native';
40
+ behavior: {
41
+ scrollToTop: boolean;
42
+ popToTop: boolean;
43
+ };
44
+ };
45
+ canPreventDefault: false;
46
+ };
33
47
  /**
34
48
  * Event which fires when a transition animation starts.
35
49
  */
@@ -87,8 +101,6 @@ type IconImage = {
87
101
  * Disabling will keep the image's original colors.
88
102
  *
89
103
  * Defaults to `true`.
90
- *
91
- * @platform ios
92
104
  */
93
105
  tinted?: boolean;
94
106
  };
@@ -104,10 +116,25 @@ type IconIOSSfSymbol = {
104
116
  * @platform ios
105
117
  */
106
118
  name: SFSymbol;
119
+ /**
120
+ * How to color the symbol.
121
+ * - `monochrome`: Single color, using the tint color.
122
+ * - `multicolor`: Uses the symbol's built-in multicolor scheme.
123
+ *
124
+ * Defaults to a single color for system symbols.
125
+ *
126
+ * @platform ios
127
+ */
128
+ renderingMode?: 'monochrome' | 'multicolor';
107
129
  };
108
130
 
109
131
  export type NativeBottomTabIcon = IconIOSSfSymbol | IconImage;
110
132
 
133
+ type TabBarLabelOffset = {
134
+ horizontal?: number;
135
+ vertical?: number;
136
+ };
137
+
111
138
  export type NativeBottomTabNavigationOptions = NativeHeaderOptions & {
112
139
  /**
113
140
  * Title text for the screen.
@@ -116,16 +143,90 @@ export type NativeBottomTabNavigationOptions = NativeHeaderOptions & {
116
143
 
117
144
  /**
118
145
  * Uses iOS built-in tab bar items with standard iOS styling and localized titles.
119
- * If set to `search`, it's positioned next to the tab bar on iOS 26 and above.
120
146
  *
121
- * The `tabBarIcon` and `tabBarLabel` options will override the icon and label from the system item.
147
+ * The `tabBarIcon` and `tabBarLabel` options override the icon and label from the system item.
122
148
  * If you want to keep the system behavior on iOS, but need to provide icon and label for other platforms,
123
149
  * Use `Platform.OS` or `Platform.select` to conditionally set `undefined` for `tabBarIcon` and `tabBarLabel` on iOS.
124
150
  *
151
+ * If set to `search`, it may receive the prominent visual treatment on iOS 26.
152
+ * See `tabBarRole` for more details on iOS 27 and above.
153
+ *
125
154
  * @platform ios
126
155
  */
127
156
  tabBarSystemItem?: TabsScreenSystemItem;
128
157
 
158
+ /**
159
+ * The role of the tab within the system tab bar.
160
+ *
161
+ * Supported values:
162
+ * - `prominent` - the tab gets the prominent visual treatment when supported
163
+ * - `search` - the tab is used as the system search tab
164
+ *
165
+ * If set to `prominent`, the tab may receive special styling to make it stand out,
166
+ * e.g., being positioned as a separate button next to the tab bar on iOS 27 and above.
167
+ * Only one tab can have the `prominent` role at a time.
168
+ * Supported on iOS 27 and above.
169
+ *
170
+ * If set to `search`, the tab gets the prominent visual treatment on iOS 26.
171
+ * On iOS 27 and above, it gets the prominent styling only if `tabBarSearchAutoFocus` is enabled,
172
+ * and no other tab has the `prominent` role.
173
+ * The tab uses system search icon and label by default.
174
+ * They can be overridden by `tabBarSystemItem`, `tabBarIcon`, and `tabBarLabel` options.
175
+ * Adding or removing the `search` role after the tab is rendered is not supported.
176
+ * Supported on iOS 26.1 and above.
177
+ *
178
+ * @platform ios
179
+ */
180
+ tabBarRole?: 'prominent' | 'search';
181
+
182
+ /**
183
+ * Whether the search field is automatically focused when the tab is selected.
184
+ * Cancelling the search selects the previously selected tab.
185
+ *
186
+ * Only applies to tabs with `tabBarRole: 'search'`,
187
+ * and requires `headerSearchBarOptions` to be set in a native stack nested in the tab.
188
+ *
189
+ * Defaults to `false`.
190
+ *
191
+ * Supported on iOS 26.1 and above.
192
+ *
193
+ * @platform ios
194
+ */
195
+ tabBarSearchAutoFocus?: boolean;
196
+
197
+ /**
198
+ * Whether the sidebar or the tab bar is preferred when only one of them can be displayed.
199
+ *
200
+ * Supported values:
201
+ * - `auto` - uses the system default (tab bar)
202
+ * - `sidebar` - the sidebar is displayed when it's available
203
+ * - `tabBar` - the tab bar is displayed
204
+ *
205
+ * Requires `tabBarControllerMode` to be set to `tabSidebar`.
206
+ *
207
+ * The sidebar is only available when the horizontal size class is regular,
208
+ * e.g. on iPad, or on larger iPhones in landscape.
209
+ *
210
+ * Defaults to `auto`.
211
+ *
212
+ * Supported on iOS 27 and above.
213
+ * Not supported on tvOS.
214
+ *
215
+ * @platform ios
216
+ */
217
+ tabBarSidebarPreferredPlacement?: 'auto' | 'sidebar' | 'tabBar';
218
+
219
+ /**
220
+ * Whether showing and hiding the tab bar is animated.
221
+ *
222
+ * Defaults to `true`.
223
+ *
224
+ * Supported on iOS 18 and above.
225
+ *
226
+ * @platform ios
227
+ */
228
+ tabBarVisibilityAnimationEnabled?: boolean;
229
+
129
230
  /**
130
231
  * Title string of the tab displayed in the tab bar
131
232
  *
@@ -172,6 +273,16 @@ export type NativeBottomTabNavigationOptions = NativeHeaderOptions & {
172
273
  | NativeBottomTabIcon
173
274
  | ((props: { focused: boolean }) => NativeBottomTabIcon);
174
275
 
276
+ /**
277
+ * Size of the tab's icon.
278
+ *
279
+ * The largest size is used for the icon area in all tabs.
280
+ * Icons smaller than the largest size are centered in this area.
281
+ *
282
+ * @platform android
283
+ */
284
+ tabBarIconSize?: number;
285
+
175
286
  /**
176
287
  * Text to show in a badge on the tab icon.
177
288
  */
@@ -214,6 +325,20 @@ export type NativeBottomTabNavigationOptions = NativeHeaderOptions & {
214
325
  */
215
326
  tabBarActiveIndicatorEnabled?: boolean;
216
327
 
328
+ /**
329
+ * Width of the tab's active indicator.
330
+ *
331
+ * @platform android
332
+ */
333
+ tabBarActiveIndicatorWidth?: number;
334
+
335
+ /**
336
+ * Height of the tab's active indicator.
337
+ *
338
+ * @platform android
339
+ */
340
+ tabBarActiveIndicatorHeight?: number;
341
+
217
342
  /**
218
343
  * Color of tab bar item's ripple effect.
219
344
  *
@@ -253,6 +378,21 @@ export type NativeBottomTabNavigationOptions = NativeHeaderOptions & {
253
378
  */
254
379
  tabBarSelectionEnabled?: boolean;
255
380
 
381
+ /**
382
+ * Behavior when pressing the tab in the tab bar while it's already focused.
383
+ *
384
+ * - `scrollToTop`: scroll the first scroll view in the screen to top
385
+ * - `popToTop`: pop a nested stack to its first screen
386
+ *
387
+ * Both default to `true`.
388
+ */
389
+ tabBarRepeatedPressBehavior?:
390
+ | {
391
+ scrollToTop?: boolean;
392
+ popToTop?: boolean;
393
+ }
394
+ | undefined;
395
+
256
396
  /**
257
397
  * Blur effect applied to the tab bar when tab screen is selected.
258
398
  *
@@ -318,6 +458,24 @@ export type NativeBottomTabNavigationOptions = NativeHeaderOptions & {
318
458
  */
319
459
  tabBarMinimizeBehavior?: 'auto' | 'none' | 'onScrollDown' | 'onScrollUp';
320
460
 
461
+ /**
462
+ * Offset applied to the tab label relative to its default position.
463
+ * Positive `vertical` moves the label down, positive `horizontal` moves it right.
464
+ *
465
+ * The offset is specified separately for each tab bar item layout:
466
+ *
467
+ * - `stacked` - icon above the label, e.g. on iPhone in portrait
468
+ * - `inline` - icon beside the label in regular width, e.g. on iPad
469
+ * - `compactInline` - icon beside the label in compact width, e.g. on iPhone in landscape
470
+ *
471
+ * @platform ios
472
+ */
473
+ tabBarLabelPositionAdjustment?: {
474
+ stacked?: TabBarLabelOffset;
475
+ inline?: TabBarLabelOffset;
476
+ compactInline?: TabBarLabelOffset;
477
+ };
478
+
321
479
  /**
322
480
  * Function which returns a React element to display as an accessory view.
323
481
  *
@@ -25,7 +25,13 @@ import {
25
25
  } from 'react-native';
26
26
  import { type EdgeInsets } from 'react-native-safe-area-context';
27
27
 
28
- import type { BottomTabBarProps, BottomTabDescriptorMap } from '../types';
28
+ import type {
29
+ BottomTabBarProps,
30
+ BottomTabDescriptor,
31
+ BottomTabDescriptorMap,
32
+ LabelVisibilityMode,
33
+ Variant,
34
+ } from '../types';
29
35
  import { BottomTabBarHeightCallbackContext } from '../utils/BottomTabBarHeightCallbackContext';
30
36
  import { useIsKeyboardShown } from '../utils/useIsKeyboardShown';
31
37
  import { BottomTabItem } from './BottomTabItem';
@@ -396,28 +402,6 @@ export function BottomTabBar({
396
402
  const focused = index === state.index;
397
403
  const { options } = descriptors[route.key];
398
404
 
399
- const onPress = () => {
400
- const event = navigation.emit({
401
- type: 'tabPress',
402
- target: route.key,
403
- canPreventDefault: true,
404
- });
405
-
406
- if (!focused && !event.defaultPrevented) {
407
- navigation.dispatch({
408
- ...CommonActions.navigate(route.name, route.params),
409
- target: state.key,
410
- });
411
- }
412
- };
413
-
414
- const onLongPress = () => {
415
- navigation.emit({
416
- type: 'tabLongPress',
417
- target: route.key,
418
- });
419
- };
420
-
421
405
  const label =
422
406
  typeof options.tabBarLabel === 'function'
423
407
  ? options.tabBarLabel
@@ -434,57 +418,28 @@ export function BottomTabBar({
434
418
  : undefined;
435
419
 
436
420
  return (
437
- <NavigationProvider
421
+ <BottomTabBarItem
438
422
  key={route.key}
439
423
  route={route}
440
- navigation={descriptors[route.key].navigation}
441
- >
442
- <BottomTabItem
443
- href={buildHref(route.name, route.params)}
444
- route={route}
445
- descriptor={descriptors[route.key]}
446
- focused={focused}
447
- horizontal={hasHorizontalLabels}
448
- compact={compact}
449
- sidebar={sidebar}
450
- variant={tabBarVariant}
451
- onPress={onPress}
452
- onLongPress={onLongPress}
453
- accessibilityLabel={accessibilityLabel}
454
- testID={options.tabBarButtonTestID}
455
- allowFontScaling={options.tabBarAllowFontScaling}
456
- activeTintColor={tabBarActiveTintColor}
457
- inactiveTintColor={tabBarInactiveTintColor}
458
- activeBackgroundColor={tabBarActiveBackgroundColor}
459
- inactiveBackgroundColor={tabBarInactiveBackgroundColor}
460
- button={options.tabBarButton}
461
- icon={
462
- options.tabBarIcon ??
463
- (({ color, size }) => (
464
- <MissingIcon color={color} size={size} />
465
- ))
466
- }
467
- badge={options.tabBarBadge}
468
- badgeStyle={options.tabBarBadgeStyle}
469
- label={label}
470
- showLabel={tabBarShowLabel}
471
- labelVisibilityMode={tabBarLabelVisibilityMode}
472
- labelStyle={options.tabBarLabelStyle}
473
- iconStyle={options.tabBarIconStyle}
474
- style={[
475
- sidebar
476
- ? {
477
- marginVertical: hasHorizontalLabels
478
- ? tabBarVariant === 'material'
479
- ? 0
480
- : 1
481
- : spacing / 2,
482
- }
483
- : styles.bottomItem,
484
- options.tabBarItemStyle,
485
- ]}
486
- />
487
- </NavigationProvider>
424
+ descriptor={descriptors[route.key]}
425
+ navigation={navigation}
426
+ navigatorKey={state.key}
427
+ href={buildHref(route.name, route.params)}
428
+ focused={focused}
429
+ label={label}
430
+ accessibilityLabel={accessibilityLabel}
431
+ hasHorizontalLabels={hasHorizontalLabels}
432
+ compact={compact}
433
+ sidebar={sidebar}
434
+ tabBarVariant={tabBarVariant}
435
+ spacing={spacing}
436
+ tabBarActiveTintColor={tabBarActiveTintColor}
437
+ tabBarInactiveTintColor={tabBarInactiveTintColor}
438
+ tabBarActiveBackgroundColor={tabBarActiveBackgroundColor}
439
+ tabBarInactiveBackgroundColor={tabBarInactiveBackgroundColor}
440
+ tabBarShowLabel={tabBarShowLabel}
441
+ tabBarLabelVisibilityMode={tabBarLabelVisibilityMode}
442
+ />
488
443
  );
489
444
  })}
490
445
  </View>
@@ -492,6 +447,132 @@ export function BottomTabBar({
492
447
  );
493
448
  }
494
449
 
450
+ type BottomTabBarItemProps = {
451
+ route: TabNavigationState<ParamListBase>['routes'][number];
452
+ descriptor: BottomTabDescriptor;
453
+ navigation: BottomTabBarProps['navigation'];
454
+ navigatorKey: string;
455
+ href: string | undefined;
456
+ focused: boolean;
457
+ label: React.ComponentProps<typeof BottomTabItem>['label'];
458
+ accessibilityLabel: string | undefined;
459
+ hasHorizontalLabels: boolean;
460
+ compact: boolean;
461
+ sidebar: boolean;
462
+ tabBarVariant: Variant;
463
+ spacing: number;
464
+ tabBarActiveTintColor: string | undefined;
465
+ tabBarInactiveTintColor: string | undefined;
466
+ tabBarActiveBackgroundColor: string | undefined;
467
+ tabBarInactiveBackgroundColor: string | undefined;
468
+ tabBarShowLabel: boolean | undefined;
469
+ tabBarLabelVisibilityMode: LabelVisibilityMode | undefined;
470
+ };
471
+
472
+ const BottomTabBarItem = React.memo(function BottomTabBarItem({
473
+ route,
474
+ descriptor,
475
+ navigation,
476
+ navigatorKey,
477
+ href,
478
+ focused,
479
+ label,
480
+ accessibilityLabel,
481
+ hasHorizontalLabels,
482
+ compact,
483
+ sidebar,
484
+ tabBarVariant,
485
+ spacing,
486
+ tabBarActiveTintColor,
487
+ tabBarInactiveTintColor,
488
+ tabBarActiveBackgroundColor,
489
+ tabBarInactiveBackgroundColor,
490
+ tabBarShowLabel,
491
+ tabBarLabelVisibilityMode,
492
+ }: BottomTabBarItemProps) {
493
+ const { options } = descriptor;
494
+
495
+ const onPress = () => {
496
+ const event = navigation.emit({
497
+ type: 'tabPress',
498
+ target: route.key,
499
+ canPreventDefault: true,
500
+ data: {
501
+ behavior: {
502
+ scrollToTop:
503
+ focused &&
504
+ options.tabBarRepeatedPressBehavior?.scrollToTop !== false,
505
+ popToTop:
506
+ focused && options.tabBarRepeatedPressBehavior?.popToTop !== false,
507
+ },
508
+ },
509
+ });
510
+
511
+ if (!focused && !event.defaultPrevented) {
512
+ navigation.dispatch({
513
+ ...CommonActions.navigate(route.name, route.params),
514
+ target: navigatorKey,
515
+ });
516
+ }
517
+ };
518
+
519
+ const onLongPress = () => {
520
+ navigation.emit({
521
+ type: 'tabLongPress',
522
+ target: route.key,
523
+ });
524
+ };
525
+
526
+ return (
527
+ <NavigationProvider route={route} navigation={descriptor.navigation}>
528
+ <BottomTabItem
529
+ href={href}
530
+ route={route}
531
+ descriptor={descriptor}
532
+ focused={focused}
533
+ horizontal={hasHorizontalLabels}
534
+ compact={compact}
535
+ sidebar={sidebar}
536
+ variant={tabBarVariant}
537
+ onPress={onPress}
538
+ onLongPress={onLongPress}
539
+ accessibilityLabel={accessibilityLabel}
540
+ testID={options.tabBarButtonTestID}
541
+ allowFontScaling={options.tabBarAllowFontScaling}
542
+ activeTintColor={tabBarActiveTintColor}
543
+ inactiveTintColor={tabBarInactiveTintColor}
544
+ activeBackgroundColor={tabBarActiveBackgroundColor}
545
+ inactiveBackgroundColor={tabBarInactiveBackgroundColor}
546
+ button={options.tabBarButton}
547
+ icon={
548
+ options.tabBarIcon ??
549
+ (({ color, size }) => <MissingIcon color={color} size={size} />)
550
+ }
551
+ badge={options.tabBarBadge}
552
+ badgeStyle={options.tabBarBadgeStyle}
553
+ label={label}
554
+ showLabel={tabBarShowLabel}
555
+ labelVisibilityMode={tabBarLabelVisibilityMode}
556
+ labelStyle={options.tabBarLabelStyle}
557
+ iconSize={options.tabBarIconSize}
558
+ iconStyle={options.tabBarIconStyle}
559
+ style={[
560
+ sidebar
561
+ ? {
562
+ marginVertical: hasHorizontalLabels
563
+ ? tabBarVariant === 'material'
564
+ ? 0
565
+ : 1
566
+ : spacing / 2,
567
+ }
568
+ : styles.bottomItem,
569
+ options.tabBarItemStyle,
570
+ ]}
571
+ />
572
+ </NavigationProvider>
573
+ );
574
+ });
575
+
495
576
  const styles = StyleSheet.create({
496
577
  start: {
497
578
  top: 0,
@@ -140,6 +140,10 @@ type Props = {
140
140
  * Style object for the label element.
141
141
  */
142
142
  labelStyle?: StyleProp<TextStyle>;
143
+ /**
144
+ * Size of the icon.
145
+ */
146
+ iconSize?: number;
143
147
  /**
144
148
  * Style object for the icon element.
145
149
  */
@@ -186,6 +190,7 @@ export function BottomTabItem({
186
190
  // https://developer.apple.com/documentation/uikit/uiview/3183939-largecontenttitle
187
191
  allowFontScaling = SUPPORTS_LARGE_CONTENT_VIEWER ? false : undefined,
188
192
  labelStyle,
193
+ iconSize,
189
194
  iconStyle,
190
195
  style,
191
196
  }: Props) {
@@ -300,6 +305,7 @@ export function BottomTabItem({
300
305
  route={route}
301
306
  variant={variant}
302
307
  size={compact ? 'compact' : 'regular'}
308
+ iconSize={iconSize}
303
309
  badge={badge}
304
310
  badgeStyle={badgeStyle}
305
311
  activeOpacity={activeOpacity}