From 876ecb291fe80471ff49df687b59f38b48d705d8 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Mon, 22 Feb 2016 16:15:35 -0800 Subject: [PATCH] Adopt NavigationExperimental in UIExplorer Summary:Use the new Navigation library to make the UIExplorer navigation more flexible. Deep linking examples are coming soon (hint: we just need to convert URIs to UIExplorerActions!) Reviewed By: javache Differential Revision: D2798050 fb-gh-sync-id: c7775393e2d7a30a161d0770192309567dcc8b0c shipit-source-id: c7775393e2d7a30a161d0770192309567dcc8b0c --- .../NavigationAnimatedExample.js | 8 +- .../NavigationExperimentalExample.js | 2 +- .../NavigationTicTacToeExample.js | 41 +- Examples/UIExplorer/UIExplorerActions.js | 51 +++ Examples/UIExplorer/UIExplorerApp.android.js | 147 ++++--- Examples/UIExplorer/UIExplorerApp.ios.js | 172 ++++++-- Examples/UIExplorer/UIExplorerExampleList.js | 218 ++++++++++ Examples/UIExplorer/UIExplorerList.android.js | 251 +++++++----- Examples/UIExplorer/UIExplorerList.ios.js | 376 +++++++++++------- Examples/UIExplorer/UIExplorerListBase.js | 190 --------- .../UIExplorer/UIExplorerNavigationReducer.js | 95 +++++ .../UIExplorer/UIExplorerStateTitleMap.js | 32 ++ .../NavigationRootContainer.js | 12 +- .../NavigationStateUtils.js | 5 - .../Reducer/NavigationFindReducer.js | 8 +- .../Reducer/NavigationTabsReducer.js | 71 +--- 16 files changed, 1074 insertions(+), 605 deletions(-) create mode 100644 Examples/UIExplorer/UIExplorerActions.js create mode 100644 Examples/UIExplorer/UIExplorerExampleList.js delete mode 100644 Examples/UIExplorer/UIExplorerListBase.js create mode 100644 Examples/UIExplorer/UIExplorerNavigationReducer.js create mode 100644 Examples/UIExplorer/UIExplorerStateTitleMap.js diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js index e9f85ed1c..7b343f08d 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js @@ -32,8 +32,8 @@ const NavigationBasicReducer = NavigationReducer.StackReducer({ initialStates: [ {key: 'First Route'} ], - matchAction: action => true, - actionStateMap: actionString => ({key: actionString}), + matchAction: action => action.type === 'push', + actionStateMap: action => ({key: action.key}), }); class NavigationAnimatedExample extends React.Component { @@ -45,7 +45,7 @@ class NavigationAnimatedExample extends React.Component { { this.navRootContainer = navRootContainer; }} - persistenceKey="NavigationAnimatedExampleState" + persistenceKey="NavigationAnimExampleState" renderNavigation={this._renderNavigated} /> ); @@ -85,7 +85,7 @@ class NavigationAnimatedExample extends React.Component { { - onNavigate('Route #' + navigationState.children.length); + onNavigate({ type: 'push', key: 'Route #' + navigationState.children.length }); }} /> >; -const evenOddPlayerMap = ['o', 'x']; +const evenOddPlayerMap = ['O', 'X']; const rowLeterMap = ['a', 'b', 'c']; function parseGame(game: string): GameGrid { @@ -99,20 +99,20 @@ function isGameOver(gameString: string): boolean { class Cell extends React.Component { cellStyle() { - switch (this.props.value) { - case 'x': + switch (this.props.player) { + case 'X': return styles.cellX; - case 'o': + case 'O': return styles.cellO; default: return null; } } textStyle() { - switch (this.props.value) { - case 'x': + switch (this.props.player) { + case 'X': return styles.cellTextX; - case 'o': + case 'O': return styles.cellTextO; default: return {}; @@ -171,6 +171,11 @@ function TicTacToeGame(props) { ); return ( + + Close + EXTREME T3 {rows} @@ -184,18 +189,18 @@ function TicTacToeGame(props) { TicTacToeGame = NavigationContainer.create(TicTacToeGame); const GameActions = { - Turn: (row, col) => ({gameAction: 'turn', row, col }), - Reset: (row, col) => ({gameAction: 'reset' }), + Turn: (row, col) => ({type: 'TicTacToeTurnAction', row, col }), + Reset: (row, col) => ({type: 'TicTacToeResetAction' }), }; function GameReducer(lastGame: ?string, action: Object): string { - if (!lastGame || !action || !action.gameAction) { - return lastGame || ''; + if (!lastGame) { + lastGame = ''; } - if (action.gameAction === 'reset') { + if (action.type === 'TicTacToeResetAction') { return ''; } - if (!isGameOver(lastGame) && action.gameAction === 'turn') { + if (!isGameOver(lastGame) && action.type === 'TicTacToeTurnAction') { return playTurn(lastGame, action.row, action.col); } return lastGame; @@ -206,10 +211,11 @@ class NavigationTicTacToeExample extends React.Component { return ( ( )} /> @@ -221,6 +227,12 @@ NavigationTicTacToeExample.GameReducer = GameReducer; NavigationTicTacToeExample.GameActions = GameActions; const styles = StyleSheet.create({ + closeButton: { + position: 'absolute', + left: 10, + top: 30, + fontSize: 14, + }, container: { flex: 1, justifyContent: 'center', @@ -257,7 +269,6 @@ const styles = StyleSheet.create({ backgroundColor: '#7ebd26', }, cellText: { - borderRadius: 5, fontSize: 50, fontFamily: 'AvenirNext-Bold', }, diff --git a/Examples/UIExplorer/UIExplorerActions.js b/Examples/UIExplorer/UIExplorerActions.js new file mode 100644 index 000000000..8f7a59f4e --- /dev/null +++ b/Examples/UIExplorer/UIExplorerActions.js @@ -0,0 +1,51 @@ +/** + * 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. + * + * @flow + */ +'use strict'; + +export type UIExplorerListWithFilterAction = { + type: 'UIExplorerListWithFilterAction', + filter: ?string; +}; + +export type UIExplorerExampleAction = { + type: 'UIExplorerExampleAction', + openExample: string; +}; + +import type {BackAction} from 'NavigationRootContainer'; + +export type UIExplorerAction = BackAction | UIExplorerListWithFilterAction | UIExplorerExampleAction; + +function ExampleListWithFilter(filter: ?string): UIExplorerListWithFilterAction { + return { + type: 'UIExplorerListWithFilterAction', + filter, + }; +} + +function ExampleAction(openExample: string): UIExplorerExampleAction { + return { + type: 'UIExplorerExampleAction', + openExample, + }; +} + +const UIExplorerActions = { + ExampleListWithFilter, + ExampleAction, +}; + +module.exports = UIExplorerActions; diff --git a/Examples/UIExplorer/UIExplorerApp.android.js b/Examples/UIExplorer/UIExplorerApp.android.js index 6446e8fa4..9e26c5f7b 100644 --- a/Examples/UIExplorer/UIExplorerApp.android.js +++ b/Examples/UIExplorer/UIExplorerApp.android.js @@ -22,84 +22,111 @@ const { BackAndroid, Dimensions, DrawerLayoutAndroid, + NavigationExperimental, StyleSheet, ToolbarAndroid, View, StatusBar, } = React; -const UIExplorerList = require('./UIExplorerList.android'); +const { + RootContainer: NavigationRootContainer, +} = NavigationExperimental; +const UIExplorerActions = require('./UIExplorerActions'); +const UIExplorerExampleList = require('./UIExplorerExampleList'); +const UIExplorerList = require('./UIExplorerList'); +const UIExplorerNavigationReducer = require('./UIExplorerNavigationReducer'); +const UIExplorerStateTitleMap = require('./UIExplorerStateTitleMap'); -const DRAWER_WIDTH_LEFT = 56; +var DRAWER_WIDTH_LEFT = 56; class UIExplorerApp extends React.Component { - constructor(props) { - super(props); - this.onSelectExample = this.onSelectExample.bind(this); - this._handleBackButtonPress = this._handleBackButtonPress.bind(this); - this.state = { - example: this._getUIExplorerHome(), - }; - } - - _getUIExplorerHome() { - return { - title: 'UIExplorer', - component: this._renderHome(), - }; - } - componentWillMount() { - BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress); + BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress.bind(this)); } render() { + return ( + { this._navigationRootRef = navRootRef; }} + reducer={UIExplorerNavigationReducer} + renderNavigation={this._renderApp.bind(this)} + /> + ); + } + + _renderApp(navigationState, onNavigate) { + if (!navigationState) { + return null; + } return ( { + this._overrideBackPressForDrawerLayout = true; + }} + onDrawerClose={() => { + this._overrideBackPressForDrawerLayout = false; + }} ref={(drawer) => { this.drawer = drawer; }} - renderNavigationView={this._renderNavigationView}> - {this._renderNavigation()} + renderNavigationView={this._renderDrawerContent.bind(this, onNavigate)}> + {this._renderNavigation(navigationState, onNavigate)} ); } - _renderNavigationView() { + _renderDrawerContent(onNavigate) { return ( - { + this.drawer && this.drawer.closeDrawer(); + onNavigate(action); + }} /> ); } - onSelectExample(example) { - this.drawer.closeDrawer(); - if (example.title === this._getUIExplorerHome().title) { - example = this._getUIExplorerHome(); + _renderNavigation(navigationState, onNavigate) { + if (navigationState.externalExample) { + var Component = UIExplorerList.Modules[navigationState.externalExample]; + return ( + { + onNavigate(NavigationRootContainer.getBackAction()); + }} + ref={(example) => { this._exampleRef = example; }} + /> + ); } - this.setState({ - example: example, - }); - } - - _renderHome() { - const onSelectExample = this.onSelectExample; - return React.createClass({ - render: function() { - return ( - + - ); - } - }); - } - - _renderNavigation() { - const Component = this.state.example.component; + this.drawer.openDrawer()} + style={styles.toolbar} + title={title} + /> + { this._exampleRef = example; }} + /> + + ); + } return ( this.drawer.openDrawer()} style={styles.toolbar} - title={this.state.example.title} + title={title} /> - { this._exampleRef = example; }} + ); } _handleBackButtonPress() { + if (this._overrideBackPressForDrawerLayout) { + // This hack is necessary because drawer layout provides an imperative API + // with open and close methods. This code would be cleaner if the drawer + // layout provided an `isOpen` prop and allowed us to pass a `onDrawerClose` handler. + this.drawer && this.drawer.closeDrawer(); + return true; + } if ( this._exampleRef && this._exampleRef.handleBackAction && @@ -127,13 +162,13 @@ class UIExplorerApp extends React.Component { ) { return true; } - if (this.state.example.title !== this._getUIExplorerHome().title) { - this.onSelectExample(this._getUIExplorerHome()); - return true; + if (this._navigationRootRef) { + return this._navigationRootRef.handleNavigation( + NavigationRootContainer.getBackAction() + ); } return false; } - } const styles = StyleSheet.create({ diff --git a/Examples/UIExplorer/UIExplorerApp.ios.js b/Examples/UIExplorer/UIExplorerApp.ios.js index 120c772d8..f9dfe5c87 100644 --- a/Examples/UIExplorer/UIExplorerApp.ios.js +++ b/Examples/UIExplorer/UIExplorerApp.ios.js @@ -12,77 +12,173 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @providesModule UIExplorerApp + * @flow */ 'use strict'; const React = require('react-native'); +const UIExplorerActions = require('./UIExplorerActions'); const UIExplorerList = require('./UIExplorerList.ios'); -const SetPropertiesExampleApp = require('./SetPropertiesExampleApp'); -const RootViewSizeFlexibilityExampleApp = require('./RootViewSizeFlexibilityExampleApp'); +const UIExplorerExampleList = require('./UIExplorerExampleList'); +const UIExplorerNavigationReducer = require('./UIExplorerNavigationReducer'); +const UIExplorerStateTitleMap = require('./UIExplorerStateTitleMap'); + const { AppRegistry, - NavigatorIOS, + NavigationExperimental, + SnapshotViewIOS, StyleSheet, + Text, + TouchableHighlight, View, - StatusBar, } = React; +const { + AnimatedView: NavigationAnimatedView, + Card: NavigationCard, + Header: NavigationHeader, + Reducer: NavigationReducer, + RootContainer: NavigationRootContainer, +} = NavigationExperimental; +const StackReducer = NavigationReducer.StackReducer; + +import type { + NavigationState, +} from 'NavigationStateUtils' + +import type { Value } from 'Animated'; +import type { Layout } from 'NavigationAnimatedView'; +import type { UIExplorerNavigationState } from './UIExplorerNavigationReducer'; + +import type { + UIExplorerExample, +} from './UIExplorerList.ios' class UIExplorerApp extends React.Component { - - constructor(props) { - super(props); - this.state = { - openExternalExample: (null: ?React.Component), - }; + _renderNavigation: Function; + componentWillMount() { + this._renderNavigation = this._renderNavigation.bind(this); } - render() { - if (this.state.openExternalExample) { - const Example = this.state.openExternalExample; + return ( + + ); + } + _renderNavigation(navigationState: UIExplorerNavigationState, onNavigate: Function) { + if (!navigationState) { + return null; + } + if (navigationState.externalExample) { + var Component = UIExplorerList.Modules[navigationState.externalExample]; return ( - { - this.setState({ openExternalExample: null, }); + onNavigate(NavigationRootContainer.getBackAction()); }} /> ); } - + const {stack} = navigationState; return ( - - - { - this.setState({ openExternalExample: example, }); - }, - } - }} - itemWrapperStyle={styles.itemWrapper} - tintColor="#008888" - /> - + ); } + _renderOverlay( + navigationState: NavigationState, + position: Value, + layout: Layout + ): ReactElement { + return ( + + ); + } + + _renderSceneContainer( + navigationState: NavigationState, + scene: NavigationState, + index: number, + position: Value, + layout: Layout + ): ReactElement { + return ( + + {this._renderScene(scene)} + + ); + } + + _renderScene(state: Object): ?ReactElement { + if (state.key === 'AppList') { + return ( + + ); + } + + const Example = UIExplorerList.Modules[state.key]; + if (Example) { + const Component = UIExplorerExampleList.makeRenderable(Example); + return ( + + + + ); + } + return null; + } } const styles = StyleSheet.create({ container: { flex: 1, }, - itemWrapper: { - backgroundColor: '#eaeaea', + exampleContainer: { + flex: 1, + paddingTop: 60, }, }); -AppRegistry.registerComponent('SetPropertiesExampleApp', () => SetPropertiesExampleApp); -AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () => RootViewSizeFlexibilityExampleApp); +AppRegistry.registerComponent('SetPropertiesExampleApp', () => require('./SetPropertiesExampleApp')); +AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () => require('./RootViewSizeFlexibilityExampleApp')); AppRegistry.registerComponent('UIExplorerApp', () => UIExplorerApp); -UIExplorerList.registerComponents(); + +// Register suitable examples for snapshot tests +UIExplorerList.ComponentExamples.concat(UIExplorerList.APIExamples).forEach((Example: UIExplorerExample) => { + const ExampleModule = Example.module; + if (ExampleModule.displayName) { + var Snapshotter = React.createClass({ + render: function() { + var Renderable = UIExplorerExampleList.makeRenderable(ExampleModule); + return ( + + + + ); + }, + }); + AppRegistry.registerComponent(ExampleModule.displayName, () => Snapshotter); + } +}); module.exports = UIExplorerApp; diff --git a/Examples/UIExplorer/UIExplorerExampleList.js b/Examples/UIExplorer/UIExplorerExampleList.js new file mode 100644 index 000000000..07a07f381 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerExampleList.js @@ -0,0 +1,218 @@ +/** + * 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. + * + * @flow + */ +'use strict'; + +const React = require('react-native'); +const UIExplorerActions = require('./UIExplorerActions'); +const { + ListView, + NavigationExperimental, + StyleSheet, + Text, + TextInput, + TouchableHighlight, + View, +} = React; +const createExamplePage = require('./createExamplePage'); +const { + Container: NavigationContainer, +} = NavigationExperimental; + +import type { + UIExplorerExample, +} from './UIExplorerList.ios' + +const ds = new ListView.DataSource({ + rowHasChanged: (r1, r2) => r1 !== r2, + sectionHeaderHasChanged: (h1, h2) => h1 !== h2, +}); + +class UIExplorerExampleList extends React.Component { + constuctor(props: { + disableTitleRow: ?boolean, + onNavigate: Function, + filter: ?string, + list: { + ComponentExamples: Array, + APIExamples: Array, + }, + style: ?any, + }) { + + } + render(): ?ReactElement { + const filterText = this.props.filter || ''; + const filterRegex = new RegExp(String(filterText), 'i'); + const filter = (example) => filterRegex.test(example.module.title); + + const dataSource = ds.cloneWithRowsAndSections({ + components: this.props.list.ComponentExamples.filter(filter), + apis: this.props.list.APIExamples.filter(filter), + }); + return ( + + {this._renderTitleRow()} + {this._renderTextInput()} + + + ); + } + + _renderTitleRow(): ?ReactElement { + if (!this.props.displayTitleRow) { + return null; + } + return this._renderRow( + 'UIExplorer', + 'React Native Examples', + 'home_key', + () => { + this.props.onNavigate( + UIExplorerActions.ExampleListWithFilter('') + ); + } + ); + } + + _renderTextInput(): ?ReactElement { + if (this.props.disableSearch) { + return null; + } + return ( + + { + this.props.onNavigate(UIExplorerActions.ExampleListWithFilter(text)); + }} + placeholder="Search..." + style={[styles.searchTextInput, this.props.searchTextInputStyle]} + testID="explorer_search" + value={this.props.filter} + /> + + ); + } + + _renderSectionHeader(data: any, section: string): ?ReactElement { + return ( + + {section.toUpperCase()} + + ); + } + + _renderExampleRow(example: {key: string, module: Object}): ?ReactElement { + return this._renderRow( + example.module.title, + example.module.description, + example.key, + () => this._handleRowPress(example.key) + ); + } + + _renderRow(title: string, description: string, key: ?string, handler: ?Function): ?ReactElement { + return ( + + + + + {title} + + + {description} + + + + + + ); + } + + _handleRowPress(exampleKey: string): void { + this.props.onNavigate(UIExplorerActions.ExampleAction(exampleKey)) + } +} + +function makeRenderable(example: any): ReactClass { + return example.examples ? + createExamplePage(null, example) : + example; +} + +UIExplorerExampleList = NavigationContainer.create(UIExplorerExampleList); +UIExplorerExampleList.makeRenderable = makeRenderable; + +var styles = StyleSheet.create({ + listContainer: { + flex: 1, + }, + list: { + backgroundColor: '#eeeeee', + }, + sectionHeader: { + padding: 5, + fontWeight: '500', + fontSize: 11, + }, + group: { + backgroundColor: 'white', + }, + row: { + backgroundColor: 'white', + justifyContent: 'center', + paddingHorizontal: 15, + paddingVertical: 8, + }, + separator: { + height: StyleSheet.hairlineWidth, + backgroundColor: '#bbbbbb', + marginLeft: 15, + }, + rowTitleText: { + fontSize: 17, + fontWeight: '500', + }, + rowDetailText: { + fontSize: 15, + color: '#888888', + lineHeight: 20, + }, + searchRow: { + backgroundColor: '#eeeeee', + padding: 10, + }, + searchTextInput: { + backgroundColor: 'white', + borderColor: '#cccccc', + borderRadius: 3, + borderWidth: 1, + paddingLeft: 8, + height: 35, + }, +}); + +module.exports = UIExplorerExampleList; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index 4cd5649d7..321d84a41 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -15,102 +15,167 @@ */ 'use strict'; -var React = require('react-native'); -var { - StyleSheet, - View, -} = React; -var UIExplorerListBase = require('./UIExplorerListBase'); - -var COMPONENTS = [ - require('./ImageExample'), - require('./ListViewExample'), - require('./PickerAndroidExample'), - require('./ProgressBarAndroidExample'), - require('./RefreshControlExample'), - require('./ScrollViewSimpleExample'), - require('./StatusBarExample'), - require('./SwitchExample'), - require('./TextExample.android'), - require('./TextInputExample.android'), - require('./ToolbarAndroidExample'), - require('./TouchableExample'), - require('./ViewExample'), - require('./ViewPagerAndroidExample.android'), - require('./WebViewExample'), -]; - -var APIS = [ - require('./AccessibilityAndroidExample.android'), - require('./AlertExample').AlertExample, - require('./AppStateExample'), - require('./BorderExample'), - require('./CameraRollExample'), - require('./ClipboardExample'), - require('./DatePickerAndroidExample'), - require('./GeolocationExample'), - require('./ImageEditingExample'), - require('./IntentAndroidExample.android'), - require('./LayoutEventsExample'), - require('./LayoutExample'), - require('./NavigationExperimental/NavigationExperimentalExample'), - require('./NetInfoExample'), - require('./PanResponderExample'), - require('./PointerEventsExample'), - require('./TimePickerAndroidExample'), - require('./TimerExample'), - require('./ToastAndroidExample.android'), - require('./XHRExample'), -]; - -type Props = { - onSelectExample: Function, - isInDrawer: bool, +export type UIExplorerExample = { + key: string; + module: React.Component; }; -class UIExplorerList extends React.Component { - props: Props; - - render() { - return ( - - ); - } - - renderAdditionalView(renderRow, renderTextInput): React.Component { - if (this.props.isInDrawer) { - var homePage = renderRow({ - title: 'UIExplorer', - description: 'List of examples', - }, -1); - return ( - - {homePage} - - ); - } - return renderTextInput(styles.searchTextInput); - } - - onPressRow(example: any) { - var Component = UIExplorerListBase.makeRenderable(example); - this.props.onSelectExample({ - title: Component.title, - component: Component, - }); - } -} - -var styles = StyleSheet.create({ - searchTextInput: { - padding: 2, +var ComponentExamples: Array = [ + { + key: 'ImageExample', + module: require('./ImageExample'), }, + { + key: 'ListViewExample', + module: require('./ListViewExample'), + }, + { + key: 'PickerAndroidExample', + module: require('./PickerAndroidExample'), + }, + { + key: 'ProgressBarAndroidExample', + module: require('./ProgressBarAndroidExample'), + }, + { + key: 'RefreshControlExample', + module: require('./RefreshControlExample'), + }, + { + key: 'ScrollViewSimpleExample', + module: require('./ScrollViewSimpleExample'), + }, + { + key: 'StatusBarExample', + module: require('./StatusBarExample'), + }, + { + key: 'SwitchExample', + module: require('./SwitchExample'), + }, + { + key: 'TextExample', + module: require('./TextExample'), + }, + { + key: 'TextInputExample', + module: require('./TextInputExample'), + }, + { + key: 'ToolbarAndroidExample', + module: require('./ToolbarAndroidExample'), + }, + { + key: 'TouchableExample', + module: require('./TouchableExample'), + }, + { + key: 'ViewExample', + module: require('./ViewExample'), + }, + { + key: 'ViewPagerAndroidExample', + module: require('./ViewPagerAndroidExample'), + }, + { + key: 'WebViewExample', + module: require('./WebViewExample'), + }, +]; + +const APIExamples = [ + { + key: 'AccessibilityAndroidExample', + module: require('./AccessibilityAndroidExample'), + }, + { + key: 'AlertExample', + module: require('./AlertExample').AlertExample, + }, + { + key: 'AppStateExample', + module: require('./AppStateExample'), + }, + { + key: 'BorderExample', + module: require('./BorderExample'), + }, + { + key: 'CameraRollExample', + module: require('./CameraRollExample'), + }, + { + key: 'ClipboardExample', + module: require('./ClipboardExample'), + }, + { + key: 'DatePickerAndroidExample', + module: require('./DatePickerAndroidExample'), + }, + { + key: 'GeolocationExample', + module: require('./GeolocationExample'), + }, + { + key: 'ImageEditingExample', + module: require('./ImageEditingExample'), + }, + { + key: 'IntentAndroidExample', + module: require('./IntentAndroidExample'), + }, + { + key: 'LayoutEventsExample', + module: require('./LayoutEventsExample'), + }, + { + key: 'LayoutExample', + module: require('./LayoutExample'), + }, + { + key: 'NavigationExperimentalExample', + module: require('./NavigationExperimental/NavigationExperimentalExample'), + }, + { + key: 'NetInfoExample', + module: require('./NetInfoExample'), + }, + { + key: 'PanResponderExample', + module: require('./PanResponderExample'), + }, + { + key: 'PointerEventsExample', + module: require('./PointerEventsExample'), + }, + { + key: 'TimePickerAndroidExample', + module: require('./TimePickerAndroidExample'), + }, + { + key: 'TimerExample', + module: require('./TimerExample'), + }, + { + key: 'ToastAndroidExample', + module: require('./ToastAndroidExample'), + }, + { + key: 'XHRExample', + module: require('./XHRExample'), + }, +]; + +const Modules = {}; + +APIExamples.concat(ComponentExamples).forEach(Example => { + Modules[Example.key] = Example.module; }); +const UIExplorerList = { + APIExamples, + ComponentExamples, + Modules, +}; + module.exports = UIExplorerList; diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 5c33d7f64..70a6dbf4c 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -15,151 +15,243 @@ */ 'use strict'; -var React = require('react-native'); -var { - AppRegistry, - Settings, - SnapshotViewIOS, - StyleSheet, -} = React; - -import type { NavigationContext } from 'NavigationContext'; - -var UIExplorerListBase = require('./UIExplorerListBase'); - -var COMPONENTS = [ - require('./ActivityIndicatorIOSExample'), - require('./DatePickerIOSExample'), - require('./ImageExample'), - require('./LayoutEventsExample'), - require('./ListViewExample'), - require('./ListViewGridLayoutExample'), - require('./ListViewPagingExample'), - require('./MapViewExample'), - require('./ModalExample'), - require('./Navigator/NavigatorExample'), - require('./NavigatorIOSColorsExample'), - require('./NavigatorIOSExample'), - require('./PickerIOSExample'), - require('./ProgressViewIOSExample'), - require('./RefreshControlExample'), - require('./ScrollViewExample'), - require('./SegmentedControlIOSExample'), - require('./SliderIOSExample'), - require('./StatusBarExample'), - require('./SwitchExample'), - require('./TabBarIOSExample'), - require('./TextExample.ios'), - require('./TextInputExample.ios'), - require('./TouchableExample'), - require('./TransparentHitTestExample'), - require('./ViewExample'), - require('./WebViewExample'), -]; - -var APIS = [ - require('./AccessibilityIOSExample'), - require('./ActionSheetIOSExample'), - require('./AdSupportIOSExample'), - require('./AlertIOSExample'), - require('./AnimatedExample'), - require('./AnimatedGratuitousApp/AnExApp'), - require('./AppStateIOSExample'), - require('./AppStateExample'), - require('./AsyncStorageExample'), - require('./BorderExample'), - require('./BoxShadowExample'), - require('./CameraRollExample'), - require('./ClipboardExample'), - require('./GeolocationExample'), - require('./ImageEditingExample'), - require('./LayoutExample'), - require('./NavigationExperimental/NavigationExperimentalExample'), - require('./NetInfoExample'), - require('./PanResponderExample'), - require('./PointerEventsExample'), - require('./PushNotificationIOSExample'), - require('./RCTRootViewIOSExample'), - require('./StatusBarIOSExample'), - require('./TimerExample'), - require('./TransformExample'), - require('./VibrationIOSExample'), - require('./XHRExample.ios'), -]; - -type Props = { - navigator: { - navigationContext: NavigationContext, - push: (route: {title: string, component: ReactClass}) => void, - }, - onExternalExampleRequested: Function, +export type UIExplorerExample = { + key: string; + module: Object; }; -class UIExplorerList extends React.Component { - props: Props; - - render() { - return ( - - ); - } - - renderAdditionalView(renderRow: Function, renderTextInput: Function): React.Component { - return renderTextInput(styles.searchTextInput); - } - - search(text: mixed) { - Settings.set({searchText: text}); - } - - _openExample(example: any) { - if (example.external) { - this.props.onExternalExampleRequested(example); - return; - } - - var Component = UIExplorerListBase.makeRenderable(example); - this.props.navigator.push({ - title: Component.title, - component: Component, - }); - } - - onPressRow(example: any) { - this._openExample(example); - } - - // Register suitable examples for snapshot tests - static registerComponents() { - COMPONENTS.concat(APIS).forEach((Example) => { - if (Example.displayName) { - var Snapshotter = React.createClass({ - render: function() { - var Renderable = UIExplorerListBase.makeRenderable(Example); - return ( - - - - ); - }, - }); - AppRegistry.registerComponent(Example.displayName, () => Snapshotter); - } - }); - } -} - -var styles = StyleSheet.create({ - searchTextInput: { - height: 30, +var ComponentExamples: Array = [ + { + key: 'ActivityIndicatorIOSExample', + module: require('./ActivityIndicatorIOSExample'), }, + { + key: 'DatePickerIOSExample', + module: require('./DatePickerIOSExample'), + }, + { + key: 'ImageExample', + module: require('./ImageExample'), + }, + { + key: 'LayoutEventsExample', + module: require('./LayoutEventsExample'), + }, + { + key: 'ListViewExample', + module: require('./ListViewExample'), + }, + { + key: 'ListViewGridLayoutExample', + module: require('./ListViewGridLayoutExample'), + }, + { + key: 'ListViewPagingExample', + module: require('./ListViewPagingExample'), + }, + { + key: 'MapViewExample', + module: require('./MapViewExample'), + }, + { + key: 'ModalExample', + module: require('./ModalExample'), + }, + { + key: 'NavigatorExample', + module: require('./Navigator/NavigatorExample'), + }, + { + key: 'NavigatorIOSColorsExample', + module: require('./NavigatorIOSColorsExample'), + }, + { + key: 'NavigatorIOSExample', + module: require('./NavigatorIOSExample'), + }, + { + key: 'PickerIOSExample', + module: require('./PickerIOSExample'), + }, + { + key: 'ProgressViewIOSExample', + module: require('./ProgressViewIOSExample'), + }, + { + key: 'RefreshControlExample', + module: require('./RefreshControlExample'), + }, + { + key: 'ScrollViewExample', + module: require('./ScrollViewExample'), + }, + { + key: 'SegmentedControlIOSExample', + module: require('./SegmentedControlIOSExample'), + }, + { + key: 'SliderIOSExample', + module: require('./SliderIOSExample'), + }, + { + key: 'StatusBarExample', + module: require('./StatusBarExample'), + }, + { + key: 'SwitchExample', + module: require('./SwitchExample'), + }, + { + key: 'TabBarIOSExample', + module: require('./TabBarIOSExample'), + }, + { + key: 'TextExample', + module: require('./TextExample.ios'), + }, + { + key: 'TextInputExample', + module: require('./TextInputExample.ios'), + }, + { + key: 'TouchableExample', + module: require('./TouchableExample'), + }, + { + key: 'TransparentHitTestExample', + module: require('./TransparentHitTestExample'), + }, + { + key: 'ViewExample', + module: require('./ViewExample'), + }, + { + key: 'WebViewExample', + module: require('./WebViewExample'), + }, +]; + +var APIExamples: Array = [ + { + key: 'AccessibilityIOSExample', + module: require('./AccessibilityIOSExample'), + }, + { + key: 'ActionSheetIOSExample', + module: require('./ActionSheetIOSExample'), + }, + { + key: 'AdSupportIOSExample', + module: require('./AdSupportIOSExample'), + }, + { + key: 'AlertIOSExample', + module: require('./AlertIOSExample'), + }, + { + key: 'AnimatedExample', + module: require('./AnimatedExample'), + }, + { + key: 'AnExApp', + module: require('./AnimatedGratuitousApp/AnExApp'), + }, + { + key: 'AppStateIOSExample', + module: require('./AppStateIOSExample'), + }, + { + key: 'AppStateExample', + module: require('./AppStateExample'), + }, + { + key: 'AsyncStorageExample', + module: require('./AsyncStorageExample'), + }, + { + key: 'BorderExample', + module: require('./BorderExample'), + }, + { + key: 'BoxShadowExample', + module: require('./BoxShadowExample'), + }, + { + key: 'CameraRollExample', + module: require('./CameraRollExample'), + }, + { + key: 'ClipboardExample', + module: require('./ClipboardExample'), + }, + { + key: 'GeolocationExample', + module: require('./GeolocationExample'), + }, + { + key: 'ImageEditingExample', + module: require('./ImageEditingExample'), + }, + { + key: 'LayoutExample', + module: require('./LayoutExample'), + }, + { + key: 'NavigationExperimentalExample', + module: require('./NavigationExperimental/NavigationExperimentalExample'), + }, + { + key: 'NetInfoExample', + module: require('./NetInfoExample'), + }, + { + key: 'PanResponderExample', + module: require('./PanResponderExample'), + }, + { + key: 'PointerEventsExample', + module: require('./PointerEventsExample'), + }, + { + key: 'PushNotificationIOSExample', + module: require('./PushNotificationIOSExample'), + }, + { + key: 'RCTRootViewIOSExample', + module: require('./RCTRootViewIOSExample'), + }, + { + key: 'StatusBarIOSExample', + module: require('./StatusBarIOSExample'), + }, + { + key: 'TimerExample', + module: require('./TimerExample'), + }, + { + key: 'TransformExample', + module: require('./TransformExample'), + }, + { + key: 'VibrationIOSExample', + module: require('./VibrationIOSExample'), + }, + { + key: 'XHRExample', + module: require('./XHRExample.ios'), + }, +]; + +const Modules = {}; + +APIExamples.concat(ComponentExamples).forEach(Example => { + Modules[Example.key] = Example.module; }); +const UIExplorerList = { + APIExamples, + ComponentExamples, + Modules, +}; + module.exports = UIExplorerList; diff --git a/Examples/UIExplorer/UIExplorerListBase.js b/Examples/UIExplorer/UIExplorerListBase.js deleted file mode 100644 index 17ecd8dc2..000000000 --- a/Examples/UIExplorer/UIExplorerListBase.js +++ /dev/null @@ -1,190 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -var React = require('react-native'); -var { - ListView, - StyleSheet, - Text, - TextInput, - TouchableHighlight, - View, -} = React; -var createExamplePage = require('./createExamplePage'); - -var ds = new ListView.DataSource({ - rowHasChanged: (r1, r2) => r1 !== r2, - sectionHeaderHasChanged: (h1, h2) => h1 !== h2, -}); - -class UIExplorerListBase extends React.Component { - constructor(props: any) { - super(props); - this.state = { - dataSource: ds.cloneWithRowsAndSections({ - components: [], - apis: [], - }), - searchText: this.props.searchText || '', - }; - } - - componentDidMount(): void { - this.search(this.state.searchText); - } - - render() { - var topView = this.props.renderAdditionalView && - this.props.renderAdditionalView(this.renderRow.bind(this), this.renderTextInput.bind(this)); - - return ( - - {topView} - - - ); - } - - renderTextInput(searchTextInputStyle: any) { - return ( - - - - ); - } - - _renderSectionHeader(data: any, section: string) { - return ( - - {section.toUpperCase()} - - ); - } - - renderRow(example: any, i: number) { - return ( - - this.onPressRow(example)}> - - - {example.title} - - - {example.description} - - - - - - ); - } - - search(text: mixed): void { - this.props.search && this.props.search(text); - - var regex = new RegExp(String(text), 'i'); - var filter = (component) => regex.test(component.title); - - this.setState({ - dataSource: ds.cloneWithRowsAndSections({ - components: this.props.components.filter(filter), - apis: this.props.apis.filter(filter), - }), - searchText: text, - }); - } - - onPressRow(example: any): void { - this.props.onPressRow && this.props.onPressRow(example); - } - - static makeRenderable(example: any): ReactClass { - return example.examples ? - createExamplePage(null, example) : - example; - } -} - -var styles = StyleSheet.create({ - listContainer: { - flex: 1, - }, - list: { - backgroundColor: '#eeeeee', - }, - sectionHeader: { - padding: 5, - fontWeight: '500', - fontSize: 11, - }, - group: { - backgroundColor: 'white', - }, - row: { - backgroundColor: 'white', - justifyContent: 'center', - paddingHorizontal: 15, - paddingVertical: 8, - }, - separator: { - height: StyleSheet.hairlineWidth, - backgroundColor: '#bbbbbb', - marginLeft: 15, - }, - rowTitleText: { - fontSize: 17, - fontWeight: '500', - }, - rowDetailText: { - fontSize: 15, - color: '#888888', - lineHeight: 20, - }, - searchRow: { - backgroundColor: '#eeeeee', - paddingTop: 75, - paddingLeft: 10, - paddingRight: 10, - paddingBottom: 10, - }, - searchTextInput: { - backgroundColor: 'white', - borderColor: '#cccccc', - borderRadius: 3, - borderWidth: 1, - paddingLeft: 8, - }, -}); - -module.exports = UIExplorerListBase; diff --git a/Examples/UIExplorer/UIExplorerNavigationReducer.js b/Examples/UIExplorer/UIExplorerNavigationReducer.js new file mode 100644 index 000000000..e2e5ebede --- /dev/null +++ b/Examples/UIExplorer/UIExplorerNavigationReducer.js @@ -0,0 +1,95 @@ +/** + * 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. + * + * @flow + */ +'use strict'; + +const React = require('react-native'); +// $FlowFixMe : This is a platform-forked component, and flow seems to only run on iOS? +const UIExplorerList = require('./UIExplorerList'); +const { + NavigationExperimental, +} = React; +const { + Reducer: NavigationReducer, +} = NavigationExperimental; +const StackReducer = NavigationReducer.StackReducer; + +import type {NavigationState} from 'NavigationStateUtils'; + +import type {UIExplorerAction} from './UIExplorerActions'; + +export type UIExplorerNavigationState = { + externalExample: ?string; + stack: NavigationState; +}; + +const UIExplorerStackReducer = StackReducer({ + key: 'UIExplorerMainStack', + initialStates: [ + {key: 'AppList'}, + ], + initialIndex: 0, + matchAction: action => action.openExample && !!UIExplorerList.Modules[action.openExample], + actionStateMap: action => ({ key: action.openExample, }), +}); + +function UIExplorerNavigationReducer(lastState: ?UIExplorerNavigationState, action: any): UIExplorerNavigationState { + if (!lastState) { + return { + externalExample: null, + stack: UIExplorerStackReducer(null, action), + }; + } + if (action.type === 'UIExplorerListWithFilterAction') { + return { + externalExample: null, + stack: { + key: 'UIExplorerMainStack', + index: 0, + children: [ + { + key: 'AppList', + filter: action.filter, + }, + ], + }, + }; + } + if (action.type === 'BackAction' && lastState.externalExample) { + return { + ...lastState, + externalExample: null, + }; + } + if (action.type === 'UIExplorerExampleAction') { + const ExampleModule = UIExplorerList.Modules[action.openExample]; + if (ExampleModule && ExampleModule.external) { + return { + ...lastState, + externalExample: action.openExample, + }; + } + } + const newStack = UIExplorerStackReducer(lastState.stack, action); + if (newStack !== lastState.stack) { + return { + externalExample: null, + stack: UIExplorerStackReducer(null, action), + } + } + return lastState; +} + +module.exports = UIExplorerNavigationReducer; diff --git a/Examples/UIExplorer/UIExplorerStateTitleMap.js b/Examples/UIExplorer/UIExplorerStateTitleMap.js new file mode 100644 index 000000000..137a7cac6 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerStateTitleMap.js @@ -0,0 +1,32 @@ +/** + * 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. + * + * @flow + */ +'use strict'; + +const UIExplorerList = require('./UIExplorerList.ios'); + +import type {NavigationState} from 'NavigationStateUtils'; + +function StateTitleMap(state: NavigationState): string { + if (UIExplorerList.Modules[state.key]) { + return UIExplorerList.Modules[state.key].title + } + if (state.key === 'AppList') { + return 'UIExplorer'; + } + return 'Unknown'; +} + +module.exports = StateTitleMap; diff --git a/Libraries/NavigationExperimental/NavigationRootContainer.js b/Libraries/NavigationExperimental/NavigationRootContainer.js index 685fd89c6..e04fe98e2 100644 --- a/Libraries/NavigationExperimental/NavigationRootContainer.js +++ b/Libraries/NavigationExperimental/NavigationRootContainer.js @@ -17,6 +17,7 @@ const BackAndroid = require('BackAndroid'); const Platform = require('Platform'); import type { + NavigationAction, NavigationState, NavigationReducer } from 'NavigationStateUtils'; @@ -38,6 +39,7 @@ type Props = { renderNavigation: NavigationRenderer; reducer: NavigationReducer; persistenceKey: ?string; + initialAction: NavigationAction; }; class NavigationRootContainer extends React.Component { @@ -47,7 +49,7 @@ class NavigationRootContainer extends React.Component { this.handleNavigation = this.handleNavigation.bind(this); let navState = null; if (!this.props.persistenceKey) { - navState = this.props.reducer(null, null); + navState = this.props.reducer(null, props.initialAction); } this.state = { navState }; } @@ -56,7 +58,7 @@ class NavigationRootContainer extends React.Component { AsyncStorage.getItem(this.props.persistenceKey, (err, storedString) => { if (err || !storedString) { this.setState({ - navState: this.props.reducer(null, null), + navState: this.props.reducer(null, this.props.initialAction), }); return; } @@ -97,6 +99,12 @@ NavigationRootContainer.childContextTypes = { onNavigate: React.PropTypes.func, }; +NavigationRootContainer.defaultProps = { + initialAction: { + type: 'NavigationRootContainerInitialAction', + }, +}; + NavigationRootContainer.getBackAction = getBackAction; module.exports = NavigationRootContainer; diff --git a/Libraries/NavigationExperimental/NavigationStateUtils.js b/Libraries/NavigationExperimental/NavigationStateUtils.js index fb5e2ac06..ba652ea87 100644 --- a/Libraries/NavigationExperimental/NavigationStateUtils.js +++ b/Libraries/NavigationExperimental/NavigationStateUtils.js @@ -30,11 +30,6 @@ export type NavigationAction = { export type NavigationReducer = ( state: ?NavigationState, action: ?NavigationAction -) => ?NavigationState; - -export type NavigationReducerWithDefault = ( - state: ?NavigationState, - action: ?any ) => NavigationState; function getParent(state: NavigationState): ?NavigationParentState { diff --git a/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js index e59405f08..bca336ee6 100644 --- a/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js +++ b/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js @@ -22,16 +22,16 @@ import type { NavigationReducer } from 'NavigationStateUtils'; -function NavigationFindReducer(reducers: Array): NavigationReducer { - return function(lastState: ?NavigationState, action: ?any): ?NavigationState { +function NavigationFindReducer(reducers: Array, defaultState: NavigationState): NavigationReducer { + return function(lastState: ?NavigationState, action: ?any): NavigationState { for (let i = 0; i < reducers.length; i++) { let reducer = reducers[i]; let newState = reducer(lastState, action); if (newState !== lastState) { - return newState; + return newState || defaultState; } } - return lastState; + return lastState || defaultState; }; } diff --git a/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js index d98a1d91d..3f849300b 100644 --- a/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js +++ b/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js @@ -16,14 +16,12 @@ const NavigationStateUtils = require('NavigationStateUtils'); import type { NavigationReducer, - NavigationReducerWithDefault, NavigationState, NavigationParentState } from 'NavigationStateUtils'; const ActionTypes = { JUMP_TO: 'react-native/NavigationExperimental/tabs-jumpTo', - ON_TAB_ACTION: 'react-native/NavigationExperimental/tabs-onTabAction', }; const DEFAULT_KEY = 'TABS_STATE_DEFAULT_KEY'; @@ -39,37 +37,21 @@ function NavigationTabsJumpToAction(index: number): JumpToAction { }; } -export type OnTabAction = { - type: string, - index: number, - action: any, -}; -function NavigationTabsOnTabAction(index: number, action: any): OnTabAction { - return { - type: ActionTypes.ON_TAB_ACTION, - index, - action, - }; -} - type TabsReducerConfig = { key: string; - initialIndex: ?number; - tabReducers: Array; + initialIndex: number; + tabReducers: Array; }; function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConfig): NavigationReducer { - if (initialIndex == null) { - initialIndex = 0; - } if (key == null) { key = DEFAULT_KEY; } - return function(lastNavState: ?NavigationState, action: ?any): ?NavigationState { + return function(lastNavState: ?NavigationState, action: ?any): NavigationState { if (!lastNavState) { lastNavState = { children: tabReducers.map(reducer => reducer(null, null)), - index: initialIndex, + index: initialIndex || 0, key, }; } @@ -86,37 +68,17 @@ function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConf action.index, ); } - if (action.type === ActionTypes.ON_TAB_ACTION) { - const onTabAction: OnTabAction = action; - const lastTabRoute = lastParentNavState.children[onTabAction.index]; - const tabReducer = tabReducers[onTabAction.index]; - if (tabReducer) { - const newTabRoute = tabReducer(lastTabRoute, action.action); - if (newTabRoute && newTabRoute !== lastTabRoute) { - let navState = NavigationStateUtils.replaceAtIndex( - lastParentNavState, - onTabAction.index, - newTabRoute - ); - navState = NavigationStateUtils.jumpToIndex( - navState, - onTabAction.index - ); - return navState; - } - } - } const subReducers = tabReducers.map((tabReducer, tabIndex) => { - return function reduceTab(lastNavState: ?NavigationState, tabAction: ?any): ?NavigationState { - if (!tabReducer || !lastNavState) { - return lastNavState; + return function(navState: ?NavigationState, tabAction: any): NavigationState { + if (!navState) { + return lastParentNavState; } - const lastParentNavState = NavigationStateUtils.getParent(lastNavState); - const lastSubTabState = lastParentNavState && lastParentNavState.children[tabIndex]; - const nextSubTabState = tabReducer(lastSubTabState, tabAction); - if (nextSubTabState && lastSubTabState !== nextSubTabState) { - const tabs = lastParentNavState && lastParentNavState.children || []; - tabs[tabIndex] = nextSubTabState; + const parentState = NavigationStateUtils.getParent(navState); + const tabState = parentState && parentState.children[tabIndex]; + const nextTabState = tabReducer(tabState, tabAction); + if (nextTabState && tabState !== nextTabState) { + const tabs = parentState && parentState.children || []; + tabs[tabIndex] = nextTabState; return { ...lastParentNavState, tabs, @@ -128,8 +90,8 @@ function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConf }); let selectedTabReducer = subReducers.splice(lastParentNavState.index, 1)[0]; subReducers.unshift(selectedTabReducer); - subReducers.push((lastParentNavState: ?NavigationState, action: ?any) => { - if (lastParentNavState && action && action.type === 'BackAction') { + subReducers.push(function(navState: ?NavigationState, action: any): NavigationState { + if (navState && action.type === 'BackAction') { return NavigationStateUtils.jumpToIndex( lastParentNavState, 0 @@ -137,12 +99,11 @@ function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConf } return lastParentNavState; }); - const findReducer = NavigationFindReducer(subReducers); + const findReducer = NavigationFindReducer(subReducers, lastParentNavState); return findReducer(lastParentNavState, action); }; } NavigationTabsReducer.JumpToAction = NavigationTabsJumpToAction; -NavigationTabsReducer.OnTabAction = NavigationTabsOnTabAction; module.exports = NavigationTabsReducer;