Repository: SuperDami/react-native-custom-navigation Branch: master Commit: 40ead26ff616 Files: 13 Total size: 33.8 KB Directory structure: gitextract_lhqzowum/ ├── .gitignore ├── .npmignore ├── README.md ├── components/ │ ├── NavBarContainer.js │ ├── NavBarContent.js │ └── StaticNavBarContent.js ├── example/ │ ├── demo1.js │ ├── demo2.js │ ├── index.ios.js │ ├── navbar.js │ └── package.json ├── index.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ lib-cov *.seed *.log *.csv *.dat *.out *.pid *.gz config.js pids logs results public npm-debug.log .DS_Store node_modules ACCOUNTS Pods .env ================================================ FILE: .npmignore ================================================ .*.swp ._* .DS_Store .git .hg .lock-wscript .svn .wafpickle-* CVS npm-debug.log node_modules example/node_modules ================================================ FILE: README.md ================================================ react-native-custom-navigation =================== 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. Inspired by [react-native-router](https://github.com/t4t5/react-native-router) Case 1: Different view stack using different navgation bar ![Example](https://www.dropbox.com/s/3jqguw37buhagu4/demo.gif?dl=1) Case 2: Using singleton navigation bar for all views ![Example](https://www.dropbox.com/s/ik6i3x6m2bgirh5/demo2.gif?dl=1) Install ------- In your React Native project directory and run: ```npm install react-native-custom-navigation --save``` Demo ------- In node_modules/react-native-custom-navigation/example directory and run: ```npm install``` In ```index.ios.js```, 2 demos are ready for you. ```javascript var React = require('react-native'); var Demo1 = require('./demo1'); var Demo2 = require('./demo2'); var { AppRegistry, } = React; AppRegistry.registerComponent('ReactTest', () => Demo1); ``` Update ------- 0.2.1: - Not specifying react-native as dependency in package.json - Demo using react-native 0.11 0.2.0: - You can pass initial props to your navbar component by setting **`navbarPassProps`** when pushing a route object. - You can update current navbar props in current view module by calling **`this.props.updateNavbarProps`**. - Access the passing props in your navbar module by **`this.props.xxx`**, - Handle the passing props in **`componentWillMound`** or **`componentWillReceiveProps`** to render navbar UI. - The usage of these features can be found in the example that had been updated. Basic Usage ------- ```javascript var Router = require('react-native-custom-navigation'); ``` Your route object should contain component object for the page to render. 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. ```javascript var BackButton = React.createClass({ render() { return ( Back) } }); var route = { component: FirstView, backButton: title: 'Root', titleStyle: { color: '#ddd', fontSize: 22 } } var RootController = React.createClass({ render() { return ( ); } }); AppRegistry.registerComponent('ReactTest', () => RootController); ``` Here we go. Now we got a scrollView here, we can have fade-in navbar-background when we scrolling down. ```javascript var FirstView = React.createClass({ render() { return ( Push with custom navbar ) }, _handleScroll(e) { var alpha = (e.nativeEvent.contentInset.top + e.nativeEvent.contentOffset.y) / 200; if (alpha < 0) alpha = 0; if (alpha > 1) alpha = 1; var style = {backgroundColor: 'rgba(102, 106, 136, ' + alpha +')'}; this.props.route.updateNavbarStyle(style); } _push() { var navbarContent = ( ); this.props.route.push({ component: FirstView, title: 'title would never show', navbarComponent: navbarContent }); }, }); ``` You can then navigate further to a new component by calling ```javascript this.props.route.push() ``` You can set "navbarComponent" as navigation-bar in next route object. If you want still have the fade-in effect, make sure the background color of your "navbarComponent" is transparent. Configurations -------------- The **``** object used to initialize the navigation can take the following props: - `initialRoute` (required) - `backButtonComponent` - `navbarComponent`: Set the component as the singleton navbar for all views. - `navbarPassProps`: Send initial props to your singleton navbar, access it by `this.props.xxx` The **`this.props.route.push()`** callback prop takes one parameter (a JavaScript object) which can have the following keys: - `title` - `titleStyle` - `component` (required) The next view component - `navbarComponent`: Set the component as the navbar in this route - `passProps`: Send object data to your view component. access the data by `this.props.xxx` - `navbarPassProps`: Send initial data to your navbar, access it by `this.props.xxx` The **`navbarComponent` and `component`** access route parameter or function by ***`this.props.route`*** which have the following keys: - `index` - `previousIndex`(for singleton navbar only) - `progress`(for singleton navbar only): current transition animation progress (0 - 1) - ~~`updateNavbarStyle`(view component only)~~ - `push` - `pop` - `popToTop` ~~The **`this.props.route.updateNavbarStyle()`** callback prop takes style object which update the style of navbar background~~ **`this.props.route.updateNavbarStyle`** this function had been abandoned, replace with **`this.props.updateBarBackgroundStyle()`** . Todos ------- - Less and clear code - Make transition animation looks naturally when using singleton navbar and stack navbar at same time. Questions? --------- feel free to [follow me on Twitter](https://twitter.com/eatdami) ================================================ FILE: components/NavBarContainer.js ================================================ 'use strict'; var React = require('react-native'); var NavBarContent = require('./NavBarContent').NavBarContent; var NavbarBackground = require('./NavBarContent').NavbarBackground; var StaticNavBarContent = require('./StaticNavBarContent'); var { StyleSheet, View } = React; var NavBarContainer = React.createClass({ getInitialState: function() { return {}; }, componentWillReceiveProps: function(newProps) { if (!this.props.currentRoute || (this.props.currentRoute.index === null)) { newProps.currentRoute.index = 0; } if (newProps.currentRoute === this.props.currentRoute) { return; } this.setState({ previousRoute: this.props.currentRoute, currentRoute: newProps.currentRoute }); }, _goBack: function() { this.props.onBack(this.props.navigator); }, _goForward: function(route) { this.props.onForward(route, this.props.navigator); }, _goFirst: function() { this.props.onFirst(this.props.navigator); }, _onAnimationChange: function(progress, fromIndex, toIndex) { this.setState({ progress: progress }); }, onAnimationStart: function(fromIndex, toIndex) { var routeStack = this.props.navState.routeStack; var fromRoute = routeStack.length > fromIndex ? routeStack[fromIndex] : null; var toRoute = routeStack.length > toIndex ? routeStack[toIndex] : null; this.state = { previousRoute: fromRoute, currentRoute: toRoute, }; }, onAnimationEnd: function() { }, updateProgress: function(progress, fromIndex, toIndex) { this._onAnimationChange(progress, fromIndex, toIndex); }, // We render both the current and the previous navbar (for animation) render: function() { var currentProps = { progress:this.state.progress, route:this.state.currentRoute, }; var previousProps = { progress:this.state.progress, route:this.state.previousRoute, willDisappear:true }; var navigatorProps = { goForward: this._goForward, goBack: this._goBack, goFirst: this._goFirst }; var previousContent, currentContent; var previousBackground, currentBackground; if (this.state.previousRoute) { previousBackground = ( ); currentBackground = ( ); currentContent = ( ); previousContent = ( ); } else if (this.state.currentRoute){ currentBackground = ( ); currentContent = ( ); } var staticContent; if (this.props.navbarComponent) { staticContent = ( ) } // Using createFragment to prevent 'Each child in an array should have a unique "key" prop.' // warning message var navbarContents = React.addons.createFragment({ "staticContent": staticContent, "previousCountent" : previousContent, "currentContent":currentContent }); return ( {previousBackground} {currentBackground} {navbarContents} ) } }); var styles = StyleSheet.create({ navbar: { position: 'absolute', top: 0, left: 0, height: 64, justifyContent: 'center', flexDirection: 'row', }, background: { position: 'absolute', top: 0, left: 0, right: 0, height: 64, }, navbarContainer: { position: 'absolute', top: 0, left: 0, right: 0, height: 64, backgroundColor: 'rgba(0,0,0,0)' } }); module.exports = NavBarContainer; ================================================ FILE: components/NavBarContent.js ================================================ 'use strict'; var React = require('react-native'); var TimerMixin = require('react-timer-mixin'); var { StyleSheet, Text, View, TouchableHighlight, Dimensions, } = React; var NavbarBackground = React.createClass({ mixins: [TimerMixin], getInitialState: function() { return {}; }, _initState: function(fadeIn) { var state = {}; state.opacityStart = fadeIn ? 0 : 1; state.opacityEnd = fadeIn ? 1 : 0; state.progress = 0; this.state = state; }, componentWillMount: function() { this.props.route.updateBarBackgroundStyle = this._updateBarBackgroundStyle; this._initState(false); }, componentWillReceiveProps: function(newProps) { if (newProps.route !== this.props.route) { this._initState(!this.props.willDisappear); if (!this.props.willDisappear) { newProps.route.updateBarBackgroundStyle = this._updateBarBackgroundStyle; } } this.state.progress = newProps.progress; }, _updateBarBackgroundStyle: function(style) { this.props.route.barBackgroundStyle = style; this.setTimeout(this.forceUpdate, 0); }, render() { var opacity = (this.state.opacityEnd - this.state.opacityStart) * this.state.progress + this.state.opacityStart; var transitionStyle = { opacity: opacity, }; return ( ); } }); var screen = Dimensions.get('window'); var NavBarContent = React.createClass({ mixins: [TimerMixin], getInitialState: function() { return {}; }, _initState: function(direction, fadeIn) { var state = {}; state.opacityStart = fadeIn ? 0 : 1; state.opacityEnd = fadeIn ? 1 : 0; if (direction > 0) { state.leftStart = fadeIn ? screen.width : 0; state.leftEnd = fadeIn ? 0 : -screen.width; } else if (direction < 0) { state.leftStart = fadeIn ? -screen.width : 0; state.leftEnd = fadeIn ? 0: screen.width; } else { //todo replace } this.state = state; }, componentWillMount: function() { this._initState(1, false); this.props.route.updateNavbarProps = this._updateNavbarProps; }, componentWillReceiveProps: function(newProps) { if (this.props.route !== newProps.route) { this._initState(newProps.direction, !this.props.willDisappear); if (!newProps.willDisappear) { newProps.route.updateNavbarProps = this._updateNavbarProps; } } }, _updateNavbarProps: function(props) { this.props.route.navbarProps = props; this.setTimeout(this.forceUpdate, 0); }, render() { var left = (this.state.leftEnd - this.state.leftStart) * this.props.progress + this.state.leftStart; var opacity = (this.state.opacityEnd - this.state.opacityStart) * this.props.progress + this.state.opacityStart; var transitionStyle = { position: 'absolute', opacity: opacity, left: left }; var mainContent; if (this.props.route.navbarComponent) { var navbarProps = this.props.route.navbarProps ? this.props.route.navbarProps : this.props.route.navbarPassProps var NavbarComponent = this.props.route.navbarComponent; mainContent = ( ); } else { var leftCorner; var leftCornerContent; var BackButton = this.props.backButtonComponent if (this.props.route.index > 0 && BackButton) { leftCornerContent = ( ); } leftCorner = ( {leftCornerContent} ); mainContent = [leftCorner]; if (this.props.route.title) { mainContent.push( {this.props.route.title} ); } } return ( {mainContent} ); } }); var styles = StyleSheet.create({ navbar: { position: 'absolute', top: 0, left: 0, height: 64, justifyContent: 'center', flexDirection: 'row', }, corner: { flex: 1, left:0, top: 18, position: 'absolute', justifyContent: 'center', }, alignLeft: { alignItems: 'flex-start' }, title: { position: 'absolute', width: screen.width - 100, top: 28, left: 50, fontSize: 18, color: '#fff', textAlign: 'center', }, backView: { width: 44, height: 44, justifyContent: 'center', } }); module.exports = { NavBarContent: NavBarContent, NavbarBackground: NavbarBackground, }; ================================================ FILE: components/StaticNavBarContent.js ================================================ 'use strict'; var React = require('react-native'); var TimerMixin = require('react-timer-mixin'); var { StyleSheet, Text, View, TouchableHighlight } = React; var StaticNavBarContent = React.createClass({ mixins: [TimerMixin], getInitialState() { return { navbarProps: this.props.navbarPassProps }; }, componentWillMount() { this.props.currentRoute && (this.props.currentRoute.updateStaticNavbarProps = this._updateNavbarProps) }, componentWillReceiveProps(newProps) { if (this.props.currentRoute !== newProps.currentRoute) { newProps.currentRoute.updateStaticNavbarProps = this._updateNavbarProps; }; }, render() { var previousIndex = this.props.previousRoute ? this.props.previousRoute.index : null; var index = this.props.currentRoute ? this.props.currentRoute.index : null; return ( ); }, _updateNavbarProps(props) { this.setTimeout( () => { this.setState({ navbarProps: props }); }, 0 ); } }); var styles = StyleSheet.create({ navbar: { position: 'absolute', top: 0, left: 0, height: 64, justifyContent: 'center', flexDirection: 'row', }, }); module.exports = StaticNavBarContent; ================================================ FILE: example/demo1.js ================================================ var React = require('react-native'); var { Image, View, ScrollView, StyleSheet, Text, TouchableHighlight } = React; var Router = require('react-native-custom-navigation'); var navbarColors = [ '#acc7bf', '#5e5f67', '#c37070', '#eae160', '#bf7aa3', '#b7d967' ]; var NavbarContent = require('./navbar'); var screen = require('Dimensions').get('window'); var BackButton = React.createClass({ render() { return ( Back) } }); var RootController = React.createClass({ render() { return ( ); } }); var NavbarWrapper = React.createClass({ getInitialState() { return {}; }, componentWillMount() { this.setState({ style: this.props.style }); }, componentWillReceiveProps(newProps) { if (newProps.style !== this.props.style) { this.setState({ style: newProps.style }); } }, render() { return ( ); }, _push() { var colorIndex = this.props.route.index % navbarColors.length; var color = navbarColors[colorIndex]; this.props.route.push({ component: DemoView, navbarComponent: NavbarWrapper, navbarPassProps: { style: { backgroundColor: color } } }); }, }); var DemoView = React.createClass({ render() { var imageUri = 'https://divnil.com/wallpaper/iphone5/img/app/c/l/clear-your-desktop-wallpaper-for-640x1136-iphone-5-311-46_33a8356f2205d7c0be8727720a21a207_raw.jpg'; return ( Push Push with custom navbar Back Pop to top *custom bar become gray ) }, _back() { this.props.route.pop(); }, _pushToNext() { var nextIndex = ++this.props.route.index; this.props.route.push({ component: DemoView, title: nextIndex, titleStyle: { fontSize: 22, color: '#eee' } }); }, _pushToNextCustomNavbar() { var colorIndex = this.props.route.index % navbarColors.length; var color = navbarColors[colorIndex]; this.props.route.push({ component: DemoView, navbarComponent: NavbarWrapper, navbarPassProps: { style: { backgroundColor: color } } }); }, _popToTop() { this.props.route.popToTop(); }, _changeColor() { this.props.updateNavbarProps({ style: { backgroundColor: '#666' } }); }, _handleScroll(e) { var alpha = (e.nativeEvent.contentInset.top + e.nativeEvent.contentOffset.y) / 200; if (alpha < 0) alpha = 0; if (alpha > 1) alpha = 1; var style = {backgroundColor: 'rgba(102, 106, 136, ' + alpha +')'}; this.props.updateBarBackgroundStyle(style); } }); var styles = StyleSheet.create({ container: { width: screen.width, height: screen.height, }, buttonView: { justifyContent: 'center', padding: 4, width: 180, height: 60, backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 4 }, button: { marginBottom: 40 }, buttonText: { fontSize: 20, textAlign: 'center', color: '#fff', backgroundColor: 'rgba(0,0,0,0)' }, image: { alignItems: 'center', width: screen.width, height: screen.height * 1.5, }, }); module.exports = RootController; ================================================ FILE: example/demo2.js ================================================ var React = require('react-native'); var { Image, View, ScrollView, StyleSheet, Text, TouchableHighlight } = React; var Router = require('react-native-custom-navigation'); var navbarColors = [ '#acc7bf', '#5e5f67', '#c37070', '#eae160', '#bf7aa3', '#b7d967' ]; var NavbarContent = require('./navbar'); var screen = require('Dimensions').get('window'); var axisWidth = screen.width - 120; var NavbarWrapper = React.createClass({ getInitialState() { return { backgroundColor: this.props.backgroundColor, progress: 0 }; }, componentWillReceiveProps(newProps) { if (newProps.route !== this.props.route) { var route = newProps.route; var progress; var n = Math.abs(route.previousIndex - route.index) * route.progress; if (route.index > route.previousIndex) { progress = (route.previousIndex + n) * 0.2; } else { progress = (route.previousIndex - n) * 0.2; } this.setState({ progress : progress }); } if (newProps.backgroundColor !== this.props.backgroundColor) { this.setState({ backgroundColor: newProps.backgroundColor }) } }, _push() { if (this.props.route.index > 4) { return; } this.props.route.push({ component: DemoView, }); }, render() { var width = this.state.progress * axisWidth; return ( ); } }); var RootController = React.createClass({ render() { return ( ); } }); var DemoView = React.createClass({ render() { var imageUri = 'https://divnil.com/wallpaper/iphone5/img/app/c/l/clear-your-desktop-wallpaper-for-640x1136-iphone-5-311-46_33a8356f2205d7c0be8727720a21a207_raw.jpg'; return ( Push Back Pop to top {'*change progress red'} ) }, _back() { this.props.route.pop(); }, _pushToNext() { if (this.props.route.index > 4) { return; } this.props.route.push({ component: DemoView, }); }, _popToTop() { this.props.route.popToTop(); }, _changeColor() { this.props.updateNavbarProps({backgroundColor: 'rgba(255,0,0,1)'}); } }); var styles = StyleSheet.create({ container: { width: screen.width, height: screen.height, }, buttonView: { justifyContent: 'center', padding: 4, width: 180, height: 60, backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 4 }, button: { marginBottom: 40 }, buttonText: { fontSize: 20, textAlign: 'center', color: '#fff', backgroundColor: 'rgba(0,0,0,0)' }, image: { alignItems: 'center', width: screen.width, height: screen.height, justifyContent: 'center' }, activity: { position: 'absolute', height: 64, width: axisWidth, top: 0, left: (screen.width - axisWidth) / 2 }, navbar: { width:screen.width, height: 64, backgroundColor: '#5e5f67', justifyContent: 'center' }, axisView: { marginTop: 40, width: axisWidth, height: 8, borderRadius: 4, backgroundColor : '#fff', alignSelf: 'center', justifyContent: 'center' }, progress: { alignSelf: 'flex-start', height: 6, borderRadius: 3, } }); module.exports = RootController; ================================================ FILE: example/index.ios.js ================================================ /** * Sample React Native App * https://github.com/facebook/react-native */ 'use strict'; var React = require('react-native'); var Demo1 = require('./demo1'); var Demo2 = require('./demo2'); var { AppRegistry, } = React; AppRegistry.registerComponent('ReactTest', () => Demo1); ================================================ FILE: example/navbar.js ================================================ 'use strict'; var React = require('react-native'); var { View, Text, StyleSheet, TouchableHighlight, } = React; var screen = require('Dimensions').get('window'); var NavbarContent = React.createClass({ render() { return ( {'<'} {'>'} {this.props.title} ) }, }); var styles = StyleSheet.create({ container: { width: screen.width, height: 64, justifyContent: 'center', }, titleText: { fontSize: 22, color: '#fff', textAlign: 'center', alignSelf: 'center', }, buttonText: { fontSize: 32, color: '#111', textAlign: 'center', }, corner: { top: 20, flex: 1, position: 'absolute', justifyContent: 'center', }, leftCorner: { left:0, }, rightCorner: { right:0, }, backView: { width: 44, height: 44, justifyContent: 'center', } }); module.exports = NavbarContent; ================================================ FILE: example/package.json ================================================ { "name": "sample-react-native-custom-navigation", "version": "0.2.1", "private": true, "scripts": { "start": "node_modules/react-native/packager/packager.sh" }, "dependencies": { "react-native" : "^0.11.0", "react-native-custom-navigation" : "file:../" } } ================================================ FILE: index.js ================================================ 'use strict'; var React = require('react-native'); var NavBarContainer = require('./components/NavBarContainer'); var { StyleSheet, Navigator, StatusBarIOS, View, } = React; var self; var Router = React.createClass({ getInitialState: function() { self = this; return { route: null, dragStartX: null, didSwitchView: null, }; }, /* * This changes the title in the navigation bar * It should preferrably be called for "onWillFocus" instad > * > but a recent update to React Native seems to break the animation */ onDidFocus: function(route) { this.setState({ route: route }); }, onBack: function(navigator) { if (this.state.route.index > 0) { navigator.pop(); } }, onForward: function(route, navigator) { route.index = this.state.route.index + 1 || 1; navigator.push(route); }, onFirst: function(navigator) { navigator.popToTop(); }, renderScene: function(route, navigator) { var goForward = function(route) { route.index = this.state.route.index + 1 || 1; navigator.push(route); }.bind(this); var goBackwards = function() { this.onBack(navigator); }.bind(this); var goToFirstRoute = function() { navigator.popToTop(); }; var didStartDrag = function(evt) { var x = evt.nativeEvent.pageX; if (x < 28) { this.setState({ dragStartX: x, didSwitchView: false }); return true; } }.bind(this); // Recognize swipe back gesture for navigation var didMoveFinger = function(evt) { var draggedAway = ((evt.nativeEvent.pageX - this.state.dragStartX) > 30); if (!this.state.didSwitchView && draggedAway) { this.onBack(navigator); this.setState({ didSwitchView: true }); } }.bind(this); // Set to false to prevent iOS from hijacking the responder var preventDefault = function(evt) { return true; }; var updateNavbarProps = function(props) { route.updateNavbarProps && route.updateNavbarProps(props); route.updateStaticNavbarProps && route.updateStaticNavbarProps(props); } var Content = route.component; return ( { route.updateBarBackgroundStyle && route.updateBarBackgroundStyle(style) } } updateNavbarProps={updateNavbarProps} {...route.passProps}/> ) }, render: function() { // Status bar color if (this.props.statusBarColor === "black") { StatusBarIOS.setStyle(0); } else { StatusBarIOS.setStyle(1); } var navigationBar = var initialRoute = this.props.initialRoute; initialRoute.index = 0; return ( ) }, }); var styles = StyleSheet.create({ container: { flex: 1, }, }); module.exports = Router; ================================================ FILE: package.json ================================================ { "name": "react-native-custom-navigation", "version": "0.2.2", "description": "A navigation that allow to custom the appearance of navigation bar in your app", "main": "index.js", "repository": { "type": "git", "url": "https://github.com/superdami/react-native-custom-navigation.git" }, "keywords": [ "react", "react component", "react native", "react navigation", "ios", "navigation", "navbar", "router" ], "author": "Chen Zhejun ", "license": "MIT", "homepage": "https://github.com/superdami/react-native-custom-navigation", "dependencies": { "react-timer-mixin" : "*" } }