@react-navigation/bottom-tabs
Version:
Bottom tab navigator following iOS design guidelines
270 lines (269 loc) • 9.38 kB
JavaScript
"use strict";
import { getLabel, Lazy, SafeAreaProviderCompat, Screen as ScreenContent } from '@react-navigation/elements';
import { CommonActions, StackActions, useTheme } from '@react-navigation/native';
import Color from 'color';
import * as React from 'react';
import { Platform, PlatformColor } from 'react-native';
import { BottomTabs, BottomTabsScreen } from 'react-native-screens';
import { NativeScreen } from "./NativeScreen/NativeScreen.js";
import { jsx as _jsx } from "react/jsx-runtime";
export function NativeBottomTabView({
state,
navigation,
descriptors
}) {
const {
dark,
colors,
fonts
} = useTheme();
const focusedRouteKey = state.routes[state.index].key;
const previousRouteKeyRef = React.useRef(focusedRouteKey);
React.useEffect(() => {
const previousRouteKey = previousRouteKeyRef.current;
if (previousRouteKey !== focusedRouteKey && descriptors[previousRouteKey]?.options.popToTopOnBlur) {
const prevRoute = state.routes.find(route => route.key === previousRouteKey);
if (prevRoute?.state?.type === 'stack' && prevRoute.state.key) {
const popToTopAction = {
...StackActions.popToTop(),
target: prevRoute.state.key
};
navigation.dispatch(popToTopAction);
}
}
previousRouteKeyRef.current = focusedRouteKey;
}, [descriptors, focusedRouteKey, navigation, state.index, state.routes]);
const currentOptions = descriptors[state.routes[state.index].key]?.options;
const {
fontFamily = Platform.select({
ios: fonts.medium.fontFamily,
default: fonts.regular.fontFamily
}),
fontWeight = Platform.select({
ios: fonts.medium.fontWeight,
default: fonts.regular.fontWeight
}),
fontSize,
fontStyle
} = currentOptions.tabBarLabelStyle || {};
const activeTintColor = currentOptions.tabBarActiveTintColor ?? colors.primary;
const inactiveTintColor = currentOptions.tabBarInactiveTintColor ?? Platform.select({
ios: PlatformColor('label'),
default: colors.text
});
const activeIndicatorColor = currentOptions?.tabBarActiveIndicatorColor ?? typeof activeTintColor === 'string' ? Color(activeTintColor)?.alpha(0.1).string() : undefined;
const onTransitionStart = ({
closing,
route
}) => {
navigation.emit({
type: 'transitionStart',
data: {
closing
},
target: route.key
});
};
const onTransitionEnd = ({
closing,
route
}) => {
navigation.emit({
type: 'transitionEnd',
data: {
closing
},
target: route.key
});
};
return /*#__PURE__*/_jsx(SafeAreaProviderCompat, {
children: /*#__PURE__*/_jsx(BottomTabs, {
tabBarItemLabelVisibilityMode: currentOptions?.tabBarLabelVisibilityMode,
tabBarControllerMode: currentOptions?.tabBarControllerMode,
tabBarMinimizeBehavior: currentOptions?.tabBarMinimizeBehavior,
tabBarTintColor: activeTintColor,
tabBarItemIconColor: inactiveTintColor,
tabBarItemIconColorActive: activeTintColor,
tabBarItemTitleFontColor: inactiveTintColor,
tabBarItemTitleFontColorActive: activeTintColor,
tabBarItemTitleFontFamily: fontFamily,
tabBarItemTitleFontWeight: fontWeight,
tabBarItemTitleFontSize: fontSize,
tabBarItemTitleFontSizeActive: fontSize,
tabBarItemTitleFontStyle: fontStyle,
tabBarBackgroundColor: currentOptions.tabBarStyle?.backgroundColor ?? colors.card,
tabBarItemActiveIndicatorColor: activeIndicatorColor,
tabBarItemActiveIndicatorEnabled: currentOptions?.tabBarActiveIndicatorEnabled,
tabBarItemRippleColor: currentOptions?.tabBarRippleColor,
experimentalControlNavigationStateInJS: false,
onNativeFocusChange: e => {
const route = state.routes.find(route => route.key === e.nativeEvent.tabKey);
if (route) {
navigation.emit({
type: 'tabPress',
target: route.key
});
const isFocused = state.index === state.routes.findIndex(r => r.key === route.key);
if (!isFocused) {
navigation.dispatch({
...CommonActions.navigate(route.name, route.params),
target: state.key
});
}
}
},
children: state.routes.map((route, index) => {
const {
options,
render,
navigation
} = descriptors[route.key];
const isFocused = state.index === index;
const isPreloaded = state.preloadedRouteKeys.includes(route.key);
const {
title,
lazy = true,
tabBarLabel,
tabBarBadgeStyle,
tabBarIcon,
tabBarBadge,
tabBarSystemItem,
tabBarBlurEffect = dark ? 'systemMaterialDark' : 'systemMaterial',
tabBarStyle
} = options;
const {
backgroundColor: tabBarBackgroundColor,
shadowColor: tabBarShadowColor
} = tabBarStyle || {};
const tabTitle =
// On iOS, `systemItem` already provides a localized label
// So we should only use `tabBarLabel` if explicitly provided
Platform.OS === 'ios' && tabBarSystemItem != null ? tabBarLabel : getLabel({
label: tabBarLabel,
title
}, route.name);
const tabItemAppearance = {
tabBarItemTitleFontFamily: fontFamily,
tabBarItemTitleFontSize: fontSize,
tabBarItemTitleFontWeight: fontWeight,
tabBarItemTitleFontStyle: fontStyle
};
const badgeBackgroundColor = tabBarBadgeStyle?.backgroundColor ?? colors.notification;
const badgeTextColor = tabBarBadgeStyle?.color ?? (typeof badgeBackgroundColor === 'string' ? Color(badgeBackgroundColor).isLight() ? 'black' : 'white' : undefined);
const icon = typeof tabBarIcon === 'function' ? getPlatformIcon(tabBarIcon({
focused: false
})) : tabBarIcon != null ? getPlatformIcon(tabBarIcon) : undefined;
const selectedIcon = typeof tabBarIcon === 'function' ? getPlatformIcon(tabBarIcon({
focused: true
})) : undefined;
return /*#__PURE__*/_jsx(BottomTabsScreen, {
onWillDisappear: () => onTransitionStart({
closing: true,
route
}),
onWillAppear: () => onTransitionStart({
closing: false,
route
}),
onDidAppear: () => onTransitionEnd({
closing: false,
route
}),
onDidDisappear: () => onTransitionEnd({
closing: true,
route
}),
tabKey: route.key,
icon: icon,
selectedIcon: selectedIcon?.ios ?? selectedIcon?.shared,
tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
tabBarItemBadgeTextColor: badgeTextColor,
badgeValue: tabBarBadge?.toString(),
systemItem: tabBarSystemItem,
isFocused: isFocused,
title: tabTitle,
standardAppearance: {
tabBarBackgroundColor,
tabBarShadowColor,
tabBarBlurEffect,
stacked: {
normal: tabItemAppearance
},
inline: {
normal: tabItemAppearance
},
compactInline: {
normal: tabItemAppearance
}
},
children: /*#__PURE__*/_jsx(Lazy, {
enabled: lazy,
visible: isFocused || isPreloaded,
children: /*#__PURE__*/_jsx(ScreenWithHeader, {
isFocused: isFocused,
route: route,
navigation: navigation,
options: options,
children: render()
})
})
}, route.key);
})
})
});
}
function ScreenWithHeader({
isFocused,
route,
navigation,
options,
children
}) {
const {
headerTransparent,
header: renderCustomHeader,
headerShown = renderCustomHeader != null
} = options;
const hasNativeHeader = headerShown && renderCustomHeader == null;
const [wasNativeHeaderShown] = React.useState(hasNativeHeader);
React.useEffect(() => {
if (wasNativeHeaderShown !== hasNativeHeader) {
throw new Error(`Changing 'headerShown' or 'header' options dynamically is not supported when using native header.`);
}
}, [wasNativeHeaderShown, hasNativeHeader]);
if (hasNativeHeader) {
return /*#__PURE__*/_jsx(NativeScreen, {
route: route,
navigation: navigation,
options: options,
children: children
});
}
return /*#__PURE__*/_jsx(ScreenContent, {
focused: isFocused,
route: route,
navigation: navigation,
headerShown: headerShown,
headerTransparent: headerTransparent,
header: renderCustomHeader?.({
route,
navigation,
options
}),
children: children
});
}
function getPlatformIcon(icon) {
return {
ios: icon?.type === 'sfSymbol' ? icon : icon?.type === 'image' && icon.tinted !== false ? {
type: 'templateSource',
templateSource: icon.source
} : undefined,
android: icon?.type === 'drawableResource' ? icon : undefined,
shared: icon?.type === 'image' ? {
type: 'imageSource',
imageSource: icon.source
} : undefined
};
}
//# sourceMappingURL=NativeBottomTabView.native.js.map