diff --git a/Example/Example.js b/Example/Example.js index 2a2f09d1c..bf5cb87ad 100644 --- a/Example/Example.js +++ b/Example/Example.js @@ -46,11 +46,25 @@ const reducerCreate = params=>{ } }; +// define this based on the styles/dimensions you use +const getSceneStyle = function (props) { + return { + flex: 1, + marginTop: props.hideNavBar ? 0 : 64, + marginBottom: props.hideTabBar ? 0 : 49.5, + backgroundColor: '#fff', + shadowColor: null, + shadowOffset: null, + shadowOpacity: null, + shadowRadius: null, + }; +} + export default class Example extends React.Component { render() { - return + return - + @@ -67,12 +81,12 @@ export default class Example extends React.Component { - + } /> alert("Left button!")} leftTitle="Left" duration={1} panHandlers={null}/> - }/> + diff --git a/Example/README.md b/Example/README.md new file mode 100644 index 000000000..5cd8b582a --- /dev/null +++ b/Example/README.md @@ -0,0 +1,14 @@ +## Example App + +Normally, after a code change to react-native-router-flux src files, +you must remove the node_modules/react-native-router-flux directory +and npm install. The react-native packager wont follow symlinks. + +To assist development, this command watches and rsyncs changes: + +``` +npm run sync-rnrf +``` + +Leave a terminal open running this command when running the Example +app and making react-native-router-flux src changes. diff --git a/Example/components/EchoView.js b/Example/components/EchoView.js index e83e79885..d2f7b6cde 100644 --- a/Example/components/EchoView.js +++ b/Example/components/EchoView.js @@ -9,12 +9,18 @@ const styles = StyleSheet.create({ justifyContent: "center", alignItems: "center", backgroundColor: "#F5FCFF", + borderWidth: 2, + borderColor: 'red', }, instructions: { textAlign: "center", color: "#333333", marginBottom: 5, }, + smaller: { + marginBottom: 5, + fontSize: 12, + }, }); @@ -24,7 +30,36 @@ export default class extends React.Component { key: {this.props.navigationState.key} sceneKey: {this.props.navigationState.sceneKey} - + + + + + ); diff --git a/Example/components/Launch.js b/Example/components/Launch.js index 16bea40e6..7329aa346 100644 --- a/Example/components/Launch.js +++ b/Example/components/Launch.js @@ -4,28 +4,28 @@ import Button from "react-native-button"; import {Actions} from "react-native-router-flux"; const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: "center", - alignItems: "center", - backgroundColor: "transparent", - } + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + backgroundColor: "transparent", + } }); class Launch extends React.Component { - render(){ - return ( - - Launch page - - - - - - - - ); - } + render(){ + return ( + + Launch page + + + + + + + + ); + } } module.exports = Launch; diff --git a/Example/components/NavigationDrawer.js b/Example/components/NavigationDrawer.js index 6a8852109..6c4148d07 100644 --- a/Example/components/NavigationDrawer.js +++ b/Example/components/NavigationDrawer.js @@ -24,7 +24,7 @@ class NavigationDrawer extends React.Component { main: { opacity: Math.max(0.54, 1 - ratio) }, })} > - + ); } diff --git a/Example/components/TabIcon.js b/Example/components/TabIcon.js index e4e5def63..49ccb00f7 100644 --- a/Example/components/TabIcon.js +++ b/Example/components/TabIcon.js @@ -1,5 +1,6 @@ import React from 'react'; -import { PropTypes, Text } from 'react-native'; +import {PropTypes} from "react"; +import {Text} from "react-native"; const propTypes = { selected: PropTypes.string, diff --git a/Example/components/TabView.js b/Example/components/TabView.js index 7f214bf1b..69fa9fddd 100644 --- a/Example/components/TabView.js +++ b/Example/components/TabView.js @@ -1,5 +1,6 @@ import React from 'react'; -import { PropTypes, StyleSheet, Text, View } from 'react-native'; +import {PropTypes} from "react"; +import {StyleSheet, Text, View} from "react-native"; import Button from 'react-native-button'; import { Actions } from 'react-native-router-flux'; @@ -19,13 +20,15 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', + borderWidth: 2, + borderColor: 'red', }, }); const TabView = (props, context) => { const drawer = context.drawer; return ( - + Tab {props.title} {props.name === 'tab1_1' && diff --git a/Example/components/TabView2.js b/Example/components/TabView2.js deleted file mode 100644 index 87f4bf448..000000000 --- a/Example/components/TabView2.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import {View, Text, StyleSheet} from "react-native"; -import Button from "react-native-button"; -import {Actions} from "react-native-router-flux"; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: "center", - alignItems: "center", - backgroundColor: "#F5FCFF", - }, - welcome: { - fontSize: 20, - textAlign: "center", - margin: 10, - }, - instructions: { - textAlign: "center", - color: "#333333", - marginBottom: 5, - }, -}); - -export default class extends React.Component { - render(){ - return ( - - Tab #{this.props.name} - - ); - } -} diff --git a/Example/iOS/Example.xcodeproj/project.pbxproj b/Example/iOS/Example.xcodeproj/project.pbxproj index 896792e53..ea71be87f 100644 --- a/Example/iOS/Example.xcodeproj/project.pbxproj +++ b/Example/iOS/Example.xcodeproj/project.pbxproj @@ -105,17 +105,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = main.jsbundle; path = main.jsbundle; sourceTree = ""; }; - 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; - 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; - 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; - 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; - 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; }; + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; 00E356EE1AD99517003FC87E /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* ExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleTests.m; sourceTree = ""; }; - 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; - 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Example/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Example/AppDelegate.m; sourceTree = ""; }; @@ -123,9 +123,9 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Example/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Example/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Example/main.m; sourceTree = ""; }; - 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../node_modules/react-native/React/React.xcodeproj; sourceTree = ""; }; - 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; - 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../node_modules/react-native/Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; + 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -526,7 +526,6 @@ runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n../node_modules/react-native/packager/react-native-xcode.sh"; - showEnvVarsInLog = 1; }; /* End PBXShellScriptBuildPhase section */ @@ -618,9 +617,12 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../node_modules/react-native/React/**", ); - INFOPLIST_FILE = "Example/Info.plist"; + INFOPLIST_FILE = Example/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + ); PRODUCT_NAME = Example; }; name = Debug; @@ -634,9 +636,12 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../node_modules/react-native/React/**", ); - INFOPLIST_FILE = "Example/Info.plist"; + INFOPLIST_FILE = Example/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + ); PRODUCT_NAME = Example; }; name = Release; diff --git a/Example/index.ios.js b/Example/index.ios.js index 3661e0cd4..f6dd03ee1 100644 --- a/Example/index.ios.js +++ b/Example/index.ios.js @@ -1,5 +1,8 @@ import { AppRegistry } from 'react-native'; +// @todo remove when RN upstream is fixed +console.ignoredYellowBox = ['Warning: Failed propType: SceneView']; + import Example from './Example'; AppRegistry.registerComponent('Example', () => Example); diff --git a/Example/package.json b/Example/package.json index 0e6aa8fd4..c76b89ba0 100644 --- a/Example/package.json +++ b/Example/package.json @@ -3,14 +3,18 @@ "version": "0.0.1", "private": true, "scripts": { - "start": "node_modules/react-native/packager/packager.sh" + "start": "node_modules/react-native/packager/packager.sh", + "sync-rnrf": "rm -rf ./node_modules/react-native-router-flux; sane '/usr/bin/rsync -v -a --exclude .git --exclude Example --exclude node_modules ../ ./node_modules/react-native-router-flux/' .. --glob='{**/*.json,**/*.js}'" }, "dependencies": { - "react": "^0.14.7", - "react-native": "0.22.2", - "react-native-button": "^1.2.1", - "react-native-drawer": "^1.16.7", - "react-native-modalbox": "^1.3.0", + "react": "^15.0.2", + "react-native": "^0.26.0", + "react-native-button": "github:ide/react-native-button", + "react-native-drawer": "^2.2.2", + "react-native-modalbox": "^1.3.3", "react-native-router-flux": "file:../" + }, + "devDependencies": { + "sane": "^1.3.4" } } diff --git a/README.md b/README.md index b5d0597f1..d3d521b00 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Router for React Native based on new React Native Navigation API. - **Highly Customizable Navigation Bar** - Show/hide the navbar depending on Scene or even the state of a Scene (e.g. Edit/Save navbar for edit mode). -- **Tab Bar Support** using [react-native-tabs](https://github.com/aksonov/react-native-tabs) (see demo). +- **Tab Bar Support** using [react-native-tab-navigator](https://github.com/exponentjs/react-native-tab-navigator) (see Example app). - **Nested Navigators** (e.g. Each tab can have its own navigator, nested in a root navigator). diff --git a/index.js b/index.js index cc5c464d8..33a7dfb3b 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,7 @@ import Scene from './src/Scene'; import Switch from './src/Switch'; import TabBar from './src/TabBar'; import getInitialState from './src/State'; +import Util from './src/Util'; export { Actions, @@ -20,4 +21,5 @@ export { Switch, TabBar, getInitialState, + Util, }; diff --git a/package.json b/package.json index e0e3c7a4e..265296c47 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-router-flux", - "version": "3.22.23", + "version": "3.26.0", "description": "React Native Router using Flux architecture", "repository": { "type": "git", @@ -36,7 +36,7 @@ } ], "dependencies": { - "react-native-tabs": "^1.0.2" + "react-native-tab-navigator": "joenoon/react-native-tab-navigator#rnrf-lock" }, "devDependencies": { "babel-cli": "^6.6.5", @@ -57,10 +57,10 @@ "expect": "^1.14.0", "mocha": "^2.4.5", "mocha-junit-reporter": "^1.11.1", - "react": "^0.14.8", + "react": "^15.0.2", "react-addons-test-utils": "^0.14.7", "react-dom": "^0.14.7", - "react-native": "^0.22.2", + "react-native": "^0.26.0", "react-native-mock": "0.0.6", "sinon": "^1.17.3" } diff --git a/src/DefaultRenderer.js b/src/DefaultRenderer.js index 06a01fe94..420604f58 100644 --- a/src/DefaultRenderer.js +++ b/src/DefaultRenderer.js @@ -14,21 +14,39 @@ import { Animated, NavigationExperimental, View, + StyleSheet, } from 'react-native'; import TabBar from './TabBar'; import NavBar from './NavBar'; import Actions from './Actions'; +import { deepestExplicitValueForKey } from './Util'; const { AnimatedView: NavigationAnimatedView, Card: NavigationCard, } = NavigationExperimental; +const { + CardStackPanResponder: NavigationCardStackPanResponder, + CardStackStyleInterpolator: NavigationCardStackStyleInterpolator, +} = NavigationCard; + +const styles = StyleSheet.create({ + animatedView: { + flex: 1, + backgroundColor: 'transparent', + }, + sceneStyle: { + flex: 1, + }, +}); + export default class DefaultRenderer extends Component { static propTypes = { navigationState: PropTypes.object, + onNavigate: PropTypes.func, }; static childContextTypes = { @@ -67,41 +85,62 @@ export default class DefaultRenderer extends Component { Actions.focus({ scene }); } - renderCard(/* NavigationSceneRendererProps*/ props) { - const { key, direction, panHandlers, getSceneStyle } = props.scene.navigationState; + renderCard(/* NavigationSceneRendererProps */ props) { + const { key, direction, getSceneStyle } = props.scene.navigationState; + let { panHandlers, animationStyle } = props.scene.navigationState; - const optionals = {}; - if (getSceneStyle) optionals.style = getSceneStyle(props); + // Since we always need to pass a style for the direction, we can avoid #526 + let style; + if (getSceneStyle) { + const hideNavBar = deepestExplicitValueForKey(props.navigationState, 'hideNavBar'); + const hideTabBar = deepestExplicitValueForKey(props.navigationState, 'hideTabBar'); + style = getSceneStyle({ ...props, hideNavBar, hideTabBar }); + } + + const isVertical = direction === 'vertical'; + + if (typeof(animationStyle) === 'undefined') { + animationStyle = (isVertical ? + NavigationCardStackStyleInterpolator.forVertical(props) : + NavigationCardStackStyleInterpolator.forHorizontal(props)); + } + if (typeof(panHandlers) === 'undefined') { + panHandlers = panHandlers || (isVertical ? + NavigationCardStackPanResponder.forVertical(props) : + NavigationCardStackPanResponder.forHorizontal(props)); + } return ( ); } - renderScene(props: Object) { + renderScene(/* NavigationSceneRendererProps */ props) { return ( ); } - renderHeader(props) { + renderHeader(/* NavigationSceneRendererProps */ props) { const state = props.navigationState; const child = state.children[state.index]; let selected = state.children[state.index]; while (selected.hasOwnProperty('children')) { selected = selected.children[selected.index]; } - if (state.hideNavBar || selected.hideNavBar || child.hideNavBar) { + + const hideNavBar = deepestExplicitValueForKey(state, 'hideNavBar'); + if (hideNavBar) { return null; } @@ -138,9 +177,10 @@ export default class DefaultRenderer extends Component { } render() { - const navigationState = this.props.navigationState; + const { navigationState, onNavigate } = this.props; - if (!navigationState) { + if (!navigationState || !onNavigate) { + console.error('navigationState and onNavigate property should be not null'); return null; } @@ -149,14 +189,14 @@ export default class DefaultRenderer extends Component { if (navigationState.tabs && !SceneComponent) { SceneComponent = TabBar; } - if (SceneComponent) { return ( @@ -167,7 +207,6 @@ export default class DefaultRenderer extends Component { const selected = navigationState.children[navigationState.index]; const applyAnimation = selected.applyAnimation || navigationState.applyAnimation; const style = selected.style || navigationState.style; - let direction = selected.direction || navigationState.direction || 'horizontal'; if (applyAnimation) { optionals.applyAnimation = applyAnimation; @@ -176,7 +215,11 @@ export default class DefaultRenderer extends Component { if (duration === null || duration === undefined) duration = navigationState.duration; if (duration !== null && duration !== undefined) { optionals.applyAnimation = (pos, navState) => { - Animated.timing(pos, { toValue: navState.index, duration }).start(); + if (duration === 0) { + pos.setValue(navState.index); + } else { + Animated.timing(pos, { toValue: navState.index, duration }).start(); + } }; } } @@ -184,19 +227,13 @@ export default class DefaultRenderer extends Component { return ( ); } + } diff --git a/src/Modal.js b/src/Modal.js index 87eccc6c3..d9177b851 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -10,6 +10,7 @@ const propTypes = { navigationState: PropTypes.shape({ children: PropTypes.array, }), + onNavigate: PropTypes.func, }; export default function Modal(props: Object) { @@ -18,7 +19,12 @@ export default function Modal(props: Object) { return ( - + {children.length > 1 && children.map((el, i) => { if (i > 0 && el.component) { const Component = el.component; diff --git a/src/Router.js b/src/Router.js index cdb95476d..36d836a27 100644 --- a/src/Router.js +++ b/src/Router.js @@ -94,7 +94,7 @@ class Router extends Component { return onNavigate(props); }; - return ; + return ; } render() { diff --git a/src/Scene.js b/src/Scene.js index d978c8581..d9dffa451 100644 --- a/src/Scene.js +++ b/src/Scene.js @@ -6,9 +6,22 @@ * LICENSE file in the root directory of this source tree. * */ -import React from 'react'; +import React, { PropTypes } from 'react'; +import { View, Text } from 'react-native'; export default class extends React.Component { + + // @todo - should all props be documented/specified here? + + static propTypes = { + tabBarStyle: View.propTypes.style, + tabSceneStyle: View.propTypes.style, + tabStyle: View.propTypes.style, + tabTitleStyle: Text.propTypes.style, + tabSelectedTitleStyle: Text.propTypes.style, + tabTitle: PropTypes.string, + }; + render() { return null; } diff --git a/src/Switch.js b/src/Switch.js index eb6f47f84..2d6acf0c1 100644 --- a/src/Switch.js +++ b/src/Switch.js @@ -1,8 +1,13 @@ -import React, { Component } from 'react'; +import React, { Component, PropTypes } from 'react'; import DefaultRenderer from './DefaultRenderer'; import Actions from './Actions'; export default class extends Component { + + static propTypes = { + onNavigate: PropTypes.func, + }; + constructor(props) { super(props); this.updateState = this.updateState.bind(this); @@ -41,7 +46,12 @@ export default class extends Component { render() { if (this.state.navigationState) { - return ; + return ( + + ); } return null; diff --git a/src/TabBar.js b/src/TabBar.js index 8743fa64c..a9dc70c1e 100644 --- a/src/TabBar.js +++ b/src/TabBar.js @@ -1,80 +1,77 @@ -import React, { PropTypes } from 'react'; -import { View, NavigationExperimental } from 'react-native'; -import Tabs from 'react-native-tabs'; +import React, { Component, PropTypes } from 'react'; +import { View } from 'react-native'; import DefaultRenderer from './DefaultRenderer'; import Actions from './Actions'; -const { - View: NavigationView, -} = NavigationExperimental; +import TabNavigator from 'react-native-tab-navigator'; +import { deepestExplicitValueForKey } from './Util'; -const propTypes = { - navigationState: PropTypes.object, - tabIcon: PropTypes.any, -}; +class TabBar extends Component { -class TabBar extends React.Component { - constructor(props) { - super(props); - this.onSelect = this.onSelect.bind(this); - this.renderScene = this.renderScene.bind(this); - } + static propTypes = { + navigationState: PropTypes.object, + tabIcon: PropTypes.any, + onNavigate: PropTypes.func, + tabBarStyle: View.propTypes.style, + tabSceneStyle: View.propTypes.style, + }; onSelect(el) { - if (!Actions[el.props.name]) { + if (!Actions[el.sceneKey]) { throw new Error( - `No action is defined for name=${el.props.name} ` + + `No action is defined for sceneKey=${el.sceneKey} ` + `actions: ${JSON.stringify(Object.keys(Actions))}`); } - Actions[el.props.name](); - } - - renderScene(props) { - if (props.layout) { - // for 0.24+, props is /*NavigationSceneRendererProps*/ - // (add flow def above when phasing out < 0.24 support) - return ( - - ); - } - // for < 0.24 - return ; + Actions[el.sceneKey](); } render() { const state = this.props.navigationState; - let selected = state.children[state.index]; - while (selected.hasOwnProperty('children')) { - selected = selected.children[selected.index]; + const selected = state.children[state.index]; + const hideTabBar = deepestExplicitValueForKey(state, 'hideTabBar'); + + const tabBarStyle = {}; + + if (hideTabBar) { + tabBarStyle.opacity = 0; + tabBarStyle.height = 0; } - const hideTabBar = state.hideTabBar || selected.hideTabBar; + return ( - - {!hideTabBar && state.children.filter(el => el.icon).length > 0 && - - {state.children.filter(el => el.icon || this.props.tabIcon).map(el => { - const Icon = el.icon || this.props.tabIcon; - return ; - })} - } + + {state.children.map(el => { + const isSelected = el.sceneKey === selected.sceneKey; + const Icon = el.icon || this.props.tabIcon; + return ( + } + renderSelectedIcon={() => } + onPress={() => this.onSelect(el)} + tabStyle={el.tabStyle} + titleStyle={el.tabTitleStyle} + selectedTitleStyle={el.tabSelectedTitleStyle} + > + + + ); + })} + ); } -} -TabBar.propTypes = propTypes; +} export default TabBar; diff --git a/src/Util.js b/src/Util.js index 03ad54f9c..834272fcb 100644 --- a/src/Util.js +++ b/src/Util.js @@ -1,5 +1,41 @@ +// searches for the deepest explicitly set value for a key +// in a navigationState tree. +export function deepestExplicitValueForKey(navigationState, key) { + let current; + let selected = navigationState; + + while (selected.hasOwnProperty('children')) { + if (!selected.tabs) { + // for pushed children, iterate through each, recording key value, + // until reaching the selected child + for (let i = 0; i < selected.index; i++) { + if (typeof(selected.children[i][key]) !== 'undefined') { + current = selected.children[i][key]; + } + } + } + // set the new selected child and check for a key value + selected = selected.children[selected.index]; + if (typeof(selected[key]) !== 'undefined') { + current = selected[key]; + } + } + + // fallback to the root key value + if (typeof(current) === 'undefined') { + current = navigationState[key]; + } + + return current; +} + export function assert(expr, failDescription) { if (!expr) { throw new Error(`[react-native-router-flux] ${failDescription}`); } } + +export default { + deepestExplicitValueForKey, + assert, +};