2016-06-06 18:15:38 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*
|
|
|
|
* The examples provided by Facebook are for non-commercial testing and
|
|
|
|
* evaluation purposes only.
|
|
|
|
*
|
|
|
|
* Facebook reserves all rights not expressly granted.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
|
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const NavigationExampleRow = require('./NavigationExampleRow');
|
|
|
|
const React = require('react');
|
|
|
|
const ReactNative = require('react-native');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Basic example that shows how to use <NavigationCardStack /> to build
|
|
|
|
* an app with composite navigation system.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const {
|
|
|
|
Component,
|
|
|
|
PropTypes,
|
|
|
|
} = React;
|
|
|
|
|
|
|
|
const {
|
|
|
|
NavigationExperimental,
|
|
|
|
ScrollView,
|
|
|
|
StyleSheet,
|
|
|
|
Text,
|
|
|
|
TouchableOpacity,
|
|
|
|
View,
|
|
|
|
} = ReactNative;
|
|
|
|
|
|
|
|
const {
|
|
|
|
CardStack: NavigationCardStack,
|
|
|
|
Header: NavigationHeader,
|
|
|
|
PropTypes: NavigationPropTypes,
|
|
|
|
StateUtils: NavigationStateUtils,
|
|
|
|
} = NavigationExperimental;
|
|
|
|
|
|
|
|
// First Step.
|
|
|
|
// Define what app navigation state will look like.
|
|
|
|
function createAppNavigationState(): Object {
|
|
|
|
return {
|
|
|
|
// Three tabs.
|
|
|
|
tabs: {
|
|
|
|
index: 0,
|
|
|
|
routes: [
|
|
|
|
{key: 'apple'},
|
|
|
|
{key: 'banana'},
|
|
|
|
{key: 'orange'},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
// Scenes for the `apple` tab.
|
|
|
|
apple: {
|
|
|
|
index: 0,
|
|
|
|
routes: [{key: 'Apple Home'}],
|
|
|
|
},
|
|
|
|
// Scenes for the `banana` tab.
|
|
|
|
banana: {
|
|
|
|
index: 0,
|
|
|
|
routes: [{key: 'Banana Home'}],
|
|
|
|
},
|
|
|
|
// Scenes for the `orange` tab.
|
|
|
|
orange: {
|
|
|
|
index: 0,
|
|
|
|
routes: [{key: 'Orange Home'}],
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next step.
|
|
|
|
// Define what app navigation state shall be updated.
|
|
|
|
function updateAppNavigationState(
|
|
|
|
state: Object,
|
|
|
|
action: Object,
|
|
|
|
): Object {
|
|
|
|
let {type} = action;
|
|
|
|
if (type === 'BackAction') {
|
|
|
|
type = 'pop';
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 'push': {
|
|
|
|
// Push a route into the scenes stack.
|
|
|
|
const route: Object = action.route;
|
|
|
|
const {tabs} = state;
|
|
|
|
const tabKey = tabs.routes[tabs.index].key;
|
|
|
|
const scenes = state[tabKey];
|
|
|
|
const nextScenes = NavigationStateUtils.push(scenes, route);
|
|
|
|
if (scenes !== nextScenes) {
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
[tabKey]: nextScenes,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case 'pop': {
|
|
|
|
// Pops a route from the scenes stack.
|
|
|
|
const {tabs} = state;
|
|
|
|
const tabKey = tabs.routes[tabs.index].key;
|
|
|
|
const scenes = state[tabKey];
|
|
|
|
const nextScenes = NavigationStateUtils.pop(scenes);
|
|
|
|
if (scenes !== nextScenes) {
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
[tabKey]: nextScenes,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case 'selectTab': {
|
|
|
|
// Switches the tab.
|
|
|
|
const tabKey: string = action.tabKey;
|
|
|
|
const tabs = NavigationStateUtils.jumpTo(state.tabs, tabKey);
|
|
|
|
if (tabs !== state.tabs) {
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
tabs,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next step.
|
|
|
|
// Defines a helper function that creates a HOC (higher-order-component)
|
|
|
|
// which provides a function `navigate` through component props. The
|
|
|
|
// `navigate` function will be used to invoke navigation changes.
|
|
|
|
// This serves a convenient way for a component to navigate.
|
|
|
|
function createAppNavigationContainer(ComponentClass) {
|
|
|
|
const key = '_yourAppNavigationContainerNavigateCall';
|
|
|
|
|
|
|
|
class Container extends Component {
|
|
|
|
static contextTypes = {
|
|
|
|
[key]: PropTypes.func,
|
|
|
|
};
|
|
|
|
|
|
|
|
static childContextTypes = {
|
|
|
|
[key]: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
navigate: PropTypes.func,
|
|
|
|
};
|
|
|
|
|
|
|
|
getChildContext(): Object {
|
|
|
|
return {
|
|
|
|
[key]: this.context[key] || this.props.navigate,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): ReactElement {
|
|
|
|
const navigate = this.context[key] || this.props.navigate;
|
|
|
|
return <ComponentClass {...this.props} navigate={navigate} />;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Container;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next step.
|
|
|
|
// Define a component for your application that owns the navigation state.
|
|
|
|
class YourApplication extends Component {
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
onExampleExit: PropTypes.func,
|
|
|
|
};
|
|
|
|
|
|
|
|
// This sets up the initial navigation state.
|
|
|
|
constructor(props, context) {
|
|
|
|
super(props, context);
|
|
|
|
// This sets up the initial navigation state.
|
|
|
|
this.state = createAppNavigationState();
|
|
|
|
this._navigate = this._navigate.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): ReactElement {
|
|
|
|
// User your own navigator (see next step).
|
|
|
|
return (
|
|
|
|
<YourNavigator
|
|
|
|
appNavigationState={this.state}
|
|
|
|
navigate={this._navigate}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This public method is optional. If exists, the UI explorer will call it
|
|
|
|
// the "back button" is pressed. Normally this is the cases for Android only.
|
|
|
|
handleBackAction(): boolean {
|
2016-06-14 23:26:40 +00:00
|
|
|
return this._navigate({type: 'pop'});
|
2016-06-06 18:15:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This handles the navigation state changes. You're free and responsible
|
|
|
|
// to define the API that changes that navigation state. In this exmaple,
|
|
|
|
// we'd simply use a `updateAppNavigationState` to update the navigation
|
|
|
|
// state.
|
|
|
|
_navigate(action: Object): void {
|
|
|
|
if (action.type === 'exit') {
|
|
|
|
// Exits the example. `this.props.onExampleExit` is provided
|
|
|
|
// by the UI Explorer.
|
|
|
|
this.props.onExampleExit && this.props.onExampleExit();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const state = updateAppNavigationState(
|
|
|
|
this.state,
|
|
|
|
action,
|
|
|
|
);
|
|
|
|
|
|
|
|
// `updateAppNavigationState` (which uses NavigationStateUtils) gives you
|
|
|
|
// back the same `state` if nothing has changed. You could use
|
|
|
|
// that to avoid redundant re-rendering.
|
|
|
|
if (this.state !== state) {
|
|
|
|
this.setState(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next step.
|
|
|
|
// Define your own controlled navigator.
|
|
|
|
const YourNavigator = createAppNavigationContainer(class extends Component {
|
|
|
|
static propTypes = {
|
|
|
|
appNavigationState: PropTypes.shape({
|
|
|
|
apple: NavigationPropTypes.navigationState.isRequired,
|
|
|
|
banana: NavigationPropTypes.navigationState.isRequired,
|
|
|
|
orange: NavigationPropTypes.navigationState.isRequired,
|
|
|
|
tabs: NavigationPropTypes.navigationState.isRequired,
|
|
|
|
}),
|
|
|
|
navigate: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
// This sets up the methods (e.g. Pop, Push) for navigation.
|
|
|
|
constructor(props: any, context: any) {
|
|
|
|
super(props, context);
|
2016-06-10 00:48:39 +00:00
|
|
|
this._back = this._back.bind(this);
|
2016-06-06 18:15:38 +00:00
|
|
|
this._renderHeader = this._renderHeader.bind(this);
|
|
|
|
this._renderScene = this._renderScene.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now use the `NavigationCardStack` to render the scenes.
|
|
|
|
render(): ReactElement {
|
|
|
|
const {appNavigationState} = this.props;
|
|
|
|
const {tabs} = appNavigationState;
|
|
|
|
const tabKey = tabs.routes[tabs.index].key;
|
|
|
|
const scenes = appNavigationState[tabKey];
|
|
|
|
|
|
|
|
return (
|
|
|
|
<View style={styles.navigator}>
|
|
|
|
<NavigationCardStack
|
|
|
|
key={'stack_' + tabKey}
|
2016-06-10 00:48:39 +00:00
|
|
|
onNavigateBack={this._back}
|
2016-06-06 18:15:38 +00:00
|
|
|
navigationState={scenes}
|
NavigationExperimental: Rename `renderOverlay` to `renderHeader`
Summary:
NavigationCardStack is a custom component, and its API should be explicit, not
too generic..
In NavigationCardStack, the prop `renderOverlay` is actually used to render
the NavigationHeader, and we uses absolute position to build the layout for
the header and the body.
One of the problem with using absolute postion and fixed height to build the
layout that contains the header is that the header can't have variant height
easily.
Ideally, if the layout for the header used flex-box, we'd ve able to be more
adaptive to deal with the header that has variant height.
That said, let's rename `renderOverlay` to `renderHeader`, then build the
proper layout that explicitly works better with the header.
If we to need to support overlay in navigation, we may consider add
`renderOverlay` later, if it's really necessary.
Reviewed By: ericvicenti
Differential Revision: D3670224
fbshipit-source-id: ff04acfe9dc995cb57117b3fd9b07d5f97b9c6ee
2016-08-04 18:16:40 +00:00
|
|
|
renderHeader={this._renderHeader}
|
2016-06-06 18:15:38 +00:00
|
|
|
renderScene={this._renderScene}
|
|
|
|
style={styles.navigatorCardStack}
|
|
|
|
/>
|
|
|
|
<YourTabs
|
|
|
|
navigationState={tabs}
|
|
|
|
/>
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render the header.
|
|
|
|
// The detailed spec of `sceneProps` is defined at `NavigationTypeDefinition`
|
|
|
|
// as type `NavigationSceneRendererProps`.
|
|
|
|
_renderHeader(sceneProps: Object): ReactElement {
|
|
|
|
return (
|
|
|
|
<YourHeader
|
|
|
|
{...sceneProps}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render a scene for route.
|
|
|
|
// The detailed spec of `sceneProps` is defined at `NavigationTypeDefinition`
|
|
|
|
// as type `NavigationSceneRendererProps`.
|
|
|
|
_renderScene(sceneProps: Object): ReactElement {
|
|
|
|
return (
|
|
|
|
<YourScene
|
|
|
|
{...sceneProps}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
2016-06-10 00:48:39 +00:00
|
|
|
|
|
|
|
_back() {
|
|
|
|
this.props.navigate({type: 'pop'});
|
|
|
|
}
|
2016-06-06 18:15:38 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Next step.
|
|
|
|
// Define your own header.
|
|
|
|
const YourHeader = createAppNavigationContainer(class extends Component {
|
|
|
|
static propTypes = {
|
|
|
|
...NavigationPropTypes.SceneRendererProps,
|
|
|
|
navigate: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props: Object, context: any) {
|
|
|
|
super(props, context);
|
2016-06-10 00:48:39 +00:00
|
|
|
this._back = this._back.bind(this);
|
2016-06-06 18:15:38 +00:00
|
|
|
this._renderTitleComponent = this._renderTitleComponent.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): ReactElement {
|
|
|
|
return (
|
|
|
|
<NavigationHeader
|
|
|
|
{...this.props}
|
|
|
|
renderTitleComponent={this._renderTitleComponent}
|
2016-06-10 00:48:39 +00:00
|
|
|
onNavigateBack={this._back}
|
2016-06-06 18:15:38 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-06-10 00:48:39 +00:00
|
|
|
_back(): void {
|
|
|
|
this.props.navigate({type: 'pop'});
|
|
|
|
}
|
|
|
|
|
2016-08-04 10:28:24 +00:00
|
|
|
_renderTitleComponent(props: Object): ReactElement {
|
2016-06-06 18:15:38 +00:00
|
|
|
return (
|
|
|
|
<NavigationHeader.Title>
|
2016-08-04 10:28:24 +00:00
|
|
|
{props.scene.route.key}
|
2016-06-06 18:15:38 +00:00
|
|
|
</NavigationHeader.Title>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Next step.
|
|
|
|
// Define your own scene.
|
|
|
|
const YourScene = createAppNavigationContainer(class extends Component {
|
|
|
|
static propTypes = {
|
|
|
|
...NavigationPropTypes.SceneRendererProps,
|
|
|
|
navigate: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props: Object, context: any) {
|
|
|
|
super(props, context);
|
|
|
|
this._exit = this._exit.bind(this);
|
|
|
|
this._popRoute = this._popRoute.bind(this);
|
|
|
|
this._pushRoute = this._pushRoute.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): ReactElement {
|
|
|
|
return (
|
2016-08-04 22:17:39 +00:00
|
|
|
<ScrollView>
|
2016-06-06 18:15:38 +00:00
|
|
|
<NavigationExampleRow
|
|
|
|
text="Push Route"
|
|
|
|
onPress={this._pushRoute}
|
|
|
|
/>
|
|
|
|
<NavigationExampleRow
|
|
|
|
text="Pop Route"
|
|
|
|
onPress={this._popRoute}
|
|
|
|
/>
|
|
|
|
<NavigationExampleRow
|
|
|
|
text="Exit Header + Scenes + Tabs Example"
|
|
|
|
onPress={this._exit}
|
|
|
|
/>
|
|
|
|
</ScrollView>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
_pushRoute(): void {
|
|
|
|
// Just push a route with a new unique key.
|
|
|
|
const route = {key: '[' + this.props.scenes.length + ']-' + Date.now()};
|
|
|
|
this.props.navigate({type: 'push', route});
|
|
|
|
}
|
|
|
|
|
|
|
|
_popRoute(): void {
|
|
|
|
this.props.navigate({type: 'pop'});
|
|
|
|
}
|
|
|
|
|
|
|
|
_exit(): void {
|
|
|
|
this.props.navigate({type: 'exit'});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Next step.
|
|
|
|
// Define your own tabs.
|
|
|
|
const YourTabs = createAppNavigationContainer(class extends Component {
|
|
|
|
static propTypes = {
|
|
|
|
navigationState: NavigationPropTypes.navigationState.isRequired,
|
|
|
|
navigate: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props: Object, context: any) {
|
|
|
|
super(props, context);
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): ReactElement {
|
|
|
|
return (
|
|
|
|
<View style={styles.tabs}>
|
|
|
|
{this.props.navigationState.routes.map(this._renderTab, this)}
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
_renderTab(route: Object, index: number): ReactElement {
|
|
|
|
return (
|
|
|
|
<YourTab
|
|
|
|
key={route.key}
|
|
|
|
route={route}
|
|
|
|
selected={this.props.navigationState.index === index}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Next step.
|
|
|
|
// Define your own Tab
|
|
|
|
const YourTab = createAppNavigationContainer(class extends Component {
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
navigate: PropTypes.func.isRequired,
|
|
|
|
route: NavigationPropTypes.navigationRoute.isRequired,
|
|
|
|
selected: PropTypes.bool.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props: Object, context: any) {
|
|
|
|
super(props, context);
|
|
|
|
this._onPress = this._onPress.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): ReactElement {
|
|
|
|
const style = [styles.tabText];
|
|
|
|
if (this.props.selected) {
|
|
|
|
style.push(styles.tabSelected);
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<TouchableOpacity style={styles.tab} onPress={this._onPress}>
|
|
|
|
<Text style={style}>
|
|
|
|
{this.props.route.key}
|
|
|
|
</Text>
|
|
|
|
</TouchableOpacity>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
_onPress() {
|
|
|
|
this.props.navigate({type: 'selectTab', tabKey: this.props.route.key});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
|
|
|
navigator: {
|
|
|
|
flex: 1,
|
|
|
|
},
|
|
|
|
navigatorCardStack: {
|
|
|
|
flex: 20,
|
|
|
|
},
|
|
|
|
tabs: {
|
|
|
|
flex: 1,
|
|
|
|
flexDirection: 'row',
|
|
|
|
},
|
|
|
|
tab: {
|
|
|
|
alignItems: 'center',
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
flex: 1,
|
|
|
|
justifyContent: 'center',
|
|
|
|
},
|
|
|
|
tabText: {
|
|
|
|
color: '#222',
|
|
|
|
fontWeight: '500',
|
|
|
|
},
|
|
|
|
tabSelected: {
|
|
|
|
color: 'blue',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
module.exports = YourApplication;
|