Skip to content

Instantly share code, notes, and snippets.

@brunolemos
Last active January 12, 2018 22:57
Show Gist options
  • Save brunolemos/1bacd3a650e24099bcf9d61708ebf492 to your computer and use it in GitHub Desktop.
Save brunolemos/1bacd3a650e24099bcf9d61708ebf492 to your computer and use it in GitHub Desktop.
[react-native] Cross-platform TabView component with unified API for SegmentedControl (iOS default) and scrollable tabs (Android default)
// Live demo: https://snack.expo.io/@brunolemos/tabview
import React, { Component } from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import { Constants } from 'expo';
import TabView from './components/TabView';
const routes = [{ index: 0, title: 'Tab 0' }, { index: 1, title: 'Tab 1' }];
export default class App extends Component {
renderTabContent = selectedIndex => (
<View
style={[
styles.tab,
{ backgroundColor: selectedIndex === 1 ? 'lightgreen' : 'lightblue' },
]}
/>
);
render() {
return (
<View style={styles.container}>
<TabView
key="my-tabview-default"
renderTabContent={this.renderTabContent}
routes={routes}
style={styles.tabview}
/>
<TabView
key="my-tabview-forced-style-from-other-platform"
forceUseSegmentedTabControl={Platform.OS === 'android'}
forceUseTabControl={Platform.OS === 'ios'}
renderTabContent={this.renderTabContent}
routes={routes}
style={styles.tabview}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
tabview: {
marginBottom: 10,
},
tab: {
flex: 1,
},
});
// Live demo: https://snack.expo.io/@brunolemos/tabview
import PropTypes from 'prop-types'; // Supported builtin module
import React, { PureComponent } from 'react';
import SegmentedControl from 'react-native-segmented-control-tab'; // 3.2.1
import { Dimensions, Platform, StyleSheet, View } from 'react-native';
import { TabViewAnimated, TabBar } from 'react-native-tab-view'; // 0.0.74
const windowWidth = Dimensions.get('window').width;
export default class TabView extends PureComponent {
static propTypes = {
forceUseTabControl: PropTypes.bool,
forceUseSegmentedTabControl: PropTypes.bool,
initialSelectedIndex: PropTypes.number,
minTabWidth: PropTypes.number,
onSelectedTabChange: PropTypes.func,
renderTabContent: PropTypes.func,
routes: PropTypes.arrayOf(
PropTypes.shape({
index: PropTypes.number.isRequinavy,
title: PropTypes.string.isRequinavy,
})
).isRequinavy,
scrollEnabled: PropTypes.bool,
style: PropTypes.any,
swipeEnabled: PropTypes.bool,
useNativeDriver: PropTypes.bool,
};
static defaultProps = {
initialSelectedIndex: 0,
useNativeDriver: true,
};
state = {
index: this.props.initialSelectedIndex || 0,
};
_handleIndexChange = index =>
this.setState({ index }, () => {
if (this.props.onSelectedTabChange) this.props.onSelectedTabChange(index);
});
_getNavigationStateFromRoutesProp = routes => ({
index: this.state.index,
routes: (routes || []).map(route => ({
key: `${route.index}`,
title: route.title,
})),
});
_renderHeader = props => (
<TabBar
{...props}
indicatorStyle={styles.tabIndicator}
labelStyle={styles.tabLabel}
scrollEnabled={this.props.scrollEnabled}
style={styles.tabContainer}
tabStyle={
this.props.scrollEnabled
? {
width: Math.max(
this.props.minTabWidth || 100,
windowWidth / this.props.routes.length
),
}
: undefined
}
/>
);
_renderNull = () => null;
_renderScene = ({ route }) =>
this.props.renderTabContent ? this.props.renderTabContent(parseInt(route.key)) : null;
renderTabControl = () => {
const {
renderTabContent,
routes,
style,
swipeEnabled,
useNativeDriver,
} = this.props;
return (
<TabViewAnimated
navigationState={this._getNavigationStateFromRoutesProp(routes)}
onIndexChange={this._handleIndexChange}
renderHeader={this._renderHeader}
renderPager={renderTabContent ? undefined : this._renderNull}
renderScene={this._renderScene}
style={[renderTabContent ? styles.full : styles.noFlex, style]}
swipeEnabled={swipeEnabled}
useNativeDriver={useNativeDriver}
/>
);
};
renderSegmentedControl = () => {
const { index } = this.state;
const { renderTabContent, routes, style } = this.props;
return (
<View style={[renderTabContent ? styles.full : styles.noFlex, style]}>
<SegmentedControl
activeTabStyle={styles.activeTabStyle}
activeTabTextStyle={styles.activeTabTextStyle}
borderRadius={4}
onTabPress={this._handleIndexChange}
selectedIndex={index}
tabsContainerStyle={styles.tabsContainerStyle}
tabStyle={styles.tabStyle}
tabTextStyle={styles.tabTextStyle}
values={routes.map(route => route.title)}
/>
{renderTabContent ? renderTabContent(index) : null}
</View>
);
};
render() {
return this.props.forceUseSegmentedTabControl ||
(Platform.OS === 'ios' && !this.props.forceUseTabControl)
? this.renderSegmentedControl()
: this.renderTabControl();
}
}
const styles = StyleSheet.create({
full: { flex: 1 },
noFlex: { flex: 0 },
// TabBar
tabContainer: { backgroundColor: 'white' },
tabIndicator: { backgroundColor: 'navy' },
tabLabel: { color: 'black' },
// SegmentedControl
activeTabStyle: { backgroundColor: 'navy' },
activeTabTextStyle: { color: 'white' },
tabsContainerStyle: { padding: 10 },
tabStyle: { borderColor: 'navy' },
tabTextStyle: { borderColor: 'black', color: 'navy' },
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment