@react-navigation/bottom-tabs 8.0.0-alpha.55 → 8.0.0-alpha.57
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/module/views/BottomTabBar.js +106 -61
- package/lib/module/views/BottomTabBar.js.map +1 -1
- package/lib/module/views/BottomTabItem.js +2 -0
- package/lib/module/views/BottomTabItem.js.map +1 -1
- package/lib/module/views/BottomTabViewCustom.js +62 -37
- package/lib/module/views/BottomTabViewCustom.js.map +1 -1
- package/lib/module/views/BottomTabViewNativeImpl.js +348 -245
- package/lib/module/views/BottomTabViewNativeImpl.js.map +1 -1
- package/lib/module/views/TabBarIcon.js +6 -2
- package/lib/module/views/TabBarIcon.js.map +1 -1
- package/lib/typescript/src/types.d.ts +130 -5
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabBar.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabItem.d.ts +5 -1
- package/lib/typescript/src/views/BottomTabItem.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabViewCustom.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabViewNativeImpl.d.ts.map +1 -1
- package/lib/typescript/src/views/TabBarIcon.d.ts +2 -1
- package/lib/typescript/src/views/TabBarIcon.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/types.tsx +141 -5
- package/src/views/BottomTabBar.tsx +158 -87
- package/src/views/BottomTabItem.tsx +6 -0
- package/src/views/BottomTabViewCustom.tsx +84 -47
- package/src/views/BottomTabViewNativeImpl.tsx +439 -284
- package/src/views/TabBarIcon.tsx +9 -2
|
@@ -6,11 +6,14 @@ import {
|
|
|
6
6
|
} from '@react-navigation/elements/internal';
|
|
7
7
|
import {
|
|
8
8
|
CommonActions,
|
|
9
|
+
type LocaleDirection,
|
|
9
10
|
MaterialSymbol,
|
|
11
|
+
type NavigationRoute,
|
|
10
12
|
type ParamListBase,
|
|
11
13
|
type Route,
|
|
12
14
|
StackActions,
|
|
13
15
|
type TabNavigationState,
|
|
16
|
+
useLocale,
|
|
14
17
|
useTheme,
|
|
15
18
|
} from '@react-navigation/native';
|
|
16
19
|
import * as React from 'react';
|
|
@@ -21,20 +24,22 @@ import {
|
|
|
21
24
|
Platform,
|
|
22
25
|
PlatformColor,
|
|
23
26
|
StyleSheet,
|
|
27
|
+
type TextStyle,
|
|
24
28
|
} from 'react-native';
|
|
25
29
|
import {
|
|
26
|
-
type PlatformIconAndroid,
|
|
27
|
-
type PlatformIconIOS,
|
|
28
30
|
type PlatformIconShared,
|
|
29
31
|
Tabs,
|
|
30
32
|
type TabsBottomAccessoryEnvironment,
|
|
31
33
|
type TabSelectedEvent,
|
|
32
34
|
type TabSelectionRejectedEvent,
|
|
35
|
+
type TabsScreenIconAndroid,
|
|
36
|
+
type TabsScreenIconIOS,
|
|
33
37
|
type TabsScreenItemStateAppearanceAndroid,
|
|
34
38
|
type TabsScreenItemStateAppearanceIOS,
|
|
35
39
|
} from 'react-native-screens';
|
|
36
40
|
|
|
37
41
|
import type {
|
|
42
|
+
BottomTabDescriptor,
|
|
38
43
|
BottomTabDescriptorMap,
|
|
39
44
|
BottomTabNavigationConfig,
|
|
40
45
|
BottomTabNavigationHelpers,
|
|
@@ -59,8 +64,8 @@ type TabSelectionPreventedEvent = {
|
|
|
59
64
|
};
|
|
60
65
|
|
|
61
66
|
type PlatformIcon = {
|
|
62
|
-
ios?:
|
|
63
|
-
android?:
|
|
67
|
+
ios?: TabsScreenIconIOS | undefined;
|
|
68
|
+
android?: TabsScreenIconAndroid | undefined;
|
|
64
69
|
shared?: PlatformIconShared | undefined;
|
|
65
70
|
};
|
|
66
71
|
|
|
@@ -88,6 +93,9 @@ const ICON_SIZE = Platform.select({
|
|
|
88
93
|
default: 24,
|
|
89
94
|
});
|
|
90
95
|
|
|
96
|
+
const SUPPORTS_PROMINENT_TAB =
|
|
97
|
+
Platform.OS === 'ios' && parseInt(Platform.Version, 10) >= 27;
|
|
98
|
+
|
|
91
99
|
function reducer(state: NativeState, action: NativeAction): NativeState {
|
|
92
100
|
switch (action.type) {
|
|
93
101
|
case 'TRACK_TRANSITION':
|
|
@@ -351,73 +359,75 @@ export function BottomTabViewNative({
|
|
|
351
359
|
|
|
352
360
|
const shouldHideTabBar = currentOptions.tabBarStyle?.display === 'none';
|
|
353
361
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
362
|
+
const { activeIndicatorColor, activeTintColor, inactiveTintColor } =
|
|
363
|
+
React.useMemo(() => {
|
|
364
|
+
let activeIndicatorColor = currentOptions?.tabBarActiveIndicatorColor;
|
|
365
|
+
let activeTintColor = currentOptions.tabBarActiveTintColor;
|
|
366
|
+
let inactiveTintColor = currentOptions.tabBarInactiveTintColor;
|
|
367
|
+
|
|
368
|
+
// Derive colors based on Material Design guidelines
|
|
369
|
+
// https://m3.material.io/components/navigation-bar/specs
|
|
370
|
+
if (Platform.OS === 'android') {
|
|
371
|
+
switch (getAndroidColorName(backgroundColor)) {
|
|
372
|
+
case 'system_surface_container_light':
|
|
373
|
+
case 'system_surface_container_high_light':
|
|
374
|
+
case 'system_surface_container_highest_light':
|
|
375
|
+
case 'system_surface_container_low_light':
|
|
376
|
+
case 'system_surface_container_lowest_light':
|
|
377
|
+
inactiveTintColor =
|
|
378
|
+
inactiveTintColor ??
|
|
379
|
+
PlatformColor('@android:color/system_on_surface_variant_light');
|
|
380
|
+
activeTintColor =
|
|
381
|
+
activeTintColor ??
|
|
382
|
+
PlatformColor(
|
|
383
|
+
'@android:color/system_on_secondary_container_light'
|
|
384
|
+
);
|
|
385
|
+
activeIndicatorColor =
|
|
386
|
+
activeIndicatorColor ??
|
|
387
|
+
PlatformColor('@android:color/system_secondary_container_light');
|
|
388
|
+
break;
|
|
389
|
+
case 'system_surface_container_dark':
|
|
390
|
+
case 'system_surface_container_high_dark':
|
|
391
|
+
case 'system_surface_container_highest_dark':
|
|
392
|
+
case 'system_surface_container_low_dark':
|
|
393
|
+
case 'system_surface_container_lowest_dark':
|
|
394
|
+
inactiveTintColor =
|
|
395
|
+
inactiveTintColor ??
|
|
396
|
+
PlatformColor('@android:color/system_on_surface_variant_dark');
|
|
397
|
+
activeTintColor =
|
|
398
|
+
activeTintColor ??
|
|
399
|
+
PlatformColor(
|
|
400
|
+
'@android:color/system_on_secondary_container_dark'
|
|
401
|
+
);
|
|
402
|
+
activeIndicatorColor =
|
|
403
|
+
activeIndicatorColor ??
|
|
404
|
+
PlatformColor('@android:color/system_secondary_container_dark');
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
398
408
|
|
|
399
|
-
|
|
409
|
+
inactiveTintColor =
|
|
410
|
+
inactiveTintColor ??
|
|
411
|
+
Platform.select({ ios: PlatformColor('label'), default: colors.text });
|
|
400
412
|
|
|
401
|
-
|
|
402
|
-
activeIndicatorColor ??
|
|
403
|
-
Platform.select({
|
|
404
|
-
android: Color(activeTintColor)?.alpha(0.075).string(),
|
|
405
|
-
default: undefined,
|
|
406
|
-
});
|
|
413
|
+
activeTintColor = activeTintColor ?? colors.primary;
|
|
407
414
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
415
|
+
activeIndicatorColor =
|
|
416
|
+
activeIndicatorColor ??
|
|
417
|
+
Platform.select({
|
|
418
|
+
android: Color(activeTintColor)?.alpha(0.075).string(),
|
|
419
|
+
default: undefined,
|
|
420
|
+
});
|
|
414
421
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
422
|
+
return { activeIndicatorColor, activeTintColor, inactiveTintColor };
|
|
423
|
+
}, [
|
|
424
|
+
currentOptions?.tabBarActiveIndicatorColor,
|
|
425
|
+
currentOptions.tabBarActiveTintColor,
|
|
426
|
+
currentOptions.tabBarInactiveTintColor,
|
|
427
|
+
backgroundColor,
|
|
428
|
+
colors.primary,
|
|
429
|
+
colors.text,
|
|
430
|
+
]);
|
|
421
431
|
|
|
422
432
|
const tabBarPosition = useTabBarPosition(currentOptions);
|
|
423
433
|
|
|
@@ -442,6 +452,17 @@ export function BottomTabViewNative({
|
|
|
442
452
|
? 'never'
|
|
443
453
|
: currentOptions.tabBarMinimizeBehavior;
|
|
444
454
|
|
|
455
|
+
const tabBarSidebarPreferredPlacement =
|
|
456
|
+
currentOptions.tabBarSidebarPreferredPlacement === 'auto'
|
|
457
|
+
? 'automatic'
|
|
458
|
+
: currentOptions.tabBarSidebarPreferredPlacement;
|
|
459
|
+
|
|
460
|
+
const prominentScreenKey = SUPPORTS_PROMINENT_TAB
|
|
461
|
+
? state.routes.find(
|
|
462
|
+
(route) => descriptors[route.key]?.options.tabBarRole === 'prominent'
|
|
463
|
+
)?.key
|
|
464
|
+
: undefined;
|
|
465
|
+
|
|
445
466
|
const bottomAccessory = currentOptions.bottomAccessory;
|
|
446
467
|
|
|
447
468
|
return (
|
|
@@ -469,11 +490,15 @@ export function BottomTabViewNative({
|
|
|
469
490
|
colorScheme={dark ? 'dark' : 'light'}
|
|
470
491
|
nativeContainerStyle={{ backgroundColor: colors.background }}
|
|
471
492
|
ios={{
|
|
493
|
+
prominentScreenKey,
|
|
472
494
|
bottomAccessory: bottomAccessory
|
|
473
495
|
? (environment: TabsBottomAccessoryEnvironment) =>
|
|
474
496
|
bottomAccessory({ placement: environment })
|
|
475
497
|
: undefined,
|
|
498
|
+
tabBarHiddenAnimationEnabled:
|
|
499
|
+
currentOptions.tabBarVisibilityAnimationEnabled,
|
|
476
500
|
tabBarControllerMode,
|
|
501
|
+
tabBarSidebarPreferredPlacement,
|
|
477
502
|
tabBarMinimizeBehavior,
|
|
478
503
|
tabBarTintColor: activeTintColor,
|
|
479
504
|
}}
|
|
@@ -487,105 +512,11 @@ export function BottomTabViewNative({
|
|
|
487
512
|
);
|
|
488
513
|
}
|
|
489
514
|
|
|
490
|
-
const { options, render, navigation } = descriptor;
|
|
491
515
|
const isFocused = state.index === index;
|
|
492
516
|
const isPreloaded = state.preloadedRouteKeys.includes(route.key);
|
|
493
517
|
|
|
494
|
-
const {
|
|
495
|
-
|
|
496
|
-
lazy = false,
|
|
497
|
-
inactiveBehavior = 'pause',
|
|
498
|
-
tabBarLabel,
|
|
499
|
-
tabBarSelectionEnabled,
|
|
500
|
-
tabBarRepeatedPressBehavior,
|
|
501
|
-
tabBarBadgeStyle,
|
|
502
|
-
tabBarIcon,
|
|
503
|
-
tabBarBadge,
|
|
504
|
-
tabBarSystemItem,
|
|
505
|
-
tabBarBlurEffect = dark ? 'systemMaterialDark' : 'systemMaterial',
|
|
506
|
-
tabBarStyle,
|
|
507
|
-
tabBarAccessibilityLabel,
|
|
508
|
-
tabBarButtonTestID,
|
|
509
|
-
sceneStyle,
|
|
510
|
-
overrideScrollViewContentInsetAdjustmentBehavior,
|
|
511
|
-
} = options;
|
|
512
|
-
|
|
513
|
-
const {
|
|
514
|
-
backgroundColor: tabBarBackgroundColor,
|
|
515
|
-
shadowColor: tabBarShadowColor,
|
|
516
|
-
} = tabBarStyle || {};
|
|
517
|
-
|
|
518
|
-
const tabTitle =
|
|
519
|
-
// On iOS, `systemItem` already provides a localized label
|
|
520
|
-
// So we should only use `tabBarLabel` if explicitly provided
|
|
521
|
-
Platform.OS === 'ios' && tabBarSystemItem != null
|
|
522
|
-
? tabBarLabel
|
|
523
|
-
: getLabel({ label: tabBarLabel, title }, route.name);
|
|
524
|
-
|
|
525
|
-
const badgeBackgroundColor =
|
|
526
|
-
tabBarBadgeStyle?.backgroundColor ?? colors.notification;
|
|
527
|
-
const badgeTextColor =
|
|
528
|
-
tabBarBadgeStyle?.color ?? Color.foreground(badgeBackgroundColor);
|
|
529
|
-
|
|
530
|
-
const tabItemAppearance: TabsScreenItemStateAppearanceIOS = {
|
|
531
|
-
tabBarItemTitleFontFamily: fontFamily,
|
|
532
|
-
tabBarItemTitleFontSize: fontSize,
|
|
533
|
-
tabBarItemTitleFontWeight: fontWeight,
|
|
534
|
-
tabBarItemTitleFontStyle: fontStyle,
|
|
535
|
-
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
536
|
-
tabBarItemIconColor: inactiveTintColor,
|
|
537
|
-
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
const normalTabItemAppearance: TabsScreenItemStateAppearanceAndroid =
|
|
541
|
-
{
|
|
542
|
-
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
543
|
-
tabBarItemIconColor: inactiveTintColor,
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
const selectedTabItemAppearance: TabsScreenItemStateAppearanceAndroid =
|
|
547
|
-
{
|
|
548
|
-
tabBarItemTitleFontColor: activeTintColor,
|
|
549
|
-
tabBarItemIconColor: activeTintColor,
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
const getIcon = (selected: boolean) => {
|
|
553
|
-
if (typeof tabBarIcon === 'function') {
|
|
554
|
-
const result = tabBarIcon({
|
|
555
|
-
focused: selected,
|
|
556
|
-
size: ICON_SIZE!,
|
|
557
|
-
color: selected ? activeTintColor : inactiveTintColor,
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
if (React.isValidElement(result)) {
|
|
561
|
-
throw new Error(
|
|
562
|
-
`Returning a React element from 'tabBarIcon' is not supported with native tab bar.`
|
|
563
|
-
);
|
|
564
|
-
} else if (
|
|
565
|
-
result &&
|
|
566
|
-
typeof result === 'object' &&
|
|
567
|
-
'type' in result
|
|
568
|
-
) {
|
|
569
|
-
return getPlatformIcon(result);
|
|
570
|
-
} else {
|
|
571
|
-
throw new Error(
|
|
572
|
-
`The 'tabBarIcon' function must return an icon object (got ${typeof result}).`
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
} else if (tabBarIcon != null) {
|
|
576
|
-
return getPlatformIcon(tabBarIcon);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
return undefined;
|
|
580
|
-
};
|
|
581
|
-
|
|
582
|
-
// An error is thrown for React Elements in `getIcon`
|
|
583
|
-
// So we only call it when we actually render a native tab bar
|
|
584
|
-
const icon = hasCustomTabBar ? undefined : getIcon(false);
|
|
585
|
-
const selectedIcon =
|
|
586
|
-
!hasCustomTabBar && typeof tabBarIcon === 'function'
|
|
587
|
-
? getIcon(true)
|
|
588
|
-
: icon;
|
|
518
|
+
const { lazy = false, inactiveBehavior = 'pause' } =
|
|
519
|
+
descriptor.options;
|
|
589
520
|
|
|
590
521
|
// For preloaded screens and if lazy is false,
|
|
591
522
|
// Keep them active so that the effects can run
|
|
@@ -597,113 +528,31 @@ export function BottomTabViewNative({
|
|
|
597
528
|
(lazy === false && !loaded.includes(route.key));
|
|
598
529
|
|
|
599
530
|
return (
|
|
600
|
-
<
|
|
601
|
-
onWillAppear={() => onTransitionStart({ route })}
|
|
602
|
-
onDidAppear={() => onTransitionEnd({ route })}
|
|
531
|
+
<TabScene
|
|
603
532
|
key={route.key}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
normal: normalTabItemAppearance,
|
|
628
|
-
selected: selectedTabItemAppearance,
|
|
629
|
-
tabBarItemActiveIndicatorColor: activeIndicatorColor,
|
|
630
|
-
tabBarItemActiveIndicatorEnabled:
|
|
631
|
-
currentOptions?.tabBarActiveIndicatorEnabled,
|
|
632
|
-
tabBarItemTitleFontFamily: fontFamily,
|
|
633
|
-
tabBarItemTitleFontWeight: fontWeight,
|
|
634
|
-
tabBarItemTitleSmallLabelFontSize: fontSize,
|
|
635
|
-
tabBarItemTitleLargeLabelFontSize: fontSize,
|
|
636
|
-
tabBarItemTitleFontStyle: fontStyle,
|
|
637
|
-
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
638
|
-
tabBarItemBadgeTextColor: badgeTextColor,
|
|
639
|
-
},
|
|
640
|
-
}}
|
|
641
|
-
ios={{
|
|
642
|
-
icon: icon?.ios ?? icon?.shared,
|
|
643
|
-
selectedIcon: selectedIcon?.ios ?? selectedIcon?.shared,
|
|
644
|
-
systemItem: tabBarSystemItem,
|
|
645
|
-
scrollEdgeAppearance: {
|
|
646
|
-
tabBarBackgroundColor,
|
|
647
|
-
tabBarShadowColor,
|
|
648
|
-
tabBarBlurEffect,
|
|
649
|
-
stacked: {
|
|
650
|
-
normal: tabItemAppearance,
|
|
651
|
-
},
|
|
652
|
-
inline: {
|
|
653
|
-
normal: tabItemAppearance,
|
|
654
|
-
},
|
|
655
|
-
compactInline: {
|
|
656
|
-
normal: tabItemAppearance,
|
|
657
|
-
},
|
|
658
|
-
},
|
|
659
|
-
standardAppearance: {
|
|
660
|
-
tabBarBackgroundColor,
|
|
661
|
-
tabBarShadowColor,
|
|
662
|
-
tabBarBlurEffect,
|
|
663
|
-
stacked: {
|
|
664
|
-
normal: tabItemAppearance,
|
|
665
|
-
},
|
|
666
|
-
inline: {
|
|
667
|
-
normal: tabItemAppearance,
|
|
668
|
-
},
|
|
669
|
-
compactInline: {
|
|
670
|
-
normal: tabItemAppearance,
|
|
671
|
-
},
|
|
672
|
-
},
|
|
673
|
-
overrideScrollViewContentInsetAdjustmentBehavior,
|
|
674
|
-
}}
|
|
675
|
-
>
|
|
676
|
-
<Deferred
|
|
677
|
-
lazy={lazy}
|
|
678
|
-
visible={isFocused || isPreloaded || loaded.includes(route.key)}
|
|
679
|
-
>
|
|
680
|
-
<ActivityView
|
|
681
|
-
key={route.key}
|
|
682
|
-
mode={isFocused ? 'normal' : isActive ? 'inert' : 'paused'}
|
|
683
|
-
visible={
|
|
684
|
-
// We don't need to hide the content since it's handled natively
|
|
685
|
-
// Hiding may also cause flash due to lag after native tab switch
|
|
686
|
-
// So we leave it always visible
|
|
687
|
-
true
|
|
688
|
-
}
|
|
689
|
-
style={StyleSheet.absoluteFill}
|
|
690
|
-
>
|
|
691
|
-
<ScreenContent
|
|
692
|
-
isFocused={isFocused}
|
|
693
|
-
route={route}
|
|
694
|
-
navigation={navigation}
|
|
695
|
-
options={options}
|
|
696
|
-
style={sceneStyle}
|
|
697
|
-
>
|
|
698
|
-
<AnimatedScreenContent isFocused={isFocused}>
|
|
699
|
-
<BottomTabBarHeightContext.Provider value={0}>
|
|
700
|
-
{render()}
|
|
701
|
-
</BottomTabBarHeightContext.Provider>
|
|
702
|
-
</AnimatedScreenContent>
|
|
703
|
-
</ScreenContent>
|
|
704
|
-
</ActivityView>
|
|
705
|
-
</Deferred>
|
|
706
|
-
</Tabs.Screen>
|
|
533
|
+
route={route}
|
|
534
|
+
descriptor={descriptor}
|
|
535
|
+
navigation={navigation}
|
|
536
|
+
isFocused={isFocused}
|
|
537
|
+
isActive={isActive}
|
|
538
|
+
lazy={lazy}
|
|
539
|
+
visible={isFocused || isPreloaded || loaded.includes(route.key)}
|
|
540
|
+
hasCustomTabBar={hasCustomTabBar}
|
|
541
|
+
fontFamily={fontFamily}
|
|
542
|
+
fontWeight={fontWeight}
|
|
543
|
+
fontSize={fontSize}
|
|
544
|
+
fontStyle={fontStyle}
|
|
545
|
+
fontColor={fontColor}
|
|
546
|
+
backgroundColor={backgroundColor}
|
|
547
|
+
activeTintColor={activeTintColor}
|
|
548
|
+
inactiveTintColor={inactiveTintColor}
|
|
549
|
+
activeIndicatorColor={activeIndicatorColor}
|
|
550
|
+
rippleColor={currentOptions.tabBarRippleColor}
|
|
551
|
+
labelVisibilityMode={currentOptions.tabBarLabelVisibilityMode}
|
|
552
|
+
activeIndicatorEnabled={
|
|
553
|
+
currentOptions.tabBarActiveIndicatorEnabled
|
|
554
|
+
}
|
|
555
|
+
/>
|
|
707
556
|
);
|
|
708
557
|
})}
|
|
709
558
|
</Tabs.Host>
|
|
@@ -714,6 +563,287 @@ export function BottomTabViewNative({
|
|
|
714
563
|
);
|
|
715
564
|
}
|
|
716
565
|
|
|
566
|
+
type TabSceneProps = {
|
|
567
|
+
route: NavigationRoute<ParamListBase, string>;
|
|
568
|
+
descriptor: BottomTabDescriptor;
|
|
569
|
+
navigation: BottomTabNavigationHelpers;
|
|
570
|
+
isFocused: boolean;
|
|
571
|
+
isActive: boolean;
|
|
572
|
+
lazy: boolean;
|
|
573
|
+
visible: boolean;
|
|
574
|
+
hasCustomTabBar: boolean;
|
|
575
|
+
fontFamily: TextStyle['fontFamily'];
|
|
576
|
+
fontWeight: TextStyle['fontWeight'];
|
|
577
|
+
fontSize: TextStyle['fontSize'];
|
|
578
|
+
fontStyle: TextStyle['fontStyle'];
|
|
579
|
+
fontColor: TextStyle['color'];
|
|
580
|
+
backgroundColor: ColorValue;
|
|
581
|
+
activeTintColor: ColorValue;
|
|
582
|
+
inactiveTintColor: ColorValue;
|
|
583
|
+
activeIndicatorColor: ColorValue | undefined;
|
|
584
|
+
rippleColor: ColorValue | undefined;
|
|
585
|
+
labelVisibilityMode: BottomTabNavigationOptions['tabBarLabelVisibilityMode'];
|
|
586
|
+
activeIndicatorEnabled: boolean | undefined;
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const TabScene = React.memo(function TabScene({
|
|
590
|
+
route,
|
|
591
|
+
descriptor,
|
|
592
|
+
navigation,
|
|
593
|
+
isFocused,
|
|
594
|
+
isActive,
|
|
595
|
+
lazy,
|
|
596
|
+
visible,
|
|
597
|
+
hasCustomTabBar,
|
|
598
|
+
fontFamily,
|
|
599
|
+
fontWeight,
|
|
600
|
+
fontSize,
|
|
601
|
+
fontStyle,
|
|
602
|
+
fontColor,
|
|
603
|
+
backgroundColor,
|
|
604
|
+
activeTintColor,
|
|
605
|
+
inactiveTintColor,
|
|
606
|
+
activeIndicatorColor,
|
|
607
|
+
rippleColor,
|
|
608
|
+
labelVisibilityMode,
|
|
609
|
+
activeIndicatorEnabled,
|
|
610
|
+
}: TabSceneProps) {
|
|
611
|
+
const { dark, colors } = useTheme();
|
|
612
|
+
const { direction } = useLocale();
|
|
613
|
+
|
|
614
|
+
const onTransitionStart = ({ route }: { route: Route<string> }) => {
|
|
615
|
+
navigation.emit({
|
|
616
|
+
type: 'transitionStart',
|
|
617
|
+
target: route.key,
|
|
618
|
+
});
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const onTransitionEnd = ({ route }: { route: Route<string> }) => {
|
|
622
|
+
navigation.emit({
|
|
623
|
+
type: 'transitionEnd',
|
|
624
|
+
target: route.key,
|
|
625
|
+
});
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const { options, render } = descriptor;
|
|
629
|
+
|
|
630
|
+
const {
|
|
631
|
+
title,
|
|
632
|
+
tabBarLabel,
|
|
633
|
+
tabBarLabelPositionAdjustment,
|
|
634
|
+
tabBarSelectionEnabled,
|
|
635
|
+
tabBarRepeatedPressBehavior,
|
|
636
|
+
tabBarBadgeStyle,
|
|
637
|
+
tabBarIcon,
|
|
638
|
+
tabBarIconSize,
|
|
639
|
+
tabBarActiveIndicatorWidth,
|
|
640
|
+
tabBarActiveIndicatorHeight,
|
|
641
|
+
tabBarBadge,
|
|
642
|
+
tabBarSystemItem,
|
|
643
|
+
tabBarRole,
|
|
644
|
+
tabBarSearchAutoFocus,
|
|
645
|
+
tabBarBlurEffect = dark ? 'systemMaterialDark' : 'systemMaterial',
|
|
646
|
+
tabBarStyle,
|
|
647
|
+
tabBarAccessibilityLabel,
|
|
648
|
+
tabBarButtonTestID,
|
|
649
|
+
sceneStyle,
|
|
650
|
+
overrideScrollViewContentInsetAdjustmentBehavior,
|
|
651
|
+
} = options;
|
|
652
|
+
|
|
653
|
+
const systemItem =
|
|
654
|
+
tabBarSystemItem ??
|
|
655
|
+
// Setting the `role` to 'search' doesn't add title and icon automatically
|
|
656
|
+
// So we also set `tabBarSystemItem` to 'search' to match SwiftUI's behavior
|
|
657
|
+
(tabBarRole === 'search' ? 'search' : undefined);
|
|
658
|
+
|
|
659
|
+
const {
|
|
660
|
+
backgroundColor: tabBarBackgroundColor,
|
|
661
|
+
shadowColor: tabBarShadowColor,
|
|
662
|
+
} = tabBarStyle || {};
|
|
663
|
+
|
|
664
|
+
const tabTitle =
|
|
665
|
+
// On iOS, `systemItem` already provides a localized label
|
|
666
|
+
// So we should only use `tabBarLabel` if explicitly provided
|
|
667
|
+
Platform.OS === 'ios' && systemItem != null
|
|
668
|
+
? tabBarLabel
|
|
669
|
+
: getLabel({ label: tabBarLabel, title }, route.name);
|
|
670
|
+
|
|
671
|
+
const badgeBackgroundColor =
|
|
672
|
+
tabBarBadgeStyle?.backgroundColor ?? colors.notification;
|
|
673
|
+
const badgeTextColor =
|
|
674
|
+
tabBarBadgeStyle?.color ?? Color.foreground(badgeBackgroundColor);
|
|
675
|
+
|
|
676
|
+
const tabItemAppearance: TabsScreenItemStateAppearanceIOS = {
|
|
677
|
+
tabBarItemTitleFontFamily: fontFamily,
|
|
678
|
+
tabBarItemTitleFontSize: fontSize,
|
|
679
|
+
tabBarItemTitleFontWeight: fontWeight,
|
|
680
|
+
tabBarItemTitleFontStyle: fontStyle,
|
|
681
|
+
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
682
|
+
tabBarItemIconColor: inactiveTintColor,
|
|
683
|
+
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const tabItemLayoutAppearance = {
|
|
687
|
+
stacked: {
|
|
688
|
+
normal: {
|
|
689
|
+
...tabItemAppearance,
|
|
690
|
+
tabBarItemTitlePositionAdjustment:
|
|
691
|
+
tabBarLabelPositionAdjustment?.stacked,
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
inline: {
|
|
695
|
+
normal: {
|
|
696
|
+
...tabItemAppearance,
|
|
697
|
+
tabBarItemTitlePositionAdjustment:
|
|
698
|
+
tabBarLabelPositionAdjustment?.inline,
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
compactInline: {
|
|
702
|
+
normal: {
|
|
703
|
+
...tabItemAppearance,
|
|
704
|
+
tabBarItemTitlePositionAdjustment:
|
|
705
|
+
tabBarLabelPositionAdjustment?.compactInline,
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
const normalTabItemAppearance: TabsScreenItemStateAppearanceAndroid = {
|
|
711
|
+
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
712
|
+
tabBarItemIconColor: inactiveTintColor,
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
const selectedTabItemAppearance: TabsScreenItemStateAppearanceAndroid = {
|
|
716
|
+
tabBarItemTitleFontColor: activeTintColor,
|
|
717
|
+
tabBarItemIconColor: activeTintColor,
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
const iconSize =
|
|
721
|
+
Platform.OS === 'android' ? (tabBarIconSize ?? ICON_SIZE!) : ICON_SIZE!;
|
|
722
|
+
|
|
723
|
+
const getIcon = (selected: boolean) => {
|
|
724
|
+
if (typeof tabBarIcon === 'function') {
|
|
725
|
+
const result = tabBarIcon({
|
|
726
|
+
focused: selected,
|
|
727
|
+
size: iconSize,
|
|
728
|
+
color: selected ? activeTintColor : inactiveTintColor,
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
if (React.isValidElement(result)) {
|
|
732
|
+
throw new Error(
|
|
733
|
+
`Returning a React element from 'tabBarIcon' is not supported with native tab bar.`
|
|
734
|
+
);
|
|
735
|
+
} else if (result && typeof result === 'object' && 'type' in result) {
|
|
736
|
+
return getPlatformIcon(result, direction, iconSize);
|
|
737
|
+
} else {
|
|
738
|
+
throw new Error(
|
|
739
|
+
`The 'tabBarIcon' function must return an icon object (got ${typeof result}).`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
} else if (tabBarIcon != null) {
|
|
743
|
+
return getPlatformIcon(tabBarIcon, direction, iconSize);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
return undefined;
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// An error is thrown for React Elements in `getIcon`
|
|
750
|
+
// So we only call it when we actually render a native tab bar
|
|
751
|
+
const icon = hasCustomTabBar ? undefined : getIcon(false);
|
|
752
|
+
const selectedIcon =
|
|
753
|
+
!hasCustomTabBar && typeof tabBarIcon === 'function' ? getIcon(true) : icon;
|
|
754
|
+
|
|
755
|
+
return (
|
|
756
|
+
<Tabs.Screen
|
|
757
|
+
onWillAppear={() => onTransitionStart({ route })}
|
|
758
|
+
onDidAppear={() => onTransitionEnd({ route })}
|
|
759
|
+
screenKey={route.key}
|
|
760
|
+
tabBarItemAccessibilityLabel={tabBarAccessibilityLabel}
|
|
761
|
+
tabBarItemTestID={tabBarButtonTestID}
|
|
762
|
+
preventNativeSelection={tabBarSelectionEnabled === false}
|
|
763
|
+
badgeValue={tabBarBadge?.toString()}
|
|
764
|
+
title={tabTitle}
|
|
765
|
+
style={{ backgroundColor: colors.background }}
|
|
766
|
+
specialEffects={{
|
|
767
|
+
repeatedTabSelection: {
|
|
768
|
+
popToRoot: tabBarRepeatedPressBehavior?.popToTop !== false,
|
|
769
|
+
scrollToTop: tabBarRepeatedPressBehavior?.scrollToTop !== false,
|
|
770
|
+
},
|
|
771
|
+
}}
|
|
772
|
+
android={{
|
|
773
|
+
icon: icon?.android ?? icon?.shared,
|
|
774
|
+
selectedIcon: selectedIcon?.android ?? selectedIcon?.shared,
|
|
775
|
+
standardAppearance: {
|
|
776
|
+
tabBarBackgroundColor: tabBarBackgroundColor ?? backgroundColor,
|
|
777
|
+
tabBarItemRippleColor: rippleColor,
|
|
778
|
+
tabBarItemLabelVisibilityMode: labelVisibilityMode,
|
|
779
|
+
normal: normalTabItemAppearance,
|
|
780
|
+
selected: selectedTabItemAppearance,
|
|
781
|
+
tabBarItemIconSize: tabBarIconSize,
|
|
782
|
+
tabBarItemActiveIndicatorColor: activeIndicatorColor,
|
|
783
|
+
tabBarItemActiveIndicatorEnabled: activeIndicatorEnabled,
|
|
784
|
+
tabBarItemActiveIndicatorWidth: tabBarActiveIndicatorWidth,
|
|
785
|
+
tabBarItemActiveIndicatorHeight: tabBarActiveIndicatorHeight,
|
|
786
|
+
tabBarItemTitleFontFamily: fontFamily,
|
|
787
|
+
tabBarItemTitleFontWeight: fontWeight,
|
|
788
|
+
tabBarItemTitleSmallLabelFontSize: fontSize,
|
|
789
|
+
tabBarItemTitleLargeLabelFontSize: fontSize,
|
|
790
|
+
tabBarItemTitleFontStyle: fontStyle,
|
|
791
|
+
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
792
|
+
tabBarItemBadgeTextColor: badgeTextColor,
|
|
793
|
+
},
|
|
794
|
+
}}
|
|
795
|
+
ios={{
|
|
796
|
+
icon: icon?.ios ?? icon?.shared,
|
|
797
|
+
selectedIcon: selectedIcon?.ios ?? selectedIcon?.shared,
|
|
798
|
+
systemItem,
|
|
799
|
+
role: tabBarRole === 'search' ? tabBarRole : undefined,
|
|
800
|
+
automaticallyActivatesSearch: tabBarSearchAutoFocus,
|
|
801
|
+
scrollEdgeAppearance: {
|
|
802
|
+
tabBarBackgroundColor,
|
|
803
|
+
tabBarShadowColor,
|
|
804
|
+
tabBarBlurEffect,
|
|
805
|
+
...tabItemLayoutAppearance,
|
|
806
|
+
},
|
|
807
|
+
standardAppearance: {
|
|
808
|
+
tabBarBackgroundColor,
|
|
809
|
+
tabBarShadowColor,
|
|
810
|
+
tabBarBlurEffect,
|
|
811
|
+
...tabItemLayoutAppearance,
|
|
812
|
+
},
|
|
813
|
+
overrideScrollViewContentInsetAdjustmentBehavior,
|
|
814
|
+
}}
|
|
815
|
+
>
|
|
816
|
+
<Deferred lazy={lazy} visible={visible}>
|
|
817
|
+
<ActivityView
|
|
818
|
+
key={route.key}
|
|
819
|
+
mode={isFocused ? 'normal' : isActive ? 'inert' : 'paused'}
|
|
820
|
+
visible={
|
|
821
|
+
// We don't need to hide the content since it's handled natively
|
|
822
|
+
// Hiding may also cause flash due to lag after native tab switch
|
|
823
|
+
// So we leave it always visible
|
|
824
|
+
true
|
|
825
|
+
}
|
|
826
|
+
style={StyleSheet.absoluteFill}
|
|
827
|
+
>
|
|
828
|
+
<ScreenContent
|
|
829
|
+
isFocused={isFocused}
|
|
830
|
+
route={route}
|
|
831
|
+
navigation={descriptor.navigation}
|
|
832
|
+
options={options}
|
|
833
|
+
style={sceneStyle}
|
|
834
|
+
>
|
|
835
|
+
<AnimatedScreenContent isFocused={isFocused}>
|
|
836
|
+
<BottomTabBarHeightContext.Provider value={0}>
|
|
837
|
+
{render()}
|
|
838
|
+
</BottomTabBarHeightContext.Provider>
|
|
839
|
+
</AnimatedScreenContent>
|
|
840
|
+
</ScreenContent>
|
|
841
|
+
</ActivityView>
|
|
842
|
+
</Deferred>
|
|
843
|
+
</Tabs.Screen>
|
|
844
|
+
);
|
|
845
|
+
});
|
|
846
|
+
|
|
717
847
|
function AnimatedScreenContent({
|
|
718
848
|
isFocused,
|
|
719
849
|
children,
|
|
@@ -747,11 +877,22 @@ function AnimatedScreenContent({
|
|
|
747
877
|
);
|
|
748
878
|
}
|
|
749
879
|
|
|
750
|
-
function getPlatformIcon(
|
|
880
|
+
function getPlatformIcon(
|
|
881
|
+
icon: Icon,
|
|
882
|
+
direction: LocaleDirection,
|
|
883
|
+
size: number
|
|
884
|
+
): PlatformIcon {
|
|
751
885
|
switch (icon.type) {
|
|
752
886
|
case 'sfSymbol':
|
|
753
887
|
return {
|
|
754
|
-
ios:
|
|
888
|
+
ios: {
|
|
889
|
+
type: 'sfSymbol',
|
|
890
|
+
name: icon.name,
|
|
891
|
+
renderingMode:
|
|
892
|
+
icon.renderingMode === 'multicolor'
|
|
893
|
+
? 'original'
|
|
894
|
+
: icon.renderingMode,
|
|
895
|
+
},
|
|
755
896
|
android: undefined,
|
|
756
897
|
shared: undefined,
|
|
757
898
|
};
|
|
@@ -764,29 +905,43 @@ function getPlatformIcon(icon: Icon): PlatformIcon {
|
|
|
764
905
|
name: icon.name,
|
|
765
906
|
variant: icon.variant,
|
|
766
907
|
weight: icon.weight,
|
|
767
|
-
|
|
908
|
+
fill: icon.fill,
|
|
909
|
+
size,
|
|
910
|
+
direction,
|
|
768
911
|
}),
|
|
769
912
|
},
|
|
770
913
|
shared: undefined,
|
|
771
914
|
};
|
|
772
|
-
case 'image':
|
|
915
|
+
case 'image': {
|
|
916
|
+
// A `{ uri: 'name' }` source maps to an Android drawable by name.
|
|
917
|
+
const drawableName =
|
|
918
|
+
typeof icon.source === 'object' &&
|
|
919
|
+
'uri' in icon.source &&
|
|
920
|
+
icon.source.uri?.includes(':') === false
|
|
921
|
+
? icon.source.uri
|
|
922
|
+
: undefined;
|
|
923
|
+
|
|
773
924
|
return {
|
|
774
|
-
ios:
|
|
775
|
-
|
|
925
|
+
ios: {
|
|
926
|
+
type: 'imageSource',
|
|
927
|
+
imageSource: icon.source,
|
|
928
|
+
renderingMode: icon.tinted === false ? 'original' : 'template',
|
|
929
|
+
},
|
|
930
|
+
android:
|
|
931
|
+
drawableName != null
|
|
776
932
|
? {
|
|
777
|
-
type: '
|
|
778
|
-
|
|
933
|
+
type: 'drawableResource',
|
|
934
|
+
name: drawableName,
|
|
935
|
+
tinting: icon.tinted === false ? 'original' : 'tinted',
|
|
779
936
|
}
|
|
780
937
|
: {
|
|
781
|
-
type: '
|
|
782
|
-
|
|
938
|
+
type: 'imageSource',
|
|
939
|
+
imageSource: icon.source,
|
|
940
|
+
tinting: icon.tinted === false ? 'original' : 'tinted',
|
|
783
941
|
},
|
|
784
|
-
|
|
785
|
-
shared: {
|
|
786
|
-
type: 'imageSource',
|
|
787
|
-
imageSource: icon.source,
|
|
788
|
-
},
|
|
942
|
+
shared: undefined,
|
|
789
943
|
};
|
|
944
|
+
}
|
|
790
945
|
default: {
|
|
791
946
|
const _exhaustiveCheck: never = icon;
|
|
792
947
|
|