[ReactNative] OSS JSNavigationStack w/ Examples

This commit is contained in:
Eric Vicenti
2015-03-24 09:55:38 -07:00
parent 20a0bea197
commit c9a40a989b
23 changed files with 4026 additions and 13 deletions

View File

@@ -0,0 +1,279 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule BreadcrumbNavSample
*/
'use strict';
var BreadcrumbNavigationBar = require('BreadcrumbNavigationBar');
var JSNavigationStack = require('JSNavigationStack');
var React = require('React');
var StyleSheet = require('StyleSheet');
var ScrollView = require('ScrollView');
var TabBarItemIOS = require('TabBarItemIOS');
var TabBarIOS = require('TabBarIOS');
var Text = require('Text');
var TouchableBounce = require('TouchableBounce');
var View = require('View');
var SAMPLE_TEXT = 'Top Pushes. Middle Replaces. Bottom Pops.';
var _getRandomRoute = function() {
return {
backButtonTitle: 'Back' + ('' + 10 * Math.random()).substr(0, 1),
content:
SAMPLE_TEXT + '\nHere\'s a random number ' + Math.random(),
title: Math.random() > 0.5 ? 'Hello' : 'There',
rightButtonTitle: Math.random() > 0.5 ? 'Right' : 'Button',
};
};
var SampleNavigationBarRouteMapper = {
rightContentForRoute: function(route, navigationOperations) {
if (route.rightButtonTitle) {
return (
<Text style={[styles.titleText, styles.filterText]}>
{route.rightButtonTitle}
</Text>
);
} else {
return null;
}
},
titleContentForRoute: function(route, navigationOperations) {
return (
<TouchableBounce
onPress={() => navigationOperations.push(_getRandomRoute())}>
<View>
<Text style={styles.titleText}>{route.title}</Text>
</View>
</TouchableBounce>
);
},
iconForRoute: function(route, navigationOperations) {
var onPress =
navigationOperations.popToRoute.bind(navigationOperations, route);
return (
<TouchableBounce onPress={onPress}>
<View style={styles.crumbIconPlaceholder} />
</TouchableBounce>
);
},
separatorForRoute: function(route, navigationOperations) {
return (
<TouchableBounce onPress={navigationOperations.pop}>
<View style={styles.crumbSeparatorPlaceholder} />
</TouchableBounce>
);
}
};
var SampleRouteMapper = {
delay: 400, // Just to test for race conditions with native nav.
navigationItemForRoute: function(route, navigationOperations) {
var content = route.content;
return (
<ScrollView>
<View style={styles.scene}>
<TouchableBounce
onPress={this._pushRouteLater(navigationOperations.push)}>
<View style={styles.button}>
<Text style={styles.buttonText}>request push soon</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={this._pushRouteLater(navigationOperations.replace)}>
<View style={styles.button}>
<Text>{content}</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={this._pushRouteLater(navigationOperations.replace)}>
<View style={styles.button}>
<Text>{content}</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={this._pushRouteLater(navigationOperations.replace)}>
<View style={styles.button}>
<Text>{content}</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={this._pushRouteLater(navigationOperations.replace)}>
<View style={styles.button}>
<Text>{content}</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={this._pushRouteLater(navigationOperations.replace)}>
<View style={styles.button}>
<Text>{content}</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={this._popRouteLater(navigationOperations.pop)}>
<View style={styles.button}>
<Text style={styles.buttonText}>request pop soon</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={
this._immediatelySetTwoItemsLater(
navigationOperations.immediatelyResetRouteStack
)
}>
<View style={styles.button}>
<Text style={styles.buttonText}>Immediate set two routes</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={this._popToTopLater(navigationOperations.popToTop)}>
<View style={styles.button}>
<Text style={styles.buttonText}>pop to top soon</Text>
</View>
</TouchableBounce>
</View>
</ScrollView>
);
},
_popToTopLater: function(popToTop) {
return () => setTimeout(popToTop, SampleRouteMapper.delay);
},
_pushRouteLater: function(push) {
return () => setTimeout(
() => push(_getRandomRoute()),
SampleRouteMapper.delay
);
},
_immediatelySetTwoItemsLater: function(immediatelyResetRouteStack) {
return () => setTimeout(
() => immediatelyResetRouteStack([
_getRandomRoute(),
_getRandomRoute(),
])
);
},
_popRouteLater: function(pop) {
return () => setTimeout(pop, SampleRouteMapper.delay);
},
};
var BreadcrumbNavSample = React.createClass({
getInitialState: function() {
return {
selectedTab: 0,
};
},
render: function() {
var initialRoute = {
backButtonTitle: 'Start', // no back button for initial scene
content: SAMPLE_TEXT,
title: 'Campaigns',
rightButtonTitle: 'Filter',
};
return (
<TabBarIOS>
<TabBarItemIOS
selected={this.state.selectedTab === 0}
onPress={this.onTabSelect.bind(this, 0)}
icon={require('image!madman_tabnav_list')}
title="One">
<JSNavigationStack
debugOverlay={false}
style={[styles.appContainer]}
initialRoute={initialRoute}
routeMapper={SampleRouteMapper}
navigationBar={
<BreadcrumbNavigationBar
navigationBarRouteMapper={SampleNavigationBarRouteMapper}
/>
}
/>
</TabBarItemIOS>
<TabBarItemIOS
selected={this.state.selectedTab === 1}
onPress={this.onTabSelect.bind(this, 1)}
icon={require('image!madman_tabnav_create')}
title="Two">
<JSNavigationStack
animationConfigRouteMapper={() => JSNavigationStack.AnimationConfigs.FloatFromBottom}
debugOverlay={false}
style={[styles.appContainer]}
initialRoute={initialRoute}
routeMapper={SampleRouteMapper}
navigationBar={
<BreadcrumbNavigationBar
navigationBarRouteMapper={SampleNavigationBarRouteMapper}
/>
}
/>
</TabBarItemIOS>
</TabBarIOS>
);
},
onTabSelect: function(tab, event) {
if (this.state.selectedTab !== tab) {
this.setState({selectedTab: tab});
}
},
});
var styles = StyleSheet.create({
navigationItem: {
backgroundColor: '#eeeeee',
},
scene: {
paddingTop: 50,
flex: 1,
},
button: {
backgroundColor: '#cccccc',
margin: 50,
marginTop: 26,
padding: 10,
},
buttonText: {
fontSize: 12,
textAlign: 'center',
},
appContainer: {
overflow: 'hidden',
backgroundColor: '#dddddd',
flex: 1,
},
titleText: {
fontSize: 18,
color: '#666666',
textAlign: 'center',
fontWeight: 'bold',
lineHeight: 32,
},
filterText: {
color: '#5577ff',
},
// TODO: Accept icons from route.
crumbIconPlaceholder: {
flex: 1,
backgroundColor: '#666666',
},
crumbSeparatorPlaceholder: {
flex: 1,
backgroundColor: '#aaaaaa',
},
});
module.exports = BreadcrumbNavSample;

