/** * 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 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 ; } } 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 ( ); } // 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 { return this._navigate({type: 'pop'}); } // 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); this._back = this._back.bind(this); 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 ( ); } // Render the header. // The detailed spec of `sceneProps` is defined at `NavigationTypeDefinition` // as type `NavigationSceneRendererProps`. _renderHeader(sceneProps: Object): ReactElement { return ( ); } // Render a scene for route. // The detailed spec of `sceneProps` is defined at `NavigationTypeDefinition` // as type `NavigationSceneRendererProps`. _renderScene(sceneProps: Object): ReactElement { return ( ); } _back() { this.props.navigate({type: 'pop'}); } }); // 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); this._back = this._back.bind(this); this._renderTitleComponent = this._renderTitleComponent.bind(this); } render(): ReactElement { return ( ); } _back(): void { this.props.navigate({type: 'pop'}); } _renderTitleComponent(): ReactElement { return ( {this.props.scene.route.key} ); } }); // 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 ( ); } _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 ( {this.props.navigationState.routes.map(this._renderTab, this)} ); } _renderTab(route: Object, index: number): ReactElement { return ( ); } }); // 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 ( {this.props.route.key} ); } _onPress() { this.props.navigate({type: 'selectTab', tabKey: this.props.route.key}); } }); const styles = StyleSheet.create({ navigator: { flex: 1, }, navigatorCardStack: { flex: 20, }, scrollView: { marginTop: 64 }, 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;