@react-navigation/bottom-tabs 7.20.0 → 7.22.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.
- package/lib/module/unstable/Deferred.js +3 -10
- package/lib/module/unstable/Deferred.js.map +1 -1
- package/lib/module/unstable/NativeBottomTabView.native.js +288 -196
- package/lib/module/unstable/NativeBottomTabView.native.js.map +1 -1
- package/lib/module/unstable/NativeScreen/NativeScreen.js +1 -1
- package/lib/module/unstable/NativeScreen/useAnimatedHeaderHeight.js.map +1 -1
- package/lib/module/unstable/NativeScreen/useHeaderConfig.js +11 -11
- package/lib/module/unstable/NativeScreen/useHeaderConfig.js.map +1 -1
- package/lib/module/views/BottomTabBar.js +130 -67
- package/lib/module/views/BottomTabBar.js.map +1 -1
- package/lib/module/views/BottomTabItem.js +4 -4
- package/lib/module/views/BottomTabItem.js.map +1 -1
- package/lib/module/views/BottomTabView.js +78 -57
- package/lib/module/views/BottomTabView.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 +17 -4
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/unstable/Deferred.d.ts.map +1 -1
- package/lib/typescript/src/unstable/NativeBottomTabView.d.ts +1 -1
- package/lib/typescript/src/unstable/NativeBottomTabView.d.ts.map +1 -1
- package/lib/typescript/src/unstable/NativeBottomTabView.native.d.ts.map +1 -1
- package/lib/typescript/src/unstable/NativeScreen/types.d.ts +1 -6
- package/lib/typescript/src/unstable/NativeScreen/types.d.ts.map +1 -1
- package/lib/typescript/src/unstable/NativeScreen/useAnimatedHeaderHeight.d.ts.map +1 -1
- package/lib/typescript/src/unstable/NativeScreen/useHeaderConfig.d.ts.map +1 -1
- package/lib/typescript/src/unstable/types.d.ts +125 -5
- package/lib/typescript/src/unstable/types.d.ts.map +1 -1
- package/lib/typescript/src/views/BottomTabBar.d.ts +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/BottomTabView.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 +14 -14
- package/src/types.tsx +33 -4
- package/src/unstable/Deferred.tsx +3 -11
- package/src/unstable/NativeBottomTabView.native.tsx +356 -218
- package/src/unstable/NativeBottomTabView.tsx +3 -3
- package/src/unstable/NativeScreen/NativeScreen.tsx +1 -1
- package/src/unstable/NativeScreen/types.ts +3 -6
- package/src/unstable/NativeScreen/useAnimatedHeaderHeight.tsx +5 -3
- package/src/unstable/NativeScreen/useHeaderConfig.tsx +8 -14
- package/src/unstable/types.tsx +134 -8
- package/src/views/BottomTabBar.tsx +176 -86
- package/src/views/BottomTabItem.tsx +11 -2
- package/src/views/BottomTabView.tsx +100 -66
- package/src/views/TabBarIcon.tsx +9 -2
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
CommonActions,
|
|
8
8
|
NavigationMetaContext,
|
|
9
|
+
type NavigationRoute,
|
|
9
10
|
type ParamListBase,
|
|
10
11
|
type Route,
|
|
11
12
|
StackActions,
|
|
@@ -19,15 +20,16 @@ import {
|
|
|
19
20
|
type NativeSyntheticEvent,
|
|
20
21
|
Platform,
|
|
21
22
|
PlatformColor,
|
|
23
|
+
type TextStyle,
|
|
22
24
|
} from 'react-native';
|
|
23
25
|
import {
|
|
24
|
-
type PlatformIconAndroid,
|
|
25
|
-
type PlatformIconIOS,
|
|
26
26
|
type PlatformIconShared,
|
|
27
27
|
Tabs,
|
|
28
28
|
type TabsBottomAccessoryEnvironment,
|
|
29
29
|
type TabSelectedEvent,
|
|
30
30
|
type TabSelectionRejectedEvent,
|
|
31
|
+
type TabsScreenIconAndroid,
|
|
32
|
+
type TabsScreenIconIOS,
|
|
31
33
|
type TabsScreenItemStateAppearanceAndroid,
|
|
32
34
|
type TabsScreenItemStateAppearanceIOS,
|
|
33
35
|
} from 'react-native-screens';
|
|
@@ -35,6 +37,7 @@ import {
|
|
|
35
37
|
import { Deferred } from './Deferred';
|
|
36
38
|
import { NativeScreen } from './NativeScreen/NativeScreen';
|
|
37
39
|
import type {
|
|
40
|
+
NativeBottomTabDescriptor,
|
|
38
41
|
NativeBottomTabDescriptorMap,
|
|
39
42
|
NativeBottomTabIcon,
|
|
40
43
|
NativeBottomTabNavigationConfig,
|
|
@@ -50,8 +53,8 @@ type Props = NativeBottomTabNavigationConfig & {
|
|
|
50
53
|
};
|
|
51
54
|
|
|
52
55
|
type PlatformIcon = {
|
|
53
|
-
ios?:
|
|
54
|
-
android?:
|
|
56
|
+
ios?: TabsScreenIconIOS;
|
|
57
|
+
android?: TabsScreenIconAndroid;
|
|
55
58
|
shared?: PlatformIconShared;
|
|
56
59
|
};
|
|
57
60
|
|
|
@@ -76,6 +79,9 @@ const meta = {
|
|
|
76
79
|
type: 'native-tabs',
|
|
77
80
|
};
|
|
78
81
|
|
|
82
|
+
const SUPPORTS_PROMINENT_TAB =
|
|
83
|
+
Platform.OS === 'ios' && parseInt(Platform.Version, 10) >= 27;
|
|
84
|
+
|
|
79
85
|
function reducer(state: NativeState, action: NativeAction): NativeState {
|
|
80
86
|
switch (action.type) {
|
|
81
87
|
case 'CONFIRM_STATE':
|
|
@@ -274,49 +280,32 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
274
280
|
color: fontColor,
|
|
275
281
|
} = currentOptions.tabBarLabelStyle || {};
|
|
276
282
|
|
|
277
|
-
const activeTintColor =
|
|
278
|
-
|
|
283
|
+
const { activeIndicatorColor, activeTintColor, inactiveTintColor } =
|
|
284
|
+
React.useMemo(() => {
|
|
285
|
+
const activeTintColor =
|
|
286
|
+
currentOptions.tabBarActiveTintColor ?? colors.primary;
|
|
279
287
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
288
|
+
const inactiveTintColor =
|
|
289
|
+
currentOptions.tabBarInactiveTintColor ??
|
|
290
|
+
Platform.select<ColorValue | string>({
|
|
291
|
+
ios: PlatformColor('label'),
|
|
292
|
+
default: colors.text,
|
|
293
|
+
});
|
|
286
294
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
type: 'transitionStart',
|
|
302
|
-
data: { closing },
|
|
303
|
-
target: route.key,
|
|
304
|
-
});
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
const onTransitionEnd = ({
|
|
308
|
-
closing,
|
|
309
|
-
route,
|
|
310
|
-
}: {
|
|
311
|
-
closing: boolean;
|
|
312
|
-
route: Route<string>;
|
|
313
|
-
}) => {
|
|
314
|
-
navigation.emit({
|
|
315
|
-
type: 'transitionEnd',
|
|
316
|
-
data: { closing },
|
|
317
|
-
target: route.key,
|
|
318
|
-
});
|
|
319
|
-
};
|
|
295
|
+
const activeIndicatorColor =
|
|
296
|
+
currentOptions?.tabBarActiveIndicatorColor ??
|
|
297
|
+
(typeof activeTintColor === 'string'
|
|
298
|
+
? Color(activeTintColor)?.alpha(0.1).string()
|
|
299
|
+
: undefined);
|
|
300
|
+
|
|
301
|
+
return { activeIndicatorColor, activeTintColor, inactiveTintColor };
|
|
302
|
+
}, [
|
|
303
|
+
currentOptions?.tabBarActiveIndicatorColor,
|
|
304
|
+
currentOptions.tabBarActiveTintColor,
|
|
305
|
+
currentOptions.tabBarInactiveTintColor,
|
|
306
|
+
colors.primary,
|
|
307
|
+
colors.text,
|
|
308
|
+
]);
|
|
320
309
|
|
|
321
310
|
const tabBarControllerMode =
|
|
322
311
|
currentOptions.tabBarControllerMode === 'auto'
|
|
@@ -330,6 +319,17 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
330
319
|
? 'never'
|
|
331
320
|
: currentOptions.tabBarMinimizeBehavior;
|
|
332
321
|
|
|
322
|
+
const tabBarSidebarPreferredPlacement =
|
|
323
|
+
currentOptions.tabBarSidebarPreferredPlacement === 'auto'
|
|
324
|
+
? 'automatic'
|
|
325
|
+
: currentOptions.tabBarSidebarPreferredPlacement;
|
|
326
|
+
|
|
327
|
+
const prominentScreenKey = SUPPORTS_PROMINENT_TAB
|
|
328
|
+
? state.routes.find(
|
|
329
|
+
(route) => descriptors[route.key]?.options.tabBarRole === 'prominent'
|
|
330
|
+
)?.key
|
|
331
|
+
: undefined;
|
|
332
|
+
|
|
333
333
|
const shouldHideTabBar = currentOptions.tabBarStyle?.display === 'none';
|
|
334
334
|
|
|
335
335
|
const bottomAccessory = currentOptions.bottomAccessory;
|
|
@@ -349,182 +349,49 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
349
349
|
colorScheme={dark ? 'dark' : 'light'}
|
|
350
350
|
nativeContainerStyle={{ backgroundColor: colors.background }}
|
|
351
351
|
ios={{
|
|
352
|
+
prominentScreenKey,
|
|
352
353
|
bottomAccessory: bottomAccessory
|
|
353
354
|
? (environment: TabsBottomAccessoryEnvironment) =>
|
|
354
355
|
bottomAccessory({ placement: environment })
|
|
355
356
|
: undefined,
|
|
357
|
+
tabBarHiddenAnimationEnabled:
|
|
358
|
+
currentOptions.tabBarVisibilityAnimationEnabled,
|
|
356
359
|
tabBarControllerMode,
|
|
360
|
+
tabBarSidebarPreferredPlacement,
|
|
357
361
|
tabBarMinimizeBehavior,
|
|
358
362
|
tabBarTintColor: activeTintColor,
|
|
359
363
|
}}
|
|
360
364
|
>
|
|
361
365
|
{state.routes.map((route, index) => {
|
|
362
|
-
const
|
|
366
|
+
const descriptor = descriptors[route.key];
|
|
363
367
|
const isFocused = state.index === index;
|
|
364
368
|
const isPreloaded = state.preloadedRouteKeys.includes(route.key);
|
|
365
369
|
|
|
366
|
-
const {
|
|
367
|
-
title,
|
|
368
|
-
lazy = false,
|
|
369
|
-
tabBarLabel,
|
|
370
|
-
tabBarSelectionEnabled,
|
|
371
|
-
tabBarRepeatedPressBehavior,
|
|
372
|
-
tabBarBadgeStyle,
|
|
373
|
-
tabBarIcon,
|
|
374
|
-
tabBarBadge,
|
|
375
|
-
tabBarSystemItem,
|
|
376
|
-
tabBarBlurEffect = dark ? 'systemMaterialDark' : 'systemMaterial',
|
|
377
|
-
tabBarStyle,
|
|
378
|
-
overrideScrollViewContentInsetAdjustmentBehavior,
|
|
379
|
-
} = options;
|
|
380
|
-
|
|
381
|
-
const {
|
|
382
|
-
backgroundColor: tabBarBackgroundColor,
|
|
383
|
-
shadowColor: tabBarShadowColor,
|
|
384
|
-
} = tabBarStyle || {};
|
|
385
|
-
|
|
386
|
-
const tabTitle =
|
|
387
|
-
// On iOS, `systemItem` already provides a localized label
|
|
388
|
-
// So we should only use `tabBarLabel` if explicitly provided
|
|
389
|
-
Platform.OS === 'ios' && tabBarSystemItem != null
|
|
390
|
-
? tabBarLabel
|
|
391
|
-
: getLabel({ label: tabBarLabel, title }, route.name);
|
|
392
|
-
|
|
393
|
-
const badgeBackgroundColor =
|
|
394
|
-
tabBarBadgeStyle?.backgroundColor ?? colors.notification;
|
|
395
|
-
const badgeTextColor =
|
|
396
|
-
tabBarBadgeStyle?.color ??
|
|
397
|
-
(typeof badgeBackgroundColor === 'string'
|
|
398
|
-
? Color(badgeBackgroundColor).isLight()
|
|
399
|
-
? 'black'
|
|
400
|
-
: 'white'
|
|
401
|
-
: undefined);
|
|
402
|
-
|
|
403
|
-
const tabItemAppearance: TabsScreenItemStateAppearanceIOS = {
|
|
404
|
-
tabBarItemTitleFontFamily: fontFamily,
|
|
405
|
-
tabBarItemTitleFontSize: fontSize,
|
|
406
|
-
tabBarItemTitleFontWeight: fontWeight,
|
|
407
|
-
tabBarItemTitleFontStyle: fontStyle,
|
|
408
|
-
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
409
|
-
tabBarItemIconColor: inactiveTintColor,
|
|
410
|
-
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
const normalTabItemAppearance: TabsScreenItemStateAppearanceAndroid =
|
|
414
|
-
{
|
|
415
|
-
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
416
|
-
tabBarItemIconColor: inactiveTintColor,
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
const selectedTabItemAppearance: TabsScreenItemStateAppearanceAndroid =
|
|
420
|
-
{
|
|
421
|
-
tabBarItemTitleFontColor: activeTintColor,
|
|
422
|
-
tabBarItemIconColor: activeTintColor,
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
const icon =
|
|
426
|
-
typeof tabBarIcon === 'function'
|
|
427
|
-
? getPlatformIcon(tabBarIcon({ focused: false }))
|
|
428
|
-
: tabBarIcon != null
|
|
429
|
-
? getPlatformIcon(tabBarIcon)
|
|
430
|
-
: undefined;
|
|
431
|
-
|
|
432
|
-
const selectedIcon =
|
|
433
|
-
typeof tabBarIcon === 'function'
|
|
434
|
-
? getPlatformIcon(tabBarIcon({ focused: true }))
|
|
435
|
-
: undefined;
|
|
370
|
+
const { lazy = false } = descriptor.options;
|
|
436
371
|
|
|
437
372
|
return (
|
|
438
|
-
<
|
|
439
|
-
onWillDisappear={() =>
|
|
440
|
-
onTransitionStart({ closing: true, route })
|
|
441
|
-
}
|
|
442
|
-
onWillAppear={() => onTransitionStart({ closing: false, route })}
|
|
443
|
-
onDidAppear={() => onTransitionEnd({ closing: false, route })}
|
|
444
|
-
onDidDisappear={() => onTransitionEnd({ closing: true, route })}
|
|
373
|
+
<TabScene
|
|
445
374
|
key={route.key}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
normal: normalTabItemAppearance,
|
|
467
|
-
selected: selectedTabItemAppearance,
|
|
468
|
-
tabBarItemActiveIndicatorColor: activeIndicatorColor,
|
|
469
|
-
tabBarItemActiveIndicatorEnabled:
|
|
470
|
-
currentOptions?.tabBarActiveIndicatorEnabled,
|
|
471
|
-
tabBarItemTitleFontFamily: fontFamily,
|
|
472
|
-
tabBarItemTitleFontWeight: fontWeight,
|
|
473
|
-
tabBarItemTitleSmallLabelFontSize: fontSize,
|
|
474
|
-
tabBarItemTitleLargeLabelFontSize: fontSize,
|
|
475
|
-
tabBarItemTitleFontStyle: fontStyle,
|
|
476
|
-
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
477
|
-
tabBarItemBadgeTextColor: badgeTextColor,
|
|
478
|
-
},
|
|
479
|
-
}}
|
|
480
|
-
ios={{
|
|
481
|
-
icon: icon?.ios ?? icon?.shared,
|
|
482
|
-
selectedIcon: selectedIcon?.ios ?? selectedIcon?.shared,
|
|
483
|
-
systemItem: tabBarSystemItem,
|
|
484
|
-
scrollEdgeAppearance: {
|
|
485
|
-
tabBarBackgroundColor,
|
|
486
|
-
tabBarShadowColor,
|
|
487
|
-
tabBarBlurEffect,
|
|
488
|
-
stacked: {
|
|
489
|
-
normal: tabItemAppearance,
|
|
490
|
-
},
|
|
491
|
-
inline: {
|
|
492
|
-
normal: tabItemAppearance,
|
|
493
|
-
},
|
|
494
|
-
compactInline: {
|
|
495
|
-
normal: tabItemAppearance,
|
|
496
|
-
},
|
|
497
|
-
},
|
|
498
|
-
standardAppearance: {
|
|
499
|
-
tabBarBackgroundColor,
|
|
500
|
-
tabBarShadowColor,
|
|
501
|
-
tabBarBlurEffect,
|
|
502
|
-
stacked: {
|
|
503
|
-
normal: tabItemAppearance,
|
|
504
|
-
},
|
|
505
|
-
inline: {
|
|
506
|
-
normal: tabItemAppearance,
|
|
507
|
-
},
|
|
508
|
-
compactInline: {
|
|
509
|
-
normal: tabItemAppearance,
|
|
510
|
-
},
|
|
511
|
-
},
|
|
512
|
-
overrideScrollViewContentInsetAdjustmentBehavior,
|
|
513
|
-
}}
|
|
514
|
-
>
|
|
515
|
-
<Deferred lazy={lazy} visible={isFocused || isPreloaded}>
|
|
516
|
-
<ScreenWithHeader
|
|
517
|
-
isFocused={isFocused}
|
|
518
|
-
route={route}
|
|
519
|
-
navigation={navigation}
|
|
520
|
-
options={options}
|
|
521
|
-
>
|
|
522
|
-
<NavigationMetaContext.Provider value={meta}>
|
|
523
|
-
{render()}
|
|
524
|
-
</NavigationMetaContext.Provider>
|
|
525
|
-
</ScreenWithHeader>
|
|
526
|
-
</Deferred>
|
|
527
|
-
</Tabs.Screen>
|
|
375
|
+
route={route}
|
|
376
|
+
descriptor={descriptor}
|
|
377
|
+
navigation={navigation}
|
|
378
|
+
isFocused={isFocused}
|
|
379
|
+
lazy={lazy}
|
|
380
|
+
visible={isFocused || isPreloaded}
|
|
381
|
+
fontFamily={fontFamily}
|
|
382
|
+
fontWeight={fontWeight}
|
|
383
|
+
fontSize={fontSize}
|
|
384
|
+
fontStyle={fontStyle}
|
|
385
|
+
fontColor={fontColor}
|
|
386
|
+
activeTintColor={activeTintColor}
|
|
387
|
+
inactiveTintColor={inactiveTintColor}
|
|
388
|
+
activeIndicatorColor={activeIndicatorColor}
|
|
389
|
+
rippleColor={currentOptions.tabBarRippleColor}
|
|
390
|
+
labelVisibilityMode={currentOptions.tabBarLabelVisibilityMode}
|
|
391
|
+
activeIndicatorEnabled={
|
|
392
|
+
currentOptions.tabBarActiveIndicatorEnabled
|
|
393
|
+
}
|
|
394
|
+
/>
|
|
528
395
|
);
|
|
529
396
|
})}
|
|
530
397
|
</Tabs.Host>
|
|
@@ -532,6 +399,258 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
532
399
|
);
|
|
533
400
|
}
|
|
534
401
|
|
|
402
|
+
type TabSceneProps = {
|
|
403
|
+
route: NavigationRoute<ParamListBase, string>;
|
|
404
|
+
descriptor: NativeBottomTabDescriptor;
|
|
405
|
+
navigation: NativeBottomTabNavigationHelpers;
|
|
406
|
+
isFocused: boolean;
|
|
407
|
+
lazy: boolean;
|
|
408
|
+
visible: boolean;
|
|
409
|
+
fontFamily: TextStyle['fontFamily'];
|
|
410
|
+
fontWeight: TextStyle['fontWeight'];
|
|
411
|
+
fontSize: TextStyle['fontSize'];
|
|
412
|
+
fontStyle: TextStyle['fontStyle'];
|
|
413
|
+
fontColor: TextStyle['color'];
|
|
414
|
+
activeTintColor: ColorValue;
|
|
415
|
+
inactiveTintColor: ColorValue;
|
|
416
|
+
activeIndicatorColor: ColorValue | undefined;
|
|
417
|
+
rippleColor: ColorValue | undefined;
|
|
418
|
+
labelVisibilityMode: NativeBottomTabNavigationOptions['tabBarLabelVisibilityMode'];
|
|
419
|
+
activeIndicatorEnabled: boolean | undefined;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const TabScene = React.memo(function TabScene({
|
|
423
|
+
route,
|
|
424
|
+
descriptor,
|
|
425
|
+
navigation,
|
|
426
|
+
isFocused,
|
|
427
|
+
lazy,
|
|
428
|
+
visible,
|
|
429
|
+
fontFamily,
|
|
430
|
+
fontWeight,
|
|
431
|
+
fontSize,
|
|
432
|
+
fontStyle,
|
|
433
|
+
fontColor,
|
|
434
|
+
activeTintColor,
|
|
435
|
+
inactiveTintColor,
|
|
436
|
+
activeIndicatorColor,
|
|
437
|
+
rippleColor,
|
|
438
|
+
labelVisibilityMode,
|
|
439
|
+
activeIndicatorEnabled,
|
|
440
|
+
}: TabSceneProps) {
|
|
441
|
+
const { dark, colors } = useTheme();
|
|
442
|
+
|
|
443
|
+
const onTransitionStart = ({
|
|
444
|
+
closing,
|
|
445
|
+
route,
|
|
446
|
+
}: {
|
|
447
|
+
closing: boolean;
|
|
448
|
+
route: Route<string>;
|
|
449
|
+
}) => {
|
|
450
|
+
navigation.emit({
|
|
451
|
+
type: 'transitionStart',
|
|
452
|
+
data: { closing },
|
|
453
|
+
target: route.key,
|
|
454
|
+
});
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const onTransitionEnd = ({
|
|
458
|
+
closing,
|
|
459
|
+
route,
|
|
460
|
+
}: {
|
|
461
|
+
closing: boolean;
|
|
462
|
+
route: Route<string>;
|
|
463
|
+
}) => {
|
|
464
|
+
navigation.emit({
|
|
465
|
+
type: 'transitionEnd',
|
|
466
|
+
data: { closing },
|
|
467
|
+
target: route.key,
|
|
468
|
+
});
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const { options, render } = descriptor;
|
|
472
|
+
|
|
473
|
+
const {
|
|
474
|
+
title,
|
|
475
|
+
tabBarLabel,
|
|
476
|
+
tabBarLabelPositionAdjustment,
|
|
477
|
+
tabBarSelectionEnabled,
|
|
478
|
+
tabBarRepeatedPressBehavior,
|
|
479
|
+
tabBarBadgeStyle,
|
|
480
|
+
tabBarIcon,
|
|
481
|
+
tabBarIconSize,
|
|
482
|
+
tabBarActiveIndicatorWidth,
|
|
483
|
+
tabBarActiveIndicatorHeight,
|
|
484
|
+
tabBarBadge,
|
|
485
|
+
tabBarSystemItem,
|
|
486
|
+
tabBarRole,
|
|
487
|
+
tabBarSearchAutoFocus,
|
|
488
|
+
tabBarBlurEffect = dark ? 'systemMaterialDark' : 'systemMaterial',
|
|
489
|
+
tabBarStyle,
|
|
490
|
+
overrideScrollViewContentInsetAdjustmentBehavior,
|
|
491
|
+
} = options;
|
|
492
|
+
|
|
493
|
+
const systemItem =
|
|
494
|
+
tabBarSystemItem ??
|
|
495
|
+
// Setting the `role` to 'search' doesn't add title and icon automatically
|
|
496
|
+
// So we also set `tabBarSystemItem` to 'search' to match SwiftUI's behavior
|
|
497
|
+
(tabBarRole === 'search' ? 'search' : undefined);
|
|
498
|
+
|
|
499
|
+
const {
|
|
500
|
+
backgroundColor: tabBarBackgroundColor,
|
|
501
|
+
shadowColor: tabBarShadowColor,
|
|
502
|
+
} = tabBarStyle || {};
|
|
503
|
+
|
|
504
|
+
const tabTitle =
|
|
505
|
+
// On iOS, `systemItem` already provides a localized label
|
|
506
|
+
// So we should only use `tabBarLabel` if explicitly provided
|
|
507
|
+
Platform.OS === 'ios' && systemItem != null
|
|
508
|
+
? tabBarLabel
|
|
509
|
+
: getLabel({ label: tabBarLabel, title }, route.name);
|
|
510
|
+
|
|
511
|
+
const badgeBackgroundColor =
|
|
512
|
+
tabBarBadgeStyle?.backgroundColor ?? colors.notification;
|
|
513
|
+
const badgeTextColor =
|
|
514
|
+
tabBarBadgeStyle?.color ??
|
|
515
|
+
(typeof badgeBackgroundColor === 'string'
|
|
516
|
+
? Color(badgeBackgroundColor).isLight()
|
|
517
|
+
? 'black'
|
|
518
|
+
: 'white'
|
|
519
|
+
: undefined);
|
|
520
|
+
|
|
521
|
+
const tabItemAppearance: TabsScreenItemStateAppearanceIOS = {
|
|
522
|
+
tabBarItemTitleFontFamily: fontFamily,
|
|
523
|
+
tabBarItemTitleFontSize: fontSize,
|
|
524
|
+
tabBarItemTitleFontWeight: fontWeight,
|
|
525
|
+
tabBarItemTitleFontStyle: fontStyle,
|
|
526
|
+
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
527
|
+
tabBarItemIconColor: inactiveTintColor,
|
|
528
|
+
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
const tabItemLayoutAppearance = {
|
|
532
|
+
stacked: {
|
|
533
|
+
normal: {
|
|
534
|
+
...tabItemAppearance,
|
|
535
|
+
tabBarItemTitlePositionAdjustment:
|
|
536
|
+
tabBarLabelPositionAdjustment?.stacked,
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
inline: {
|
|
540
|
+
normal: {
|
|
541
|
+
...tabItemAppearance,
|
|
542
|
+
tabBarItemTitlePositionAdjustment:
|
|
543
|
+
tabBarLabelPositionAdjustment?.inline,
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
compactInline: {
|
|
547
|
+
normal: {
|
|
548
|
+
...tabItemAppearance,
|
|
549
|
+
tabBarItemTitlePositionAdjustment:
|
|
550
|
+
tabBarLabelPositionAdjustment?.compactInline,
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
const normalTabItemAppearance: TabsScreenItemStateAppearanceAndroid = {
|
|
556
|
+
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
557
|
+
tabBarItemIconColor: inactiveTintColor,
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
const selectedTabItemAppearance: TabsScreenItemStateAppearanceAndroid = {
|
|
561
|
+
tabBarItemTitleFontColor: activeTintColor,
|
|
562
|
+
tabBarItemIconColor: activeTintColor,
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const icon =
|
|
566
|
+
typeof tabBarIcon === 'function'
|
|
567
|
+
? getPlatformIcon(tabBarIcon({ focused: false }))
|
|
568
|
+
: tabBarIcon != null
|
|
569
|
+
? getPlatformIcon(tabBarIcon)
|
|
570
|
+
: undefined;
|
|
571
|
+
|
|
572
|
+
const selectedIcon =
|
|
573
|
+
typeof tabBarIcon === 'function'
|
|
574
|
+
? getPlatformIcon(tabBarIcon({ focused: true }))
|
|
575
|
+
: undefined;
|
|
576
|
+
|
|
577
|
+
return (
|
|
578
|
+
<Tabs.Screen
|
|
579
|
+
onWillDisappear={() => onTransitionStart({ closing: true, route })}
|
|
580
|
+
onWillAppear={() => onTransitionStart({ closing: false, route })}
|
|
581
|
+
onDidAppear={() => onTransitionEnd({ closing: false, route })}
|
|
582
|
+
onDidDisappear={() => onTransitionEnd({ closing: true, route })}
|
|
583
|
+
screenKey={route.key}
|
|
584
|
+
preventNativeSelection={tabBarSelectionEnabled === false}
|
|
585
|
+
badgeValue={tabBarBadge?.toString()}
|
|
586
|
+
title={tabTitle}
|
|
587
|
+
style={{ backgroundColor: colors.background }}
|
|
588
|
+
specialEffects={{
|
|
589
|
+
repeatedTabSelection: {
|
|
590
|
+
popToRoot: tabBarRepeatedPressBehavior?.popToTop !== false,
|
|
591
|
+
scrollToTop: tabBarRepeatedPressBehavior?.scrollToTop !== false,
|
|
592
|
+
},
|
|
593
|
+
}}
|
|
594
|
+
android={{
|
|
595
|
+
icon: icon?.android ?? icon?.shared,
|
|
596
|
+
selectedIcon: selectedIcon?.android ?? selectedIcon?.shared,
|
|
597
|
+
standardAppearance: {
|
|
598
|
+
tabBarBackgroundColor: tabBarBackgroundColor ?? colors.card,
|
|
599
|
+
tabBarItemRippleColor: rippleColor,
|
|
600
|
+
tabBarItemLabelVisibilityMode: labelVisibilityMode,
|
|
601
|
+
normal: normalTabItemAppearance,
|
|
602
|
+
selected: selectedTabItemAppearance,
|
|
603
|
+
tabBarItemIconSize: tabBarIconSize,
|
|
604
|
+
tabBarItemActiveIndicatorColor: activeIndicatorColor,
|
|
605
|
+
tabBarItemActiveIndicatorEnabled: activeIndicatorEnabled,
|
|
606
|
+
tabBarItemActiveIndicatorWidth: tabBarActiveIndicatorWidth,
|
|
607
|
+
tabBarItemActiveIndicatorHeight: tabBarActiveIndicatorHeight,
|
|
608
|
+
tabBarItemTitleFontFamily: fontFamily,
|
|
609
|
+
tabBarItemTitleFontWeight: fontWeight,
|
|
610
|
+
tabBarItemTitleSmallLabelFontSize: fontSize,
|
|
611
|
+
tabBarItemTitleLargeLabelFontSize: fontSize,
|
|
612
|
+
tabBarItemTitleFontStyle: fontStyle,
|
|
613
|
+
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
614
|
+
tabBarItemBadgeTextColor: badgeTextColor,
|
|
615
|
+
},
|
|
616
|
+
}}
|
|
617
|
+
ios={{
|
|
618
|
+
icon: icon?.ios ?? icon?.shared,
|
|
619
|
+
selectedIcon: selectedIcon?.ios ?? selectedIcon?.shared,
|
|
620
|
+
systemItem,
|
|
621
|
+
role: tabBarRole === 'search' ? tabBarRole : undefined,
|
|
622
|
+
automaticallyActivatesSearch: tabBarSearchAutoFocus,
|
|
623
|
+
scrollEdgeAppearance: {
|
|
624
|
+
tabBarBackgroundColor,
|
|
625
|
+
tabBarShadowColor,
|
|
626
|
+
tabBarBlurEffect,
|
|
627
|
+
...tabItemLayoutAppearance,
|
|
628
|
+
},
|
|
629
|
+
standardAppearance: {
|
|
630
|
+
tabBarBackgroundColor,
|
|
631
|
+
tabBarShadowColor,
|
|
632
|
+
tabBarBlurEffect,
|
|
633
|
+
...tabItemLayoutAppearance,
|
|
634
|
+
},
|
|
635
|
+
overrideScrollViewContentInsetAdjustmentBehavior,
|
|
636
|
+
}}
|
|
637
|
+
>
|
|
638
|
+
<Deferred lazy={lazy} visible={visible}>
|
|
639
|
+
<ScreenWithHeader
|
|
640
|
+
isFocused={isFocused}
|
|
641
|
+
route={route}
|
|
642
|
+
navigation={descriptor.navigation}
|
|
643
|
+
options={options}
|
|
644
|
+
>
|
|
645
|
+
<NavigationMetaContext.Provider value={meta}>
|
|
646
|
+
{render()}
|
|
647
|
+
</NavigationMetaContext.Provider>
|
|
648
|
+
</ScreenWithHeader>
|
|
649
|
+
</Deferred>
|
|
650
|
+
</Tabs.Screen>
|
|
651
|
+
);
|
|
652
|
+
});
|
|
653
|
+
|
|
535
654
|
function ScreenWithHeader({
|
|
536
655
|
isFocused,
|
|
537
656
|
route,
|
|
@@ -593,28 +712,47 @@ function getPlatformIcon(icon: NativeBottomTabIcon): PlatformIcon {
|
|
|
593
712
|
switch (icon.type) {
|
|
594
713
|
case 'sfSymbol':
|
|
595
714
|
return {
|
|
596
|
-
ios:
|
|
715
|
+
ios: {
|
|
716
|
+
type: 'sfSymbol',
|
|
717
|
+
name: icon.name,
|
|
718
|
+
renderingMode:
|
|
719
|
+
icon.renderingMode === 'multicolor'
|
|
720
|
+
? 'original'
|
|
721
|
+
: icon.renderingMode,
|
|
722
|
+
},
|
|
597
723
|
android: undefined,
|
|
598
724
|
shared: undefined,
|
|
599
725
|
};
|
|
600
|
-
case 'image':
|
|
726
|
+
case 'image': {
|
|
727
|
+
// A `{ uri: 'name' }` source maps to an Android drawable by name.
|
|
728
|
+
const drawableName =
|
|
729
|
+
typeof icon.source === 'object' &&
|
|
730
|
+
'uri' in icon.source &&
|
|
731
|
+
icon.source.uri?.includes(':') === false
|
|
732
|
+
? icon.source.uri
|
|
733
|
+
: undefined;
|
|
734
|
+
|
|
601
735
|
return {
|
|
602
|
-
ios:
|
|
603
|
-
|
|
736
|
+
ios: {
|
|
737
|
+
type: 'imageSource',
|
|
738
|
+
imageSource: icon.source,
|
|
739
|
+
renderingMode: icon.tinted === false ? 'original' : 'template',
|
|
740
|
+
},
|
|
741
|
+
android:
|
|
742
|
+
drawableName != null
|
|
604
743
|
? {
|
|
605
|
-
type: '
|
|
606
|
-
|
|
744
|
+
type: 'drawableResource',
|
|
745
|
+
name: drawableName,
|
|
746
|
+
tinting: icon.tinted === false ? 'original' : 'tinted',
|
|
607
747
|
}
|
|
608
748
|
: {
|
|
609
|
-
type: '
|
|
610
|
-
|
|
749
|
+
type: 'imageSource',
|
|
750
|
+
imageSource: icon.source,
|
|
751
|
+
tinting: icon.tinted === false ? 'original' : 'tinted',
|
|
611
752
|
},
|
|
612
|
-
|
|
613
|
-
shared: {
|
|
614
|
-
type: 'imageSource',
|
|
615
|
-
imageSource: icon.source,
|
|
616
|
-
},
|
|
753
|
+
shared: undefined,
|
|
617
754
|
};
|
|
755
|
+
}
|
|
618
756
|
default: {
|
|
619
757
|
const _exhaustiveCheck: never = icon;
|
|
620
758
|
|
|
@@ -64,7 +64,7 @@ export function NativeScreen({ route, navigation, options, children }: Props) {
|
|
|
64
64
|
|
|
65
65
|
const [headerHeight, setHeaderHeight] = React.useState(defaultHeaderHeight);
|
|
66
66
|
|
|
67
|
-
// eslint-disable-next-line react
|
|
67
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
68
68
|
const setHeaderHeightDebounced = React.useCallback(
|
|
69
69
|
// Debounce the header height updates to avoid excessive re-renders
|
|
70
70
|
debounce(setHeaderHeight, 100),
|