Skip to content
This repository has been archived by the owner on Feb 8, 2020. It is now read-only.

Commit

Permalink
feat: upgrade react-native-tab-view to 2.0
Browse files Browse the repository at this point in the history
BREAKING CHANGES:

- Animated nodes are not from `react-native-reanimated`, which means custom tab bars need to be updated
- Changed behaviour: `activeTintColor` and `inactiveTintColor` also controls opacity now
- Removed props: `animationsEnabled`, `optimizationsEnabled`
- Dropped support for React < 16.3, which means the minimum supported React Native version is 0.56

New features:

- Added prop: `lazyPlaceholderComponent`
  • Loading branch information
satya164 committed Aug 18, 2019
1 parent df52eb0 commit d8b4774
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 300 deletions.
18 changes: 13 additions & 5 deletions packages/bottom-tabs/src/navigators/createBottomTabNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { polyfill } from 'react-lifecycles-compat';

// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
Expand Down Expand Up @@ -43,6 +42,18 @@ class TabNavigationView extends React.PureComponent<Props, State> {
loaded: [this.props.navigation.state.index],
};

_getButtonComponent = ({ route }) => {
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;

if (options.tabBarButtonComponent) {
return options.tabBarButtonComponent;
}

return null;
};

_renderTabBar = () => {
const {
tabBarComponent: TabBarComponent = BottomTabBar,
Expand All @@ -51,7 +62,6 @@ class TabNavigationView extends React.PureComponent<Props, State> {
screenProps,
getLabelText,
getAccessibilityLabel,
getButtonComponent,
getTestID,
renderIcon,
onTabPress,
Expand All @@ -77,7 +87,7 @@ class TabNavigationView extends React.PureComponent<Props, State> {
onTabPress={onTabPress}
onTabLongPress={onTabLongPress}
getLabelText={getLabelText}
getButtonComponent={getButtonComponent}
getButtonComponent={this._getButtonComponent}
getAccessibilityLabel={getAccessibilityLabel}
getTestID={getTestID}
renderIcon={renderIcon}
Expand Down Expand Up @@ -126,8 +136,6 @@ class TabNavigationView extends React.PureComponent<Props, State> {
}
}

polyfill(TabNavigationView);

const styles = StyleSheet.create({
container: {
flex: 1,
Expand Down
245 changes: 57 additions & 188 deletions packages/bottom-tabs/src/navigators/createMaterialTopTabNavigator.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,44 @@
/* @flow */

import * as React from 'react';
import { View, Platform } from 'react-native';
import { polyfill } from 'react-lifecycles-compat';
import { TabView, PagerPan } from 'react-native-tab-view';
import { TabView } from 'react-native-tab-view';
import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
import createTabNavigator, {
type InjectedProps,
} from '../utils/createTabNavigator';
import MaterialTopTabBar, {
type TabBarOptions,
} from '../views/MaterialTopTabBar';
import ResourceSavingScene from '../views/ResourceSavingScene';

type Props = InjectedProps & {
animationEnabled?: boolean,
lazy?: boolean,
optimizationsEnabled?: boolean,
type Route = {
key: string,
routeName: string,
};

type Props = {|
...InjectedProps,
keyboardDismissMode?: 'none' | 'on-drag',
swipeEnabled?: boolean,
renderPager?: (props: *) => React.Node,
swipeDistanceThreshold?: number,
swipeVelocityThreshold?: number,
onSwipeStart?: () => mixed,
onSwipeEnd?: () => mixed,
initialLayout?: { width?: number, height?: number },
lazy?: boolean,
lazyPlaceholderComponent?: React.ComponentType<{ route: Route }>,
tabBarComponent?: React.ComponentType<*>,
tabBarOptions?: TabBarOptions,
tabBarPosition?: 'top' | 'bottom',
};

type State = {
index: number,
isSwiping: boolean,
loaded: Array<number>,
transitioningFromIndex: ?number,
};

class MaterialTabView extends React.PureComponent<Props, State> {
static defaultProps = {
// fix for https://github.com/react-native-community/react-native-tab-view/issues/312
initialLayout: Platform.select({
android: { width: 1, height: 0 },
}),
animationEnabled: true,
lazy: false,
optimizationsEnabled: false,
};

static getDerivedStateFromProps(nextProps, prevState) {
const { index } = nextProps.navigation.state;

if (prevState.index === index) {
return null;
}

return {
loaded: prevState.loaded.includes(index)
? prevState.loaded
: [...prevState.loaded, index],
index,
};
}
sceneContainerStyle?: ViewStyleProp,
style?: ViewStyleProp,
|};

state = {
index: 0,
isSwiping: false,
loaded: [this.props.navigation.state.index],
transitioningFromIndex: null,
};

_renderIcon = ({ focused, route, tintColor }) => {
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
class MaterialTabView extends React.PureComponent<Props> {
_renderLazyPlaceholder = props => {
const { lazyPlaceholderComponent: LazyPlaceholder } = this.props;

if (options.tabBarIcon) {
return typeof options.tabBarIcon === 'function'
? options.tabBarIcon({ tintColor, focused })
: options.tabBarIcon;
if (LazyPlaceholder != null) {
return <LazyPlaceholder {...props} />;
}

return null;
Expand All @@ -88,146 +55,62 @@ class MaterialTabView extends React.PureComponent<Props, State> {
options.tabBarVisible == null ? true : options.tabBarVisible;

const {
navigation,
getLabelText,
getAccessibilityLabel,
getTestID,
renderIcon,
onTabPress,
onTabLongPress,
tabBarComponent: TabBarComponent = MaterialTopTabBar,
tabBarPosition,
tabBarOptions,
screenProps,
} = this.props;

if (TabBarComponent === null || !tabBarVisible) {
return null;
}

return (
/* $FlowFixMe */
<TabBarComponent
{...tabBarOptions}
{...props}
tabBarPosition={tabBarPosition}
screenProps={this.props.screenProps}
navigation={this.props.navigation}
getLabelText={this.props.getLabelText}
getAccessibilityLabel={this.props.getAccessibilityLabel}
getTestID={this.props.getTestID}
renderIcon={this._renderIcon}
onTabPress={this.props.onTabPress}
onTabLongPress={this.props.onTabLongPress}
screenProps={screenProps}
navigation={navigation}
getLabelText={getLabelText}
getAccessibilityLabel={getAccessibilityLabel}
getTestID={getTestID}
renderIcon={renderIcon}
onTabPress={onTabPress}
onTabLongPress={onTabLongPress}
/>
);
};

_renderPanPager = props => <PagerPan {...props} />;

_handleAnimationEnd = () => {
const { lazy } = this.props;

if (lazy) {
this.setState({
transitioningFromIndex: null,
isSwiping: false,
});
}
};

_handleSwipeStart = () => {
const { navigation, lazy } = this.props;

if (lazy) {
this.setState({
isSwiping: true,
loaded: [
...new Set([
...this.state.loaded,
Math.max(navigation.state.index - 1, 0),
Math.min(
navigation.state.index + 1,
navigation.state.routes.length - 1
),
]),
],
});
}
};

_handleIndexChange = index => {
const { animationEnabled, navigation, onIndexChange, lazy } = this.props;

if (lazy && animationEnabled) {
this.setState({
transitioningFromIndex: navigation.state.index || 0,
});
}

onIndexChange(index);
};

_mustBeVisible = ({ index, focused }) => {
const { animationEnabled, navigation } = this.props;
const { isSwiping, transitioningFromIndex } = this.state;

if (isSwiping) {
const isSibling =
navigation.state.index === index - 1 ||
navigation.state.index === index + 1;

if (isSibling) {
return true;
}
}

// The previous tab should remain visible while transitioning
if (animationEnabled && transitioningFromIndex === index) {
return true;
}

return focused;
};

_renderScene = ({ route }) => {
const { renderScene, descriptors, lazy, optimizationsEnabled } = this.props;

if (lazy) {
const { loaded } = this.state;
const { routes } = this.props.navigation.state;
const index = routes.findIndex(({ key }) => key === route.key);
const { navigation } = descriptors[route.key];

const mustBeVisible = this._mustBeVisible({
index,
focused: navigation.isFocused(),
});

if (!loaded.includes(index) && !mustBeVisible) {
return <View />;
}

if (optimizationsEnabled) {
return (
<ResourceSavingScene isVisible={mustBeVisible}>
{renderScene({ route })}
</ResourceSavingScene>
);
}
}

return renderScene({ route });
};

render() {
const {
/* eslint-disable no-unused-vars */
getLabelText,
getAccessibilityLabel,
getTestID,
renderIcon,
onTabPress,
onTabLongPress,
screenProps,
lazyPlaceholderComponent,
tabBarComponent,
tabBarOptions,
/* eslint-enable no-unused-vars */
navigation,
animationEnabled,
// eslint-disable-next-line no-unused-vars
renderScene,
// eslint-disable-next-line no-unused-vars
onIndexChange,
descriptors,
...rest
} = this.props;

let renderPager = rest.renderPager;

const { state } = this.props.navigation;
const { state } = navigation;
const route = state.routes[state.index];
const { descriptors } = this.props;

const descriptor = descriptors[route.key];
const options = descriptor.options;

Expand All @@ -240,30 +123,16 @@ class MaterialTabView extends React.PureComponent<Props, State> {
swipeEnabled = swipeEnabled(state);
}

if (animationEnabled === false && swipeEnabled === false) {
renderPager = this._renderPanPager;
}

return (
<TabView
{...rest}
navigationState={navigation.state}
animationEnabled={animationEnabled}
swipeEnabled={swipeEnabled}
onAnimationEnd={this._handleAnimationEnd}
onIndexChange={this._handleIndexChange}
onSwipeStart={this._handleSwipeStart}
renderPager={renderPager}
renderTabBar={this._renderTabBar}
renderScene={
/* $FlowFixMe */
this._renderScene
}
renderLazyPlaceholder={this._renderLazyPlaceholder}
/>
);
}
}

polyfill(MaterialTabView);

export default createTabNavigator(MaterialTabView);
Loading

0 comments on commit d8b4774

Please sign in to comment.