mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-25 21:15:26 +08:00
feat: initial commit
This commit is contained in:
committed by
satyajit.happy
parent
7f0486a2af
commit
89934b93c0
3
packages/bottom-tabs/.babelrc
Normal file
3
packages/bottom-tabs/.babelrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["react-native"]
|
||||
}
|
||||
14
packages/bottom-tabs/src/index.js
Normal file
14
packages/bottom-tabs/src/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/* @flow */
|
||||
/* eslint-disable import/no-commonjs */
|
||||
|
||||
module.exports = {
|
||||
get createBottomTabNavigator() {
|
||||
return require('./navigators/createBottomTabNavigator').default;
|
||||
},
|
||||
get createMaterialTopTabNavigator() {
|
||||
return require('./navigators/createMaterialTopTabNavigator').default;
|
||||
},
|
||||
get createMaterialBottomTabNavigator() {
|
||||
return require('./navigators/createMaterialBottomTabNavigator').default;
|
||||
},
|
||||
};
|
||||
129
packages/bottom-tabs/src/navigators/createBottomTabNavigator.js
Normal file
129
packages/bottom-tabs/src/navigators/createBottomTabNavigator.js
Normal file
@@ -0,0 +1,129 @@
|
||||
/* @flow */
|
||||
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import createTabNavigator, {
|
||||
type InjectedProps,
|
||||
} from '../utils/createTabNavigator';
|
||||
import TabBarBottom, { type TabBarOptions } from '../views/TabBarBottom';
|
||||
|
||||
type Props = InjectedProps & {
|
||||
tabBarComponent?: React.ComponentType<*>,
|
||||
tabBarOptions?: TabBarOptions,
|
||||
};
|
||||
|
||||
type State = {
|
||||
loaded: number[],
|
||||
};
|
||||
|
||||
class TabNavigationView extends React.PureComponent<Props, State> {
|
||||
state = {
|
||||
loaded: [this.props.navigation.state.index],
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (
|
||||
nextProps.navigation.state.index !== this.props.navigation.state.index
|
||||
) {
|
||||
const { index } = nextProps.navigation.state;
|
||||
|
||||
this.setState(state => ({
|
||||
loaded: state.loaded.includes(index)
|
||||
? state.loaded
|
||||
: [...state.loaded, index],
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
_getLabel = ({ route, focused, tintColor }) => {
|
||||
const label = this.props.getLabelText({ route });
|
||||
|
||||
if (typeof label === 'function') {
|
||||
return label({ focused, tintColor });
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
_renderTabBar = () => {
|
||||
const {
|
||||
tabBarComponent: TabBarComponent = TabBarBottom,
|
||||
tabBarOptions,
|
||||
navigation,
|
||||
onIndexChange,
|
||||
screenProps,
|
||||
getLabelText,
|
||||
renderIcon,
|
||||
onTabPress,
|
||||
} = this.props;
|
||||
|
||||
const { descriptors } = this.props;
|
||||
const { state } = this.props.navigation;
|
||||
const route = state.routes[state.index];
|
||||
const descriptor = descriptors[route.key];
|
||||
const options = descriptor.options;
|
||||
|
||||
if (options.tabBarVisible === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TabBarComponent
|
||||
{...tabBarOptions}
|
||||
navigation={navigation}
|
||||
jumpToIndex={onIndexChange}
|
||||
screenProps={screenProps}
|
||||
onTabPress={onTabPress}
|
||||
getLabelText={getLabelText}
|
||||
renderIcon={renderIcon}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation, renderScene } = this.props;
|
||||
const { routes } = navigation.state;
|
||||
const { loaded } = this.state;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.pages}>
|
||||
{routes.map((route, index) => {
|
||||
if (!loaded.includes(index)) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
|
||||
const isFocused = navigation.state.index === index;
|
||||
|
||||
return (
|
||||
<View
|
||||
key={route.key}
|
||||
pointerEvents={isFocused ? 'auto' : 'none'}
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
{ opacity: isFocused ? 1 : 0 },
|
||||
]}
|
||||
>
|
||||
{renderScene({ route })}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
{this._renderTabBar()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
pages: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export default createTabNavigator(TabNavigationView);
|
||||
@@ -0,0 +1,40 @@
|
||||
/* @flow */
|
||||
|
||||
import * as React from 'react';
|
||||
import { BottomNavigation } from 'react-native-paper';
|
||||
import createTabNavigator, {
|
||||
type InjectedProps,
|
||||
} from '../utils/createTabNavigator';
|
||||
|
||||
type Props = InjectedProps & {
|
||||
activeTintColor?: string,
|
||||
};
|
||||
|
||||
class BottomNavigationView extends React.Component<Props> {
|
||||
_getColor = ({ route }) => {
|
||||
const { descriptors } = this.props;
|
||||
const descriptor = descriptors[route.key];
|
||||
const options = descriptor.options;
|
||||
|
||||
return options.tabBarColor;
|
||||
};
|
||||
|
||||
render() {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { activeTintColor, navigation, descriptors, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<BottomNavigation
|
||||
{...rest}
|
||||
navigationState={navigation.state}
|
||||
getColor={this._getColor}
|
||||
theme={
|
||||
/* $FlowFixMe */
|
||||
activeTintColor ? { colors: { primary: activeTintColor } } : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default createTabNavigator(BottomNavigationView);
|
||||
@@ -0,0 +1,169 @@
|
||||
/* @flow */
|
||||
|
||||
import * as React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { TabViewAnimated, TabViewPagerPan } from 'react-native-tab-view';
|
||||
import createTabNavigator, {
|
||||
type InjectedProps,
|
||||
} from '../utils/createTabNavigator';
|
||||
import TabBarTop, { type TabBarOptions } from '../views/TabBarTop';
|
||||
|
||||
type Props = InjectedProps & {
|
||||
animationEnabled?: boolean,
|
||||
swipeEnabled?: boolean,
|
||||
tabBarPosition?: 'top' | 'bottom',
|
||||
tabBarComponent?: React.ComponentType<*>,
|
||||
tabBarOptions?: TabBarOptions,
|
||||
};
|
||||
|
||||
class TabView extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
// fix for https://github.com/react-native-community/react-native-tab-view/issues/312
|
||||
initialLayout: Platform.select({
|
||||
android: { width: 1, height: 0 },
|
||||
}),
|
||||
};
|
||||
|
||||
_getLabel = ({ route, tintColor, focused }) => {
|
||||
const { 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, focused }) => {
|
||||
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 { 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;
|
||||
|
||||
const {
|
||||
tabBarComponent: TabBarComponent = TabBarTop,
|
||||
tabBarPosition,
|
||||
tabBarOptions,
|
||||
} = this.props;
|
||||
|
||||
if (TabBarComponent === null || !tabBarVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TabBarComponent
|
||||
{...tabBarOptions}
|
||||
{...props}
|
||||
tabBarPosition={tabBarPosition}
|
||||
screenProps={this.props.screenProps}
|
||||
navigation={this.props.navigation}
|
||||
getLabelText={this.props.getLabelText}
|
||||
getTestIDProps={this._getTestIDProps}
|
||||
renderIcon={this._renderIcon}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
_renderPanPager = props => <TabViewPagerPan {...props} />;
|
||||
|
||||
render() {
|
||||
const {
|
||||
navigation,
|
||||
tabBarPosition,
|
||||
animationEnabled,
|
||||
renderScene,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
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;
|
||||
|
||||
let swipeEnabled =
|
||||
options.swipeEnabled == null
|
||||
? this.props.swipeEnabled
|
||||
: options.swipeEnabled;
|
||||
|
||||
if (typeof swipeEnabled === 'function') {
|
||||
swipeEnabled = swipeEnabled(state);
|
||||
}
|
||||
|
||||
if (tabBarPosition === 'bottom') {
|
||||
renderFooter = this._renderTabBar;
|
||||
} else {
|
||||
renderHeader = this._renderTabBar;
|
||||
}
|
||||
|
||||
if (animationEnabled === false && swipeEnabled === false) {
|
||||
renderPager = this._renderPanPager;
|
||||
}
|
||||
|
||||
return (
|
||||
<TabViewAnimated
|
||||
{...rest}
|
||||
navigationState={navigation.state}
|
||||
animationEnabled={animationEnabled}
|
||||
swipeEnabled={swipeEnabled}
|
||||
renderScene={
|
||||
/* $FlowFixMe */
|
||||
renderScene
|
||||
}
|
||||
renderPager={renderPager}
|
||||
renderHeader={renderHeader}
|
||||
renderFooter={renderFooter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default createTabNavigator(TabView);
|
||||
133
packages/bottom-tabs/src/utils/createTabNavigator.js
Normal file
133
packages/bottom-tabs/src/utils/createTabNavigator.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/* @flow */
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
TabRouter,
|
||||
NavigationActions,
|
||||
createNavigator,
|
||||
createNavigationContainer,
|
||||
} from 'react-navigation';
|
||||
import SceneView from 'react-navigation/src/views/SceneView';
|
||||
|
||||
export type InjectedProps = {
|
||||
getLabelText: (props: { route: any }) => any,
|
||||
renderIcon: (props: {
|
||||
route: any,
|
||||
focused: boolean,
|
||||
tintColor: string,
|
||||
}) => React.Node,
|
||||
renderScene: (props: { route: any }) => ?React.Node,
|
||||
onIndexChange: (index: number) => any,
|
||||
onTabPress: (props: { route: any }) => mixed,
|
||||
navigation: any,
|
||||
descriptors: any,
|
||||
screenProps?: any,
|
||||
};
|
||||
|
||||
export default function createTabNavigator(TabView: React.ComponentType<*>) {
|
||||
class NavigationView extends React.Component<*> {
|
||||
_renderScene = ({ route }) => {
|
||||
const { screenProps, descriptors } = this.props;
|
||||
const descriptor = descriptors[route.key];
|
||||
const TabComponent = descriptor.getComponent();
|
||||
return (
|
||||
<SceneView
|
||||
screenProps={screenProps}
|
||||
navigation={descriptor.navigation}
|
||||
component={TabComponent}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
_renderIcon = ({ route, focused = true, tintColor }) => {
|
||||
const { descriptors } = this.props;
|
||||
const descriptor = descriptors[route.key];
|
||||
const options = descriptor.options;
|
||||
|
||||
if (options.tabBarIcon) {
|
||||
return typeof options.tabBarIcon === 'function'
|
||||
? options.tabBarIcon({ focused, tintColor })
|
||||
: options.tabBarIcon;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
_getLabelText = ({ route }) => {
|
||||
const { descriptors } = this.props;
|
||||
const descriptor = descriptors[route.key];
|
||||
const options = descriptor.options;
|
||||
|
||||
if (options.tabBarLabel) {
|
||||
return options.tabBarLabel;
|
||||
}
|
||||
|
||||
if (typeof options.title === 'string') {
|
||||
return options.title;
|
||||
}
|
||||
|
||||
return route.routeName;
|
||||
};
|
||||
|
||||
_handleOnTabPress = ({ route }) => {
|
||||
const { descriptors } = this.props;
|
||||
const descriptor = descriptors[route.key];
|
||||
const { navigation, options } = descriptor;
|
||||
|
||||
if (options.tabBarOnPress) {
|
||||
options.tabBarOnPress({
|
||||
navigation,
|
||||
});
|
||||
} else {
|
||||
const isFocused =
|
||||
this.props.navigation.state.index ===
|
||||
this.props.navigation.state.routes.indexOf(route);
|
||||
|
||||
if (isFocused) {
|
||||
if (route.hasOwnProperty('index') && route.index > 0) {
|
||||
navigation.dispatch(NavigationActions.popToTop({ key: route.key }));
|
||||
} else {
|
||||
// TODO: do something to scroll to top
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_handleIndexChange = index => {
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate(navigation.state.routes[index].routeName);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { descriptors, navigation, screenProps } = this.props;
|
||||
const { state } = navigation;
|
||||
const route = state.routes[state.index];
|
||||
const descriptor = descriptors[route.key];
|
||||
const options = {
|
||||
...this.props.navigationConfig,
|
||||
...descriptor.options,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabView
|
||||
{...options}
|
||||
getLabelText={this._getLabelText}
|
||||
renderIcon={this._renderIcon}
|
||||
renderScene={this._renderScene}
|
||||
onIndexChange={this._handleIndexChange}
|
||||
onTabPress={this._handleOnTabPress}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
screenProps={screenProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (routes, config = {}) => {
|
||||
const router = TabRouter(routes, config);
|
||||
const navigator = createNavigator(NavigationView, router, config);
|
||||
|
||||
return createNavigationContainer(navigator);
|
||||
};
|
||||
}
|
||||
37
packages/bottom-tabs/src/utils/withDimensions.js
Normal file
37
packages/bottom-tabs/src/utils/withDimensions.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import { Dimensions } from 'react-native';
|
||||
import hoistNonReactStatic from 'hoist-non-react-statics';
|
||||
|
||||
export const isOrientationLandscape = ({ width, height }) => width > height;
|
||||
|
||||
export default function withDimensions(WrappedComponent) {
|
||||
const { width, height } = Dimensions.get('window');
|
||||
|
||||
class EnhancedComponent extends React.Component {
|
||||
static displayName = `withDimensions(${WrappedComponent.displayName})`;
|
||||
|
||||
state = {
|
||||
dimensions: { width, height },
|
||||
isLandscape: isOrientationLandscape({ width, height }),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
Dimensions.addEventListener('change', this.handleOrientationChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Dimensions.removeEventListener('change', this.handleOrientationChange);
|
||||
}
|
||||
|
||||
handleOrientationChange = ({ window }) => {
|
||||
const isLandscape = isOrientationLandscape(window);
|
||||
this.setState({ isLandscape });
|
||||
};
|
||||
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} {...this.state} />;
|
||||
}
|
||||
}
|
||||
|
||||
return hoistNonReactStatic(EnhancedComponent, WrappedComponent);
|
||||
}
|
||||
286
packages/bottom-tabs/src/views/TabBarBottom.js
Normal file
286
packages/bottom-tabs/src/views/TabBarBottom.js
Normal file
@@ -0,0 +1,286 @@
|
||||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Animated,
|
||||
TouchableWithoutFeedback,
|
||||
StyleSheet,
|
||||
View,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
|
||||
import TabBarIcon from './TabBarIcon';
|
||||
import withDimensions from '../utils/withDimensions';
|
||||
|
||||
export type TabBarOptions = {
|
||||
activeTintColor?: string,
|
||||
inactiveTintColor?: string,
|
||||
activeBackgroundColor?: string,
|
||||
inactiveBackgroundColor?: string,
|
||||
allowFontScaling: boolean,
|
||||
showLabel: boolean,
|
||||
showIcon: boolean,
|
||||
labelStyle: any,
|
||||
tabStyle: any,
|
||||
adaptive?: boolean,
|
||||
style: any,
|
||||
};
|
||||
|
||||
type Props = TabBarOptions & {
|
||||
navigation: any,
|
||||
descriptors: any,
|
||||
jumpToIndex: any,
|
||||
onTabPress: any,
|
||||
getLabelText: ({ route: any }) => any,
|
||||
renderIcon: any,
|
||||
dimensions: { width: number, height: number },
|
||||
isLandscape: boolean,
|
||||
};
|
||||
|
||||
const majorVersion = parseInt(Platform.Version, 10);
|
||||
const isIos = Platform.OS === 'ios';
|
||||
const isIOS11 = majorVersion >= 11 && isIos;
|
||||
|
||||
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
|
||||
|
||||
class TabBarBottom extends React.Component<Props> {
|
||||
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 = ({ route, focused }) => {
|
||||
const {
|
||||
activeTintColor,
|
||||
inactiveTintColor,
|
||||
labelStyle,
|
||||
showLabel,
|
||||
showIcon,
|
||||
allowFontScaling,
|
||||
} = this.props;
|
||||
|
||||
if (showLabel === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const label = this.props.getLabelText({ route });
|
||||
const tintColor = focused ? activeTintColor : inactiveTintColor;
|
||||
|
||||
if (typeof label === 'string') {
|
||||
return (
|
||||
<Animated.Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
styles.label,
|
||||
{ color: tintColor },
|
||||
showIcon && this._shouldUseHorizontalLabels()
|
||||
? styles.labelBeside
|
||||
: styles.labelBeneath,
|
||||
styles.labelBeneath,
|
||||
labelStyle,
|
||||
]}
|
||||
allowFontScaling={allowFontScaling}
|
||||
>
|
||||
{label}
|
||||
</Animated.Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof label === 'function') {
|
||||
return label({ route, focused, tintColor });
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
_renderIcon = ({ route, focused }) => {
|
||||
const {
|
||||
navigation,
|
||||
activeTintColor,
|
||||
inactiveTintColor,
|
||||
renderIcon,
|
||||
showIcon,
|
||||
showLabel,
|
||||
} = this.props;
|
||||
if (showIcon === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const horizontal = this._shouldUseHorizontalLabels();
|
||||
|
||||
const activeOpacity = focused ? 1 : 0;
|
||||
const inactiveOpacity = focused ? 0 : 1;
|
||||
|
||||
return (
|
||||
<TabBarIcon
|
||||
route={route}
|
||||
navigation={navigation}
|
||||
activeOpacity={activeOpacity}
|
||||
inactiveOpacity={inactiveOpacity}
|
||||
activeTintColor={activeTintColor}
|
||||
inactiveTintColor={inactiveTintColor}
|
||||
renderIcon={renderIcon}
|
||||
style={[
|
||||
styles.iconWithExplicitHeight,
|
||||
styles.iconWithLabel,
|
||||
showLabel === false && !horizontal && styles.iconWithoutLabel,
|
||||
showLabel !== false && !horizontal && styles.iconWithLabel,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
_shouldUseHorizontalLabels = () => {
|
||||
const { routes } = this.props.navigation.state;
|
||||
const { isLandscape, dimensions, adaptive, tabStyle } = this.props;
|
||||
|
||||
if (!adaptive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
navigation,
|
||||
activeBackgroundColor,
|
||||
inactiveBackgroundColor,
|
||||
onTabPress,
|
||||
jumpToIndex,
|
||||
style,
|
||||
tabStyle,
|
||||
} = this.props;
|
||||
|
||||
const { routes } = navigation.state;
|
||||
|
||||
const tabBarStyle = [
|
||||
styles.tabBar,
|
||||
!Platform.isPad ? styles.tabBarCompact : styles.tabBarRegular,
|
||||
styles.tabBarRegular,
|
||||
style,
|
||||
];
|
||||
|
||||
return (
|
||||
<Animated.View style={tabBarStyle}>
|
||||
<SafeAreaView
|
||||
style={styles.container}
|
||||
forceInset={{ bottom: 'always', top: 'never' }}
|
||||
>
|
||||
{routes.map((route, index) => {
|
||||
const focused = index === navigation.state.index;
|
||||
const scene = { route, focused };
|
||||
|
||||
const backgroundColor = focused
|
||||
? activeBackgroundColor
|
||||
: inactiveBackgroundColor;
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
key={route.key}
|
||||
onPress={() => {
|
||||
jumpToIndex(index);
|
||||
onTabPress({ route });
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.tab,
|
||||
{ backgroundColor },
|
||||
this._shouldUseHorizontalLabels()
|
||||
? styles.tabLandscape
|
||||
: styles.tabPortrait,
|
||||
styles.tabPortrait,
|
||||
tabStyle,
|
||||
]}
|
||||
>
|
||||
{this._renderIcon(scene)}
|
||||
{this._renderLabel(scene)}
|
||||
</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)',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
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: {
|
||||
flex: 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 withDimensions(TabBarBottom);
|
||||
63
packages/bottom-tabs/src/views/TabBarIcon.js
Normal file
63
packages/bottom-tabs/src/views/TabBarIcon.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import { Animated, View, StyleSheet } from 'react-native';
|
||||
|
||||
type Props = {
|
||||
route: any,
|
||||
activeOpacity: any,
|
||||
inactiveOpacity: any,
|
||||
activeTintColor: any,
|
||||
inactiveTintColor: any,
|
||||
renderIcon: any,
|
||||
style: any,
|
||||
};
|
||||
|
||||
export default class TabBarIcon extends React.Component<Props> {
|
||||
render() {
|
||||
const {
|
||||
route,
|
||||
activeOpacity,
|
||||
inactiveOpacity,
|
||||
activeTintColor,
|
||||
inactiveTintColor,
|
||||
renderIcon,
|
||||
style,
|
||||
} = this.props;
|
||||
|
||||
// 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 }]}>
|
||||
{renderIcon({
|
||||
route,
|
||||
focused: true,
|
||||
tintColor: activeTintColor,
|
||||
})}
|
||||
</Animated.View>
|
||||
<Animated.View style={[styles.icon, { opacity: inactiveOpacity }]}>
|
||||
{renderIcon({
|
||||
route,
|
||||
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%',
|
||||
},
|
||||
});
|
||||
159
packages/bottom-tabs/src/views/TabBarTop.js
Normal file
159
packages/bottom-tabs/src/views/TabBarTop.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/* @flow */
|
||||
|
||||
import * as React from 'react';
|
||||
import { Animated, StyleSheet } from 'react-native';
|
||||
import { TabBar } from 'react-native-tab-view';
|
||||
import TabBarIcon from './TabBarIcon';
|
||||
|
||||
export type TabBarOptions = {
|
||||
activeTintColor?: string,
|
||||
inactiveTintColor?: string,
|
||||
showLabel?: boolean,
|
||||
showIcon?: boolean,
|
||||
upperCaseLabel?: boolean,
|
||||
labelStyle?: any,
|
||||
iconStyle?: any,
|
||||
allowFontScaling?: boolean,
|
||||
};
|
||||
|
||||
type Props = TabBarOptions & {
|
||||
position: Animated.Value,
|
||||
offsetX: Animated.Value,
|
||||
panX: Animated.Value,
|
||||
layout: any,
|
||||
navigation: any,
|
||||
renderIcon: (props: {
|
||||
route: any,
|
||||
focused: boolean,
|
||||
tintColor: string,
|
||||
}) => React.Node,
|
||||
getLabelText: (props: { route: any }) => any,
|
||||
useNativeDriver?: boolean,
|
||||
jumpToIndex: (index: number) => any,
|
||||
};
|
||||
|
||||
export default class TabBarTop extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
activeTintColor: '#fff',
|
||||
inactiveTintColor: '#fff',
|
||||
showIcon: false,
|
||||
showLabel: true,
|
||||
upperCaseLabel: true,
|
||||
allowFontScaling: true,
|
||||
};
|
||||
|
||||
_renderLabel = ({ route, index, focused }) => {
|
||||
const {
|
||||
position,
|
||||
navigation,
|
||||
activeTintColor,
|
||||
inactiveTintColor,
|
||||
showLabel,
|
||||
upperCaseLabel,
|
||||
labelStyle,
|
||||
allowFontScaling,
|
||||
} = this.props;
|
||||
|
||||
if (showLabel === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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 = focused ? activeTintColor : inactiveTintColor;
|
||||
const label = this.props.getLabelText({ route });
|
||||
|
||||
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({ focused, tintColor });
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
_renderIcon = ({ route, index }) => {
|
||||
const {
|
||||
position,
|
||||
navigation,
|
||||
activeTintColor,
|
||||
inactiveTintColor,
|
||||
renderIcon,
|
||||
showIcon,
|
||||
iconStyle,
|
||||
} = this.props;
|
||||
|
||||
if (showIcon === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Prepend '-1', so there are always at least 2 items in inputRange
|
||||
const inputRange = [-1, ...navigation.state.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)),
|
||||
});
|
||||
|
||||
return (
|
||||
<TabBarIcon
|
||||
route={route}
|
||||
navigation={navigation}
|
||||
activeOpacity={activeOpacity}
|
||||
inactiveOpacity={inactiveOpacity}
|
||||
activeTintColor={activeTintColor}
|
||||
inactiveTintColor={inactiveTintColor}
|
||||
renderIcon={renderIcon}
|
||||
style={[styles.icon, iconStyle]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
/* eslint-disable no-unused-vars */
|
||||
const { navigation, renderIcon, getLabelText, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
/* $FlowFixMe */
|
||||
<TabBar
|
||||
{...rest}
|
||||
navigationState={navigation.state}
|
||||
renderIcon={this._renderIcon}
|
||||
renderLabel={this._renderLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
icon: {
|
||||
height: 24,
|
||||
width: 24,
|
||||
},
|
||||
label: {
|
||||
textAlign: 'center',
|
||||
fontSize: 13,
|
||||
margin: 8,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user