UNPKG

react-navigation-tabs

Version:

Tab Navigation components for React Navigation

569 lines (501 loc) 16.5 kB
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import * as React from 'react'; import { Animated, TouchableWithoutFeedback, StyleSheet, View, Keyboard, Platform } from 'react-native'; import { getStatusBarHeight, getBottomSpace } from 'react-native-iphone-x-helper'; import { ThemeColors, ThemeContext } from 'react-navigation'; import CrossFadeIcon from './CrossFadeIcon'; import withDimensions from '../utils/withDimensions'; const majorVersion = parseInt(Platform.Version, 10); const isIos = Platform.OS === 'ios'; const isIOS11 = majorVersion >= 11 && isIos; const DEFAULT_HEIGHT = 49; const COMPACT_HEIGHT = 29; const DEFAULT_MAX_TAB_ITEM_WIDTH = 125; const DEFAULT_KEYBOARD_ANIMATION_CONFIG = { show: { animation: 'timing', config: { useNativeDriver: true, duration: 150 } }, hide: { animation: 'timing', config: { useNativeDriver: true, duration: 100 } } }; class TouchableWithoutFeedbackWrapper extends React.Component { render() { const { // eslint-disable-next-line @typescript-eslint/no-unused-vars route, // eslint-disable-next-line @typescript-eslint/no-unused-vars focused, onPress, onLongPress, testID, accessibilityLabel, accessibilityRole, accessibilityState, accessibilityStates, ...rest } = this.props; return /*#__PURE__*/React.createElement(TouchableWithoutFeedback, { onPress: onPress, onLongPress: onLongPress, testID: testID, hitSlop: { left: 15, right: 15, top: 0, bottom: 5 }, accessibilityLabel: accessibilityLabel, accessibilityRole: accessibilityRole, accessibilityState: accessibilityState // @ts-expect-error: support older RN versions , accessibilityStates: accessibilityStates }, /*#__PURE__*/React.createElement(View, rest)); } } class TabBarBottom extends React.Component { constructor(...args) { super(...args); _defineProperty(this, "state", { layout: { height: 0, width: 0 }, keyboard: false, visible: new Animated.Value(1) }); _defineProperty(this, "keyboardDidShowSubscription", null); _defineProperty(this, "keyboardDidHideSubscription", null); _defineProperty(this, "context", void 0); _defineProperty(this, "_getKeyboardAnimationConfigByType", type => { const { keyboardHidesTabBarAnimationConfig } = this.props; const defaultKeyboardAnimationConfig = DEFAULT_KEYBOARD_ANIMATION_CONFIG[type]; const keyboardAnimationConfig = (keyboardHidesTabBarAnimationConfig === null || keyboardHidesTabBarAnimationConfig === void 0 ? void 0 : keyboardHidesTabBarAnimationConfig[type]) || defaultKeyboardAnimationConfig; // merge config only `timing` animation if (keyboardAnimationConfig && keyboardAnimationConfig.animation === 'timing') { return { ...defaultKeyboardAnimationConfig, ...keyboardAnimationConfig, config: { ...defaultKeyboardAnimationConfig.config, ...keyboardAnimationConfig.config } }; } return keyboardAnimationConfig; }); _defineProperty(this, "_handleKeyboardShow", () => { this.setState({ keyboard: true }, () => { const { animation, config } = this._getKeyboardAnimationConfigByType('show'); // @ts-expect-error Animated[animation](this.state.visible, { toValue: 0, ...config }).start(); }); }); _defineProperty(this, "_handleKeyboardHide", () => { const { animation, config } = this._getKeyboardAnimationConfigByType('hide'); // @ts-expect-error Animated[animation](this.state.visible, { toValue: 1, ...config }).start(() => { this.setState({ keyboard: false }); }); }); _defineProperty(this, "_handleLayout", e => { const { layout } = this.state; const { height, width } = e.nativeEvent.layout; if (height === layout.height && width === layout.width) { return; } this.setState({ layout: { height, width } }); }); _defineProperty(this, "_getActiveTintColor", () => { let { activeTintColor } = this.props; if (!activeTintColor) { return; } else if (typeof activeTintColor === 'string') { return activeTintColor; } return activeTintColor[this.context]; }); _defineProperty(this, "_getInactiveTintColor", () => { let { inactiveTintColor } = this.props; if (!inactiveTintColor) { return; } else if (typeof inactiveTintColor === 'string') { return inactiveTintColor; } return inactiveTintColor[this.context]; }); _defineProperty(this, "_getActiveBackgroundColor", () => { let { activeBackgroundColor } = this.props; if (!activeBackgroundColor) { return; } else if (typeof activeBackgroundColor === 'string') { return activeBackgroundColor; } return activeBackgroundColor[this.context]; }); _defineProperty(this, "_getInactiveBackgroundColor", () => { let { inactiveBackgroundColor } = this.props; if (!inactiveBackgroundColor) { return; } else if (typeof inactiveBackgroundColor === 'string') { return inactiveBackgroundColor; } return inactiveBackgroundColor[this.context]; }); _defineProperty(this, "_renderLabel", ({ route, focused }) => { const { labelStyle, showLabel, showIcon, allowFontScaling } = this.props; if (showLabel === false) { return null; } const activeTintColor = this._getActiveTintColor(); const inactiveTintColor = this._getInactiveTintColor(); const label = this.props.getLabelText({ route }); const tintColor = focused ? activeTintColor : inactiveTintColor; const horizontal = this._shouldUseHorizontalLabels(); if (typeof label === 'string') { return /*#__PURE__*/React.createElement(Animated.Text, { numberOfLines: 1, style: [styles.label, { color: tintColor }, showIcon && horizontal ? styles.labelBeside : styles.labelBeneath, labelStyle], allowFontScaling: allowFontScaling }, label); } if (typeof label === 'function') { return label({ focused, tintColor, orientation: horizontal ? 'horizontal' : 'vertical' }); } return label; }); _defineProperty(this, "_renderIcon", ({ route, focused }) => { const { renderIcon, showIcon, showLabel } = this.props; if (showIcon === false) { return null; } const horizontal = this._shouldUseHorizontalLabels(); const activeTintColor = this._getActiveTintColor(); const inactiveTintColor = this._getInactiveTintColor(); const activeOpacity = focused ? 1 : 0; const inactiveOpacity = focused ? 0 : 1; return /*#__PURE__*/React.createElement(CrossFadeIcon, { route: route, horizontal: horizontal, activeOpacity: activeOpacity, inactiveOpacity: inactiveOpacity, activeTintColor: activeTintColor, inactiveTintColor: inactiveTintColor, renderIcon: renderIcon, style: [styles.iconWithExplicitHeight, showLabel === false && !horizontal && styles.iconWithoutLabel, showLabel !== false && !horizontal && styles.iconWithLabel] }); }); _defineProperty(this, "_shouldUseHorizontalLabels", () => { const { routes } = this.props.navigation.state; const { isLandscape, dimensions, adaptive, tabStyle, labelPosition } = this.props; if (labelPosition) { let position; if (typeof labelPosition === 'string') { position = labelPosition; } else { position = labelPosition({ deviceOrientation: isLandscape ? 'horizontal' : 'vertical' }); } if (position) { return position === 'beside-icon'; } } if (!adaptive) { return false; } // @ts-ignore if (Platform.isPad) { let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH; const flattenedStyle = StyleSheet.flatten(tabStyle); if (flattenedStyle) { if (typeof flattenedStyle.width === 'number') { maxTabItemWidth = flattenedStyle.width; } else if (typeof flattenedStyle.maxWidth === 'number') { maxTabItemWidth = flattenedStyle.maxWidth; } } return routes.length * maxTabItemWidth <= dimensions.width; } else { return isLandscape; } }); } componentDidMount() { if (Platform.OS === 'ios') { this.keyboardDidShowSubscription = Keyboard.addListener('keyboardWillShow', this._handleKeyboardShow); this.keyboardDidHideSubscription = Keyboard.addListener('keyboardWillHide', this._handleKeyboardHide); } else { this.keyboardDidShowSubscription = Keyboard.addListener('keyboardDidShow', this._handleKeyboardShow); this.keyboardDidHideSubscription = Keyboard.addListener('keyboardDidHide', this._handleKeyboardHide); } } componentWillUnmount() { var _this$keyboardDidShow, _this$keyboardDidHide; (_this$keyboardDidShow = this.keyboardDidShowSubscription) === null || _this$keyboardDidShow === void 0 ? void 0 : _this$keyboardDidShow.remove(); (_this$keyboardDidHide = this.keyboardDidHideSubscription) === null || _this$keyboardDidHide === void 0 ? void 0 : _this$keyboardDidHide.remove(); } // @ts-ignore render() { const { navigation, keyboardHidesTabBar, onTabPress, onTabLongPress, isLandscape, safeAreaInset, style, tabStyle } = this.props; const { routes } = navigation.state; const isDark = this.context === 'dark'; const activeBackgroundColor = this._getActiveBackgroundColor(); const inactiveBackgroundColor = this._getInactiveBackgroundColor(); const { position, top, left = 0, bottom = 0, right = 0, margin, marginTop, marginLeft, marginBottom, marginRight, marginHorizontal, marginVertical, height, ...innerStyle } = StyleSheet.flatten(style || {}); const containerStyle = { position, top, left, bottom, right, margin, marginTop, marginLeft, marginBottom, marginRight, marginHorizontal, marginVertical }; const statusBarHeight = getStatusBarHeight(true); const horizontalInset = isLandscape ? statusBarHeight : 0; const insets = { bottom: typeof (safeAreaInset === null || safeAreaInset === void 0 ? void 0 : safeAreaInset.bottom) === 'number' ? safeAreaInset.bottom : (safeAreaInset === null || safeAreaInset === void 0 ? void 0 : safeAreaInset.bottom) === 'never' ? 0 : getBottomSpace(), left: typeof (safeAreaInset === null || safeAreaInset === void 0 ? void 0 : safeAreaInset.left) === 'number' ? safeAreaInset.left : (safeAreaInset === null || safeAreaInset === void 0 ? void 0 : safeAreaInset.left) === 'never' ? 0 : horizontalInset, right: typeof (safeAreaInset === null || safeAreaInset === void 0 ? void 0 : safeAreaInset.right) === 'number' ? safeAreaInset.right : (safeAreaInset === null || safeAreaInset === void 0 ? void 0 : safeAreaInset.right) === 'never' ? 0 : horizontalInset }; const tabBarStyle = [{ height: height != null ? typeof height === 'number' ? height + insets.bottom : height : // @ts-ignore: isPad exists in runtime but not available in type defs (this._shouldUseHorizontalLabels() && !Platform.isPad ? COMPACT_HEIGHT : DEFAULT_HEIGHT) + insets.bottom, paddingBottom: insets.bottom, paddingLeft: insets.left, paddingRight: insets.right }, styles.tabBar, isDark ? styles.tabBarDark : styles.tabBarLight, innerStyle]; return /*#__PURE__*/React.createElement(Animated.View, { style: [styles.container, containerStyle, keyboardHidesTabBar ? { // When the keyboard is shown, slide down the tab bar transform: [{ translateY: this.state.visible.interpolate({ inputRange: [0, 1], outputRange: [this.state.layout.height, 0] }) }], // Absolutely position the tab bar so that the content is below it // This is needed to avoid gap at bottom when the tab bar is hidden position: this.state.keyboard ? 'absolute' : position } : null], pointerEvents: keyboardHidesTabBar && this.state.keyboard ? 'none' : 'auto', onLayout: this._handleLayout }, /*#__PURE__*/React.createElement(View, { style: tabBarStyle }, routes.map((route, index) => { const focused = index === navigation.state.index; const scene = { route, focused }; const accessibilityLabel = this.props.getAccessibilityLabel({ route }); const accessibilityRole = this.props.getAccessibilityRole({ route }); const accessibilityStates = this.props.getAccessibilityStates(scene); const testID = this.props.getTestID({ route }); const backgroundColor = focused ? activeBackgroundColor : inactiveBackgroundColor; const ButtonComponent = this.props.getButtonComponent({ route }) || TouchableWithoutFeedbackWrapper; return /*#__PURE__*/React.createElement(ButtonComponent, { key: route.key, route: route, focused: focused, onPress: () => onTabPress({ route }), onLongPress: () => onTabLongPress({ route }), testID: testID, accessibilityLabel: accessibilityLabel, accessibilityRole: accessibilityRole, accessibilityStates: accessibilityStates, style: [styles.tab, { backgroundColor }, this._shouldUseHorizontalLabels() ? styles.tabLandscape : styles.tabPortrait, tabStyle] }, this._renderIcon(scene), this._renderLabel(scene)); }))); } } _defineProperty(TabBarBottom, "defaultProps", { keyboardHidesTabBar: true, keyboardHidesTabBarAnimationConfig: DEFAULT_KEYBOARD_ANIMATION_CONFIG, activeTintColor: { light: '#007AFF', dark: '#fff' }, inactiveTintColor: { light: '#8e8e93', dark: '#7f7f7f' }, activeBackgroundColor: 'transparent', inactiveBackgroundColor: 'transparent', showLabel: true, showIcon: true, allowFontScaling: true, adaptive: isIOS11, safeAreaInset: { bottom: 'always', top: 'never' } }); _defineProperty(TabBarBottom, "contextType", ThemeContext); const styles = StyleSheet.create({ tabBar: { borderTopWidth: StyleSheet.hairlineWidth, flexDirection: 'row' }, tabBarLight: { backgroundColor: ThemeColors.light.header, borderTopColor: ThemeColors.light.headerBorder }, tabBarDark: { backgroundColor: ThemeColors.dark.header, borderTopColor: ThemeColors.dark.headerBorder }, container: { elevation: 8 }, tab: { flex: 1, alignItems: isIos ? 'center' : 'stretch' }, tabPortrait: { justifyContent: 'flex-end', flexDirection: 'column' }, tabLandscape: { justifyContent: 'center', flexDirection: 'row' }, iconWithoutLabel: { flex: 1 }, iconWithLabel: { flex: 1 }, iconWithExplicitHeight: { // @ts-ignore height: Platform.isPad ? DEFAULT_HEIGHT : COMPACT_HEIGHT }, label: { textAlign: 'center', backgroundColor: 'transparent' }, labelBeneath: { fontSize: 11, marginBottom: 1.5 }, labelBeside: { fontSize: 12, marginLeft: 20 } }); export default withDimensions(TabBarBottom); //# sourceMappingURL=BottomTabBar.js.map