diff --git a/.eslintrc b/.eslintrc
index 430f354..01a2c88 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -157,11 +157,9 @@
"no-lonely-if": 0,
"no-new-object": 1,
"no-spaced-func": 1,
- "semi-spacing": 1,
"no-ternary": 0,
"no-trailing-spaces": 1,
"no-underscore-dangle": 0,
- "no-extra-parens": 0,
"no-mixed-spaces-and-tabs": 1,
"quotes": [1, "single", "avoid-escape"],
"quote-props": 0,
diff --git a/.flowconfig b/.flowconfig
new file mode 100644
index 0000000..f56848d
--- /dev/null
+++ b/.flowconfig
@@ -0,0 +1,96 @@
+[ignore]
+
+# We fork some components by platform.
+.*/*.web.js
+.*/*.android.js
+
+# Some modules have their own node_modules with overlap
+.*/node_modules/node-haste/.*
+
+# Ugh
+.*/node_modules/babel.*
+.*/node_modules/babylon.*
+.*/node_modules/invariant.*
+
+# Ignore react and fbjs where there are overlaps, but don't ignore
+# anything that react-native relies on
+.*/node_modules/fbjs/lib/Map.js
+.*/node_modules/fbjs/lib/fetch.js
+.*/node_modules/fbjs/lib/ExecutionEnvironment.js
+.*/node_modules/fbjs/lib/ErrorUtils.js
+
+# Flow has a built-in definition for the 'react' module which we prefer to use
+# over the currently-untyped source
+.*/node_modules/react/react.js
+.*/node_modules/react/lib/React.js
+.*/node_modules/react/lib/ReactDOM.js
+
+.*/__mocks__/.*
+.*/__tests__/.*
+
+.*/commoner/test/source/widget/share.js
+
+# Ignore commoner tests
+.*/node_modules/commoner/test/.*
+
+# See https://github.com/facebook/flow/issues/442
+.*/react-tools/node_modules/commoner/lib/reader.js
+
+# Ignore jest
+.*/node_modules/jest-cli/.*
+
+# Ignore Website
+.*/website/.*
+
+# Ignore generators
+.*/local-cli/generator.*
+
+# Ignore BUCK generated folders
+.*\.buckd/
+
+.*/node_modules/is-my-json-valid/test/.*\.json
+.*/node_modules/iconv-lite/encodings/tables/.*\.json
+.*/node_modules/y18n/test/.*\.json
+.*/node_modules/spdx-license-ids/spdx-license-ids.json
+.*/node_modules/spdx-exceptions/index.json
+.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json
+.*/node_modules/resolve/lib/core.json
+.*/node_modules/jsonparse/samplejson/.*\.json
+.*/node_modules/json5/test/.*\.json
+.*/node_modules/ua-parser-js/test/.*\.json
+.*/node_modules/builtin-modules/builtin-modules.json
+.*/node_modules/binary-extensions/binary-extensions.json
+.*/node_modules/url-regex/tlds.json
+.*/node_modules/joi/.*\.json
+.*/node_modules/isemail/.*\.json
+.*/node_modules/tr46/.*\.json
+
+
+[include]
+
+[libs]
+node_modules/react-native/Libraries/react-native/react-native-interface.js
+node_modules/react-native/flow
+flow/
+
+[options]
+module.system=haste
+
+esproposal.class_static_fields=enable
+esproposal.class_instance_fields=enable
+
+munge_underscores=true
+
+module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
+module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\)$' -> 'RelativeImageStub'
+
+suppress_type=$FlowIssue
+suppress_type=$FlowFixMe
+suppress_type=$FixMe
+
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-3]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-3]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
+
+[version]
+0.23.0
diff --git a/README.md b/README.md
index 00580dc..a29e898 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,13 @@
A React Native Navigator component wrapper that implements nested navigators for
both push and modal transitions.
+
+
+
+
+## Disclaimer
+This component uses the JS `Navigator` implementation of React Native. Future versions of the component will use the actual `NavigatorExperimental`.
+
## Installation
You can install this component through ``npm``:
@@ -9,9 +16,9 @@ You can install this component through ``npm``:
npm i react-native-navigator-wrapper --save
```
-You also need to install the awesome
+Configure the awesome
[``react-native-vector-icons``](https://github.com/oblador/react-native-vector-icons#installation)
-from Joel Oblador (in order to use the back button arrow icon) and include the
+from Joel Oblador to display the back button icons. Remember to include the
``Ionicons.ttf`` font in your project. All the components of the library are
written in ES6/ES7 style.
@@ -48,8 +55,7 @@ modal component.
## Usage
This library can be used in several ways. It's composed from a couple of different
components that interact with each other. In short, it has a navigation bar that
-mimics the iOS navigation bar and two navigation wrappers. Expect an Android
-style navigation bar soon.
+mimics the iOS and Android navigation bar and two navigation wrappers.
### Nested navigation with ``TopNavigatorWrapper``
You can use ``TopNavigatorWrapper`` component to bring the nested navigator
@@ -57,34 +63,29 @@ strategy just importing the component and wrapping whatever you want to render
inside it:
```js
-import React from 'react-native'
+import React from 'react'
import { TopNavigatorWrapper } from 'react-native-navigator-wrapper'
import MyComponent from './MyComponent'
class MyApp extends React.Component {
render () {
return (
-
+
+
+
)
}
}
```
-You component ``MyComponent`` will have two props, **navigator** and
-**topNavigator**. They will let you to push new components from right using
-the first one or open a modal pushing from the second one.
+You component ``MyComponent`` will have a **topNavigator** prop. It will let you to push new components in a modal-style, opening from bottom to top.
### Navigation with ``NavigatorWrapper``
If you just want to use the navigation bar inside a navigator, use the
``NavigatorWrapper`` component:
```js
-import React from 'react-native'
+import React from 'react'
import { NavigatorWrapper } from 'react-native-navigator-wrapper'
class MyComponent extends React.Component {
@@ -109,12 +110,15 @@ that will let you to keep pushing components in the stack.
The React Native ``Navigator.NavigatorBar`` component has an object called
``routeMapper`` that configures the three components that can be displayed
inside the navigation bar: ``LeftButton``, ``RightButton`` and ``Title``.
-This library auto-generates a default route mapper object that displays an iOS
-style back button, a title and accepts a right element to render.
+This library auto-generates a default route mapper object that displays an iOS & Android style back button, a title and accepts a right element to render.
It also provides functions to generate each of the route mapper components so
you can build a completely custom navigation bar for each ``NavigatorWrapper``.
See the source code for more information.
+## 🚧 Roadmap
+
+- [ ] Handle several hardware back button actions with multiple navigators (Android).
+
## License
MIT.
diff --git a/index.js b/index.js
index b0cdc6a..bae946c 100644
--- a/index.js
+++ b/index.js
@@ -1,3 +1,5 @@
+/* @flow */
+
import NavBarBackButton from './lib/NavBarBackButton'
import NavBar from './lib/NavBar'
import NavigatorWrapper from './lib/NavigatorWrapper'
diff --git a/lib/NavBar.android.js b/lib/NavBar.android.js
index 362f979..07ce843 100644
--- a/lib/NavBar.android.js
+++ b/lib/NavBar.android.js
@@ -1,11 +1,17 @@
-import React, {
+/* @flow */
+
+import React from 'react'
+import {
Navigator,
StyleSheet,
} from 'react-native'
const stylesAndroid = StyleSheet.create({
navBar: {
- backgroundColor: 'white',
+ backgroundColor: '#f5f5f5',
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
}
})
diff --git a/lib/NavBar.ios.js b/lib/NavBar.ios.js
index 8c59751..853c4f1 100644
--- a/lib/NavBar.ios.js
+++ b/lib/NavBar.ios.js
@@ -1,4 +1,7 @@
-import React, { Navigator, StyleSheet, PixelRatio } from 'react-native'
+/* @flow */
+
+import React from 'react'
+import { Navigator, StyleSheet, PixelRatio } from 'react-native'
class NavBar extends React.Component {
updateProgress (progress, fromIndex, toIndex) {
diff --git a/lib/NavBarBackButton.android.js b/lib/NavBarBackButton.android.js
index a3ec67a..5796195 100644
--- a/lib/NavBarBackButton.android.js
+++ b/lib/NavBarBackButton.android.js
@@ -1,6 +1,7 @@
-import React, { TouchableOpacity, Text, PropTypes, StyleSheet } from 'react-native'
-import StyleSheetPropType from 'react-native/Libraries/StyleSheet/StyleSheetPropType'
-import TextStylePropTypes from 'react-native/Libraries/Text/TextStylePropTypes'
+/* @flow */
+
+import React, { PropTypes } from 'react'
+import { TouchableOpacity, Text, StyleSheet } from 'react-native'
import Ionicon from 'react-native-vector-icons/Ionicons'
class NavBarBackButton extends React.Component {
@@ -32,7 +33,7 @@ class NavBarBackButton extends React.Component {
-
{this._renderBackTitle.bind(this)}
@@ -44,7 +45,7 @@ NavBarBackButton.propTypes = {
...TouchableOpacity.propTypes,
tintColor: PropTypes.string,
children: PropTypes.string.isRequired,
- style: StyleSheetPropType(TextStylePropTypes),
+ style: Text.propTypes.style,
showBackTitle: PropTypes.bool,
}
diff --git a/lib/NavBarBackButton.ios.js b/lib/NavBarBackButton.ios.js
index d74067b..90df14a 100644
--- a/lib/NavBarBackButton.ios.js
+++ b/lib/NavBarBackButton.ios.js
@@ -1,10 +1,13 @@
-import React, { TouchableOpacity, Text, PropTypes, StyleSheet } from 'react-native'
-import StyleSheetPropType from 'react-native/Libraries/StyleSheet/StyleSheetPropType'
-import TextStylePropTypes from 'react-native/Libraries/Text/TextStylePropTypes'
+/* @flow */
+
+import React, { PropTypes } from 'react'
+import { TouchableOpacity, Text, StyleSheet } from 'react-native'
import Ionicon from 'react-native-vector-icons/Ionicons'
class NavBarBackButton extends React.Component {
- constructor (props) {
+ state: Object;
+
+ constructor (props: Object) {
super(props)
this.state = {
tintColor: props.tintColor || 'black'
@@ -34,7 +37,7 @@ class NavBarBackButton extends React.Component {
style={styles.container}>
- {this._renderBackTitle.bind(this)}
+ {this._renderBackTitle()}
)
}
@@ -44,7 +47,7 @@ NavBarBackButton.propTypes = {
...TouchableOpacity.propTypes,
tintColor: PropTypes.string,
children: PropTypes.string.isRequired,
- style: StyleSheetPropType(TextStylePropTypes),
+ style: Text.propTypes.style,
showBackTitle: PropTypes.bool,
}
diff --git a/lib/NavigatorWrapper.js b/lib/NavigatorWrapper.js
index 3c3b6cd..628e346 100644
--- a/lib/NavigatorWrapper.js
+++ b/lib/NavigatorWrapper.js
@@ -1,15 +1,23 @@
-import React, {
+/* @flow */
+
+import React, { PropTypes } from 'react'
+import {
View,
- PropTypes,
Navigator,
BackAndroid,
- Platform
+ Platform,
} from 'react-native'
-import NavBar from './NavBar'
import { defaultRouteMapper } from './RouteMapper'
+import NavBar from './NavBar'
class NavigatorWrapper extends React.Component {
static isAndroid = Platform.OS !== 'ios';
+ static androidToolbarHeight = 56;
+ static iosStatusAndNavbarHeight = 64;
+
+ navigator: Object;
+ firstComponentInStack: boolean;
+ bindedBackFunction: Function;
_handleAndroidBackButton () {
if (this.navigator && !this.firstComponentInStack) {
@@ -19,15 +27,14 @@ class NavigatorWrapper extends React.Component {
return false
}
- constructor (props) {
+ constructor (props: Object) {
super(props)
- this.navigator = undefined
this.firstComponentInStack = true
}
- componentDidMount() {
+ componentDidMount () {
// Automatically handle back button under Android platform
- if (NavigatorWrapper.isAndroid && this.props.initialRoute.handleBackAndroid) {
+ if (NavigatorWrapper.isAndroid && this.props.initialRoute.handleBackAndroid !== false) {
this.bindedBackFunction = this._handleAndroidBackButton.bind(this)
BackAndroid.addEventListener('hardwareBackPress', this.bindedBackFunction)
}
@@ -35,23 +42,27 @@ class NavigatorWrapper extends React.Component {
componentWillUnmount () {
if (NavigatorWrapper.isAndroid) {
- BackAndroid.removeEventListener('hardwareBackPress', this.bindedBackFunction)
+ BackAndroid.removeEventListener(
+ 'hardwareBackPress',
+ this.bindedBackFunction
+ )
}
}
- renderScene (route, navigator) {
- let marginTop = 64
- this.firstComponentInStack = route.handleBackAndroid
+ renderScene (route: Object, navigator: Object) {
+ let marginTop = NavigatorWrapper.iosStatusAndNavbarHeight
+ this.firstComponentInStack = (navigator.state.routeStack.length === 1)
if (NavigatorWrapper.isAndroid) {
// Save navigator to handle back button under Android
if (!this.navigator) {
this.navigator = navigator
}
- marginTop = 56
+ // Configure right navbar height
+ marginTop = NavigatorWrapper.androidToolbarHeight
}
const RenderComponent = route.component
return (
-
+
return (
navAnimation}
+ configureScene={(route, routeStack) => this.props.navigationBarAnimation || navAnimation}
initialRoute={this.props.initialRoute}
initialRouteStack={this.props.initialRouteStack}
- navigationBar={
-
- }
+ navigationBar={NavigationBar}
renderScene={this.renderScene.bind(this)}
/>
)
@@ -96,6 +104,7 @@ NavigatorWrapper.propTypes = {
leftElement: PropTypes.node,
textElement: PropTypes.node,
rightElement: PropTypes.node,
+ // Set to false to disable the back button under Android
handleBackAndroid: PropTypes.bool,
}),
initialRouteStack: PropTypes.arrayOf(PropTypes.object),
@@ -111,11 +120,27 @@ NavigatorWrapper.propTypes = {
*/
navBarStyle: View.propTypes.style,
+ /**
+ * Defines the navigator style. Useful for changing the background color
+ * while transitioning
+ */
+ style: View.propTypes.style,
+
/**
* A ``routeMapper`` object to customize Left, Title and Right components for
* the ``NavigationBar``.
*/
routeMapper: PropTypes.object,
+
+ /**
+ * Optional prop to hide the navigation bar
+ */
+ hideNavBar: PropTypes.bool,
+
+ /**
+ * Optional navigation scene config animation
+ */
+ navigationBarAnimation: PropTypes.object,
}
export default NavigatorWrapper
diff --git a/lib/RouteMapper.js b/lib/RouteMapper.js
index fa78a2c..a7fe815 100644
--- a/lib/RouteMapper.js
+++ b/lib/RouteMapper.js
@@ -1,14 +1,22 @@
-import React, { View, Text } from 'react-native'
+/* @flow */
+
+import React from 'react'
+import { View, Text, StyleSheet, Platform } from 'react-native'
import NavBarBackButton from './NavBarBackButton'
export function leftButtonRouteMapperGenerator (BackComponent, styles, tintColor, topNavigator) {
return {
LeftButton: (route, navigator, index, navState) => {
if (route.leftElement) {
- return React.cloneElement(route.leftElement, {
+ const leftElement = React.cloneElement(route.leftElement, {
navigator: navigator,
topNavigator: topNavigator,
})
+ return (
+
+ {leftElement}
+
+ )
} else if (index > 0) {
const previousRoute = navState.routeStack[index - 1]
return (
@@ -16,7 +24,8 @@ export function leftButtonRouteMapperGenerator (BackComponent, styles, tintColor
navigator.pop()}
style={[{flex: 1}, styles]}
- tintColor={tintColor}>
+ tintColor={tintColor}
+ showBackTitle={true}>
{previousRoute.title}
@@ -31,10 +40,15 @@ export function rightButtonRouteMapperGenerator (RightComponent, topNavigator) {
return {
RightButton: (route, navigator, index, navState) => {
if (route.rightElement) {
- return React.cloneElement(route.rightElement, {
+ const rightElement = React.cloneElement(route.rightElement, {
navigator: navigator,
topNavigator: topNavigator,
})
+ return (
+
+ {rightElement}
+
+ )
} else if (RightComponent) {
return
}
@@ -68,10 +82,10 @@ export class CenteredText extends React.Component {
}
}
-const defaultStyles = React.StyleSheet.create({
+const defaultStyles = StyleSheet.create({
back: {
flex: 1,
- color: 'black',
+ color: (Platform.OS === 'ios') ? '#0076ff' : '#607D8B',
},
navFont: {
fontSize: 17,
@@ -79,11 +93,16 @@ const defaultStyles = React.StyleSheet.create({
navText: {
flex: 1,
},
+ elementContainer: {
+ flex: 1,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
})
export function defaultRouteMapper () {
return {
- ...leftButtonRouteMapperGenerator(NavBarBackButton, [defaultStyles.navFont, defaultStyles.back], 'black'),
+ ...leftButtonRouteMapperGenerator(NavBarBackButton, [defaultStyles.navFont, defaultStyles.back], (Platform.OS === 'ios') ? '#0076ff' : '#607D8B'),
...rightButtonRouteMapperGenerator(),
...titleRouteMapperGenerator(CenteredText, [defaultStyles.navFont, defaultStyles.navText])
}
diff --git a/lib/TopNavigatorWrapper.js b/lib/TopNavigatorWrapper.js
index 780280a..1d3fca0 100644
--- a/lib/TopNavigatorWrapper.js
+++ b/lib/TopNavigatorWrapper.js
@@ -1,23 +1,27 @@
-import React, { View, Navigator, PropTypes, Platform } from 'react-native'
+/* @flow */
+
+import React, { PropTypes } from 'react'
+import { View, Navigator, Platform } from 'react-native'
import NavigatorWrapper from './NavigatorWrapper'
+import { defaultRouteMapper } from './RouteMapper'
class TopNavigatorWrapper extends React.Component {
static isAndroid = Platform.OS !== 'ios';
- _renderScene (route, navigator) {
- // Render the inner component or the modal. This component will be the one
- // with push-like transitions.
+ _renderScene (route: Object, navigator: Object) {
+ // Render the inner component or the modal. This is basically a container
+ // that can handle anything, usually a TabBarIOS with more NavigatorWrappers
+ // inside.
if (route.id === 'mainComponent') {
+ // Inject the top navigator into the inner component in order to be able
+ // to open a modal from anywhere
+ const children = React.cloneElement(this.props.children, {
+ topNavigator: navigator
+ })
return (
-
+
+ {children}
+
)
}
@@ -27,17 +31,17 @@ class TopNavigatorWrapper extends React.Component {
//
// By generating the routeMapper from a function, we can pass the outer
// modal navigator into the route mapper.
+ const modalRouteMapper = this.props.modalRouteMapper || defaultRouteMapper
return (
)
}
@@ -50,6 +54,7 @@ class TopNavigatorWrapper extends React.Component {
renderScene={(route, navigator) => (this._renderScene(route, navigator))}
initialRoute={{id: 'mainComponent'}}
configureScene={(route, routeStack) => modalAnimation}
+ style={this.props.modalStyle}
/>
)
}
@@ -57,27 +62,29 @@ class TopNavigatorWrapper extends React.Component {
TopNavigatorWrapper.propTypes = {
/**
- * Provide the initial route or the initial route stack.
+ * Optional style for the default modal navigation bar.
*/
- initialRoute: PropTypes.shape({
- component: PropTypes.func.isRequired,
- title: PropTypes.string.isRequired,
- passProps: PropTypes.object,
- }),
- initialRouteStack: PropTypes.arrayOf(PropTypes.object),
+ modalNavBarStyle: View.propTypes.style,
/**
- * Optional style for the default navigation bar.
+ * Route mapper for the modal component
*/
- navBarStyle: View.propTypes.style,
+ modalRouteMapper: PropTypes.func,
/**
- * Optional style for the default modal navigation bar.
+ * The style of the inner container
*/
- modalNavBarStyle: View.propTypes.style,
+ containerStyle: View.propTypes.style,
- routeMapper: PropTypes.func,
- modalRouteMapper: PropTypes.func,
+ /**
+ * The style of the modal transition
+ */
+ modalStyle: View.propTypes.style,
+
+ /**
+ * Hides the modal navigation bar
+ */
+ hideNavBar: PropTypes.bool,
}
export default TopNavigatorWrapper
diff --git a/package.json b/package.json
index 4789f67..481a692 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-navigator-wrapper",
- "version": "0.1.0",
+ "version": "0.2.0",
"description": "A React Native Navigator component wrapper that implements nested navigators for both push and modal transitions.",
"main": "index.js",
"scripts": {
@@ -30,6 +30,6 @@
"eslint-plugin-react": "^3.7.1"
},
"dependencies": {
- "react-native-vector-icons": ">=1.0.4"
+ "react-native-vector-icons": "^2.0.0"
}
}