@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.
- package/lib/module/unstable/Deferred.js +3 -10
- package/lib/module/unstable/Deferred.js.map +1 -1
- package/lib/module/unstable/NativeBottomTabView.native.js +309 -198
- package/lib/module/unstable/NativeBottomTabView.native.js.map +1 -1
- package/lib/module/views/BottomTabBar.js +111 -58
- 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/BottomTabView.js +81 -60
- 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 +26 -1
- 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.native.d.ts.map +1 -1
- package/lib/typescript/src/unstable/types.d.ts +148 -5
- package/lib/typescript/src/unstable/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/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 +6 -6
- package/src/types.tsx +36 -2
- package/src/unstable/Deferred.tsx +3 -11
- package/src/unstable/NativeBottomTabView.native.tsx +376 -217
- package/src/unstable/types.tsx +164 -6
- package/src/views/BottomTabBar.tsx +153 -72
- package/src/views/BottomTabItem.tsx +6 -0
- package/src/views/BottomTabView.tsx +101 -69
- 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':
|
|
@@ -149,7 +155,8 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
149
155
|
// JS sends a requested tab with the native provenance it was based on.
|
|
150
156
|
// Native replies with the selected tab and its new provenance.
|
|
151
157
|
const onTabSelected = (event: NativeSyntheticEvent<TabSelectedEvent>) => {
|
|
152
|
-
const { selectedScreenKey, provenance, actionOrigin } =
|
|
158
|
+
const { selectedScreenKey, provenance, actionOrigin, isRepeated } =
|
|
159
|
+
event.nativeEvent;
|
|
153
160
|
|
|
154
161
|
const confirmed = {
|
|
155
162
|
routeKey: selectedScreenKey,
|
|
@@ -167,9 +174,20 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
167
174
|
}
|
|
168
175
|
|
|
169
176
|
if (actionOrigin === 'user') {
|
|
177
|
+
const { tabBarRepeatedPressBehavior } = descriptors[route.key].options;
|
|
178
|
+
|
|
170
179
|
navigation.emit({
|
|
171
180
|
type: 'tabPress',
|
|
172
181
|
target: route.key,
|
|
182
|
+
data: {
|
|
183
|
+
origin: 'native',
|
|
184
|
+
behavior: {
|
|
185
|
+
scrollToTop:
|
|
186
|
+
isRepeated && tabBarRepeatedPressBehavior?.scrollToTop !== false,
|
|
187
|
+
popToTop:
|
|
188
|
+
isRepeated && tabBarRepeatedPressBehavior?.popToTop !== false,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
173
191
|
});
|
|
174
192
|
}
|
|
175
193
|
|
|
@@ -236,6 +254,13 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
236
254
|
navigation.emit({
|
|
237
255
|
type: 'tabPress',
|
|
238
256
|
target: preventedScreenKey,
|
|
257
|
+
data: {
|
|
258
|
+
origin: 'native',
|
|
259
|
+
behavior: {
|
|
260
|
+
scrollToTop: false,
|
|
261
|
+
popToTop: false,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
239
264
|
});
|
|
240
265
|
};
|
|
241
266
|
|
|
@@ -255,49 +280,32 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
255
280
|
color: fontColor,
|
|
256
281
|
} = currentOptions.tabBarLabelStyle || {};
|
|
257
282
|
|
|
258
|
-
const activeTintColor =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
currentOptions.tabBarInactiveTintColor ??
|
|
263
|
-
Platform.select<ColorValue | string>({
|
|
264
|
-
ios: PlatformColor('label'),
|
|
265
|
-
default: colors.text,
|
|
266
|
-
});
|
|
283
|
+
const { activeIndicatorColor, activeTintColor, inactiveTintColor } =
|
|
284
|
+
React.useMemo(() => {
|
|
285
|
+
const activeTintColor =
|
|
286
|
+
currentOptions.tabBarActiveTintColor ?? colors.primary;
|
|
267
287
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
288
|
+
const inactiveTintColor =
|
|
289
|
+
currentOptions.tabBarInactiveTintColor ??
|
|
290
|
+
Platform.select<ColorValue | string>({
|
|
291
|
+
ios: PlatformColor('label'),
|
|
292
|
+
default: colors.text,
|
|
293
|
+
});
|
|
273
294
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const onTransitionEnd = ({
|
|
289
|
-
closing,
|
|
290
|
-
route,
|
|
291
|
-
}: {
|
|
292
|
-
closing: boolean;
|
|
293
|
-
route: Route<string>;
|
|
294
|
-
}) => {
|
|
295
|
-
navigation.emit({
|
|
296
|
-
type: 'transitionEnd',
|
|
297
|
-
data: { closing },
|
|
298
|
-
target: route.key,
|
|
299
|
-
});
|
|
300
|
-
};
|
|
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
|
+
]);
|
|
301
309
|
|
|
302
310
|
const tabBarControllerMode =
|
|
303
311
|
currentOptions.tabBarControllerMode === 'auto'
|
|
@@ -311,6 +319,17 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
311
319
|
? 'never'
|
|
312
320
|
: currentOptions.tabBarMinimizeBehavior;
|
|
313
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
|
+
|
|
314
333
|
const shouldHideTabBar = currentOptions.tabBarStyle?.display === 'none';
|
|
315
334
|
|
|
316
335
|
const bottomAccessory = currentOptions.bottomAccessory;
|
|
@@ -330,180 +349,49 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
330
349
|
colorScheme={dark ? 'dark' : 'light'}
|
|
331
350
|
nativeContainerStyle={{ backgroundColor: colors.background }}
|
|
332
351
|
ios={{
|
|
352
|
+
prominentScreenKey,
|
|
333
353
|
bottomAccessory: bottomAccessory
|
|
334
354
|
? (environment: TabsBottomAccessoryEnvironment) =>
|
|
335
355
|
bottomAccessory({ placement: environment })
|
|
336
356
|
: undefined,
|
|
357
|
+
tabBarHiddenAnimationEnabled:
|
|
358
|
+
currentOptions.tabBarVisibilityAnimationEnabled,
|
|
337
359
|
tabBarControllerMode,
|
|
360
|
+
tabBarSidebarPreferredPlacement,
|
|
338
361
|
tabBarMinimizeBehavior,
|
|
339
362
|
tabBarTintColor: activeTintColor,
|
|
340
363
|
}}
|
|
341
364
|
>
|
|
342
365
|
{state.routes.map((route, index) => {
|
|
343
|
-
const
|
|
366
|
+
const descriptor = descriptors[route.key];
|
|
344
367
|
const isFocused = state.index === index;
|
|
345
368
|
const isPreloaded = state.preloadedRouteKeys.includes(route.key);
|
|
346
369
|
|
|
347
|
-
const {
|
|
348
|
-
title,
|
|
349
|
-
lazy = false,
|
|
350
|
-
tabBarLabel,
|
|
351
|
-
tabBarSelectionEnabled,
|
|
352
|
-
tabBarBadgeStyle,
|
|
353
|
-
tabBarIcon,
|
|
354
|
-
tabBarBadge,
|
|
355
|
-
tabBarSystemItem,
|
|
356
|
-
tabBarBlurEffect = dark ? 'systemMaterialDark' : 'systemMaterial',
|
|
357
|
-
tabBarStyle,
|
|
358
|
-
overrideScrollViewContentInsetAdjustmentBehavior,
|
|
359
|
-
} = options;
|
|
360
|
-
|
|
361
|
-
const {
|
|
362
|
-
backgroundColor: tabBarBackgroundColor,
|
|
363
|
-
shadowColor: tabBarShadowColor,
|
|
364
|
-
} = tabBarStyle || {};
|
|
365
|
-
|
|
366
|
-
const tabTitle =
|
|
367
|
-
// On iOS, `systemItem` already provides a localized label
|
|
368
|
-
// So we should only use `tabBarLabel` if explicitly provided
|
|
369
|
-
Platform.OS === 'ios' && tabBarSystemItem != null
|
|
370
|
-
? tabBarLabel
|
|
371
|
-
: getLabel({ label: tabBarLabel, title }, route.name);
|
|
372
|
-
|
|
373
|
-
const badgeBackgroundColor =
|
|
374
|
-
tabBarBadgeStyle?.backgroundColor ?? colors.notification;
|
|
375
|
-
const badgeTextColor =
|
|
376
|
-
tabBarBadgeStyle?.color ??
|
|
377
|
-
(typeof badgeBackgroundColor === 'string'
|
|
378
|
-
? Color(badgeBackgroundColor).isLight()
|
|
379
|
-
? 'black'
|
|
380
|
-
: 'white'
|
|
381
|
-
: undefined);
|
|
382
|
-
|
|
383
|
-
const tabItemAppearance: TabsScreenItemStateAppearanceIOS = {
|
|
384
|
-
tabBarItemTitleFontFamily: fontFamily,
|
|
385
|
-
tabBarItemTitleFontSize: fontSize,
|
|
386
|
-
tabBarItemTitleFontWeight: fontWeight,
|
|
387
|
-
tabBarItemTitleFontStyle: fontStyle,
|
|
388
|
-
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
389
|
-
tabBarItemIconColor: inactiveTintColor,
|
|
390
|
-
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
const normalTabItemAppearance: TabsScreenItemStateAppearanceAndroid =
|
|
394
|
-
{
|
|
395
|
-
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
396
|
-
tabBarItemIconColor: inactiveTintColor,
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
const selectedTabItemAppearance: TabsScreenItemStateAppearanceAndroid =
|
|
400
|
-
{
|
|
401
|
-
tabBarItemTitleFontColor: activeTintColor,
|
|
402
|
-
tabBarItemIconColor: activeTintColor,
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
const icon =
|
|
406
|
-
typeof tabBarIcon === 'function'
|
|
407
|
-
? getPlatformIcon(tabBarIcon({ focused: false }))
|
|
408
|
-
: tabBarIcon != null
|
|
409
|
-
? getPlatformIcon(tabBarIcon)
|
|
410
|
-
: undefined;
|
|
411
|
-
|
|
412
|
-
const selectedIcon =
|
|
413
|
-
typeof tabBarIcon === 'function'
|
|
414
|
-
? getPlatformIcon(tabBarIcon({ focused: true }))
|
|
415
|
-
: undefined;
|
|
370
|
+
const { lazy = false } = descriptor.options;
|
|
416
371
|
|
|
417
372
|
return (
|
|
418
|
-
<
|
|
419
|
-
onWillDisappear={() =>
|
|
420
|
-
onTransitionStart({ closing: true, route })
|
|
421
|
-
}
|
|
422
|
-
onWillAppear={() => onTransitionStart({ closing: false, route })}
|
|
423
|
-
onDidAppear={() => onTransitionEnd({ closing: false, route })}
|
|
424
|
-
onDidDisappear={() => onTransitionEnd({ closing: true, route })}
|
|
373
|
+
<TabScene
|
|
425
374
|
key={route.key}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
selected: selectedTabItemAppearance,
|
|
447
|
-
tabBarItemActiveIndicatorColor: activeIndicatorColor,
|
|
448
|
-
tabBarItemActiveIndicatorEnabled:
|
|
449
|
-
currentOptions?.tabBarActiveIndicatorEnabled,
|
|
450
|
-
tabBarItemTitleFontFamily: fontFamily,
|
|
451
|
-
tabBarItemTitleFontWeight: fontWeight,
|
|
452
|
-
tabBarItemTitleSmallLabelFontSize: fontSize,
|
|
453
|
-
tabBarItemTitleLargeLabelFontSize: fontSize,
|
|
454
|
-
tabBarItemTitleFontStyle: fontStyle,
|
|
455
|
-
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
456
|
-
tabBarItemBadgeTextColor: badgeTextColor,
|
|
457
|
-
},
|
|
458
|
-
}}
|
|
459
|
-
ios={{
|
|
460
|
-
icon: icon?.ios ?? icon?.shared,
|
|
461
|
-
selectedIcon: selectedIcon?.ios ?? selectedIcon?.shared,
|
|
462
|
-
systemItem: tabBarSystemItem,
|
|
463
|
-
scrollEdgeAppearance: {
|
|
464
|
-
tabBarBackgroundColor,
|
|
465
|
-
tabBarShadowColor,
|
|
466
|
-
tabBarBlurEffect,
|
|
467
|
-
stacked: {
|
|
468
|
-
normal: tabItemAppearance,
|
|
469
|
-
},
|
|
470
|
-
inline: {
|
|
471
|
-
normal: tabItemAppearance,
|
|
472
|
-
},
|
|
473
|
-
compactInline: {
|
|
474
|
-
normal: tabItemAppearance,
|
|
475
|
-
},
|
|
476
|
-
},
|
|
477
|
-
standardAppearance: {
|
|
478
|
-
tabBarBackgroundColor,
|
|
479
|
-
tabBarShadowColor,
|
|
480
|
-
tabBarBlurEffect,
|
|
481
|
-
stacked: {
|
|
482
|
-
normal: tabItemAppearance,
|
|
483
|
-
},
|
|
484
|
-
inline: {
|
|
485
|
-
normal: tabItemAppearance,
|
|
486
|
-
},
|
|
487
|
-
compactInline: {
|
|
488
|
-
normal: tabItemAppearance,
|
|
489
|
-
},
|
|
490
|
-
},
|
|
491
|
-
overrideScrollViewContentInsetAdjustmentBehavior,
|
|
492
|
-
}}
|
|
493
|
-
>
|
|
494
|
-
<Deferred lazy={lazy} visible={isFocused || isPreloaded}>
|
|
495
|
-
<ScreenWithHeader
|
|
496
|
-
isFocused={isFocused}
|
|
497
|
-
route={route}
|
|
498
|
-
navigation={navigation}
|
|
499
|
-
options={options}
|
|
500
|
-
>
|
|
501
|
-
<NavigationMetaContext.Provider value={meta}>
|
|
502
|
-
{render()}
|
|
503
|
-
</NavigationMetaContext.Provider>
|
|
504
|
-
</ScreenWithHeader>
|
|
505
|
-
</Deferred>
|
|
506
|
-
</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
|
+
/>
|
|
507
395
|
);
|
|
508
396
|
})}
|
|
509
397
|
</Tabs.Host>
|
|
@@ -511,6 +399,258 @@ export function NativeBottomTabView({ state, navigation, descriptors }: Props) {
|
|
|
511
399
|
);
|
|
512
400
|
}
|
|
513
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
|
+
|
|
514
654
|
function ScreenWithHeader({
|
|
515
655
|
isFocused,
|
|
516
656
|
route,
|
|
@@ -572,28 +712,47 @@ function getPlatformIcon(icon: NativeBottomTabIcon): PlatformIcon {
|
|
|
572
712
|
switch (icon.type) {
|
|
573
713
|
case 'sfSymbol':
|
|
574
714
|
return {
|
|
575
|
-
ios:
|
|
715
|
+
ios: {
|
|
716
|
+
type: 'sfSymbol',
|
|
717
|
+
name: icon.name,
|
|
718
|
+
renderingMode:
|
|
719
|
+
icon.renderingMode === 'multicolor'
|
|
720
|
+
? 'original'
|
|
721
|
+
: icon.renderingMode,
|
|
722
|
+
},
|
|
576
723
|
android: undefined,
|
|
577
724
|
shared: undefined,
|
|
578
725
|
};
|
|
579
|
-
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
|
+
|
|
580
735
|
return {
|
|
581
|
-
ios:
|
|
582
|
-
|
|
736
|
+
ios: {
|
|
737
|
+
type: 'imageSource',
|
|
738
|
+
imageSource: icon.source,
|
|
739
|
+
renderingMode: icon.tinted === false ? 'original' : 'template',
|
|
740
|
+
},
|
|
741
|
+
android:
|
|
742
|
+
drawableName != null
|
|
583
743
|
? {
|
|
584
|
-
type: '
|
|
585
|
-
|
|
744
|
+
type: 'drawableResource',
|
|
745
|
+
name: drawableName,
|
|
746
|
+
tinting: icon.tinted === false ? 'original' : 'tinted',
|
|
586
747
|
}
|
|
587
748
|
: {
|
|
588
|
-
type: '
|
|
589
|
-
|
|
749
|
+
type: 'imageSource',
|
|
750
|
+
imageSource: icon.source,
|
|
751
|
+
tinting: icon.tinted === false ? 'original' : 'tinted',
|
|
590
752
|
},
|
|
591
|
-
|
|
592
|
-
shared: {
|
|
593
|
-
type: 'imageSource',
|
|
594
|
-
imageSource: icon.source,
|
|
595
|
-
},
|
|
753
|
+
shared: undefined,
|
|
596
754
|
};
|
|
755
|
+
}
|
|
597
756
|
default: {
|
|
598
757
|
const _exhaustiveCheck: never = icon;
|
|
599
758
|
|