UNPKG

react-native-fancy-navigation-bar

Version:
309 lines (289 loc) 9.26 kB
import React from 'react'; import {View, Text, TouchableOpacity, Image, Keyboard, Platform, StyleSheet, StatusBar} from 'react-native'; import {isIphoneX} from 'react-native-iphone-x-helper'; import PropTypes from 'prop-types'; /** * Status bar height for different device type. * @type {number} */ export const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? isIphoneX() ? 44 : 20 : StatusBar.currentHeight; /** * Navigation bar height for different device type. * @type {number} */ export const NAVBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 44; /** * Total height (include status bar and navigation bar) for different device type. * @type {number} */ export const TOTALBAR_HEIGHT = NAVBAR_HEIGHT + STATUSBAR_HEIGHT; /** * Goback button identifier. * @type {string} */ export const GOBACK_BUTTON = '__gobackbutton__'; /** * Set global custom style. * @param key a string which is also a key of 'styles' object below * @param style an object to override the default style */ export const setCustomStyle = (key, style) => { if (styles[key]) { custom.style[key] = style; } else { console.error('Navigation bar global style key must be one of [ ' + Object.keys(styles).join(' ') + ' ]'); } }; /** * Set global goback function. * So you donot need to pass 'navigation' property each time. * @param func a function without any params */ export const setGlobalGobackFunc = (func) => { custom.gobackFunc = func; }; /** * Set global goback image. * @param image a local image of url */ export const setGlobalGobackImage = (image) => { if (typeof image === 'number') { custom.gobackImage = image; } else if (typeof image === 'string') { custom.gobackImage = {uri: image}; } else { console.error('Global goback image is wrong'); } }; const custom = { style: {}, gobackImage: require('./image/nav_back.png'), gobackFunc: null, }; /** * Navigation Bar. */ export default class NaviBar extends React.Component { static propTypes = { title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), titleCenter: PropTypes.bool, hasSeperatorLine: PropTypes.bool, leftElement: PropTypes.oneOfType([ PropTypes.string, PropTypes.element, PropTypes.array ]), rightElement: PropTypes.oneOfType([ PropTypes.string, PropTypes.element, PropTypes.array ]), onLeft: PropTypes.func, onRight: PropTypes.func, autoCloseKeyboard: PropTypes.bool, navigation: PropTypes.object, lockEnabled: PropTypes.bool, style: PropTypes.any, }; static defaultProps = { title: '', titleCenter: true, hasSeperatorLine: true, leftElement: GOBACK_BUTTON, rightElement: null, autoCloseKeyboard: true, navigation: null, lockEnabled: true, style: {}, }; constructor(props) { super(props); this.locks = {}; this.state = { Left: 0, Right: 0, }; } _combineStyle = (key, innerStyle = undefined) => { return [styles[key], innerStyle, custom.style[key], this.props.style[key]]; }; _clickButton = (clicktype, identifier, index) => { const lockKey = clicktype + index; const lockEnabled = this.props.lockEnabled; if (lockEnabled && this.locks[lockKey]) { return; } if (lockEnabled) { this.locks[lockKey] = true; } // Dismiss Keyboard this.props.autoCloseKeyboard && Keyboard.dismiss(); // General Button, use 'onLeft' or 'onRight' in this.props const clickKey = 'on' + clicktype; const doDefaultAction = this.props[clickKey] && this.props[clickKey](index); // Goback Button, use global action or navigation's goBack if (identifier === GOBACK_BUTTON && doDefaultAction !== false) { if (this.props.navigation) { this.props.navigation.goBack(); } else { custom.gobackFunc && custom.gobackFunc(); } } if (lockEnabled) { this.locks[lockKey] = false; } }; _renderButton = (type, item, index) => { const func = this._clickButton.bind(this, type, item, index); const specStyle = {marginHorizontal: 5, ['margin' + type]: 0}; const buttonViewStyle = this._combineStyle('buttonView', specStyle); if (item === GOBACK_BUTTON) { return ( <TouchableOpacity key={index} onPress={func} style={this._combineStyle('gobackView', specStyle)}> <Image source={custom.gobackImage} style={this._combineStyle('gobackImage')} /> </TouchableOpacity> ); } else if (typeof item === 'string') { return ( <TouchableOpacity key={index} onPress={func} style={buttonViewStyle}> <Text style={this._combineStyle('buttonText')}> {item} </Text> </TouchableOpacity> ); } else { return ( <TouchableOpacity key={index} onPress={func} style={buttonViewStyle}> {item} </TouchableOpacity> ); } }; _renderButtons = (type) => { const lowerType = type.toLowerCase(); const elementKey = lowerType + 'Element'; let element = this.props[elementKey]; if (!Array.isArray(element)) { element = [element]; } let extra; const viewStyleKey = lowerType + 'View'; if (type === 'Left') { extra = element.length > 0 && element[0] === GOBACK_BUTTON ? 0 : 8; } else { extra = element.length > 0 && element[element.length - 1] === GOBACK_BUTTON ? 0 : 8; } return ( <View onLayout={e => this.setState({[type]: e.nativeEvent.layout.width})} style={this._combineStyle(viewStyleKey, {['padding' + type]: extra})} > {element.map(this._renderButton.bind(this, type))} </View> ); }; _renderTitleView = () => { const {title} = this.props; if (typeof title === 'string') { return ( <Text numberOfLines={1} style={this._combineStyle('title')}> {title} </Text> ); } else { return title; } }; render() { const {titleCenter, hasSeperatorLine} = this.props; const edge = Math.max(this.state.Left, this.state.Right) + 15; const maxWidthCenterStyle = titleCenter ? {left: edge, right: edge} : {}; const seperatorLineStyle = hasSeperatorLine ? this._combineStyle('seperator') : {}; return ( <View style={this._combineStyle('container', seperatorLineStyle)}> {this._renderButtons('Left')} {!titleCenter ? ( <View style={this._combineStyle('titleContainer')}> {this._renderTitleView()} </View> ) : ( <View style={this._combineStyle('titleCenterContainer', maxWidthCenterStyle)}> {this._renderTitleView()} </View> )} {this._renderButtons('Right')} </View> ); } } const minWidth = 30; const styles = StyleSheet.create({ container: { justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center', paddingTop: STATUSBAR_HEIGHT, height: TOTALBAR_HEIGHT, backgroundColor: 'white', }, seperator: { borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#e6e6ea', }, title: { fontSize: 18, color: '#394352', textAlign: 'center', overflow: 'hidden', }, titleContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, titleCenterContainer: { position: 'absolute', left: 0, right: 0, top: STATUSBAR_HEIGHT, bottom: 0, justifyContent: 'center', alignItems: 'center', }, buttonView: { justifyContent: 'center', alignItems: 'center', minWidth: minWidth, height: NAVBAR_HEIGHT, paddingHorizontal: 8, }, buttonText: { color: '#394352', fontSize: 17, }, leftView: { flexDirection: 'row', justifyContent: 'center', alignItems: 'flex-start', }, rightView: { flexDirection: 'row', justifyContent: 'center', alignItems: 'flex-end', }, gobackView: { minWidth: minWidth, height: NAVBAR_HEIGHT, justifyContent: 'center', paddingHorizontal: 16, }, gobackImage: { width: 18, height: 16, }, });