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
This commit is contained in:
Eric Vicenti 2016-02-22 16:15:35 -08:00 committed by facebook-github-bot-6
parent 2551540540
commit 876ecb291f
16 changed files with 1074 additions and 605 deletions

View File

@ -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 {
<NavigationRootContainer
reducer={NavigationBasicReducer}
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
persistenceKey="NavigationAnimatedExampleState"
persistenceKey="NavigationAnimExampleState"
renderNavigation={this._renderNavigated}
/>
);
@ -85,7 +85,7 @@ class NavigationAnimatedExample extends React.Component {
<NavigationExampleRow
text="Push!"
onPress={() => {
onNavigate('Route #' + navigationState.children.length);
onNavigate({ type: 'push', key: 'Route #' + navigationState.children.length });
}}
/>
<NavigationExampleRow

View File

@ -35,7 +35,7 @@ var EXAMPLES = {
'Tic Tac Toe': require('./NavigationTicTacToeExample'),
};
var EXAMPLE_STORAGE_KEY = 'NavigationExampleExample';
var EXAMPLE_STORAGE_KEY = 'NavigationExperimentalExample';
var NavigationExperimentalExample = React.createClass({
statics: {

View File

@ -31,7 +31,7 @@ const {
type GameGrid = Array<Array<?string>>;
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 (
<View style={styles.container}>
<Text
style={styles.closeButton}
onPress={props.onExampleExit}>
Close
</Text>
<Text style={styles.title}>EXTREME T3</Text>
<View style={styles.board}>
{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 (
<NavigationRootContainer
reducer={GameReducer}
persistenceKey="TicTacToeGame"
persistenceKey="TicTacToeGameState"
renderNavigation={(game) => (
<TicTacToeGame
game={game}
onExampleExit={this.props.onExampleExit}
/>
)}
/>
@ -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',
},

View File

@ -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;

View File

@ -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 (
<NavigationRootContainer
persistenceKey="UIExplorerStateNavState"
ref={navRootRef => { this._navigationRootRef = navRootRef; }}
reducer={UIExplorerNavigationReducer}
renderNavigation={this._renderApp.bind(this)}
/>
);
}
_renderApp(navigationState, onNavigate) {
if (!navigationState) {
return null;
}
return (
<DrawerLayoutAndroid
drawerPosition={DrawerLayoutAndroid.positions.Left}
drawerWidth={Dimensions.get('window').width - DRAWER_WIDTH_LEFT}
keyboardDismissMode="on-drag"
onDrawerOpen={() => {
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)}
</DrawerLayoutAndroid>
);
}
_renderNavigationView() {
_renderDrawerContent(onNavigate) {
return (
<UIExplorerList
onSelectExample={this.onSelectExample}
isInDrawer={true}
<UIExplorerExampleList
list={UIExplorerList}
displayTitleRow={true}
disableSearch={true}
onNavigate={(action) => {
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 (
<Component
onExampleExit={() => {
onNavigate(NavigationRootContainer.getBackAction());
}}
ref={(example) => { this._exampleRef = example; }}
/>
);
}
this.setState({
example: example,
});
}
_renderHome() {
const onSelectExample = this.onSelectExample;
return React.createClass({
render: function() {
return (
<UIExplorerList
onSelectExample={onSelectExample}
isInDrawer={false}
const {stack} = navigationState;
const title = UIExplorerStateTitleMap(stack.children[stack.index]);
if (stack && stack.children[1]) {
const {key} = stack.children[1];
const ExampleModule = UIExplorerList.Modules[key];
const ExampleComponent = UIExplorerExampleList.makeRenderable(ExampleModule);
return (
<View style={styles.container}>
<StatusBar
backgroundColor="#589c90"
/>
);
}
});
}
_renderNavigation() {
const Component = this.state.example.component;
<ToolbarAndroid
logo={require('image!launcher_icon')}
navIcon={require('image!ic_menu_black_24dp')}
onIconClicked={() => this.drawer.openDrawer()}
style={styles.toolbar}
title={title}
/>
<ExampleComponent
ref={(example) => { this._exampleRef = example; }}
/>
</View>
);
}
return (
<View style={styles.container}>
<StatusBar
@ -110,16 +137,24 @@ class UIExplorerApp extends React.Component {
navIcon={require('image!ic_menu_black_24dp')}
onIconClicked={() => this.drawer.openDrawer()}
style={styles.toolbar}
title={this.state.example.title}
title={title}
/>
<Component
ref={(example) => { this._exampleRef = example; }}
<UIExplorerExampleList
list={UIExplorerList}
{...stack.children[0]}
/>
</View>
);
}
_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({

View File

@ -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 (
<NavigationRootContainer
persistenceKey="UIExplorerState"
reducer={UIExplorerNavigationReducer}
renderNavigation={this._renderNavigation}
/>
);
}
_renderNavigation(navigationState: UIExplorerNavigationState, onNavigate: Function) {
if (!navigationState) {
return null;
}
if (navigationState.externalExample) {
var Component = UIExplorerList.Modules[navigationState.externalExample];
return (
<Example
<Component
onExampleExit={() => {
this.setState({ openExternalExample: null, });
onNavigate(NavigationRootContainer.getBackAction());
}}
/>
);
}
const {stack} = navigationState;
return (
<View style={styles.container}>
<StatusBar barStyle="default" />
<NavigatorIOS
style={styles.container}
initialRoute={{
title: 'UIExplorer',
component: UIExplorerList,
passProps: {
onExternalExampleRequested: (example) => {
this.setState({ openExternalExample: example, });
},
}
}}
itemWrapperStyle={styles.itemWrapper}
tintColor="#008888"
/>
</View>
<NavigationAnimatedView
navigationState={stack}
style={styles.container}
renderOverlay={this._renderOverlay.bind(this, stack)}
renderScene={this._renderSceneContainer.bind(this, stack)}
/>
);
}
_renderOverlay(
navigationState: NavigationState,
position: Value,
layout: Layout
): ReactElement {
return (
<NavigationHeader
navigationState={navigationState}
position={position}
getTitle={UIExplorerStateTitleMap}
/>
);
}
_renderSceneContainer(
navigationState: NavigationState,
scene: NavigationState,
index: number,
position: Value,
layout: Layout
): ReactElement {
return (
<NavigationCard
key={scene.key}
index={index}
navigationState={navigationState}
position={position}
layout={layout}>
{this._renderScene(scene)}
</NavigationCard>
);
}
_renderScene(state: Object): ?ReactElement {
if (state.key === 'AppList') {
return (
<UIExplorerExampleList
list={UIExplorerList}
style={styles.exampleContainer}
{...state}
/>
);
}
const Example = UIExplorerList.Modules[state.key];
if (Example) {
const Component = UIExplorerExampleList.makeRenderable(Example);
return (
<View style={styles.exampleContainer}>
<Component />
</View>
);
}
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 (
<SnapshotViewIOS>
<Renderable />
</SnapshotViewIOS>
);
},
});
AppRegistry.registerComponent(ExampleModule.displayName, () => Snapshotter);
}
});
module.exports = UIExplorerApp;

View File

@ -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<UIExplorerExample>,
APIExamples: Array<UIExplorerExample>,
},
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 (
<View style={[styles.listContainer, this.props.style]}>
{this._renderTitleRow()}
{this._renderTextInput()}
<ListView
style={styles.list}
dataSource={dataSource}
renderRow={this._renderExampleRow.bind(this)}
renderSectionHeader={this._renderSectionHeader}
keyboardShouldPersistTaps={true}
automaticallyAdjustContentInsets={false}
keyboardDismissMode="on-drag"
/>
</View>
);
}
_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 (
<View style={styles.searchRow}>
<TextInput
autoCapitalize="none"
autoCorrect={false}
clearButtonMode="always"
onChangeText={text => {
this.props.onNavigate(UIExplorerActions.ExampleListWithFilter(text));
}}
placeholder="Search..."
style={[styles.searchTextInput, this.props.searchTextInputStyle]}
testID="explorer_search"
value={this.props.filter}
/>
</View>
);
}
_renderSectionHeader(data: any, section: string): ?ReactElement {
return (
<Text style={styles.sectionHeader}>
{section.toUpperCase()}
</Text>
);
}
_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 (
<View key={key || title}>
<TouchableHighlight onPress={handler}>
<View style={styles.row}>
<Text style={styles.rowTitleText}>
{title}
</Text>
<Text style={styles.rowDetailText}>
{description}
</Text>
</View>
</TouchableHighlight>
<View style={styles.separator} />
</View>
);
}
_handleRowPress(exampleKey: string): void {
this.props.onNavigate(UIExplorerActions.ExampleAction(exampleKey))
}
}
function makeRenderable(example: any): ReactClass<any, any, any> {
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;

View File

@ -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 (
<UIExplorerListBase
components={COMPONENTS}
apis={APIS}
searchText=""
renderAdditionalView={this.renderAdditionalView.bind(this)}
onPressRow={this.onPressRow.bind(this)}
/>
);
}
renderAdditionalView(renderRow, renderTextInput): React.Component {
if (this.props.isInDrawer) {
var homePage = renderRow({
title: 'UIExplorer',
description: 'List of examples',
}, -1);
return (
<View>
{homePage}
</View>
);
}
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<UIExplorerExample> = [
{
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;

View File

@ -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<any,any,any>}) => void,
},
onExternalExampleRequested: Function,
export type UIExplorerExample = {
key: string;
module: Object;
};
class UIExplorerList extends React.Component {
props: Props;
render() {
return (
<UIExplorerListBase
components={COMPONENTS}
apis={APIS}
searchText={Settings.get('searchText')}
renderAdditionalView={this.renderAdditionalView.bind(this)}
search={this.search.bind(this)}
onPressRow={this.onPressRow.bind(this)}
/>
);
}
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 (
<SnapshotViewIOS>
<Renderable />
</SnapshotViewIOS>
);
},
});
AppRegistry.registerComponent(Example.displayName, () => Snapshotter);
}
});
}
}
var styles = StyleSheet.create({
searchTextInput: {
height: 30,
var ComponentExamples: Array<UIExplorerExample> = [
{
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<UIExplorerExample> = [
{
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;

View File

@ -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 (
<View style={styles.listContainer}>
{topView}
<ListView
style={styles.list}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
renderSectionHeader={this._renderSectionHeader}
keyboardShouldPersistTaps={true}
automaticallyAdjustContentInsets={false}
keyboardDismissMode="on-drag"
/>
</View>
);
}
renderTextInput(searchTextInputStyle: any) {
return (
<View style={styles.searchRow}>
<TextInput
autoCapitalize="none"
autoCorrect={false}
clearButtonMode="always"
onChangeText={this.search.bind(this)}
placeholder="Search..."
style={[styles.searchTextInput, searchTextInputStyle]}
testID="explorer_search"
value={this.state.searchText}
/>
</View>
);
}
_renderSectionHeader(data: any, section: string) {
return (
<Text style={styles.sectionHeader}>
{section.toUpperCase()}
</Text>
);
}
renderRow(example: any, i: number) {
return (
<View key={i}>
<TouchableHighlight onPress={() => this.onPressRow(example)}>
<View style={styles.row}>
<Text style={styles.rowTitleText}>
{example.title}
</Text>
<Text style={styles.rowDetailText}>
{example.description}
</Text>
</View>
</TouchableHighlight>
<View style={styles.separator} />
</View>
);
}
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<any, any, any> {
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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -22,16 +22,16 @@ import type {
NavigationReducer
} from 'NavigationStateUtils';
function NavigationFindReducer(reducers: Array<NavigationReducer>): NavigationReducer {
return function(lastState: ?NavigationState, action: ?any): ?NavigationState {
function NavigationFindReducer(reducers: Array<NavigationReducer>, 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;
};
}

View File

@ -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<NavigationReducerWithDefault>;
initialIndex: number;
tabReducers: Array<NavigationReducer>;
};
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;