├── .npmignore ├── .gitignore ├── example ├── package.json ├── index.ios.js ├── navbar.js ├── demo2.js └── demo1.js ├── package.json ├── components ├── StaticNavBarContent.js ├── NavBarContainer.js └── NavBarContent.js ├── index.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | ._* 3 | .DS_Store 4 | .git 5 | .hg 6 | .lock-wscript 7 | .svn 8 | .wafpickle-* 9 | CVS 10 | npm-debug.log 11 | node_modules 12 | example/node_modules -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | config.js 10 | 11 | pids 12 | logs 13 | results 14 | public 15 | 16 | npm-debug.log 17 | .DS_Store 18 | 19 | node_modules 20 | ACCOUNTS 21 | Pods 22 | 23 | .env -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-react-native-custom-navigation", 3 | "version": "0.2.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node_modules/react-native/packager/packager.sh" 7 | }, 8 | "dependencies": { 9 | "react-native" : "^0.11.0", 10 | "react-native-custom-navigation" : "file:../" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/index.ios.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | */ 5 | 'use strict'; 6 | 7 | var React = require('react-native'); 8 | var Demo1 = require('./demo1'); 9 | var Demo2 = require('./demo2'); 10 | 11 | var { 12 | AppRegistry, 13 | } = React; 14 | 15 | AppRegistry.registerComponent('ReactTest', () => Demo1); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-custom-navigation", 3 | "version": "0.2.2", 4 | "description": "A navigation that allow to custom the appearance of navigation bar in your app", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/superdami/react-native-custom-navigation.git" 9 | }, 10 | "keywords": [ 11 | "react", 12 | "react component", 13 | "react native", 14 | "react navigation", 15 | "ios", 16 | "navigation", 17 | "navbar", 18 | "router" 19 | ], 20 | "author": "Chen Zhejun ", 21 | "license": "MIT", 22 | "homepage": "https://github.com/superdami/react-native-custom-navigation", 23 | "dependencies": { 24 | "react-timer-mixin" : "*" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/navbar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react-native'); 4 | 5 | var { 6 | View, 7 | Text, 8 | StyleSheet, 9 | TouchableHighlight, 10 | } = React; 11 | 12 | var screen = require('Dimensions').get('window'); 13 | var NavbarContent = React.createClass({ 14 | render() { 15 | return ( 16 | 17 | 18 | 19 | 20 | {'<'} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {'>'} 29 | 30 | 31 | 32 | 33 | {this.props.title} 34 | 35 | ) 36 | }, 37 | }); 38 | 39 | var styles = StyleSheet.create({ 40 | container: { 41 | width: screen.width, 42 | height: 64, 43 | justifyContent: 'center', 44 | }, 45 | 46 | titleText: { 47 | fontSize: 22, 48 | color: '#fff', 49 | textAlign: 'center', 50 | alignSelf: 'center', 51 | }, 52 | 53 | buttonText: { 54 | fontSize: 32, 55 | color: '#111', 56 | textAlign: 'center', 57 | }, 58 | 59 | corner: { 60 | top: 20, 61 | flex: 1, 62 | position: 'absolute', 63 | justifyContent: 'center', 64 | }, 65 | 66 | leftCorner: { 67 | left:0, 68 | }, 69 | 70 | rightCorner: { 71 | right:0, 72 | }, 73 | 74 | backView: { 75 | width: 44, 76 | height: 44, 77 | justifyContent: 'center', 78 | } 79 | }); 80 | 81 | module.exports = NavbarContent; -------------------------------------------------------------------------------- /components/StaticNavBarContent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react-native'); 4 | var TimerMixin = require('react-timer-mixin'); 5 | 6 | var { 7 | StyleSheet, 8 | Text, 9 | View, 10 | TouchableHighlight 11 | } = React; 12 | 13 | var StaticNavBarContent = React.createClass({ 14 | mixins: [TimerMixin], 15 | 16 | getInitialState() { 17 | return { 18 | navbarProps: this.props.navbarPassProps 19 | }; 20 | }, 21 | 22 | componentWillMount() { 23 | this.props.currentRoute && 24 | (this.props.currentRoute.updateStaticNavbarProps = this._updateNavbarProps) 25 | }, 26 | 27 | componentWillReceiveProps(newProps) { 28 | if (this.props.currentRoute !== newProps.currentRoute) { 29 | newProps.currentRoute.updateStaticNavbarProps = this._updateNavbarProps; 30 | }; 31 | }, 32 | 33 | render() { 34 | var previousIndex = this.props.previousRoute ? this.props.previousRoute.index : null; 35 | var index = this.props.currentRoute ? this.props.currentRoute.index : null; 36 | 37 | return ( 38 | 39 | 49 | 50 | ); 51 | }, 52 | 53 | _updateNavbarProps(props) { 54 | this.setTimeout( 55 | () => { 56 | this.setState({ 57 | navbarProps: props 58 | }); 59 | }, 60 | 0 61 | ); 62 | } 63 | }); 64 | 65 | var styles = StyleSheet.create({ 66 | navbar: { 67 | position: 'absolute', 68 | top: 0, 69 | left: 0, 70 | height: 64, 71 | justifyContent: 'center', 72 | flexDirection: 'row', 73 | }, 74 | }); 75 | 76 | module.exports = StaticNavBarContent; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react-native'); 4 | 5 | var NavBarContainer = require('./components/NavBarContainer'); 6 | 7 | var { 8 | StyleSheet, 9 | Navigator, 10 | StatusBarIOS, 11 | View, 12 | } = React; 13 | 14 | var self; 15 | var Router = React.createClass({ 16 | 17 | getInitialState: function() { 18 | self = this; 19 | return { 20 | route: null, 21 | dragStartX: null, 22 | didSwitchView: null, 23 | }; 24 | }, 25 | 26 | /* 27 | * This changes the title in the navigation bar 28 | * It should preferrably be called for "onWillFocus" instad > 29 | * > but a recent update to React Native seems to break the animation 30 | */ 31 | onDidFocus: function(route) { 32 | this.setState({ route: route }); 33 | }, 34 | 35 | onBack: function(navigator) { 36 | if (this.state.route.index > 0) { 37 | navigator.pop(); 38 | } 39 | }, 40 | 41 | onForward: function(route, navigator) { 42 | route.index = this.state.route.index + 1 || 1; 43 | navigator.push(route); 44 | }, 45 | 46 | onFirst: function(navigator) { 47 | navigator.popToTop(); 48 | }, 49 | 50 | renderScene: function(route, navigator) { 51 | var goForward = function(route) { 52 | route.index = this.state.route.index + 1 || 1; 53 | navigator.push(route); 54 | }.bind(this); 55 | 56 | var goBackwards = function() { 57 | this.onBack(navigator); 58 | }.bind(this); 59 | 60 | var goToFirstRoute = function() { 61 | navigator.popToTop(); 62 | }; 63 | 64 | var didStartDrag = function(evt) { 65 | var x = evt.nativeEvent.pageX; 66 | if (x < 28) { 67 | this.setState({ 68 | dragStartX: x, 69 | didSwitchView: false 70 | }); 71 | return true; 72 | } 73 | }.bind(this); 74 | 75 | // Recognize swipe back gesture for navigation 76 | var didMoveFinger = function(evt) { 77 | var draggedAway = ((evt.nativeEvent.pageX - this.state.dragStartX) > 30); 78 | if (!this.state.didSwitchView && draggedAway) { 79 | this.onBack(navigator); 80 | this.setState({ didSwitchView: true }); 81 | } 82 | }.bind(this); 83 | 84 | // Set to false to prevent iOS from hijacking the responder 85 | var preventDefault = function(evt) { 86 | return true; 87 | }; 88 | 89 | var updateNavbarProps = function(props) { 90 | route.updateNavbarProps && route.updateNavbarProps(props); 91 | route.updateStaticNavbarProps && route.updateStaticNavbarProps(props); 92 | } 93 | 94 | var Content = route.component; 95 | return ( 96 | 101 | { 110 | route.updateBarBackgroundStyle && 111 | route.updateBarBackgroundStyle(style) 112 | } 113 | } 114 | updateNavbarProps={updateNavbarProps} 115 | {...route.passProps}/> 116 | 117 | ) 118 | 119 | }, 120 | 121 | render: function() { 122 | 123 | // Status bar color 124 | if (this.props.statusBarColor === "black") { 125 | StatusBarIOS.setStyle(0); 126 | } else { 127 | StatusBarIOS.setStyle(1); 128 | } 129 | 130 | var navigationBar = 131 | 139 | 140 | var initialRoute = this.props.initialRoute; 141 | initialRoute.index = 0; 142 | return ( 143 | ) 148 | }, 149 | }); 150 | 151 | 152 | var styles = StyleSheet.create({ 153 | container: { 154 | flex: 1, 155 | }, 156 | }); 157 | 158 | 159 | module.exports = Router; -------------------------------------------------------------------------------- /components/NavBarContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react-native'); 4 | 5 | var NavBarContent = require('./NavBarContent').NavBarContent; 6 | var NavbarBackground = require('./NavBarContent').NavbarBackground; 7 | var StaticNavBarContent = require('./StaticNavBarContent'); 8 | 9 | var { 10 | StyleSheet, 11 | View 12 | } = React; 13 | 14 | var NavBarContainer = React.createClass({ 15 | 16 | getInitialState: function() { 17 | return {}; 18 | }, 19 | 20 | componentWillReceiveProps: function(newProps) { 21 | if (!this.props.currentRoute || (this.props.currentRoute.index === null)) { 22 | newProps.currentRoute.index = 0; 23 | } 24 | 25 | if (newProps.currentRoute === this.props.currentRoute) { 26 | return; 27 | } 28 | 29 | this.setState({ 30 | previousRoute: this.props.currentRoute, 31 | currentRoute: newProps.currentRoute 32 | }); 33 | }, 34 | 35 | _goBack: function() { 36 | this.props.onBack(this.props.navigator); 37 | }, 38 | 39 | _goForward: function(route) { 40 | this.props.onForward(route, this.props.navigator); 41 | }, 42 | 43 | _goFirst: function() { 44 | this.props.onFirst(this.props.navigator); 45 | }, 46 | 47 | _onAnimationChange: function(progress, fromIndex, toIndex) { 48 | this.setState({ 49 | progress: progress 50 | }); 51 | }, 52 | 53 | onAnimationStart: function(fromIndex, toIndex) { 54 | var routeStack = this.props.navState.routeStack; 55 | var fromRoute = routeStack.length > fromIndex ? routeStack[fromIndex] : null; 56 | var toRoute = routeStack.length > toIndex ? routeStack[toIndex] : null; 57 | this.state = { 58 | previousRoute: fromRoute, 59 | currentRoute: toRoute, 60 | }; 61 | }, 62 | 63 | onAnimationEnd: function() { 64 | 65 | }, 66 | 67 | updateProgress: function(progress, fromIndex, toIndex) { 68 | this._onAnimationChange(progress, fromIndex, toIndex); 69 | }, 70 | 71 | // We render both the current and the previous navbar (for animation) 72 | render: function() { 73 | 74 | var currentProps = { 75 | progress:this.state.progress, 76 | route:this.state.currentRoute, 77 | }; 78 | 79 | var previousProps = { 80 | progress:this.state.progress, 81 | route:this.state.previousRoute, 82 | willDisappear:true 83 | }; 84 | 85 | var navigatorProps = { 86 | goForward: this._goForward, 87 | goBack: this._goBack, 88 | goFirst: this._goFirst 89 | }; 90 | 91 | var previousContent, currentContent; 92 | var previousBackground, currentBackground; 93 | if (this.state.previousRoute) { 94 | previousBackground = ( 95 | ); 98 | 99 | currentBackground = ( 100 | ); 103 | 104 | currentContent = ( 105 | ); 110 | 111 | previousContent = ( 112 | ); 117 | 118 | } else if (this.state.currentRoute){ 119 | currentBackground = ( 120 | ); 123 | 124 | currentContent = ( 125 | ); 130 | } 131 | 132 | var staticContent; 133 | if (this.props.navbarComponent) { 134 | staticContent = ( 135 | 142 | ) 143 | } 144 | 145 | // Using createFragment to prevent 'Each child in an array should have a unique "key" prop.' 146 | // warning message 147 | var navbarContents = React.addons.createFragment({ 148 | "staticContent": staticContent, 149 | "previousCountent" : previousContent, 150 | "currentContent":currentContent 151 | }); 152 | 153 | return ( 154 | 155 | {previousBackground} 156 | {currentBackground} 157 | {navbarContents} 158 | 159 | ) 160 | } 161 | }); 162 | 163 | 164 | var styles = StyleSheet.create({ 165 | navbar: { 166 | position: 'absolute', 167 | top: 0, 168 | left: 0, 169 | height: 64, 170 | justifyContent: 'center', 171 | flexDirection: 'row', 172 | }, 173 | 174 | background: { 175 | position: 'absolute', 176 | top: 0, 177 | left: 0, 178 | right: 0, 179 | height: 64, 180 | }, 181 | 182 | navbarContainer: { 183 | position: 'absolute', 184 | top: 0, 185 | left: 0, 186 | right: 0, 187 | height: 64, 188 | backgroundColor: 'rgba(0,0,0,0)' 189 | } 190 | }); 191 | 192 | 193 | module.exports = NavBarContainer; 194 | -------------------------------------------------------------------------------- /example/demo2.js: -------------------------------------------------------------------------------- 1 | var React = require('react-native'); 2 | var { 3 | Image, 4 | View, 5 | ScrollView, 6 | StyleSheet, 7 | Text, 8 | TouchableHighlight 9 | } = React; 10 | 11 | var Router = require('react-native-custom-navigation'); 12 | 13 | var navbarColors = [ 14 | '#acc7bf', 15 | '#5e5f67', 16 | '#c37070', 17 | '#eae160', 18 | '#bf7aa3', 19 | '#b7d967' 20 | ]; 21 | 22 | var NavbarContent = require('./navbar'); 23 | var screen = require('Dimensions').get('window'); 24 | var axisWidth = screen.width - 120; 25 | 26 | var NavbarWrapper = React.createClass({ 27 | getInitialState() { 28 | return { 29 | backgroundColor: this.props.backgroundColor, 30 | progress: 0 31 | }; 32 | }, 33 | 34 | componentWillReceiveProps(newProps) { 35 | if (newProps.route !== this.props.route) { 36 | var route = newProps.route; 37 | var progress; 38 | var n = Math.abs(route.previousIndex - route.index) * route.progress; 39 | if (route.index > route.previousIndex) { 40 | progress = (route.previousIndex + n) * 0.2; 41 | } else { 42 | progress = (route.previousIndex - n) * 0.2; 43 | } 44 | 45 | this.setState({ 46 | progress : progress 47 | }); 48 | } 49 | 50 | if (newProps.backgroundColor !== this.props.backgroundColor) { 51 | this.setState({ 52 | backgroundColor: newProps.backgroundColor 53 | }) 54 | } 55 | }, 56 | 57 | _push() { 58 | if (this.props.route.index > 4) { 59 | return; 60 | } 61 | 62 | this.props.route.push({ 63 | component: DemoView, 64 | }); 65 | }, 66 | 67 | render() { 68 | var width = this.state.progress * axisWidth; 69 | return ( 70 | 71 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | ); 82 | } 83 | }); 84 | 85 | var RootController = React.createClass({ 86 | render() { 87 | return ( 88 | ); 96 | } 97 | }); 98 | 99 | var DemoView = React.createClass({ 100 | render() { 101 | var imageUri = 'https://divnil.com/wallpaper/iphone5/img/app/c/l/clear-your-desktop-wallpaper-for-640x1136-iphone-5-311-46_33a8356f2205d7c0be8727720a21a207_raw.jpg'; 102 | 103 | return ( 104 | 105 | 108 | 111 | 112 | Push 113 | 114 | 115 | 118 | 119 | Back 120 | 121 | 122 | 125 | 126 | Pop to top 127 | 128 | 129 | 132 | 133 | {'*change progress red'} 134 | 135 | 136 | 137 | 138 | ) 139 | }, 140 | 141 | _back() { 142 | this.props.route.pop(); 143 | }, 144 | 145 | _pushToNext() { 146 | if (this.props.route.index > 4) { 147 | return; 148 | } 149 | 150 | this.props.route.push({ 151 | component: DemoView, 152 | }); 153 | }, 154 | 155 | _popToTop() { 156 | this.props.route.popToTop(); 157 | }, 158 | 159 | _changeColor() { 160 | this.props.updateNavbarProps({backgroundColor: 'rgba(255,0,0,1)'}); 161 | } 162 | }); 163 | 164 | var styles = StyleSheet.create({ 165 | container: { 166 | width: screen.width, 167 | height: screen.height, 168 | }, 169 | 170 | buttonView: { 171 | justifyContent: 'center', 172 | padding: 4, 173 | width: 180, 174 | height: 60, 175 | backgroundColor: 'rgba(0,0,0,0.5)', 176 | borderRadius: 4 177 | }, 178 | 179 | button: { 180 | marginBottom: 40 181 | }, 182 | 183 | buttonText: { 184 | fontSize: 20, 185 | textAlign: 'center', 186 | color: '#fff', 187 | backgroundColor: 'rgba(0,0,0,0)' 188 | }, 189 | 190 | image: { 191 | alignItems: 'center', 192 | width: screen.width, 193 | height: screen.height, 194 | justifyContent: 'center' 195 | }, 196 | 197 | activity: { 198 | position: 'absolute', 199 | height: 64, 200 | width: axisWidth, 201 | top: 0, 202 | left: (screen.width - axisWidth) / 2 203 | }, 204 | 205 | navbar: { 206 | width:screen.width, 207 | height: 64, 208 | backgroundColor: '#5e5f67', 209 | justifyContent: 'center' 210 | }, 211 | 212 | axisView: { 213 | marginTop: 40, 214 | width: axisWidth, 215 | height: 8, 216 | borderRadius: 4, 217 | backgroundColor : '#fff', 218 | alignSelf: 'center', 219 | justifyContent: 'center' 220 | }, 221 | 222 | progress: { 223 | alignSelf: 'flex-start', 224 | height: 6, 225 | borderRadius: 3, 226 | } 227 | }); 228 | 229 | module.exports = RootController; -------------------------------------------------------------------------------- /components/NavBarContent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react-native'); 4 | var TimerMixin = require('react-timer-mixin'); 5 | 6 | var { 7 | StyleSheet, 8 | Text, 9 | View, 10 | TouchableHighlight, 11 | Dimensions, 12 | } = React; 13 | 14 | var NavbarBackground = React.createClass({ 15 | mixins: [TimerMixin], 16 | 17 | getInitialState: function() { 18 | return {}; 19 | }, 20 | 21 | _initState: function(fadeIn) { 22 | var state = {}; 23 | state.opacityStart = fadeIn ? 0 : 1; 24 | state.opacityEnd = fadeIn ? 1 : 0; 25 | state.progress = 0; 26 | this.state = state; 27 | }, 28 | 29 | componentWillMount: function() { 30 | this.props.route.updateBarBackgroundStyle = this._updateBarBackgroundStyle; 31 | this._initState(false); 32 | }, 33 | 34 | componentWillReceiveProps: function(newProps) { 35 | if (newProps.route !== this.props.route) { 36 | this._initState(!this.props.willDisappear); 37 | if (!this.props.willDisappear) { 38 | newProps.route.updateBarBackgroundStyle = this._updateBarBackgroundStyle; 39 | } 40 | } 41 | 42 | this.state.progress = newProps.progress; 43 | }, 44 | 45 | _updateBarBackgroundStyle: function(style) { 46 | this.props.route.barBackgroundStyle = style; 47 | this.setTimeout(this.forceUpdate, 0); 48 | }, 49 | 50 | render() { 51 | var opacity = (this.state.opacityEnd - this.state.opacityStart) * this.state.progress + this.state.opacityStart; 52 | var transitionStyle = { 53 | opacity: opacity, 54 | }; 55 | 56 | return ( 57 | 58 | ); 59 | } 60 | }); 61 | 62 | var screen = Dimensions.get('window'); 63 | var NavBarContent = React.createClass({ 64 | mixins: [TimerMixin], 65 | 66 | getInitialState: function() { 67 | return {}; 68 | }, 69 | 70 | _initState: function(direction, fadeIn) { 71 | var state = {}; 72 | state.opacityStart = fadeIn ? 0 : 1; 73 | state.opacityEnd = fadeIn ? 1 : 0; 74 | 75 | if (direction > 0) { 76 | state.leftStart = fadeIn ? screen.width : 0; 77 | state.leftEnd = fadeIn ? 0 : -screen.width; 78 | } else if (direction < 0) { 79 | state.leftStart = fadeIn ? -screen.width : 0; 80 | state.leftEnd = fadeIn ? 0: screen.width; 81 | } else { 82 | //todo replace 83 | } 84 | this.state = state; 85 | }, 86 | 87 | componentWillMount: function() { 88 | this._initState(1, false); 89 | this.props.route.updateNavbarProps = this._updateNavbarProps; 90 | }, 91 | 92 | componentWillReceiveProps: function(newProps) { 93 | if (this.props.route !== newProps.route) { 94 | this._initState(newProps.direction, !this.props.willDisappear); 95 | if (!newProps.willDisappear) { 96 | newProps.route.updateNavbarProps = this._updateNavbarProps; 97 | } 98 | } 99 | }, 100 | 101 | _updateNavbarProps: function(props) { 102 | this.props.route.navbarProps = props; 103 | this.setTimeout(this.forceUpdate, 0); 104 | }, 105 | 106 | render() { 107 | var left = (this.state.leftEnd - this.state.leftStart) * this.props.progress + this.state.leftStart; 108 | var opacity = (this.state.opacityEnd - this.state.opacityStart) * this.props.progress + this.state.opacityStart; 109 | 110 | var transitionStyle = { 111 | position: 'absolute', 112 | opacity: opacity, 113 | left: left 114 | }; 115 | 116 | var mainContent; 117 | if (this.props.route.navbarComponent) { 118 | var navbarProps = this.props.route.navbarProps ? this.props.route.navbarProps : this.props.route.navbarPassProps 119 | var NavbarComponent = this.props.route.navbarComponent; 120 | mainContent = ( 121 | 129 | ); 130 | } else { 131 | var leftCorner; 132 | var leftCornerContent; 133 | var BackButton = this.props.backButtonComponent 134 | 135 | if (this.props.route.index > 0 && BackButton) { 136 | leftCornerContent = ( 137 | 138 | 139 | 140 | 141 | ); 142 | } 143 | 144 | leftCorner = ( 145 | 148 | {leftCornerContent} 149 | 150 | ); 151 | 152 | mainContent = [leftCorner]; 153 | if (this.props.route.title) { 154 | mainContent.push( 155 | {this.props.route.title} 159 | ); 160 | } 161 | } 162 | 163 | return ( 164 | 165 | {mainContent} 166 | 167 | ); 168 | } 169 | }); 170 | 171 | 172 | var styles = StyleSheet.create({ 173 | navbar: { 174 | position: 'absolute', 175 | top: 0, 176 | left: 0, 177 | height: 64, 178 | justifyContent: 'center', 179 | flexDirection: 'row', 180 | }, 181 | 182 | corner: { 183 | flex: 1, 184 | left:0, 185 | top: 18, 186 | position: 'absolute', 187 | justifyContent: 'center', 188 | }, 189 | 190 | alignLeft: { 191 | alignItems: 'flex-start' 192 | }, 193 | 194 | title: { 195 | position: 'absolute', 196 | width: screen.width - 100, 197 | top: 28, 198 | left: 50, 199 | fontSize: 18, 200 | color: '#fff', 201 | textAlign: 'center', 202 | }, 203 | 204 | backView: { 205 | width: 44, 206 | height: 44, 207 | justifyContent: 'center', 208 | } 209 | }); 210 | 211 | 212 | module.exports = { 213 | NavBarContent: NavBarContent, 214 | NavbarBackground: NavbarBackground, 215 | }; 216 | -------------------------------------------------------------------------------- /example/demo1.js: -------------------------------------------------------------------------------- 1 | var React = require('react-native'); 2 | var { 3 | Image, 4 | View, 5 | ScrollView, 6 | StyleSheet, 7 | Text, 8 | TouchableHighlight 9 | } = React; 10 | 11 | var Router = require('react-native-custom-navigation'); 12 | 13 | var navbarColors = [ 14 | '#acc7bf', 15 | '#5e5f67', 16 | '#c37070', 17 | '#eae160', 18 | '#bf7aa3', 19 | '#b7d967' 20 | ]; 21 | 22 | var NavbarContent = require('./navbar'); 23 | var screen = require('Dimensions').get('window'); 24 | 25 | var BackButton = React.createClass({ 26 | render() { 27 | return ( 28 | Back) 34 | } 35 | }); 36 | 37 | var RootController = React.createClass({ 38 | render() { 39 | return ( 40 | ); 50 | } 51 | }); 52 | 53 | var NavbarWrapper = React.createClass({ 54 | getInitialState() { 55 | return {}; 56 | }, 57 | 58 | componentWillMount() { 59 | this.setState({ 60 | style: this.props.style 61 | }); 62 | }, 63 | 64 | componentWillReceiveProps(newProps) { 65 | if (newProps.style !== this.props.style) { 66 | this.setState({ 67 | style: newProps.style 68 | }); 69 | } 70 | }, 71 | 72 | render() { 73 | return ( 74 | ); 78 | }, 79 | 80 | _push() { 81 | var colorIndex = this.props.route.index % navbarColors.length; 82 | var color = navbarColors[colorIndex]; 83 | 84 | this.props.route.push({ 85 | component: DemoView, 86 | navbarComponent: NavbarWrapper, 87 | navbarPassProps: { 88 | style: { 89 | backgroundColor: color 90 | } 91 | } 92 | }); 93 | }, 94 | }); 95 | 96 | var DemoView = React.createClass({ 97 | render() { 98 | var imageUri = 'https://divnil.com/wallpaper/iphone5/img/app/c/l/clear-your-desktop-wallpaper-for-640x1136-iphone-5-311-46_33a8356f2205d7c0be8727720a21a207_raw.jpg'; 99 | 100 | return ( 101 | 102 | 105 | 108 | 111 | 112 | Push 113 | 114 | 115 | 118 | 119 | Push with custom navbar 120 | 121 | 122 | 125 | 126 | Back 127 | 128 | 129 | 132 | 133 | Pop to top 134 | 135 | 136 | 139 | 140 | *custom bar become gray 141 | 142 | 143 | 144 | 145 | 146 | ) 147 | }, 148 | 149 | _back() { 150 | this.props.route.pop(); 151 | }, 152 | 153 | _pushToNext() { 154 | var nextIndex = ++this.props.route.index; 155 | 156 | this.props.route.push({ 157 | component: DemoView, 158 | title: nextIndex, 159 | titleStyle: { 160 | fontSize: 22, 161 | color: '#eee' 162 | } 163 | }); 164 | }, 165 | 166 | _pushToNextCustomNavbar() { 167 | var colorIndex = this.props.route.index % navbarColors.length; 168 | var color = navbarColors[colorIndex]; 169 | 170 | this.props.route.push({ 171 | component: DemoView, 172 | navbarComponent: NavbarWrapper, 173 | navbarPassProps: { 174 | style: { 175 | backgroundColor: color 176 | } 177 | } 178 | }); 179 | }, 180 | 181 | _popToTop() { 182 | this.props.route.popToTop(); 183 | }, 184 | 185 | _changeColor() { 186 | this.props.updateNavbarProps({ 187 | style: { 188 | backgroundColor: '#666' 189 | } 190 | }); 191 | }, 192 | 193 | _handleScroll(e) { 194 | var alpha = (e.nativeEvent.contentInset.top + e.nativeEvent.contentOffset.y) / 200; 195 | if (alpha < 0) alpha = 0; 196 | if (alpha > 1) alpha = 1; 197 | 198 | var style = {backgroundColor: 'rgba(102, 106, 136, ' + alpha +')'}; 199 | this.props.updateBarBackgroundStyle(style); 200 | } 201 | }); 202 | 203 | var styles = StyleSheet.create({ 204 | container: { 205 | width: screen.width, 206 | height: screen.height, 207 | }, 208 | 209 | buttonView: { 210 | justifyContent: 'center', 211 | padding: 4, 212 | width: 180, 213 | height: 60, 214 | backgroundColor: 'rgba(0,0,0,0.5)', 215 | borderRadius: 4 216 | }, 217 | 218 | button: { 219 | marginBottom: 40 220 | }, 221 | 222 | buttonText: { 223 | fontSize: 20, 224 | textAlign: 'center', 225 | color: '#fff', 226 | backgroundColor: 'rgba(0,0,0,0)' 227 | }, 228 | 229 | image: { 230 | alignItems: 'center', 231 | width: screen.width, 232 | height: screen.height * 1.5, 233 | }, 234 | }); 235 | 236 | module.exports = RootController; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-native-custom-navigation 2 | =================== 3 | The goal is making a easy navigation router for react-native, you could plug-in different navigation-bar in each view stack, and update navigation-bar background style at any time. The router would provide your navigation-bar a smooth transition animation when push, pop or swipe-back gesture is activating. 4 | 5 | Inspired by [react-native-router](https://github.com/t4t5/react-native-router) 6 | 7 | Case 1: 8 | Different view stack using different navgation bar 9 | 10 | ![Example](https://www.dropbox.com/s/3jqguw37buhagu4/demo.gif?dl=1) 11 | 12 | 13 | Case 2: 14 | Using singleton navigation bar for all views 15 | 16 | ![Example](https://www.dropbox.com/s/ik6i3x6m2bgirh5/demo2.gif?dl=1) 17 | 18 | 19 | 20 | Install 21 | ------- 22 | 23 | In your React Native project directory and run: 24 | 25 | ```npm install react-native-custom-navigation --save``` 26 | 27 | 28 | 29 | Demo 30 | ------- 31 | 32 | In node_modules/react-native-custom-navigation/example directory and run: 33 | 34 | ```npm install``` 35 | 36 | In ```index.ios.js```, 2 demos are ready for you. 37 | 38 | 39 | ```javascript 40 | var React = require('react-native'); 41 | var Demo1 = require('./demo1'); 42 | var Demo2 = require('./demo2'); 43 | 44 | var { 45 | AppRegistry, 46 | } = React; 47 | 48 | AppRegistry.registerComponent('ReactTest', () => Demo1); 49 | ``` 50 | 51 | Update 52 | ------- 53 | 0.2.1: 54 | - Not specifying react-native as dependency in package.json 55 | - Demo using react-native 0.11 56 | 57 | 0.2.0: 58 | - You can pass initial props to your navbar component by 59 | setting **`navbarPassProps`** when pushing a route object. 60 | - You can update current navbar props in current view module by calling **`this.props.updateNavbarProps`**. 61 | - Access the passing props in your navbar module by **`this.props.xxx`**, 62 | - Handle the passing props in **`componentWillMound`** or **`componentWillReceiveProps`** to render navbar UI. 63 | - The usage of these features can be found in the example that had been updated. 64 | 65 | 66 | Basic Usage 67 | ------- 68 | 69 | ```javascript 70 | var Router = require('react-native-custom-navigation'); 71 | ``` 72 | 73 | Your route object should contain component object for the page to render. 74 | I would like setting a back-button component for each view stack, also you can pass this and manage the back-button by your navigation-bar. 75 | 76 | ```javascript 77 | 78 | var BackButton = React.createClass({ 79 | render() { 80 | return ( 81 | Back) 87 | } 88 | }); 89 | 90 | var route = { 91 | component: FirstView, 92 | backButton: 93 | title: 'Root', 94 | titleStyle: { 95 | color: '#ddd', 96 | fontSize: 22 97 | } 98 | } 99 | 100 | var RootController = React.createClass({ 101 | render() { 102 | return ( 103 | ); 106 | } 107 | }); 108 | 109 | AppRegistry.registerComponent('ReactTest', () => RootController); 110 | ``` 111 | 112 | Here we go. 113 | Now we got a scrollView here, we can have fade-in navbar-background when we scrolling down. 114 | 115 | ```javascript 116 | var FirstView = React.createClass({ 117 | render() { 118 | return ( 119 | 122 | 124 | 125 | Push with custom navbar 126 | 127 | 128 | 129 | ) 130 | }, 131 | 132 | _handleScroll(e) { 133 | var alpha = (e.nativeEvent.contentInset.top + e.nativeEvent.contentOffset.y) / 200; 134 | if (alpha < 0) alpha = 0; 135 | if (alpha > 1) alpha = 1; 136 | 137 | var style = {backgroundColor: 'rgba(102, 106, 136, ' + alpha +')'}; 138 | this.props.route.updateNavbarStyle(style); 139 | } 140 | 141 | _push() { 142 | var navbarContent = ( 143 | ); 145 | 146 | this.props.route.push({ 147 | component: FirstView, 148 | title: 'title would never show', 149 | navbarComponent: navbarContent 150 | }); 151 | }, 152 | }); 153 | ``` 154 | 155 | You can then navigate further to a new component by calling 156 | ```javascript 157 | this.props.route.push() 158 | ``` 159 | 160 | You can set "navbarComponent" as navigation-bar in next route object. 161 | If you want still have the fade-in effect, make sure the background color of your "navbarComponent" is transparent. 162 | 163 | 164 | Configurations 165 | -------------- 166 | The **``** object used to initialize the navigation can take the following props: 167 | - `initialRoute` (required) 168 | - `backButtonComponent` 169 | - `navbarComponent`: Set the component as the singleton navbar for all views. 170 | - `navbarPassProps`: Send initial props to your singleton navbar, access it by `this.props.xxx` 171 | 172 | The **`this.props.route.push()`** callback prop takes one parameter (a JavaScript object) which can have the following keys: 173 | - `title` 174 | - `titleStyle` 175 | - `component` (required) The next view component 176 | - `navbarComponent`: Set the component as the navbar in this route 177 | - `passProps`: Send object data to your view component. access the data by `this.props.xxx` 178 | - `navbarPassProps`: Send initial data to your navbar, access it by `this.props.xxx` 179 | 180 | The **`navbarComponent` and `component`** access route parameter or function by ***`this.props.route`*** which have the following keys: 181 | - `index` 182 | - `previousIndex`(for singleton navbar only) 183 | - `progress`(for singleton navbar only): current transition animation progress (0 - 1) 184 | - ~~`updateNavbarStyle`(view component only)~~ 185 | - `push` 186 | - `pop` 187 | - `popToTop` 188 | 189 | ~~The **`this.props.route.updateNavbarStyle()`** callback prop takes style object which update the style of navbar background~~ 190 | 191 | **`this.props.route.updateNavbarStyle`** this function had been abandoned, replace with **`this.props.updateBarBackgroundStyle()`** . 192 | 193 | 194 | Todos 195 | ------- 196 | 197 | - Less and clear code 198 | - Make transition animation looks naturally when using singleton navbar and stack navbar at same time. 199 | 200 | Questions? 201 | --------- 202 | feel free to [follow me on Twitter](https://twitter.com/eatdami) --------------------------------------------------------------------------------