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

Case 2:
Using singleton navigation bar for all views

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" : "*"
}
}