react-native-router-flux-custom-tabs
Version:
React Native Router using Flux architecture this is my fork for testing
516 lines (490 loc) • 14.8 kB
JavaScript
/**
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
*
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
* all intellectual property and other proprietary rights, in and to the React
* Native CustomComponents software (the "Software"). Subject to your
* compliance with these terms, you are hereby granted a non-exclusive,
* worldwide, royalty-free copyright license to (1) use and copy the Software;
* and (2) reproduce and distribute the Software as part of your own software
* ("Your Software"). Facebook reserves all rights not expressly granted to
* you in this license agreement.
*
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
import React, {
PropTypes,
} from 'react';
import {
Platform,
Animated,
Image,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import Actions from './Actions';
import _drawerImage from './menu_burger.png';
import _backButtonImage from './back_chevron.png';
const styles = StyleSheet.create({
title: {
textAlign: 'center',
color: '#0A0A0A',
fontSize: 18,
width: 180,
alignSelf: 'center',
},
titleWrapper: {
marginTop: 10,
position: 'absolute',
...Platform.select({
ios: {
top: 20,
},
android: {
top: 5,
},
}),
left: 0,
right: 0,
},
header: {
backgroundColor: '#EFEFF2',
paddingTop: 0,
top: 0,
...Platform.select({
ios: {
height: 64,
},
android: {
height: 54,
},
}),
right: 0,
left: 0,
borderBottomWidth: 0.5,
borderBottomColor: '#828287',
position: 'absolute',
},
backButton: {
height: 37,
position: 'absolute',
...Platform.select({
ios: {
top: 22,
},
android: {
top: 10,
},
}),
left: 2,
padding: 8,
flexDirection: 'row',
},
rightButton: {
height: 37,
position: 'absolute',
...Platform.select({
ios: {
top: 22,
},
android: {
top: 10,
},
}),
right: 2,
padding: 8,
},
leftButton: {
height: 37,
position: 'absolute',
...Platform.select({
ios: {
top: 20,
},
android: {
top: 8,
},
}),
left: 2,
padding: 8,
},
barRightButtonText: {
color: 'rgb(0, 122, 255)',
textAlign: 'right',
fontSize: 17,
},
barBackButtonText: {
color: 'rgb(0, 122, 255)',
textAlign: 'left',
fontSize: 17,
paddingLeft: 6,
},
barLeftButtonText: {
color: 'rgb(0, 122, 255)',
textAlign: 'left',
fontSize: 17,
},
backButtonImage: {
width: 13,
height: 21,
},
defaultImageStyle: {
height: 24,
resizeMode: 'contain',
},
});
const propTypes = {
navigationState: PropTypes.object,
backButtonImage: Image.propTypes.source,
wrapBy: PropTypes.any,
component: PropTypes.any,
backButtonTextStyle: Text.propTypes.style,
leftButtonStyle: View.propTypes.style,
leftButtonIconStyle: Image.propTypes.style,
getTitle: PropTypes.func,
titleWrapperStyle: Text.propTypes.style,
titleStyle: Text.propTypes.style,
titleOpacity: PropTypes.number,
titleProps: PropTypes.any,
position: PropTypes.object,
navigationBarStyle: View.propTypes.style,
navigationBarBackgroundImage: Image.propTypes.source,
renderTitle: PropTypes.any,
};
const contextTypes = {
drawer: PropTypes.object,
};
const defaultProps = {
drawerImage: _drawerImage,
backButtonImage: _backButtonImage,
titleOpacity: 1,
};
class NavBar extends React.Component {
constructor(props) {
super(props);
this.renderRightButton = this.renderRightButton.bind(this);
this.renderBackButton = this.renderBackButton.bind(this);
this.renderLeftButton = this.renderLeftButton.bind(this);
this.renderTitle = this.renderTitle.bind(this);
}
renderBackButton() {
const state = this.props.navigationState;
const childState = state.children[state.index];
const BackButton = (childState.component && childState.component.backButton) || state.backButton
|| childState.backButton;
const textButtonStyle = [
styles.barBackButtonText,
this.props.backButtonTextStyle,
state.backButtonTextStyle,
childState.backButtonTextStyle,
];
const style = [
styles.backButton,
this.props.leftButtonStyle,
state.leftButtonStyle,
childState.leftButtonStyle,
];
if (state.index === 0 && (!state.parentIndex || state.parentIndex === 0)) {
return null;
}
if (BackButton) {
return (
<BackButton
testID="backNavButton"
textButtonStyle={textButtonStyle}
{...childState}
style={style}
/>
);
}
let buttonImage = childState.backButtonImage ||
state.backButtonImage || this.props.backButtonImage;
let onPress = childState.onBack || childState.component.onBack;
if (onPress) {
onPress = onPress.bind(null, state);
} else {
onPress = Actions.pop;
}
let text = childState.backTitle ?
<Text style={textButtonStyle}>
{childState.backTitle}
</Text>
: null;
return (
<TouchableOpacity
testID="backNavButton"
style={style}
onPress={onPress}
>
{buttonImage && !childState.hideBackImage &&
<Image
source={buttonImage}
style={[
styles.backButtonImage,
this.props.leftButtonIconStyle,
state.barButtonIconStyle,
state.leftButtonIconStyle,
childState.leftButtonIconStyle,
]}
/>
}
{text}
</TouchableOpacity>
);
}
renderRightButton(navProps) {
const self = this;
function tryRender(state, wrapBy) {
if (!state) {
return null;
}
const rightTitle = state.getRightTitle ? state.getRightTitle(navProps) : state.rightTitle;
const textStyle = [styles.barRightButtonText, self.props.rightButtonTextStyle,
state.rightButtonTextStyle];
const style = [styles.rightButton, self.props.rightButtonStyle, state.rightButtonStyle];
if (state.rightButton) {
let Button = state.rightButton;
if (wrapBy) {
Button = wrapBy(Button);
}
return (
<Button
{...self.props}
{...state}
key={'rightNavBarBtn'}
testID="rightNavButton"
style={style}
textButtonStyle={textStyle}
/>
);
}
if (state.onRight && (rightTitle || state.rightButtonImage)) {
const onPress = state.onRight.bind(null, state);
return (
<TouchableOpacity
key={'rightNavBarBtn'}
testID="rightNavButton"
style={style}
onPress={onPress}
>
{rightTitle &&
<Text style={textStyle}>
{rightTitle}
</Text>
}
{state.rightButtonImage &&
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'flex-end' }}>
<Image
source={state.rightButtonImage}
style={state.rightButtonIconStyle}
/>
</View>
}
</TouchableOpacity>
);
}
if ((!!state.onRight ^ !!(typeof(rightTitle) !== 'undefined'
|| typeof(state.rightButtonImage) !== 'undefined'))) {
console.warn(
`Both onRight and rightTitle/rightButtonImage
must be specified for the scene: ${state.name}`
);
}
return null;
}
return tryRender(this.props.component, this.props.wrapBy) || tryRender(this.props);
}
renderLeftButton(navProps) {
const self = this;
const drawer = this.context.drawer;
function tryRender(state, wrapBy) {
let onPress = state.onLeft;
let buttonImage = state.leftButtonImage;
let menuIcon = state.drawerIcon;
const style = [styles.leftButton, self.props.leftButtonStyle, state.leftButtonStyle];
const textStyle = [styles.barLeftButtonText, self.props.leftButtonTextStyle,
state.leftButtonTextStyle];
const leftButtonStyle = [styles.defaultImageStyle, state.leftButtonIconStyle];
const leftTitle = state.getLeftTitle ? state.getLeftTitle(navProps) : state.leftTitle;
if (state.leftButton) {
let Button = state.leftButton;
if (wrapBy) {
Button = wrapBy(Button);
}
return (
<Button
{...self.props}
{...state}
key={'leftNavBarBtn'}
testID="leftNavButton"
style={style}
textStyle={textStyle}
/>
);
}
if (!onPress && !!drawer && typeof drawer.toggle === 'function') {
buttonImage = state.drawerImage;
if (buttonImage || menuIcon) {
onPress = drawer.toggle;
}
if (!menuIcon) {
menuIcon = (
<Image
source={buttonImage}
style={leftButtonStyle}
/>
);
}
}
if (onPress && (leftTitle || buttonImage)) {
onPress = onPress.bind(null, state);
return (
<TouchableOpacity
key={'leftNavBarBtn'}
testID="leftNavButton"
style={style}
onPress={onPress}
>
{leftTitle &&
<Text style={textStyle}>
{leftTitle}
</Text>
}
{buttonImage &&
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'flex-start' }}>
{menuIcon || <Image
source={buttonImage}
style={state.leftButtonIconStyle || styles.defaultImageStyle}
/>
}
</View>
}
</TouchableOpacity>
);
}
if ((!!state.onLeft ^ !!(leftTitle || buttonImage))) {
console.warn(
`Both onLeft and leftTitle/leftButtonImage
must be specified for the scene: ${state.name}`
);
}
return null;
}
return tryRender(this.props.component, this.props.wrapBy) || tryRender(this.props);
}
renderTitle(childState, index:number) {
let title = this.props.getTitle ? this.props.getTitle(childState) : childState.title;
if (title === undefined && childState.component && childState.component.title) {
title = childState.component.title;
}
if (typeof(title) === 'function') {
title = title(childState);
}
return (
<Animated.View
key={childState.key}
style={[
styles.titleWrapper,
this.props.titleWrapperStyle,
]}
>
<Animated.Text
lineBreakMode="tail"
numberOfLines={1}
{...this.props.titleProps}
style={[
styles.title,
this.props.titleStyle,
this.props.navigationState.titleStyle,
childState.titleStyle,
{
opacity: this.props.position.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [0, this.props.titleOpacity, 0],
}),
left: this.props.position.interpolate({
inputRange: [index - 1, index + 1],
outputRange: [200, -200],
}),
right: this.props.position.interpolate({
inputRange: [index - 1, index + 1],
outputRange: [-200, 200],
}),
},
]}
>
{title}
</Animated.Text>
</Animated.View>
);
}
render() {
let state = this.props.navigationState;
let selected = state.children[state.index];
while (selected.hasOwnProperty('children')) {
state = selected;
selected = selected.children[selected.index];
}
const navProps = { ...this.props, ...selected };
const wrapByStyle = (component, wrapStyle) => {
if (!component) { return null; }
return (props) => <View style={wrapStyle}>{component(props)}</View>;
};
const leftButtonStyle = [styles.leftButton, { alignItems: 'flex-start' }];
const rightButtonStyle = [styles.rightButton, { alignItems: 'flex-end' }];
const renderLeftButton = wrapByStyle(selected.renderLeftButton, leftButtonStyle) ||
wrapByStyle(selected.component.renderLeftButton, leftButtonStyle) ||
this.renderLeftButton;
const renderRightButton = wrapByStyle(selected.renderRightButton, rightButtonStyle) ||
wrapByStyle(selected.component.renderRightButton, rightButtonStyle) ||
this.renderRightButton;
const renderBackButton = wrapByStyle(selected.renderBackButton, leftButtonStyle) ||
wrapByStyle(selected.component.renderBackButton, leftButtonStyle) ||
this.renderBackButton;
const renderTitle = selected.renderTitle ||
selected.component.renderTitle ||
this.props.renderTitle;
const navigationBarBackgroundImage = this.props.navigationBarBackgroundImage ||
state.navigationBarBackgroundImage;
const contents = (
<View>
{renderTitle ? renderTitle(navProps) : state.children.map(this.renderTitle, this)}
{renderBackButton(navProps) || renderLeftButton(navProps)}
{renderRightButton(navProps)}
</View>
);
return (
<Animated.View
style={[
styles.header,
this.props.navigationBarStyle,
state.navigationBarStyle,
selected.navigationBarStyle,
]}
>
{navigationBarBackgroundImage ? (
<Image source={navigationBarBackgroundImage}>
{contents}
</Image>
) : contents}
</Animated.View>
);
}
}
NavBar.propTypes = propTypes;
NavBar.contextTypes = contextTypes;
NavBar.defaultProps = defaultProps;
export default NavBar;