@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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { getLabel } from '@react-navigation/elements';
|
|
4
4
|
import { ActivityView, Color, SafeAreaProviderCompat } from '@react-navigation/elements/internal';
|
|
5
|
-
import { CommonActions, MaterialSymbol, StackActions, useTheme } from '@react-navigation/native';
|
|
5
|
+
import { CommonActions, MaterialSymbol, StackActions, useLocale, useTheme } from '@react-navigation/native';
|
|
6
6
|
import * as React from 'react';
|
|
7
7
|
import { Animated, Platform, PlatformColor, StyleSheet } from 'react-native';
|
|
8
8
|
import { Tabs } from 'react-native-screens';
|
|
@@ -16,6 +16,7 @@ const ICON_SIZE = Platform.select({
|
|
|
16
16
|
ios: 25,
|
|
17
17
|
default: 24
|
|
18
18
|
});
|
|
19
|
+
const SUPPORTS_PROMINENT_TAB = Platform.OS === 'ios' && parseInt(Platform.Version, 10) >= 27;
|
|
19
20
|
function reducer(state, action) {
|
|
20
21
|
switch (action.type) {
|
|
21
22
|
case 'TRACK_TRANSITION':
|
|
@@ -221,59 +222,54 @@ export function BottomTabViewNative({
|
|
|
221
222
|
} = currentOptions.tabBarLabelStyle || {};
|
|
222
223
|
const backgroundColor = currentOptions.tabBarStyle?.backgroundColor ?? colors.background;
|
|
223
224
|
const shouldHideTabBar = currentOptions.tabBarStyle?.display === 'none';
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
const {
|
|
226
|
+
activeIndicatorColor,
|
|
227
|
+
activeTintColor,
|
|
228
|
+
inactiveTintColor
|
|
229
|
+
} = React.useMemo(() => {
|
|
230
|
+
let activeIndicatorColor = currentOptions?.tabBarActiveIndicatorColor;
|
|
231
|
+
let activeTintColor = currentOptions.tabBarActiveTintColor;
|
|
232
|
+
let inactiveTintColor = currentOptions.tabBarInactiveTintColor;
|
|
227
233
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
234
|
+
// Derive colors based on Material Design guidelines
|
|
235
|
+
// https://m3.material.io/components/navigation-bar/specs
|
|
236
|
+
if (Platform.OS === 'android') {
|
|
237
|
+
switch (getAndroidColorName(backgroundColor)) {
|
|
238
|
+
case 'system_surface_container_light':
|
|
239
|
+
case 'system_surface_container_high_light':
|
|
240
|
+
case 'system_surface_container_highest_light':
|
|
241
|
+
case 'system_surface_container_low_light':
|
|
242
|
+
case 'system_surface_container_lowest_light':
|
|
243
|
+
inactiveTintColor = inactiveTintColor ?? PlatformColor('@android:color/system_on_surface_variant_light');
|
|
244
|
+
activeTintColor = activeTintColor ?? PlatformColor('@android:color/system_on_secondary_container_light');
|
|
245
|
+
activeIndicatorColor = activeIndicatorColor ?? PlatformColor('@android:color/system_secondary_container_light');
|
|
246
|
+
break;
|
|
247
|
+
case 'system_surface_container_dark':
|
|
248
|
+
case 'system_surface_container_high_dark':
|
|
249
|
+
case 'system_surface_container_highest_dark':
|
|
250
|
+
case 'system_surface_container_low_dark':
|
|
251
|
+
case 'system_surface_container_lowest_dark':
|
|
252
|
+
inactiveTintColor = inactiveTintColor ?? PlatformColor('@android:color/system_on_surface_variant_dark');
|
|
253
|
+
activeTintColor = activeTintColor ?? PlatformColor('@android:color/system_on_secondary_container_dark');
|
|
254
|
+
activeIndicatorColor = activeIndicatorColor ?? PlatformColor('@android:color/system_secondary_container_dark');
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
250
257
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
default: colors.text
|
|
255
|
-
});
|
|
256
|
-
activeTintColor = activeTintColor ?? colors.primary;
|
|
257
|
-
activeIndicatorColor = activeIndicatorColor ?? Platform.select({
|
|
258
|
-
android: Color(activeTintColor)?.alpha(0.075).string(),
|
|
259
|
-
default: undefined
|
|
260
|
-
});
|
|
261
|
-
const onTransitionStart = ({
|
|
262
|
-
route
|
|
263
|
-
}) => {
|
|
264
|
-
navigation.emit({
|
|
265
|
-
type: 'transitionStart',
|
|
266
|
-
target: route.key
|
|
258
|
+
inactiveTintColor = inactiveTintColor ?? Platform.select({
|
|
259
|
+
ios: PlatformColor('label'),
|
|
260
|
+
default: colors.text
|
|
267
261
|
});
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
navigation.emit({
|
|
273
|
-
type: 'transitionEnd',
|
|
274
|
-
target: route.key
|
|
262
|
+
activeTintColor = activeTintColor ?? colors.primary;
|
|
263
|
+
activeIndicatorColor = activeIndicatorColor ?? Platform.select({
|
|
264
|
+
android: Color(activeTintColor)?.alpha(0.075).string(),
|
|
265
|
+
default: undefined
|
|
275
266
|
});
|
|
276
|
-
|
|
267
|
+
return {
|
|
268
|
+
activeIndicatorColor,
|
|
269
|
+
activeTintColor,
|
|
270
|
+
inactiveTintColor
|
|
271
|
+
};
|
|
272
|
+
}, [currentOptions?.tabBarActiveIndicatorColor, currentOptions.tabBarActiveTintColor, currentOptions.tabBarInactiveTintColor, backgroundColor, colors.primary, colors.text]);
|
|
277
273
|
const tabBarPosition = useTabBarPosition(currentOptions);
|
|
278
274
|
const hasCustomTabBar = tabBar != null;
|
|
279
275
|
const tabBarElement = tabBar ? tabBar({
|
|
@@ -283,6 +279,8 @@ export function BottomTabViewNative({
|
|
|
283
279
|
}) : null;
|
|
284
280
|
const tabBarControllerMode = currentOptions.tabBarControllerMode === 'auto' ? 'automatic' : currentOptions.tabBarControllerMode;
|
|
285
281
|
const tabBarMinimizeBehavior = currentOptions.tabBarMinimizeBehavior === 'auto' ? 'automatic' : currentOptions.tabBarMinimizeBehavior === 'none' ? 'never' : currentOptions.tabBarMinimizeBehavior;
|
|
282
|
+
const tabBarSidebarPreferredPlacement = currentOptions.tabBarSidebarPreferredPlacement === 'auto' ? 'automatic' : currentOptions.tabBarSidebarPreferredPlacement;
|
|
283
|
+
const prominentScreenKey = SUPPORTS_PROMINENT_TAB ? state.routes.find(route => descriptors[route.key]?.options.tabBarRole === 'prominent')?.key : undefined;
|
|
286
284
|
const bottomAccessory = currentOptions.bottomAccessory;
|
|
287
285
|
return /*#__PURE__*/_jsxs(SafeAreaProviderCompat, {
|
|
288
286
|
style: {
|
|
@@ -303,10 +301,13 @@ export function BottomTabViewNative({
|
|
|
303
301
|
backgroundColor: colors.background
|
|
304
302
|
},
|
|
305
303
|
ios: {
|
|
304
|
+
prominentScreenKey,
|
|
306
305
|
bottomAccessory: bottomAccessory ? environment => bottomAccessory({
|
|
307
306
|
placement: environment
|
|
308
307
|
}) : undefined,
|
|
308
|
+
tabBarHiddenAnimationEnabled: currentOptions.tabBarVisibilityAnimationEnabled,
|
|
309
309
|
tabBarControllerMode,
|
|
310
|
+
tabBarSidebarPreferredPlacement,
|
|
310
311
|
tabBarMinimizeBehavior,
|
|
311
312
|
tabBarTintColor: activeTintColor
|
|
312
313
|
},
|
|
@@ -315,197 +316,286 @@ export function BottomTabViewNative({
|
|
|
315
316
|
if (descriptor == null) {
|
|
316
317
|
throw new Error(`Couldn't find a descriptor for route '${route.key}'.`);
|
|
317
318
|
}
|
|
318
|
-
const {
|
|
319
|
-
options,
|
|
320
|
-
render,
|
|
321
|
-
navigation
|
|
322
|
-
} = descriptor;
|
|
323
319
|
const isFocused = state.index === index;
|
|
324
320
|
const isPreloaded = state.preloadedRouteKeys.includes(route.key);
|
|
325
321
|
const {
|
|
326
|
-
title,
|
|
327
322
|
lazy = false,
|
|
328
|
-
inactiveBehavior = 'pause'
|
|
329
|
-
|
|
330
|
-
tabBarSelectionEnabled,
|
|
331
|
-
tabBarRepeatedPressBehavior,
|
|
332
|
-
tabBarBadgeStyle,
|
|
333
|
-
tabBarIcon,
|
|
334
|
-
tabBarBadge,
|
|
335
|
-
tabBarSystemItem,
|
|
336
|
-
tabBarBlurEffect = dark ? 'systemMaterialDark' : 'systemMaterial',
|
|
337
|
-
tabBarStyle,
|
|
338
|
-
tabBarAccessibilityLabel,
|
|
339
|
-
tabBarButtonTestID,
|
|
340
|
-
sceneStyle,
|
|
341
|
-
overrideScrollViewContentInsetAdjustmentBehavior
|
|
342
|
-
} = options;
|
|
343
|
-
const {
|
|
344
|
-
backgroundColor: tabBarBackgroundColor,
|
|
345
|
-
shadowColor: tabBarShadowColor
|
|
346
|
-
} = tabBarStyle || {};
|
|
347
|
-
const tabTitle =
|
|
348
|
-
// On iOS, `systemItem` already provides a localized label
|
|
349
|
-
// So we should only use `tabBarLabel` if explicitly provided
|
|
350
|
-
Platform.OS === 'ios' && tabBarSystemItem != null ? tabBarLabel : getLabel({
|
|
351
|
-
label: tabBarLabel,
|
|
352
|
-
title
|
|
353
|
-
}, route.name);
|
|
354
|
-
const badgeBackgroundColor = tabBarBadgeStyle?.backgroundColor ?? colors.notification;
|
|
355
|
-
const badgeTextColor = tabBarBadgeStyle?.color ?? Color.foreground(badgeBackgroundColor);
|
|
356
|
-
const tabItemAppearance = {
|
|
357
|
-
tabBarItemTitleFontFamily: fontFamily,
|
|
358
|
-
tabBarItemTitleFontSize: fontSize,
|
|
359
|
-
tabBarItemTitleFontWeight: fontWeight,
|
|
360
|
-
tabBarItemTitleFontStyle: fontStyle,
|
|
361
|
-
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
362
|
-
tabBarItemIconColor: inactiveTintColor,
|
|
363
|
-
tabBarItemBadgeBackgroundColor: badgeBackgroundColor
|
|
364
|
-
};
|
|
365
|
-
const normalTabItemAppearance = {
|
|
366
|
-
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
367
|
-
tabBarItemIconColor: inactiveTintColor
|
|
368
|
-
};
|
|
369
|
-
const selectedTabItemAppearance = {
|
|
370
|
-
tabBarItemTitleFontColor: activeTintColor,
|
|
371
|
-
tabBarItemIconColor: activeTintColor
|
|
372
|
-
};
|
|
373
|
-
const getIcon = selected => {
|
|
374
|
-
if (typeof tabBarIcon === 'function') {
|
|
375
|
-
const result = tabBarIcon({
|
|
376
|
-
focused: selected,
|
|
377
|
-
size: ICON_SIZE,
|
|
378
|
-
color: selected ? activeTintColor : inactiveTintColor
|
|
379
|
-
});
|
|
380
|
-
if (/*#__PURE__*/React.isValidElement(result)) {
|
|
381
|
-
throw new Error(`Returning a React element from 'tabBarIcon' is not supported with native tab bar.`);
|
|
382
|
-
} else if (result && typeof result === 'object' && 'type' in result) {
|
|
383
|
-
return getPlatformIcon(result);
|
|
384
|
-
} else {
|
|
385
|
-
throw new Error(`The 'tabBarIcon' function must return an icon object (got ${typeof result}).`);
|
|
386
|
-
}
|
|
387
|
-
} else if (tabBarIcon != null) {
|
|
388
|
-
return getPlatformIcon(tabBarIcon);
|
|
389
|
-
}
|
|
390
|
-
return undefined;
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
// An error is thrown for React Elements in `getIcon`
|
|
394
|
-
// So we only call it when we actually render a native tab bar
|
|
395
|
-
const icon = hasCustomTabBar ? undefined : getIcon(false);
|
|
396
|
-
const selectedIcon = !hasCustomTabBar && typeof tabBarIcon === 'function' ? getIcon(true) : icon;
|
|
323
|
+
inactiveBehavior = 'pause'
|
|
324
|
+
} = descriptor.options;
|
|
397
325
|
|
|
398
326
|
// For preloaded screens and if lazy is false,
|
|
399
327
|
// Keep them active so that the effects can run
|
|
400
328
|
const isActive = inactiveBehavior === 'none' || isPreloaded || nativeState.lastTransition?.from === route.key || nativeState.lastTransition?.to === route.key || lazy === false && !loaded.includes(route.key);
|
|
401
|
-
return /*#__PURE__*/_jsx(
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
},
|
|
423
|
-
android: {
|
|
424
|
-
icon: icon?.android ?? icon?.shared,
|
|
425
|
-
selectedIcon: selectedIcon?.android ?? selectedIcon?.shared,
|
|
426
|
-
standardAppearance: {
|
|
427
|
-
tabBarBackgroundColor: tabBarBackgroundColor ?? backgroundColor,
|
|
428
|
-
tabBarItemRippleColor: currentOptions?.tabBarRippleColor,
|
|
429
|
-
tabBarItemLabelVisibilityMode: currentOptions?.tabBarLabelVisibilityMode,
|
|
430
|
-
normal: normalTabItemAppearance,
|
|
431
|
-
selected: selectedTabItemAppearance,
|
|
432
|
-
tabBarItemActiveIndicatorColor: activeIndicatorColor,
|
|
433
|
-
tabBarItemActiveIndicatorEnabled: currentOptions?.tabBarActiveIndicatorEnabled,
|
|
434
|
-
tabBarItemTitleFontFamily: fontFamily,
|
|
435
|
-
tabBarItemTitleFontWeight: fontWeight,
|
|
436
|
-
tabBarItemTitleSmallLabelFontSize: fontSize,
|
|
437
|
-
tabBarItemTitleLargeLabelFontSize: fontSize,
|
|
438
|
-
tabBarItemTitleFontStyle: fontStyle,
|
|
439
|
-
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
440
|
-
tabBarItemBadgeTextColor: badgeTextColor
|
|
441
|
-
}
|
|
442
|
-
},
|
|
443
|
-
ios: {
|
|
444
|
-
icon: icon?.ios ?? icon?.shared,
|
|
445
|
-
selectedIcon: selectedIcon?.ios ?? selectedIcon?.shared,
|
|
446
|
-
systemItem: tabBarSystemItem,
|
|
447
|
-
scrollEdgeAppearance: {
|
|
448
|
-
tabBarBackgroundColor,
|
|
449
|
-
tabBarShadowColor,
|
|
450
|
-
tabBarBlurEffect,
|
|
451
|
-
stacked: {
|
|
452
|
-
normal: tabItemAppearance
|
|
453
|
-
},
|
|
454
|
-
inline: {
|
|
455
|
-
normal: tabItemAppearance
|
|
456
|
-
},
|
|
457
|
-
compactInline: {
|
|
458
|
-
normal: tabItemAppearance
|
|
459
|
-
}
|
|
460
|
-
},
|
|
461
|
-
standardAppearance: {
|
|
462
|
-
tabBarBackgroundColor,
|
|
463
|
-
tabBarShadowColor,
|
|
464
|
-
tabBarBlurEffect,
|
|
465
|
-
stacked: {
|
|
466
|
-
normal: tabItemAppearance
|
|
467
|
-
},
|
|
468
|
-
inline: {
|
|
469
|
-
normal: tabItemAppearance
|
|
470
|
-
},
|
|
471
|
-
compactInline: {
|
|
472
|
-
normal: tabItemAppearance
|
|
473
|
-
}
|
|
474
|
-
},
|
|
475
|
-
overrideScrollViewContentInsetAdjustmentBehavior
|
|
476
|
-
},
|
|
477
|
-
children: /*#__PURE__*/_jsx(Deferred, {
|
|
478
|
-
lazy: lazy,
|
|
479
|
-
visible: isFocused || isPreloaded || loaded.includes(route.key),
|
|
480
|
-
children: /*#__PURE__*/_jsx(ActivityView, {
|
|
481
|
-
mode: isFocused ? 'normal' : isActive ? 'inert' : 'paused',
|
|
482
|
-
visible:
|
|
483
|
-
// We don't need to hide the content since it's handled natively
|
|
484
|
-
// Hiding may also cause flash due to lag after native tab switch
|
|
485
|
-
// So we leave it always visible
|
|
486
|
-
true,
|
|
487
|
-
style: StyleSheet.absoluteFill,
|
|
488
|
-
children: /*#__PURE__*/_jsx(ScreenContent, {
|
|
489
|
-
isFocused: isFocused,
|
|
490
|
-
route: route,
|
|
491
|
-
navigation: navigation,
|
|
492
|
-
options: options,
|
|
493
|
-
style: sceneStyle,
|
|
494
|
-
children: /*#__PURE__*/_jsx(AnimatedScreenContent, {
|
|
495
|
-
isFocused: isFocused,
|
|
496
|
-
children: /*#__PURE__*/_jsx(BottomTabBarHeightContext.Provider, {
|
|
497
|
-
value: 0,
|
|
498
|
-
children: render()
|
|
499
|
-
})
|
|
500
|
-
})
|
|
501
|
-
})
|
|
502
|
-
}, route.key)
|
|
503
|
-
})
|
|
329
|
+
return /*#__PURE__*/_jsx(TabScene, {
|
|
330
|
+
route: route,
|
|
331
|
+
descriptor: descriptor,
|
|
332
|
+
navigation: navigation,
|
|
333
|
+
isFocused: isFocused,
|
|
334
|
+
isActive: isActive,
|
|
335
|
+
lazy: lazy,
|
|
336
|
+
visible: isFocused || isPreloaded || loaded.includes(route.key),
|
|
337
|
+
hasCustomTabBar: hasCustomTabBar,
|
|
338
|
+
fontFamily: fontFamily,
|
|
339
|
+
fontWeight: fontWeight,
|
|
340
|
+
fontSize: fontSize,
|
|
341
|
+
fontStyle: fontStyle,
|
|
342
|
+
fontColor: fontColor,
|
|
343
|
+
backgroundColor: backgroundColor,
|
|
344
|
+
activeTintColor: activeTintColor,
|
|
345
|
+
inactiveTintColor: inactiveTintColor,
|
|
346
|
+
activeIndicatorColor: activeIndicatorColor,
|
|
347
|
+
rippleColor: currentOptions.tabBarRippleColor,
|
|
348
|
+
labelVisibilityMode: currentOptions.tabBarLabelVisibilityMode,
|
|
349
|
+
activeIndicatorEnabled: currentOptions.tabBarActiveIndicatorEnabled
|
|
504
350
|
}, route.key);
|
|
505
351
|
})
|
|
506
352
|
}), tabBarPosition === 'bottom' || tabBarPosition === 'right' ? tabBarElement : null]
|
|
507
353
|
});
|
|
508
354
|
}
|
|
355
|
+
const TabScene = /*#__PURE__*/React.memo(function TabScene({
|
|
356
|
+
route,
|
|
357
|
+
descriptor,
|
|
358
|
+
navigation,
|
|
359
|
+
isFocused,
|
|
360
|
+
isActive,
|
|
361
|
+
lazy,
|
|
362
|
+
visible,
|
|
363
|
+
hasCustomTabBar,
|
|
364
|
+
fontFamily,
|
|
365
|
+
fontWeight,
|
|
366
|
+
fontSize,
|
|
367
|
+
fontStyle,
|
|
368
|
+
fontColor,
|
|
369
|
+
backgroundColor,
|
|
370
|
+
activeTintColor,
|
|
371
|
+
inactiveTintColor,
|
|
372
|
+
activeIndicatorColor,
|
|
373
|
+
rippleColor,
|
|
374
|
+
labelVisibilityMode,
|
|
375
|
+
activeIndicatorEnabled
|
|
376
|
+
}) {
|
|
377
|
+
const {
|
|
378
|
+
dark,
|
|
379
|
+
colors
|
|
380
|
+
} = useTheme();
|
|
381
|
+
const {
|
|
382
|
+
direction
|
|
383
|
+
} = useLocale();
|
|
384
|
+
const onTransitionStart = ({
|
|
385
|
+
route
|
|
386
|
+
}) => {
|
|
387
|
+
navigation.emit({
|
|
388
|
+
type: 'transitionStart',
|
|
389
|
+
target: route.key
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
const onTransitionEnd = ({
|
|
393
|
+
route
|
|
394
|
+
}) => {
|
|
395
|
+
navigation.emit({
|
|
396
|
+
type: 'transitionEnd',
|
|
397
|
+
target: route.key
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
const {
|
|
401
|
+
options,
|
|
402
|
+
render
|
|
403
|
+
} = descriptor;
|
|
404
|
+
const {
|
|
405
|
+
title,
|
|
406
|
+
tabBarLabel,
|
|
407
|
+
tabBarLabelPositionAdjustment,
|
|
408
|
+
tabBarSelectionEnabled,
|
|
409
|
+
tabBarRepeatedPressBehavior,
|
|
410
|
+
tabBarBadgeStyle,
|
|
411
|
+
tabBarIcon,
|
|
412
|
+
tabBarIconSize,
|
|
413
|
+
tabBarActiveIndicatorWidth,
|
|
414
|
+
tabBarActiveIndicatorHeight,
|
|
415
|
+
tabBarBadge,
|
|
416
|
+
tabBarSystemItem,
|
|
417
|
+
tabBarRole,
|
|
418
|
+
tabBarSearchAutoFocus,
|
|
419
|
+
tabBarBlurEffect = dark ? 'systemMaterialDark' : 'systemMaterial',
|
|
420
|
+
tabBarStyle,
|
|
421
|
+
tabBarAccessibilityLabel,
|
|
422
|
+
tabBarButtonTestID,
|
|
423
|
+
sceneStyle,
|
|
424
|
+
overrideScrollViewContentInsetAdjustmentBehavior
|
|
425
|
+
} = options;
|
|
426
|
+
const systemItem = tabBarSystemItem ?? (
|
|
427
|
+
// Setting the `role` to 'search' doesn't add title and icon automatically
|
|
428
|
+
// So we also set `tabBarSystemItem` to 'search' to match SwiftUI's behavior
|
|
429
|
+
tabBarRole === 'search' ? 'search' : undefined);
|
|
430
|
+
const {
|
|
431
|
+
backgroundColor: tabBarBackgroundColor,
|
|
432
|
+
shadowColor: tabBarShadowColor
|
|
433
|
+
} = tabBarStyle || {};
|
|
434
|
+
const tabTitle =
|
|
435
|
+
// On iOS, `systemItem` already provides a localized label
|
|
436
|
+
// So we should only use `tabBarLabel` if explicitly provided
|
|
437
|
+
Platform.OS === 'ios' && systemItem != null ? tabBarLabel : getLabel({
|
|
438
|
+
label: tabBarLabel,
|
|
439
|
+
title
|
|
440
|
+
}, route.name);
|
|
441
|
+
const badgeBackgroundColor = tabBarBadgeStyle?.backgroundColor ?? colors.notification;
|
|
442
|
+
const badgeTextColor = tabBarBadgeStyle?.color ?? Color.foreground(badgeBackgroundColor);
|
|
443
|
+
const tabItemAppearance = {
|
|
444
|
+
tabBarItemTitleFontFamily: fontFamily,
|
|
445
|
+
tabBarItemTitleFontSize: fontSize,
|
|
446
|
+
tabBarItemTitleFontWeight: fontWeight,
|
|
447
|
+
tabBarItemTitleFontStyle: fontStyle,
|
|
448
|
+
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
449
|
+
tabBarItemIconColor: inactiveTintColor,
|
|
450
|
+
tabBarItemBadgeBackgroundColor: badgeBackgroundColor
|
|
451
|
+
};
|
|
452
|
+
const tabItemLayoutAppearance = {
|
|
453
|
+
stacked: {
|
|
454
|
+
normal: {
|
|
455
|
+
...tabItemAppearance,
|
|
456
|
+
tabBarItemTitlePositionAdjustment: tabBarLabelPositionAdjustment?.stacked
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
inline: {
|
|
460
|
+
normal: {
|
|
461
|
+
...tabItemAppearance,
|
|
462
|
+
tabBarItemTitlePositionAdjustment: tabBarLabelPositionAdjustment?.inline
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
compactInline: {
|
|
466
|
+
normal: {
|
|
467
|
+
...tabItemAppearance,
|
|
468
|
+
tabBarItemTitlePositionAdjustment: tabBarLabelPositionAdjustment?.compactInline
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
const normalTabItemAppearance = {
|
|
473
|
+
tabBarItemTitleFontColor: inactiveTintColor ?? fontColor,
|
|
474
|
+
tabBarItemIconColor: inactiveTintColor
|
|
475
|
+
};
|
|
476
|
+
const selectedTabItemAppearance = {
|
|
477
|
+
tabBarItemTitleFontColor: activeTintColor,
|
|
478
|
+
tabBarItemIconColor: activeTintColor
|
|
479
|
+
};
|
|
480
|
+
const iconSize = Platform.OS === 'android' ? tabBarIconSize ?? ICON_SIZE : ICON_SIZE;
|
|
481
|
+
const getIcon = selected => {
|
|
482
|
+
if (typeof tabBarIcon === 'function') {
|
|
483
|
+
const result = tabBarIcon({
|
|
484
|
+
focused: selected,
|
|
485
|
+
size: iconSize,
|
|
486
|
+
color: selected ? activeTintColor : inactiveTintColor
|
|
487
|
+
});
|
|
488
|
+
if (/*#__PURE__*/React.isValidElement(result)) {
|
|
489
|
+
throw new Error(`Returning a React element from 'tabBarIcon' is not supported with native tab bar.`);
|
|
490
|
+
} else if (result && typeof result === 'object' && 'type' in result) {
|
|
491
|
+
return getPlatformIcon(result, direction, iconSize);
|
|
492
|
+
} else {
|
|
493
|
+
throw new Error(`The 'tabBarIcon' function must return an icon object (got ${typeof result}).`);
|
|
494
|
+
}
|
|
495
|
+
} else if (tabBarIcon != null) {
|
|
496
|
+
return getPlatformIcon(tabBarIcon, direction, iconSize);
|
|
497
|
+
}
|
|
498
|
+
return undefined;
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// An error is thrown for React Elements in `getIcon`
|
|
502
|
+
// So we only call it when we actually render a native tab bar
|
|
503
|
+
const icon = hasCustomTabBar ? undefined : getIcon(false);
|
|
504
|
+
const selectedIcon = !hasCustomTabBar && typeof tabBarIcon === 'function' ? getIcon(true) : icon;
|
|
505
|
+
return /*#__PURE__*/_jsx(Tabs.Screen, {
|
|
506
|
+
onWillAppear: () => onTransitionStart({
|
|
507
|
+
route
|
|
508
|
+
}),
|
|
509
|
+
onDidAppear: () => onTransitionEnd({
|
|
510
|
+
route
|
|
511
|
+
}),
|
|
512
|
+
screenKey: route.key,
|
|
513
|
+
tabBarItemAccessibilityLabel: tabBarAccessibilityLabel,
|
|
514
|
+
tabBarItemTestID: tabBarButtonTestID,
|
|
515
|
+
preventNativeSelection: tabBarSelectionEnabled === false,
|
|
516
|
+
badgeValue: tabBarBadge?.toString(),
|
|
517
|
+
title: tabTitle,
|
|
518
|
+
style: {
|
|
519
|
+
backgroundColor: colors.background
|
|
520
|
+
},
|
|
521
|
+
specialEffects: {
|
|
522
|
+
repeatedTabSelection: {
|
|
523
|
+
popToRoot: tabBarRepeatedPressBehavior?.popToTop !== false,
|
|
524
|
+
scrollToTop: tabBarRepeatedPressBehavior?.scrollToTop !== false
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
android: {
|
|
528
|
+
icon: icon?.android ?? icon?.shared,
|
|
529
|
+
selectedIcon: selectedIcon?.android ?? selectedIcon?.shared,
|
|
530
|
+
standardAppearance: {
|
|
531
|
+
tabBarBackgroundColor: tabBarBackgroundColor ?? backgroundColor,
|
|
532
|
+
tabBarItemRippleColor: rippleColor,
|
|
533
|
+
tabBarItemLabelVisibilityMode: labelVisibilityMode,
|
|
534
|
+
normal: normalTabItemAppearance,
|
|
535
|
+
selected: selectedTabItemAppearance,
|
|
536
|
+
tabBarItemIconSize: tabBarIconSize,
|
|
537
|
+
tabBarItemActiveIndicatorColor: activeIndicatorColor,
|
|
538
|
+
tabBarItemActiveIndicatorEnabled: activeIndicatorEnabled,
|
|
539
|
+
tabBarItemActiveIndicatorWidth: tabBarActiveIndicatorWidth,
|
|
540
|
+
tabBarItemActiveIndicatorHeight: tabBarActiveIndicatorHeight,
|
|
541
|
+
tabBarItemTitleFontFamily: fontFamily,
|
|
542
|
+
tabBarItemTitleFontWeight: fontWeight,
|
|
543
|
+
tabBarItemTitleSmallLabelFontSize: fontSize,
|
|
544
|
+
tabBarItemTitleLargeLabelFontSize: fontSize,
|
|
545
|
+
tabBarItemTitleFontStyle: fontStyle,
|
|
546
|
+
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
|
|
547
|
+
tabBarItemBadgeTextColor: badgeTextColor
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
ios: {
|
|
551
|
+
icon: icon?.ios ?? icon?.shared,
|
|
552
|
+
selectedIcon: selectedIcon?.ios ?? selectedIcon?.shared,
|
|
553
|
+
systemItem,
|
|
554
|
+
role: tabBarRole === 'search' ? tabBarRole : undefined,
|
|
555
|
+
automaticallyActivatesSearch: tabBarSearchAutoFocus,
|
|
556
|
+
scrollEdgeAppearance: {
|
|
557
|
+
tabBarBackgroundColor,
|
|
558
|
+
tabBarShadowColor,
|
|
559
|
+
tabBarBlurEffect,
|
|
560
|
+
...tabItemLayoutAppearance
|
|
561
|
+
},
|
|
562
|
+
standardAppearance: {
|
|
563
|
+
tabBarBackgroundColor,
|
|
564
|
+
tabBarShadowColor,
|
|
565
|
+
tabBarBlurEffect,
|
|
566
|
+
...tabItemLayoutAppearance
|
|
567
|
+
},
|
|
568
|
+
overrideScrollViewContentInsetAdjustmentBehavior
|
|
569
|
+
},
|
|
570
|
+
children: /*#__PURE__*/_jsx(Deferred, {
|
|
571
|
+
lazy: lazy,
|
|
572
|
+
visible: visible,
|
|
573
|
+
children: /*#__PURE__*/_jsx(ActivityView, {
|
|
574
|
+
mode: isFocused ? 'normal' : isActive ? 'inert' : 'paused',
|
|
575
|
+
visible:
|
|
576
|
+
// We don't need to hide the content since it's handled natively
|
|
577
|
+
// Hiding may also cause flash due to lag after native tab switch
|
|
578
|
+
// So we leave it always visible
|
|
579
|
+
true,
|
|
580
|
+
style: StyleSheet.absoluteFill,
|
|
581
|
+
children: /*#__PURE__*/_jsx(ScreenContent, {
|
|
582
|
+
isFocused: isFocused,
|
|
583
|
+
route: route,
|
|
584
|
+
navigation: descriptor.navigation,
|
|
585
|
+
options: options,
|
|
586
|
+
style: sceneStyle,
|
|
587
|
+
children: /*#__PURE__*/_jsx(AnimatedScreenContent, {
|
|
588
|
+
isFocused: isFocused,
|
|
589
|
+
children: /*#__PURE__*/_jsx(BottomTabBarHeightContext.Provider, {
|
|
590
|
+
value: 0,
|
|
591
|
+
children: render()
|
|
592
|
+
})
|
|
593
|
+
})
|
|
594
|
+
})
|
|
595
|
+
}, route.key)
|
|
596
|
+
})
|
|
597
|
+
});
|
|
598
|
+
});
|
|
509
599
|
function AnimatedScreenContent({
|
|
510
600
|
isFocused,
|
|
511
601
|
children
|
|
@@ -531,11 +621,15 @@ function AnimatedScreenContent({
|
|
|
531
621
|
children: children
|
|
532
622
|
});
|
|
533
623
|
}
|
|
534
|
-
function getPlatformIcon(icon) {
|
|
624
|
+
function getPlatformIcon(icon, direction, size) {
|
|
535
625
|
switch (icon.type) {
|
|
536
626
|
case 'sfSymbol':
|
|
537
627
|
return {
|
|
538
|
-
ios:
|
|
628
|
+
ios: {
|
|
629
|
+
type: 'sfSymbol',
|
|
630
|
+
name: icon.name,
|
|
631
|
+
renderingMode: icon.renderingMode === 'multicolor' ? 'original' : icon.renderingMode
|
|
632
|
+
},
|
|
539
633
|
android: undefined,
|
|
540
634
|
shared: undefined
|
|
541
635
|
};
|
|
@@ -548,26 +642,35 @@ function getPlatformIcon(icon) {
|
|
|
548
642
|
name: icon.name,
|
|
549
643
|
variant: icon.variant,
|
|
550
644
|
weight: icon.weight,
|
|
551
|
-
|
|
645
|
+
fill: icon.fill,
|
|
646
|
+
size,
|
|
647
|
+
direction
|
|
552
648
|
})
|
|
553
649
|
},
|
|
554
650
|
shared: undefined
|
|
555
651
|
};
|
|
556
652
|
case 'image':
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
653
|
+
{
|
|
654
|
+
// A `{ uri: 'name' }` source maps to an Android drawable by name.
|
|
655
|
+
const drawableName = typeof icon.source === 'object' && 'uri' in icon.source && icon.source.uri?.includes(':') === false ? icon.source.uri : undefined;
|
|
656
|
+
return {
|
|
657
|
+
ios: {
|
|
658
|
+
type: 'imageSource',
|
|
659
|
+
imageSource: icon.source,
|
|
660
|
+
renderingMode: icon.tinted === false ? 'original' : 'template'
|
|
661
|
+
},
|
|
662
|
+
android: drawableName != null ? {
|
|
663
|
+
type: 'drawableResource',
|
|
664
|
+
name: drawableName,
|
|
665
|
+
tinting: icon.tinted === false ? 'original' : 'tinted'
|
|
666
|
+
} : {
|
|
667
|
+
type: 'imageSource',
|
|
668
|
+
imageSource: icon.source,
|
|
669
|
+
tinting: icon.tinted === false ? 'original' : 'tinted'
|
|
670
|
+
},
|
|
671
|
+
shared: undefined
|
|
672
|
+
};
|
|
673
|
+
}
|
|
571
674
|
default:
|
|
572
675
|
{
|
|
573
676
|
const _exhaustiveCheck = icon;
|