View File

@@ -0,0 +1,101 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*/
'use strict';
var React = require('React');
var JSNavigationStack = require('JSNavigationStack');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var ScrollView = require('ScrollView');
var TouchableHighlight = require('TouchableHighlight');
var BreadcrumbNavSample = require('./BreadcrumbNavSample');
var NavigationBarSample = require('./NavigationBarSample');
var JumpingNavSample = require('./JumpingNavSample');
class NavMenu extends React.Component {
render() {
return (
<ScrollView style={styles.scene}>
<TouchableHighlight style={styles.button} onPress={() => {
this.props.navigator.push({ id: 'breadcrumbs' });
}}>
<Text style={styles.buttonText}>Breadcrumbs Example</Text>
</TouchableHighlight>
<TouchableHighlight style={styles.button} onPress={() => {
this.props.navigator.push({ id: 'navbar' });
}}>
<Text style={styles.buttonText}>Navbar Example</Text>
</TouchableHighlight>
<TouchableHighlight style={styles.button} onPress={() => {
this.props.navigator.push({ id: 'jumping' });
}}>
<Text style={styles.buttonText}>Jumping Example</Text>
</TouchableHighlight>
<TouchableHighlight style={styles.button} onPress={() => {
this.props.onExampleExit();
}}>
<Text style={styles.buttonText}>Exit JSNavigationStack Example</Text>
</TouchableHighlight>
</ScrollView>
);
}
}
var TabBarExample = React.createClass({
statics: {
title: '<JSNavigationStack>',
description: 'JS-implemented navigation',
},
renderSceneForRoute: function(route, nav) {
switch (route.id) {
case 'menu':
return (
<NavMenu
navigator={nav}
onExampleExit={this.props.onExampleExit}
/>
);
case 'navbar':
return <NavigationBarSample />;
case 'breadcrumbs':
return <BreadcrumbNavSample />;
case 'jumping':
return <JumpingNavSample />;
}
},
render: function() {
return (
<JSNavigationStack
style={styles.container}
initialRoute={{ id: 'menu', }}
routeMapper={{
navigationItemForRoute: this.renderSceneForRoute,
}}
animationConfigRouteMapper={(route) => JSNavigationStack.AnimationConfigs.FloatFromBottom}
/>
);
},
});
var styles = StyleSheet.create({
container: {
flex: 1,
},
button: {
backgroundColor: 'white',
padding: 15,
},
buttonText: {
},
scene: {
flex: 1,
paddingTop: 64,
}
});
module.exports = TabBarExample;

