Deprecate TabNavigator in favor of react-navigation-tabs

This commit is contained in:
Brent Vatne
2018-03-15 13:31:03 -07:00
parent 167cde5d17
commit d7710bcb87
9 changed files with 1799 additions and 1502 deletions

View File

@@ -37,7 +37,7 @@
"react-lifecycles-compat": "^1.0.2",
"react-native-drawer-layout-polyfill": "^1.3.2",
"react-native-safe-area-view": "^0.7.0",
"react-native-tab-view": "^0.0.74"
"react-navigation-deprecated-tab-navigator": "1.0.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",

View File

@@ -1,64 +0,0 @@
import React from 'react';
import { Platform } from 'react-native';
import createNavigator from './createNavigator';
import createNavigationContainer from '../createNavigationContainer';
import TabRouter from '../routers/TabRouter';
import TabView from '../views/TabView/TabView';
import TabBarTop from '../views/TabView/TabBarTop';
import TabBarBottom from '../views/TabView/TabBarBottom';
const TabNavigator = (routeConfigs, config = {}) => {
// Use the look native to the platform by default
const tabsConfig = { ...TabNavigator.Presets.Default, ...config };
const router = TabRouter(routeConfigs, tabsConfig);
const navigator = createNavigator(TabView, router, tabsConfig);
return createNavigationContainer(navigator);
};
const Presets = {
iOSBottomTabs: {
tabBarComponent: TabBarBottom,
tabBarPosition: 'bottom',
swipeEnabled: false,
animationEnabled: false,
initialLayout: undefined,
},
AndroidTopTabs: {
tabBarComponent: TabBarTop,
tabBarPosition: 'top',
swipeEnabled: true,
animationEnabled: true,
initialLayout: undefined,
},
};
/**
* Use these to get Android-style top tabs even on iOS or vice versa.
*
* Example:
* ```
* const HomeScreenTabNavigator = TabNavigator({
* Chat: {
* screen: ChatScreen,
* },
* ...
* }, {
* ...TabNavigator.Presets.AndroidTopTabs,
* tabBarOptions: {
* ...
* },
* });
*```
*/
TabNavigator.Presets = {
iOSBottomTabs: Presets.iOSBottomTabs,
AndroidTopTabs: Presets.AndroidTopTabs,
Default:
Platform.OS === 'ios' ? Presets.iOSBottomTabs : Presets.AndroidTopTabs,
};
export default TabNavigator;

View File

