diff --git a/LICENSE b/LICENSE index 6a6dbbe..f9e1b1c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Tristan Edwards +Copyright (c) 2015 Mikael Carpenter Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index bc06e87..5adfeed 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,23 @@ -React Native Router +GB Native Router =================== + Awesome navigation for your React Native app. -![Twitter navigation](http://tristanedwards.me/u/react-native-router/native-router.gif) +![NavBar Example](http://i.imgur.com/h5jUDC8.png) Install ------- Make sure that you are in your React Native project directory and run: -```npm install react-native-router --save``` + +```$ npm install gb-native-router --save``` Usage ----- ```javascript -var Router = require('react-native-router'); +var Router = require('gb-native-router'); ``` The basics: @@ -75,26 +77,6 @@ var HelloPage = React.createClass({ Now, when you click on "Next page please!", it will go to the next page (which in this case is still HelloPage but with a new title). Keep in mind that ```this.props.toRoute()``` needs to be called from one of the top-level routes, therefore, if your link is deeply nested within multiple components, you need to make sure that the action "bubbles up" until it reaches the parent route, which in turn calls ```this.props.toRoute()```. - -A more advanced example: Twitter app ------------------------------------- - -To see more of the router in action, you can check out the Twitter example app that comes with the package. Just make sure that you first drag all the images from ```node_modules/react-native-router/twitter-example/images``` to your project's Images.xcassets: - -![Drag the assets to xcassets](http://tristanedwards.me/u/react-native-router/drag-assets.gif) - -After that, don't forget to rebuild the app in XCode before you launch the simulator. Then test the app by requiring the TwitterApp component: - -```javascript -var TwitterApp = require('./node_modules/react-native-router/twitter-example'); - -var { - AppRegistry -} = React; - -AppRegistry.registerComponent('routerTest', () => TwitterApp); -``` - Configurations -------------- @@ -102,9 +84,12 @@ The **``** object used to initialize the navigation can take the follo - `firstRoute` (required): A React class corresponding to the first page of your navigation - `headerStyle`: Apply a StyleSheet to the navigation bar. You'll probably want to change the backgroundColor for example. - `titleStyle`: Apply a StyleSheet to the navigation bar titles. Useful for changing the font or text color. +- `borderBottomWidth`: Apply a bottom border to your navbar. +- `borderColor`: Apply a border color to your bottom border. - `backButtonComponent`: By default, the navigation bar will display a simple "Back" text for the back button. To change this, you can specify your own backButton component (like in the Twitter app). - `rightCorner`: If you have the same occuring action buttons on the right side of your navigation bar (like the Twitter "Compose"-button), you can specify a component for that view. - `customAction`: A special callback prop for your action buttons (this can be handy for triggering a side menu for example). The action gets triggered from your custom `leftCorner` or `rightCorner` components by calling `this.props.customAction("someActionName")` from them. It is then picked up like this: ``. +- `hideNavigationBar`: Hide the navigation bar, always The **`this.props.toRoute()`** callback prop takes one parameter (a JavaScript object) which can have the following keys: - `name`: The name of your route, which will be shown as the title of the navigation bar unless it is changed. @@ -112,16 +97,60 @@ The **`this.props.toRoute()`** callback prop takes one parameter (a JavaScript o - `leftCorner`: Specify a component to render on the left side of the navigation bar (like the "Add people"-button on the first page of the Twitter app) - `rightCorner`: Specify a component to render on the right side of the navigation bar - `titleComponent`: Specify a component to replace the title. This could for example be your logo (as in the first page of the Instagram app) -- `headerStyle`: change the style of your header for the new route. You could for example specify a new backgroundColor and the router will automatically make a nice transition from one color to the other! -- `data`: Send custom data to your route. +- `headerStyle`: Change the style of your header for the new route. You could for example specify a new backgroundColor and the router will automatically make a nice transition from one color to the other! +- `passProps`: Takes in an object. Passes each `key: value` pair to your component as a prop. i.e. +- `trans`: If set to a truthy value it will make the navbar transparent and move your component content so that it sits behind the nav. +- `hideNavigationBar`: If set to a truthy value will hide the navigationbar out of view, and move the component so that it is at the top of the screen. +- `leftCornerProps`: If you set a `leftCorner` component you can use this property to pass props to that component. +- `rightCornerProps`: If you set a `rightCorner` component you can use this property to pass props to that component. +- `sceneConfig`: Control the animation of the route being switched. Possible values are: + - Navigator.SceneConfigs.FadeAndroid + - Navigator.SceneConfigs.FloatFromBottom + - Navigator.SceneConfigs.FloatFromBottomAndroid + - Navigator.SceneConfigs.FloatFromLeft + - Navigator.SceneConfigs.FloatFromRight + - Navigator.SceneConfigs.HorizontalSwipeJump + - Navigator.SceneConfigs.PushFromRight + - Navigator.SceneConfigs.VerticalDownSwipeJump + - Navigator.SceneConfigs.VerticalUpSwipeJump + +The **`this.props.replaceRoute`** takes in an object that can contain the same keys as `toRoute()`. The difference is that instead of adding a route to your stack, it replaces the route +that you're on with the new route that you pass it. +- This is useful for login or signup screens. If you don't want your user to be able to navigate back to it, then use `replaceRoute()` rather than `toRoute()`. + +The **`this.props.resetToRoute`** takes in an object that can contain the same keys as `toRoute()`. The difference is that instead of adding a route to your stack, it replaces the route +that you're on with the new route that you pass it, and empties the navigation stack as well. +- This is useful for going to an application after a login or signup screens. If you don't want your user to be able to navigate back to it, then use `resetToRoute()` rather than `replaceRoute()`. + +The **`this.props.setRightProps`** and **`this.props.setLeftProps`** take in an object of props and sends that to your navbar's `RightComponent` and `LeftComponent`. +- This allows you to talk directly to your navbar, because previously you could only talk to it when navigating forward or backward. + + +Events emitted by the router: + `didFocus`, emits route name + You can add a listener to a component as such: +```javascript + this.props.routeEmitter.addListener('didFocus', (name) => { + //Check if name is the current component, and if it is, act on this focus event. + }); +``` -Todos ------ -When swiping from left to right to go back, it would be nice if the navigation bar's opacity gradually changed with the user's finger position. (See [Issue #1](https://github.com/t4t5/react-native-router/issues/1)) -Questions? ---------- -If something is unclear or if you just want to say hi, feel free to [follow me on Twitter](https://twitter.com/t4t5)! +A more advanced example: Twitter app +------------------------------------ + +To see more of the router in action, you can check out the Twitter example app that comes with the package. Just make sure that you first drag all the images from ```node_modules/react-native-router/twitter-example/images``` to your project's Images.xcassets + +After that, don't forget to rebuild the app in XCode before you launch the simulator. Then test the app by requiring the TwitterApp component: + +```javascript +var TwitterApp = require('./node_modules/react-native-router/twitter-example'); +var { + AppRegistry +} = React; + +AppRegistry.registerComponent('routerTest', () => TwitterApp); +``` diff --git a/components/NavBarContainer.js b/components/NavBarContainer.js index 7fda8ff..c2cd6d1 100644 --- a/components/NavBarContainer.js +++ b/components/NavBarContainer.js @@ -9,7 +9,6 @@ var { View } = React; - var NavBarContainer = React.createClass({ getInitialState: function() { @@ -41,40 +40,90 @@ var NavBarContainer = React.createClass({ // We render both the current and the previous navbar (for animation) render: function() { - return ( - - + ); + } else if (this.props.currentRoute.hideNavigationBar) { + navbarContent = ( + - + + ); + } else { + navbarContent = ( + + leftProps={this.props.leftProps} + rightProps={this.props.rightProps} + titleProps={this.props.titleProps} + customAction={this.customAction}> + + ); + } + + return ( + + {navbarContent} - ) + ); } }); - var styles = StyleSheet.create({ navbarContainer: { position: 'absolute', top: 0, left: 0, right: 0, - height: 64, - backgroundColor: '#5589B7' + height: 64 + }, + navbarContainerHidden: { + position: 'absolute', + top: -64, + left: 0, + right: 0, + height: 64 } }); - module.exports = NavBarContainer; diff --git a/components/NavBarContent.js b/components/NavBarContent.js index 243f251..cff57a6 100644 --- a/components/NavBarContent.js +++ b/components/NavBarContent.js @@ -1,39 +1,38 @@ 'use strict'; var React = require('react-native'); -var tweenState = require('react-tween-state'); // Animate header - var NavButton = require('./NavButton'); var { StyleSheet, Text, - View + View, + Animated, + Easing } = React; - var NavBarContent = React.createClass({ - mixins: [tweenState.Mixin], - getInitialState: function() { return { - opacity: this.props.willDisappear ? 1 : 0 + opacity: this.props.willDisappear ? new Animated.Value(1) : new Animated.Value(0) }; }, componentWillReceiveProps: function(newProps) { if (newProps.route !== this.props.route) { - this.setState({ - opacity: this.props.willDisappear ? 1 : 0 - }); + this.state.opacity.setValue(this.props.willDisappear ? 1 : 0); setTimeout(() => { - this.tweenState('opacity', { - easing: tweenState.easingTypes.easeInOutQuad, - duration: 200, - endValue: 1 - }); + Animated.timing( + this.state.opacity, + { + fromValue: this.props.willDisappear ? 1 : 0, + toValue: this.props.willDisappear ? 0 : 1, + duration: 300, + easing: Easing.easeOutQuad + } + ).start(); }, 0); } }, @@ -53,24 +52,30 @@ var NavBarContent = React.createClass({ }, render() { - var transitionStyle = { - opacity: this.getTweeningValue('opacity'), - }; - - var leftCorner; - var rightCorner; - var titleComponent; - + var transitionStyle = { + opacity: this.state.opacity, + }, + leftCorner, + LeftCorner, + rightCorner, + RightCorner, + titleComponent, + leftCornerContent, + rightCornerContent, + titleContent, + TitleComponent, + trans, + width, + color; /** * Set leftCorner * (defaults to "Back"-button for routes with index > 0) */ - var leftCornerContent; if (this.props.route.leftCorner) { - var LeftCorner = this.props.route.leftCorner; - leftCornerContent = ; + LeftCorner = this.props.route.leftCorner; + leftCornerContent = ; } else if (this.props.route.index > 0) { leftCornerContent = ; } @@ -84,11 +89,10 @@ var NavBarContent = React.createClass({ /** * Set rightCorner */ - var rightCornerContent; if (this.props.route.rightCorner || this.props.rightCorner) { - var RightCorner = this.props.route.rightCorner || this.props.rightCorner; - rightCornerContent = ; + RightCorner = this.props.route.rightCorner || this.props.rightCorner; + rightCornerContent = ; } rightCorner = ( @@ -100,32 +104,39 @@ var NavBarContent = React.createClass({ /** * Set title message */ - var titleContent; + if (this.props.route.titleComponent) { - var TitleComponent = this.props.route.titleComponent; - titleContent = ; + TitleComponent = this.props.route.titleComponent; + titleContent = ; } else { titleContent = ( - + {this.props.route.name} ); } titleComponent = ( - + {titleContent} ); + if(this.props.route.trans === true) + trans = { backgroundColor: 'transparent', borderBottomWidth: 0 }; + else + trans = {}; + + width = this.props.borderBottomWidth ? this.props.borderBottomWidth : 0; + color = this.props.borderColor ? this.props.borderColor : null; return ( - + {leftCorner} {titleComponent} {rightCorner} - + ); } }); diff --git a/components/NavButton.js b/components/NavButton.js index 2ec6f26..f22e11f 100644 --- a/components/NavButton.js +++ b/components/NavButton.js @@ -17,11 +17,12 @@ var NavButton = React.createClass({ }, render() { - var backButton; + var backButton, + BackButton; if (this.props.backButtonComponent) { - var BackButton = this.props.backButtonComponent; - backButton = + BackButton = this.props.backButtonComponent; + backButton = } else { backButton = Back } @@ -30,7 +31,7 @@ var NavButton = React.createClass({ {backButton} - ) + ); } }); diff --git a/index.js b/index.js index e9e4350..b0ee030 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ 'use strict'; var React = require('react-native'); +var {EventEmitter} = require('fbemitter'); var NavBarContainer = require('./components/NavBarContainer'); @@ -9,89 +10,101 @@ var { Navigator, StatusBarIOS, View, + Platform } = React; - -var Router = React.createClass({ - - getInitialState: function() { - return { +class Router extends React.Component{ + constructor(props) { + super(props); + this.state = { route: { name: null, index: null - }, - dragStartX: null, - didSwitchView: null, - } - }, + } + }; + this.emitter = new EventEmitter(); + } - /* - * 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) { + onWillFocus(route) { this.setState({ route: route }); - }, + this.emitter.emit('willFocus', route.name); + } + + onDidFocus(route) { + this.emitter.emit('didFocus', route.name); + } - onBack: function(navigator) { + onBack(navigator) { if (this.state.route.index > 0) { navigator.pop(); } - }, + } - onForward: function(route, navigator) { + onForward(route, navigator) { route.index = this.state.route.index + 1 || 1; navigator.push(route); - }, + } + + setRightProps(props) { + this.setState({ rightProps: props }); + } - customAction: function(opts) { + setLeftProps(props) { + this.setState({ leftProps: props }); + } + + setTitleProps(props) { + this.setState({ titleProps: props }); + } + + customAction(opts) { this.props.customAction(opts); - }, + } - renderScene: function(route, navigator) { + configureScene(route) { + return route.sceneConfig || Navigator.SceneConfigs.FloatFromRight; + } + + renderScene(route, navigator) { var goForward = function(route) { route.index = this.state.route.index + 1 || 1; navigator.push(route); }.bind(this); + var replaceRoute = function(route) { + route.index = this.state.route.index || 0; + navigator.replace(route); + }.bind(this); + + var resetToRoute = function(route) { + route.index = 0; + navigator.resetTo(route); + }.bind(this); + var goBackwards = function() { this.onBack(navigator); }.bind(this); var goToFirstRoute = function() { - navigator.popToTop() + navigator.popToTop(); }; - var customAction = function(opts) { - this.customAction(opts); + var setRightProps = function(props) { + this.setState({ rightProps: props }); }.bind(this); - var didStartDrag = function(evt) { - var x = evt.nativeEvent.pageX; - if (x < 28) { - this.setState({ - dragStartX: x, - didSwitchView: false - }); - return true; - } + var setLeftProps = function(props) { + this.setState({ leftProps: props }); }.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); + var setTitleProps = function(props) { + this.setState({ titleProps: props }); + }.bind(this); - // Set to false to prevent iOS from hijacking the responder - var preventDefault = function(evt) { - return true; - }; + var customAction = function(opts) { + this.customAction(opts); + }.bind(this); var Content = route.component; @@ -100,72 +113,92 @@ var Router = React.createClass({ if (this.props.hideNavigationBar) { extraStyling.marginTop = 0; } - + + var margin; + if(route.trans) { + margin = 0; + } else if (this.props.hideNavigationBar || route.hideNavigationBar) { + margin = this.props.noStatusBar ? 0 : 20; + } else { + margin = 64; + } + return ( + style={[styles.container, this.props.bgStyle, extraStyling, {marginTop: margin}]}> - ) - - }, + ); - render: function() { + } + render() { + var navigationBar; // Status bar color - if (this.props.statusBarColor === "black") { - StatusBarIOS.setStyle(0); - } else { - StatusBarIOS.setStyle(1); + if (Platform.OS === 'ios') { + if (this.props.statusBarColor === 'black') { + StatusBarIOS.setStyle(0); + } else { + StatusBarIOS.setStyle(1); + } + } else if (Platform.OS === 'android') { + // no android version yet } - var navigationBar; - if (!this.props.hideNavigationBar) { - navigationBar = + navigationBar = } return ( - ) + ); } -}); - +} var styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#F5FCFF', - marginTop: 64 + backgroundColor: '#FFFFFF' }, }); - module.exports = Router; diff --git a/package.json b/package.json index 7351d7a..803fdd0 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,15 @@ { - "name": "react-native-router", - "version": "0.2.1", - "description": "Awesome navigation for your native app.", + "name": "gb-native-router", + "version": "0.6.0", + "description": "Easy to use Navigator for your native app.", + "author": "Mikael Carpenter ", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", - "url": "https://github.com/t4t5/react-native-router.git" + "url": "https://github.com/MikaelCarpenter/gb-native-router.git" }, "keywords": [ "react", @@ -20,14 +21,14 @@ "navbar", "router" ], - "author": "Tristan Edwards (http://tristanedwards.me)", "license": "MIT", "bugs": { - "url": "https://github.com/t4t5/react-native-router/issues" + "url": "https://github.com/MikaelCarpenter/gb-native-router/issues" }, - "homepage": "https://github.com/t4t5/react-native-router", - "dependencies": { - "react-native": "^0.4.x", - "react-tween-state": "0.0.5" + "homepage": "https://github.com/MikaelCarpenter/gb-native-router", + "dependencies": {}, + "peerDependencies": { + "react-native": ">=0.12.0 || 0.5.0-rc1 || 0.6.0-rc || 0.7.0-rc || 0.7.0-rc.2 || 0.8.0-rc || 0.8.0-rc.2 || 0.9.0-rc || 0.10.0-rc || 0.11.0-rc || 0.12.0-rc || 0.13.0-rc || 0.14.0-rc || 0.15.0-rc || 0.16.0-rc", + "fbemitter": "^2.0.0" } }