View File

@@ -0,0 +1,195 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule JumpingNavSample
*/
'use strict';
var JSNavigationStack = require('JSNavigationStack');
var React = require('React');
var StyleSheet = require('StyleSheet');
var ScrollView = require('ScrollView');
var Text = require('Text');
var TouchableBounce = require('TouchableBounce');
var View = require('View');
var _getRandomRoute = function() {
return {
randNumber: Math.random(),
};
};
var INIT_ROUTE = _getRandomRoute();
var ROUTE_STACK = [
_getRandomRoute(),
_getRandomRoute(),
INIT_ROUTE,
_getRandomRoute(),
_getRandomRoute(),
];
var SampleRouteMapper = {
navigationItemForRoute: function(route, navigationOperations) {
return (
<ScrollView style={styles.scene}>
<View style={styles.scroll}>
<Text>{route.randNumber}</Text>
<TouchableBounce
onPress={() => {
navigationOperations.jumpBack();
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>jumpBack</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={() => {
navigationOperations.jumpForward();
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>jumpForward</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={() => {
navigationOperations.jumpTo(INIT_ROUTE);
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>jumpTo initial route</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={() => {
navigationOperations.push(_getRandomRoute());
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>destructive: push</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={() => {
navigationOperations.replace(_getRandomRoute());
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>destructive: replace</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={() => {
navigationOperations.pop();
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>destructive: pop</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={() => {
navigationOperations.immediatelyResetRouteStack([
_getRandomRoute(),
_getRandomRoute(),
]);
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>destructive: Immediate set two routes</Text>
</View>
</TouchableBounce>
<TouchableBounce
onPress={() => {
navigationOperations.popToTop();
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>destructive: pop to top</Text>
</View>
</TouchableBounce>
</View>
</ScrollView>
);
},
};
class JumpingNavBar extends React.Component {
render() {
return (
<View style={styles.navBar}>
{this.props.routeStack.map((route, index) => (
<TouchableBounce onPress={() => {
this.props.navigationOperations.jumpTo(route);
}}>
<View style={styles.navButton}>
<Text
style={[
styles.navButtonText,
this.props.navState.toIndex === index && styles.navButtonActive
]}>
{index}
</Text>
</View>
</TouchableBounce>
))}
</View>
);
}
}
var JumpingNavSample = React.createClass({
render: function() {
return (
<JSNavigationStack
debugOverlay={false}
style={[styles.appContainer]}
initialRoute={INIT_ROUTE}
initialRouteStack={ROUTE_STACK}
routeMapper={SampleRouteMapper}
navigationBar={<JumpingNavBar routeStack={ROUTE_STACK} />}
shouldJumpOnBackstackPop={true}
/>
);
},
});
var styles = StyleSheet.create({
scene: {
backgroundColor: '#eeeeee',
},
scroll: {
flex: 1,
},
button: {
backgroundColor: '#cccccc',
margin: 50,
marginTop: 26,
padding: 10,
},
buttonText: {
fontSize: 12,
textAlign: 'center',
},
appContainer: {
overflow: 'hidden',
backgroundColor: '#dddddd',
flex: 1,
},
navBar: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: 90,
flexDirection: 'row',
},
navButton: {
flex: 1,
},
navButtonText: {
textAlign: 'center',
fontSize: 32,
marginTop: 25,
},
navButtonActive: {
color: 'green',
},
});
module.exports = JumpingNavSample;

View File

@@ -0,0 +1,126 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule NavigationBarSample
*/
'use strict';
var JSNavigationStack = require('JSNavigationStack');
var NavigationBar = require('NavigationBar');
var React = require('React');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var TouchableOpacity = require('TouchableOpacity');
var View = require('View');
var cssVar = require('cssVar');
var NavigationBarRouteMapper = {
LeftButton: function(route, navigationOperations, index, navState) {
if (index === 0) {
return null;
}
var previousRoute = navState.routeStack[index - 1];
return (
<TouchableOpacity onPress={() => navigationOperations.pop()}>
<View>
<Text style={[styles.navBarText, styles.navBarButtonText]}>
{previousRoute.title}
</Text>
</View>
</TouchableOpacity>
);
},
RightButton: function(route, navigationOperations, index, navState) {
return (
<TouchableOpacity
onPress={() => navigationOperations.push(newRandomRoute())}>
<View>
<Text style={[styles.navBarText, styles.navBarButtonText]}>
Next
</Text>
</View>
</TouchableOpacity>
);
},
Title: function(route, navigationOperations, index, navState) {
return (
<Text style={[styles.navBarText, styles.navBarTitleText]}>
{route.title} [{index}]
</Text>
);
},
};
function newRandomRoute() {
return {
content: 'Hello World!',
title: 'Random ' + Math.round(Math.random() * 100),
};
}
var RouteMapper = {
navigationItemForRoute: function(route, navigationOperations) {
return (
<View style={styles.scene}>
<Text>{route.content}</Text>
</View>
);
},
};
var NavigationBarSample = React.createClass({
render: function() {
return (
<View style={styles.appContainer}>
<JSNavigationStack
debugOverlay={false}
style={styles.appContainer}
initialRoute={newRandomRoute()}
routeMapper={RouteMapper}
navigationBar={
<NavigationBar
navigationBarRouteMapper={NavigationBarRouteMapper}
/>
}
/>
</View>
);
},
});
var styles = StyleSheet.create({
appContainer: {
overflow: 'hidden',
backgroundColor: '#ffffff',
flex: 1,
},
scene: {
paddingTop: 50,
flex: 1,
},
navBarText: {
fontSize: 16,
marginVertical: 10,
},
navBarTitleText: {
color: cssVar('fbui-bluegray-60'),
fontWeight: 'bold',
marginVertical: 9,
},
navBarButtonText: {
color: cssVar('fbui-accent-blue'),
},
});
module.exports = NavigationBarSample;

View File

@@ -0,0 +1,235 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule NestedBreadcrumbNavSample
*/
'use strict';
var BreadcrumbNavigationBar = require('BreadcrumbNavigationBar');
var JSNavigationStack = require('JSNavigationStack');
var React = require('React');
var ScrollView = require('ScrollView');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var TouchableBounce = require('TouchableBounce');
var View = require('View');
var SAMPLE_TEXT = 'Top Pushes. Middle Replaces. Bottom Pops.';
var _getRandomRoute = function() {
return {
backButtonTitle: 'Back' + ('' + 10 * Math.random()).substr(0, 1),
content:
SAMPLE_TEXT + '\nHere\'s a random number ' + Math.random(),
title: 'Pushed!',
rightButtonTitle: Math.random() > 0.5 ? 'Right' : 'Button',
};
};
var HorizontalNavigationBarRouteMapper = {
rightContentForRoute: function(route, navigationOperations) {
if (route.rightButtonTitle) {
return (
<Text style={[styles.titleText, styles.filterText]}>
{route.rightButtonTitle}
</Text>
);
} else {
return null;
}
},
titleContentForRoute: function(route, navigationOperations) {
return (
<TouchableBounce
onPress={() => navigationOperations.push(_getRandomRoute())}>
<View>
<Text style={styles.titleText}>{route.title}</Text>
</View>
</TouchableBounce>
);
},
iconForRoute: function(route, navigationOperations) {
var onPress =
navigationOperations.popToRoute.bind(navigationOperations, route);
return (
<TouchableBounce onPress={onPress}>
<View style={styles.crumbIconPlaceholder} />
</TouchableBounce>
);
},
separatorForRoute: function(route, navigationOperations) {
return (
<TouchableBounce onPress={navigationOperations.pop}>
<View style={styles.crumbSeparatorPlaceholder} />
</TouchableBounce>
);
}
};
var ThirdDeepRouteMapper = {
navigationItemForRoute: function(route, navigationOperations) {
return (
<View style={styles.navigationItem}>
<ScrollView>
<View style={styles.thirdDeepScrollContent}>
<TouchableBounce
onPress={this._pushRoute(navigationOperations.push)}>
<View style={styles.button}>
<Text style={styles.buttonText}>request push soon</Text>
</View>
</TouchableBounce>
</View>
</ScrollView>
</View>
);
},
_pushRoute: function(push) {
return () => push(_getRandomRoute());
},
};
var SecondDeepRouteMapper = {
navigationItemForRoute: function(route, navigationOperations) {
return (
<View style={styles.navigationItem}>
<TouchableBounce
onPress={this._pushRoute(navigationOperations.push)}>
<View style={styles.button}>
<Text style={styles.buttonText}>Push Horizontal</Text>
</View>
</TouchableBounce>
<JSNavigationStack
style={styles.thirdDeepNavigator}
initialRoute={{title: '3x Nested Horizontal'}}
routeMapper={ThirdDeepRouteMapper}
navigationBar={
<BreadcrumbNavigationBar
navigationBarRouteMapper={HorizontalNavigationBarRouteMapper}
/>
}
/>
</View>
);
},
_pushRoute: function(push) {
return () => push(_getRandomRoute());
},
};
var FirstDeepRouteMapper = {
navigationItemForRoute: function(route, navigationOperations) {
return (
<View style={styles.navigationItem}>
<TouchableBounce
onPress={this._pushRoute(navigationOperations.push)}>
<View style={styles.button}>
<Text style={styles.buttonText}>Push Outer Vertical Stack</Text>
</View>
</TouchableBounce>
<JSNavigationStack
style={styles.secondDeepNavigator}
initialRoute={{title: '2x Nested Horizontal Nav'}}
routeMapper={SecondDeepRouteMapper}
navigationBar={
<BreadcrumbNavigationBar
navigationBarRouteMapper={HorizontalNavigationBarRouteMapper}
/>
}
/>
</View>
);
},
_pushRoute: function(push) {
return () => push(_getRandomRoute());
},
};
/**
* The outer component.
*/
var NestedBreadcrumbNavSample = React.createClass({
render: function() {
var initialRoute = {title: 'Vertical'};
// No navigation bar.
return (
<JSNavigationStack
style={[styles.appContainer]}
animationConfigRouteMapper={() => JSNavigationStack.AnimationConfigs.FloatFromBottom}
initialRoute={initialRoute}
routeMapper={FirstDeepRouteMapper}
/>
);
}
});
var styles = StyleSheet.create({
navigationItem: {
backgroundColor: '#eeeeee',
shadowColor: 'black',
shadowRadius: 20,
shadowOffset: {w: 0, h: -10},
},
paddingForNavBar: {
paddingTop: 60,
},
paddingForMenuBar: {
paddingTop: 10,
},
button: {
backgroundColor: '#888888',
margin: 10,
marginTop: 10,
padding: 10,
marginRight: 20,
},
buttonText: {
fontSize: 12,
textAlign: 'center',
color: 'white',
},
appContainer: {
overflow: 'hidden',
backgroundColor: '#dddddd',
flex: 1,
},
titleText: {
fontSize: 18,
color: '#666666',
textAlign: 'center',
fontWeight: 'bold',
lineHeight: 32,
},
filterText: {
color: '#5577ff',
},
// TODO: Accept icons from route.
crumbIconPlaceholder: {
flex: 1,
backgroundColor: '#666666',
},
crumbSeparatorPlaceholder: {
flex: 1,
backgroundColor: '#aaaaaa',
},
secondDeepNavigator: {
margin: 0,
borderColor: '#666666',
borderWidth: 0.5,
height: 400,
},
thirdDeepNavigator: {
margin: 0,
borderColor: '#aaaaaa',
borderWidth: 0.5,
height: 400,
},
thirdDeepScrollContent: {
height: 1000,
}
});
module.exports = NestedBreadcrumbNavSample;

View File

@@ -13,23 +13,42 @@
var React = require('react-native');
var UIExplorerList = require('./UIExplorerList');
var {
AppRegistry,
NavigatorIOS,
StyleSheet,
} = React;
var UIExplorerApp = React.createClass({
getInitialState: function() {
return {
openExternalExample: (null: ?React.Component),
};
},
render: function() {
if (this.state.openExternalExample) {
var Example = this.state.openExternalExample;
return (
<Example
onExampleExit={() => {
this.setState({ openExternalExample: null, });
}}
/>
);
}
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
title: 'UIExplorer',
component: UIExplorerList,
passProps: {
onExternalExampleRequested: (example) => {
this.setState({ openExternalExample: example, });
},
}
}}
itemWrapperStyle={styles.itemWrapper}
tintColor='#008888'

View File

@@ -21,6 +21,7 @@ var {
TouchableHighlight,
View,
} = React;
var JSNavigationStackExample = require('./JSNavigationStack/JSNavigationStackExample');
var createExamplePage = require('./createExamplePage');
@@ -32,6 +33,7 @@ var COMPONENTS = [
require('./ListViewSimpleExample'),
require('./MapViewExample'),
require('./NavigatorIOSExample'),
JSNavigationStackExample,
require('./PickerExample'),
require('./ScrollViewExample'),
require('./SliderIOSExample'),
@@ -143,6 +145,12 @@ class UIExplorerList extends React.Component {
}
_onPressRow(example) {
if (example === JSNavigationStackExample) {
this.props.onExternalExampleRequested(
JSNavigationStackExample
);
return;
}
var Component = example.examples ?
createExamplePage(null, example) :
example;