@@ -38,13 +38,17 @@ module.exports = {
return require('./navigators/createSwitchNavigator').default;
},
get createTabNavigator() {
return require('./navigators/createTabNavigator').default;
console.warn(
'TabNavigator is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
);
return require('react-navigation-deprecated-tab-navigator')
.createTabNavigator;
},
get TabNavigator() {
console.warn(
'The TabNavigator function name is deprecated, please use createTabNavigator instead'
'TabNavigator is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
);
return require('./navigators/createTabNavigator').default;
return require('react-navigation-deprecated-tab-navigator').default;
},
get createDrawerNavigator() {
return require('./navigators/createDrawerNavigator').default;
@@ -80,6 +84,12 @@ module.exports = {
get SafeAreaView() {
return require('react-native-safe-area-view').default;
},
get SceneView() {
return require('./views/SceneView').default;
},
get ResourceSavingSceneView() {
return require('./views/ResourceSavingSceneView').default;
},
// Header
get Header() {
@@ -102,13 +112,22 @@ module.exports = {
// TabView
get TabView() {
return require('./views/TabView/TabView').default;
console.warn(
'TabView is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
);
return require('react-navigation-deprecated-tab-navigator').TabView;
},
get TabBarTop() {
return require('./views/TabView/TabBarTop').default;
console.warn(
'TabBarTop is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
);
return require('react-navigation-deprecated-tab-navigator').TabBarTop;
},
get TabBarBottom() {
return require('./views/TabView/TabBarBottom').default;
console.warn(
'TabBarBottom is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
);
return require('react-navigation-deprecated-tab-navigator').TabBarBottom;
},
// SwitchView
@@ -123,4 +142,7 @@ module.exports = {
get withNavigationFocus() {
return require('./views/withNavigationFocus').default;
},
get withOrientation() {
return require('./views/withOrientation').default;
},
};

View File

@@ -1,347 +0,0 @@
import React from 'react';
import {
Animated,
TouchableWithoutFeedback,
StyleSheet,
View,
Platform,
Keyboard,
} from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import TabBarIcon from './TabBarIcon';
import NavigationActions from '../../NavigationActions';
import withOrientation from '../withOrientation';
const majorVersion = parseInt(Platform.Version, 10);
const isIos = Platform.OS === 'ios';
const isIOS11 = majorVersion >= 11 && isIos;
const defaultMaxTabBarItemWidth = 125;
class TabBarBottom extends React.PureComponent {
// See https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/UIKitUICatalog/UITabBar.html
static defaultProps = {
activeTintColor: '#3478f6', // Default active tint color in iOS 10
activeBackgroundColor: 'transparent',
inactiveTintColor: '#929292', // Default inactive tint color in iOS 10
inactiveBackgroundColor: 'transparent',
showLabel: true,
showIcon: true,
allowFontScaling: true,
adaptive: isIOS11,
};
_renderLabel = scene => {
const {
position,
navigation,
activeTintColor,
inactiveTintColor,
labelStyle,
showLabel,
showIcon,
isLandscape,
allowFontScaling,
} = this.props;
if (showLabel === false) {
return null;
}
const { index } = scene;
const { routes } = navigation.state;
// Prepend '-1', so there are always at least 2 items in inputRange
const inputRange = [-1, ...routes.map((x, i) => i)];
const outputRange = inputRange.map(
inputIndex => (inputIndex === index ? activeTintColor : inactiveTintColor)
);
const color = position.interpolate({
inputRange,
outputRange: outputRange,
});
const tintColor = scene.focused ? activeTintColor : inactiveTintColor;
const label = this.props.getLabel({ ...scene, tintColor });
if (typeof label === 'string') {
return (
<Animated.Text
numberOfLines={1}
style={[
styles.label,
{ color },
showIcon && this._shouldUseHorizontalTabs()
? styles.labelBeside
: styles.labelBeneath,
labelStyle,
]}
allowFontScaling={allowFontScaling}
>
{label}
</Animated.Text>
);
}
if (typeof label === 'function') {
return label({ ...scene, tintColor });
}
return label;
};
_renderIcon = scene => {
const {
position,
navigation,
activeTintColor,
inactiveTintColor,
renderIcon,
showIcon,
showLabel,
} = this.props;
if (showIcon === false) {
return null;
}
const horizontal = this._shouldUseHorizontalTabs();
return (
<TabBarIcon
position={position}
navigation={navigation}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
renderIcon={renderIcon}
scene={scene}
style={[
styles.iconWithExplicitHeight,
showLabel === false && !horizontal && styles.iconWithoutLabel,
showLabel !== false && !horizontal && styles.iconWithLabel,
]}
/>
);
};
_renderTestIDProps = scene => {
const testIDProps =
this.props.getTestIDProps && this.props.getTestIDProps(scene);
return testIDProps;
};
_tabItemMaxWidth() {
const { tabStyle, layout } = this.props;
let maxTabBarItemWidth;
const flattenedTabStyle = StyleSheet.flatten(tabStyle);
if (flattenedTabStyle) {
if (typeof flattenedTabStyle.width === 'number') {
maxTabBarItemWidth = flattenedTabStyle.width;
} else if (
typeof flattenedTabStyle.width === 'string' &&
flattenedTabStyle.endsWith('%')
) {
const width = parseFloat(flattenedTabStyle.width);
if (Number.isFinite(width)) {
maxTabBarItemWidth = layout.width * (width / 100);
}
} else if (typeof flattenedTabStyle.maxWidth === 'number') {
maxTabBarItemWidth = flattenedTabStyle.maxWidth;
} else if (
typeof flattenedTabStyle.maxWidth === 'string' &&
flattenedTabStyle.endsWith('%')
) {
const width = parseFloat(flattenedTabStyle.maxWidth);
if (Number.isFinite(width)) {
maxTabBarItemWidth = layout.width * (width / 100);
}
}
}
if (!maxTabBarItemWidth) {
maxTabBarItemWidth = defaultMaxTabBarItemWidth;
}
return maxTabBarItemWidth;
}
_shouldUseHorizontalTabs() {
const { routes } = this.props.navigation.state;
const { isLandscape, layout, adaptive, tabStyle } = this.props;
if (!adaptive) {
return false;
}
let tabBarWidth = layout.width;
if (tabBarWidth === 0) {
return Platform.isPad;
}
if (!Platform.isPad) {
return isLandscape;
} else {
const maxTabBarItemWidth = this._tabItemMaxWidth();
return routes.length * maxTabBarItemWidth <= tabBarWidth;
}
}
_handleTabPress = index => {
const { jumpToIndex, navigation } = this.props;
const currentIndex = navigation.state.index;
if (currentIndex === index) {
let childRoute = navigation.state.routes[index];
if (childRoute.hasOwnProperty('index') && childRoute.index > 0) {
navigation.dispatch(
NavigationActions.popToTop({ key: childRoute.key })
);
} else {
// TODO: do something to scroll to top
}
} else {
jumpToIndex(index);
}
};
render() {
const {
position,
navigation,
jumpToIndex,
getOnPress,
getTestIDProps,
activeBackgroundColor,
inactiveBackgroundColor,
style,
animateStyle,
tabStyle,
isLandscape,
} = this.props;
const { routes } = navigation.state;
const previousScene = routes[navigation.state.index];
// Prepend '-1', so there are always at least 2 items in inputRange
const inputRange = [-1, ...routes.map((x, i) => i)];
const tabBarStyle = [
styles.tabBar,
this._shouldUseHorizontalTabs() && !Platform.isPad
? styles.tabBarCompact
: styles.tabBarRegular,
style,
];
return (
<Animated.View style={animateStyle}>
<SafeAreaView
style={tabBarStyle}
forceInset={{ bottom: 'always', top: 'never' }}
>
{routes.map((route, index) => {
const focused = index === navigation.state.index;
const scene = { route, index, focused };
const onPress = getOnPress(previousScene, scene);
const outputRange = inputRange.map(
inputIndex =>
inputIndex === index
? activeBackgroundColor
: inactiveBackgroundColor
);
const backgroundColor = position.interpolate({
inputRange,
outputRange: outputRange,
});
const justifyContent = this.props.showIcon ? 'flex-end' : 'center';
const extraProps = this._renderTestIDProps(scene) || {};
const { testID, accessibilityLabel } = extraProps;
return (
<TouchableWithoutFeedback
key={route.key}
testID={testID}
accessibilityLabel={accessibilityLabel}
onPress={() =>
onPress
? onPress({
previousScene,
scene,
jumpToIndex,
defaultHandler: this._handleTabPress,
})
: this._handleTabPress(index)
}
>
<Animated.View style={[styles.tab, { backgroundColor }]}>
<View
style={[
styles.tab,
this._shouldUseHorizontalTabs()
? styles.tabLandscape
: styles.tabPortrait,
tabStyle,
]}
>
{this._renderIcon(scene)}
{this._renderLabel(scene)}
</View>
</Animated.View>
</TouchableWithoutFeedback>
);
})}
</SafeAreaView>
</Animated.View>
);
}
}
const DEFAULT_HEIGHT = 49;
const COMPACT_HEIGHT = 29;
const styles = StyleSheet.create({
tabBar: {
backgroundColor: '#F7F7F7', // Default background color in iOS 10
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: 'rgba(0, 0, 0, .3)',
flexDirection: 'row',
},
tabBarCompact: {
height: COMPACT_HEIGHT,
},
tabBarRegular: {
height: DEFAULT_HEIGHT,
},
tab: {
flex: 1,
alignItems: isIos ? 'center' : 'stretch',
},
tabPortrait: {
justifyContent: 'flex-end',
flexDirection: 'column',
},
tabLandscape: {
justifyContent: 'center',
flexDirection: 'row',
},
iconWithoutLabel: {
flex: 1,
},
iconWithLabel: {
flexGrow: 1,
},
iconWithExplicitHeight: {
height: Platform.isPad ? DEFAULT_HEIGHT : COMPACT_HEIGHT,
},
label: {
textAlign: 'center',
backgroundColor: 'transparent',
},
labelBeneath: {
fontSize: 10,
marginBottom: 1.5,
},
labelBeside: {
fontSize: 13,
marginLeft: 20,
},
});
export default withOrientation(TabBarBottom);

View File

@@ -1,64 +0,0 @@
import React from 'react';
import { Animated, View, StyleSheet } from 'react-native';
export default class TabBarIcon extends React.PureComponent {
render() {
const {
position,
scene,
navigation,
activeTintColor,
inactiveTintColor,
style,
} = this.props;
const { route, index } = scene;
const { routes } = navigation.state;
// Prepend '-1', so there are always at least 2 items in inputRange
const inputRange = [-1, ...routes.map((x, i) => i)];
const activeOpacity = position.interpolate({
inputRange,
outputRange: inputRange.map(i => (i === index ? 1 : 0)),
});
const inactiveOpacity = position.interpolate({
inputRange,
outputRange: inputRange.map(i => (i === index ? 0 : 1)),
});
// We render the icon twice at the same position on top of each other:
// active and inactive one, so we can fade between them.
return (
<View style={style}>
<Animated.View style={[styles.icon, { opacity: activeOpacity }]}>
{this.props.renderIcon({
route,
index,
focused: true,
tintColor: activeTintColor,
})}
</Animated.View>
<Animated.View style={[styles.icon, { opacity: inactiveOpacity }]}>
{this.props.renderIcon({
route,
index,
focused: false,
tintColor: inactiveTintColor,
})}
</Animated.View>
</View>
);
}
}
const styles = StyleSheet.create({
icon: {
// We render the icon twice at the same position on top of each other:
// active and inactive one, so we can fade between them:
// Cover the whole iconContainer:
position: 'absolute',
alignSelf: 'center',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
width: '100%',
},
});

View File

@@ -1,135 +0,0 @@
import React from 'react';
import { Animated, StyleSheet } from 'react-native';
import { TabBar } from 'react-native-tab-view';
import TabBarIcon from './TabBarIcon';
export default class TabBarTop extends React.PureComponent {
static defaultProps = {
activeTintColor: '#fff',
inactiveTintColor: '#fff',
showIcon: false,
showLabel: true,
upperCaseLabel: true,
allowFontScaling: true,
};
_renderLabel = scene => {
const {
position,
tabBarPosition,
navigation,
activeTintColor,
inactiveTintColor,
showLabel,
upperCaseLabel,
labelStyle,
allowFontScaling,
} = this.props;
if (showLabel === false) {
return null;
}
const { index } = scene;
const { routes } = navigation.state;
// Prepend '-1', so there are always at least 2 items in inputRange
const inputRange = [-1, ...routes.map((x, i) => i)];
const outputRange = inputRange.map(
inputIndex => (inputIndex === index ? activeTintColor : inactiveTintColor)
);
const color = position.interpolate({
inputRange,
outputRange: outputRange,
});
const tintColor = scene.focused ? activeTintColor : inactiveTintColor;
const label = this.props.getLabel({ ...scene, tintColor });
if (typeof label === 'string') {
return (
<Animated.Text
style={[styles.label, { color }, labelStyle]}
allowFontScaling={allowFontScaling}
>
{upperCaseLabel ? label.toUpperCase() : label}
</Animated.Text>
);
}
if (typeof label === 'function') {
return label({ ...scene, tintColor });
}
return label;
};
_renderIcon = scene => {
const {
position,
navigation,
activeTintColor,
inactiveTintColor,
renderIcon,
showIcon,
iconStyle,
} = this.props;
if (showIcon === false) {
return null;
}
return (
<TabBarIcon
position={position}
navigation={navigation}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
renderIcon={renderIcon}
scene={scene}
style={[styles.icon, iconStyle]}
/>
);
};
_handleOnPress = scene => {
const { getOnPress, jumpToIndex, navigation } = this.props;
const previousScene = navigation.state.routes[navigation.state.index];
const onPress = getOnPress(previousScene, scene);
if (onPress) {
// To maintain the same API as `TabbarBottom`, we pass in a `defaultHandler`
// even though I don't believe in this case it should be any different
// than `jumpToIndex`.
onPress({
previousScene,
scene,
jumpToIndex,
defaultHandler: jumpToIndex,
});
} else {
jumpToIndex(scene.index);
}
};
render() {
// TODO: Define full proptypes
const props = this.props;
return (
<TabBar
{...props}
onTabPress={this._handleOnPress}
jumpToIndex={() => {}}
renderIcon={this._renderIcon}
renderLabel={this._renderLabel}
/>
);
}
}
const styles = StyleSheet.create({
icon: {
height: 24,
width: 24,
},
label: {
textAlign: 'center',
fontSize: 13,
margin: 8,
backgroundColor: 'transparent',
},
});

View File

@@ -1,198 +0,0 @@
import React from 'react';
import { View, StyleSheet, Platform } from 'react-native';
import { TabViewAnimated, TabViewPagerPan } from 'react-native-tab-view';
import ResourceSavingSceneView from '../ResourceSavingSceneView';
class TabView extends React.PureComponent {
static defaultProps = {
lazy: true,
removedClippedSubviews: true,
// fix for https://github.com/react-native-community/react-native-tab-view/issues/312
initialLayout: Platform.select({
android: { width: 1, height: 0 },
}),
};
_handlePageChanged = index => {
const { navigation } = this.props;
navigation.navigate(navigation.state.routes[index].routeName);
};
_renderScene = ({ route }) => {
const { screenProps, navigation, descriptors } = this.props;
const {
lazy,
removeClippedSubviews,
animationEnabled,
swipeEnabled,
} = this.props.navigationConfig;
const descriptor = descriptors[route.key];
const focusedIndex = navigation.state.index;
const focusedKey = navigation.state.routes[focusedIndex].key;
const key = route.key;
const TabComponent = descriptor.getComponent();
return (
<ResourceSavingSceneView
lazy={lazy}
isFocused={focusedKey === key}
removeClippedSubViews={removeClippedSubviews}
animationEnabled={animationEnabled}
swipeEnabled={swipeEnabled}
screenProps={screenProps}
component={TabComponent}
navigation={navigation}
childNavigation={descriptor.navigation}
/>
);
};
_getLabel = ({ route, tintColor, focused }) => {
const { screenProps, descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
if (options.tabBarLabel) {
return typeof options.tabBarLabel === 'function'
? options.tabBarLabel({ tintColor, focused })
: options.tabBarLabel;
}
if (typeof options.title === 'string') {
return options.title;
}
return route.routeName;
};
_getOnPress = (previousScene, { route }) => {
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
return options.tabBarOnPress;
};
_getTestIDProps = ({ route }) => {
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
return typeof options.tabBarTestIDProps === 'function'
? options.tabBarTestIDProps({ focused })
: options.tabBarTestIDProps;
};
_renderIcon = ({ focused, route, tintColor }) => {
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
if (options.tabBarIcon) {
return typeof options.tabBarIcon === 'function'
? options.tabBarIcon({ tintColor, focused })
: options.tabBarIcon;
}
return null;
};
_renderTabBar = props => {
const {
tabBarOptions,
tabBarComponent: TabBarComponent,
animationEnabled,
tabBarPosition,
} = this.props.navigationConfig;
if (typeof TabBarComponent === 'undefined') {
return null;
}
return (
<TabBarComponent
{...props}
{...tabBarOptions}
tabBarPosition={tabBarPosition}
screenProps={this.props.screenProps}
navigation={this.props.navigation}
getLabel={this._getLabel}
getTestIDProps={this._getTestIDProps}
getOnPress={this._getOnPress}
renderIcon={this._renderIcon}
animationEnabled={animationEnabled}
/>
);
};
_renderPager = props => <TabViewPagerPan {...props} />;
render() {
const {
tabBarComponent,
tabBarPosition,
animationEnabled,
configureTransition,
initialLayout,
} = this.props.navigationConfig;
let renderHeader;
let renderFooter;
let renderPager;
const { state } = this.props.navigation;
const route = state.routes[state.index];
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
const tabBarVisible =
options.tabBarVisible == null ? true : options.tabBarVisible;
let swipeEnabled =
options.swipeEnabled == null
? this.props.navigationConfig.swipeEnabled
: options.swipeEnabled;
if (typeof swipeEnabled === 'function') {
swipeEnabled = swipeEnabled(state);
}
if (tabBarComponent !== undefined && tabBarVisible) {
if (tabBarPosition === 'bottom') {
renderFooter = this._renderTabBar;
} else {
renderHeader = this._renderTabBar;
}
}
if (
(animationEnabled === false && swipeEnabled === false) ||
typeof configureTransition === 'function'
) {
renderPager = this._renderPager;
}
const props = {
initialLayout,
animationEnabled,
configureTransition,
swipeEnabled,
renderPager,
renderHeader,
renderFooter,
renderScene: this._renderScene,
onIndexChange: this._handlePageChanged,
navigationState: this.props.navigation.state,
style: styles.container,
};
return <TabViewAnimated {...props} />;
}
}
export default TabView;
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

File diff suppressed because it is too large Load Diff