diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js index e74d80e26..8ef14c56a 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js @@ -27,6 +27,7 @@ var { RootContainer: NavigationRootContainer, Reducer: NavigationReducer, Header: NavigationHeader, + HeaderTitle: NavigationHeaderTitle, } = NavigationExperimental; const NavigationBasicReducer = NavigationReducer.StackReducer({ @@ -60,12 +61,6 @@ class NavigationAnimatedExample extends React.Component { /> ); } - handleBackAction() { - return ( - this.navRootContainer && - this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) - ); - } _renderNavigated(navigationState, onNavigate) { if (!navigationState) { return null; @@ -74,11 +69,13 @@ class NavigationAnimatedExample extends React.Component { ( + renderOverlay={(scenes, index, position, layout) => ( state.key} + scenes={scenes} + index={index} + position={position} + layout={layout} + renderTitleComponent={pageState => {pageState.key}} /> )} setTiming={(pos, navState) => { @@ -121,7 +118,7 @@ const styles = StyleSheet.create({ flex: 1, }, scrollView: { - marginTop: 64 + marginTop: NavigationHeader.APPBAR_HEIGHT + NavigationHeader.STATUSBAR_HEIGHT, }, }); diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js index 447ab2051..8508c0473 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js @@ -23,6 +23,7 @@ const NavigationExampleRow = require('./NavigationExampleRow'); const { RootContainer: NavigationRootContainer, Reducer: NavigationReducer, + Header: NavigationHeader, } = NavigationExperimental; const StackReducer = NavigationReducer.StackReducer; @@ -93,7 +94,7 @@ const styles = StyleSheet.create({ topView: { backgroundColor: '#E9E9EF', flex: 1, - paddingTop: 30, + paddingTop: NavigationHeader.STATUSBAR_HEIGHT, }, }); diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js index bc1d184db..6a79a5cdc 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js @@ -28,6 +28,7 @@ const { Container: NavigationContainer, RootContainer: NavigationRootContainer, Header: NavigationHeader, + HeaderTitle: NavigationHeaderTitle, Reducer: NavigationReducer, View: NavigationView, } = NavigationExperimental; @@ -154,13 +155,15 @@ class ExampleTabScreen extends React.Component { /> ); } - _renderHeader(props) { + + _renderHeader(scenes, index, position, layout) { return ( stateTypeTitleMap(state)} + scenes={scenes} + index={index} + position={position} + layout={layout} + renderTitleComponent={pageState => {stateTypeTitleMap(pageState)}} /> ); } @@ -270,7 +273,7 @@ const styles = StyleSheet.create({ flex: 1, }, scrollView: { - marginTop: 64 + marginTop: NavigationHeader.APPBAR_HEIGHT + NavigationHeader.STATUSBAR_HEIGHT, }, tabContent: { flex: 1, diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js b/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js index 4e431e30a..9fceec7bc 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js @@ -16,7 +16,6 @@ var React = require('react-native'); var { Text, - PixelRatio, StyleSheet, View, TouchableHighlight, @@ -50,7 +49,7 @@ const styles = StyleSheet.create({ row: { padding: 15, backgroundColor: 'white', - borderBottomWidth: 1 / PixelRatio.get(), + borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#CDCDCD', }, rowText: { diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index 78e74c48e..3dfd365d1 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -15,6 +15,8 @@ */ 'use strict'; +const React = require('react'); + export type UIExplorerExample = { key: string; module: React.Component; diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js b/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js index 82a807e2f..fac681c1a 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js @@ -27,123 +27,227 @@ */ 'use strict'; -const Animated = require('Animated'); -const Image = require('Image'); -const NavigationContainer = require('NavigationContainer'); -const NavigationRootContainer = require('NavigationRootContainer'); const React = require('react-native'); -const StyleSheet = require('StyleSheet'); -const Text = require('Text'); -const TouchableOpacity = require('TouchableOpacity'); -const View = require('View'); +const NavigationContainer = require('NavigationContainer'); +const NavigationHeaderTitle = require('NavigationHeaderTitle'); +const NavigationHeaderBackButton = require('NavigationHeaderBackButton'); + +const { + Animated, + Platform, + StyleSheet, + View, +} = React; + +const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56; +const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? 20 : 0; import type { - NavigationState, - NavigationParentState + NavigationScene, } from 'NavigationStateUtils'; -type Props = { - navigationState: NavigationParentState, - onNavigate: Function, - position: Animated.Value, - getTitle: (navState: NavigationState) => string, +type Renderer = (scene: NavigationScene) => ReactElement; + +type DefaultProps = { + renderTitleComponent: Renderer; + renderLeftComponent: Renderer; }; -class NavigationHeader extends React.Component { - _handleBackPress: Function; - props: Props; - componentWillMount() { - this._handleBackPress = this._handleBackPress.bind(this); - } - render() { - var state = this.props.navigationState; - return ( - - {state.children.map(this._renderTitle, this)} - {this._renderBackButton()} - - ); - } - _renderBackButton() { - if (this.props.navigationState.index === 0) { - return null; +type Props = { + position: Animated.Value, + scenes: Array; + index: number; + renderTitleComponent: Renderer; + renderLeftComponent: Renderer; + renderRightComponent: Renderer; + style: any, +}; + +class NavigationHeader extends React.Component { + _renderLeftComponent(scene) { + const { + renderLeftComponent, + position, + } = this.props; + + if (renderLeftComponent) { + const { + index, + state, + } = scene; + + return ( + + {renderLeftComponent(state, index)} + + ); } - return ( - - - - ); + + return null; } - _renderTitle(childState, index) { - return ( - - {this.props.getTitle(childState)} - - ); + + _renderRightComponent(scene) { + const { + renderRightComponent, + position, + } = this.props; + + if (renderRightComponent) { + const { + index, + state, + } = scene; + + return ( + + {renderRightComponent(state, index)} + + ); + } + + return null; } - _handleBackPress() { - this.props.onNavigate(NavigationRootContainer.getBackAction()); + + _renderTitleComponent(scene) { + const { + renderTitleComponent, + position, + } = this.props; + + if (renderTitleComponent) { + const { + index, + state, + } = scene; + + return ( + + {renderTitleComponent(state, index)} + + ); + } + + return null; + } + + render() { + const { scenes } = this.props; + + return ( + + {scenes.map(this._renderLeftComponent, this)} + {scenes.map(this._renderTitleComponent, this)} + {scenes.map(this._renderRightComponent, this)} + + ); } } -NavigationHeader = NavigationContainer.create(NavigationHeader); +const renderTitleComponent = pageState => {pageState.title}; +const renderLeftComponent = (pageState, index) => index !== 0 ? : null; + +NavigationHeader.defaultProps = { + renderTitleComponent, + renderLeftComponent, +}; + +NavigationHeader.propTypes = { + position: React.PropTypes.object.isRequired, + scenes: React.PropTypes.arrayOf(React.PropTypes.shape({ + index: React.PropTypes.number, + state: React.PropTypes.any, + })).isRequired, + index: React.PropTypes.number.isRequired, + renderTitleComponent: React.PropTypes.func, + renderLeftComponent: React.PropTypes.func, + renderRightComponent: React.PropTypes.func, + style: View.propTypes.style, +}; + +NavigationHeader.APPBAR_HEIGHT = APPBAR_HEIGHT; +NavigationHeader.STATUSBAR_HEIGHT = STATUSBAR_HEIGHT; const styles = StyleSheet.create({ + appbar: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + backgroundColor: Platform.OS === 'ios' ? 'rgba(255, 255, 255, .9)' : 'rgba(255, 255, 255, 1)', + borderBottomWidth: Platform.OS === 'ios' ? StyleSheet.hairlineWidth : 0, + borderBottomColor: 'rgba(0, 0, 0, .15)', + height: APPBAR_HEIGHT + STATUSBAR_HEIGHT, + marginBottom: 16, // This is needed for elevation shadow + elevation: 2, + }, + title: { - textAlign: 'center', - marginTop: 10, - fontSize: 18, - fontWeight: '500', - color: '#0A0A0A', position: 'absolute', - top: 20, - left: 0, - right: 0, - }, - header: { - backgroundColor: '#EFEFF2', - paddingTop: 20, top: 0, - height: 64, - right: 0, + bottom: 0, + left: APPBAR_HEIGHT, + right: APPBAR_HEIGHT, + marginTop: STATUSBAR_HEIGHT, + }, + + left: { + position: 'absolute', + top: 0, + bottom: 0, left: 0, - borderBottomWidth: 0.5, - borderBottomColor: '#828287', + marginTop: STATUSBAR_HEIGHT, + }, + + right: { position: 'absolute', - }, - backButton: { - width: 29, - height: 37, - position: 'absolute', - bottom: 4, - left: 2, - padding: 8, - }, - backButtonImage: { - width: 13, - height: 21, - }, + top: 0, + bottom: 0, + right: 0, + marginTop: STATUSBAR_HEIGHT, + } }); -module.exports = NavigationHeader; +module.exports = NavigationContainer.create(NavigationHeader); diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationHeaderBackButton.js b/Libraries/CustomComponents/NavigationExperimental/NavigationHeaderBackButton.js new file mode 100644 index 000000000..4cd4a8676 --- /dev/null +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationHeaderBackButton.js @@ -0,0 +1,59 @@ +/** + * 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. + * + * @providesModule NavigationHeaderBackButton + * @flow +*/ +'use strict'; + +const React = require('react-native'); +const NavigationContainer = require('NavigationContainer'); +const NavigationRootContainer = require('NavigationRootContainer'); + +const { + Image, + Platform, + StyleSheet, + TouchableOpacity, +} = React; + +type Props = { + onNavigate: Function +} + +const NavigationHeaderBackButton = (props: Props) => ( + props.onNavigate(NavigationRootContainer.getBackAction())}> + + +); + +NavigationHeaderBackButton.propTypes = { + onNavigate: React.PropTypes.func.isRequired +}; + +const styles = StyleSheet.create({ + buttonContainer: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + button: { + height: 24, + width: 24, + margin: Platform.OS === 'ios' ? 10 : 16, + resizeMode: 'contain' + } +}); + +module.exports = NavigationContainer.create(NavigationHeaderBackButton); diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationHeaderTitle.js b/Libraries/CustomComponents/NavigationExperimental/NavigationHeaderTitle.js new file mode 100644 index 000000000..94fd4c00a --- /dev/null +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationHeaderTitle.js @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * + * Facebook, Inc. ("Facebook") owns all right, title and interest, including + * all intellectual property and other proprietary rights, in and to the React + * Native CustomComponents software (the "Software"). Subject to your + * compliance with these terms, you are hereby granted a non-exclusive, + * worldwide, royalty-free copyright license to (1) use and copy the Software; + * and (2) reproduce and distribute the Software as part of your own software + * ("Your Software"). Facebook reserves all rights not expressly granted to + * you in this license agreement. + * + * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. + * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR + * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @providesModule NavigationHeaderTitle + * @flow + */ +'use strict'; + +const React = require('react-native'); + +const { + Platform, + StyleSheet, + View, + Text, +} = React; + +type Props = { + children: ReactElement; + style: any; + textStyle: any; +} + +const NavigationHeaderTitle = ({ children, style, textStyle }: Props) => ( + + {children} + +); + +const styles = StyleSheet.create({ + title: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + marginHorizontal: 16 + }, + + titleText: { + flex: 1, + fontSize: 18, + fontWeight: '500', + color: 'rgba(0, 0, 0, .9)', + textAlign: Platform.OS === 'ios' ? 'center' : 'left' + } +}); + +NavigationHeaderTitle.propTypes = { + children: React.PropTypes.string.isRequired, + style: View.propTypes.style, + textStyle: Text.propTypes.style +}; + +module.exports = NavigationHeaderTitle; diff --git a/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1.5x.android.png b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1.5x.android.png new file mode 100644 index 000000000..ad03a63bf Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1.5x.android.png differ diff --git a/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1.5x.ios.png b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1.5x.ios.png new file mode 100644 index 000000000..e43fa0622 Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1.5x.ios.png differ diff --git a/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1x.android.png b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1x.android.png new file mode 100644 index 000000000..083db295f Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1x.android.png differ diff --git a/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1x.ios.png b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1x.ios.png new file mode 100644 index 000000000..4244656b9 Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@1x.ios.png differ diff --git a/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@2x.android.png b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@2x.android.png new file mode 100644 index 000000000..6de0a1cbb Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@2x.android.png differ diff --git a/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@2x.ios.png b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@2x.ios.png new file mode 100644 index 000000000..a8b6e9a63 Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@2x.ios.png differ diff --git a/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@3x.android.png b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@3x.android.png new file mode 100644 index 000000000..15a983a67 Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@3x.android.png differ diff --git a/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@3x.ios.png b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@3x.ios.png new file mode 100644 index 000000000..07ea37b4e Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@3x.ios.png differ diff --git a/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@4x.android.png b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@4x.android.png new file mode 100644 index 000000000..17e52e855 Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@4x.android.png differ diff --git a/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@4x.ios.png b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@4x.ios.png new file mode 100644 index 000000000..7899053f6 Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/assets/back-icon@4x.ios.png differ diff --git a/Libraries/CustomComponents/NavigationExperimental/back_chevron.png b/Libraries/CustomComponents/NavigationExperimental/back_chevron.png deleted file mode 100644 index af21cf9d5..000000000 Binary files a/Libraries/CustomComponents/NavigationExperimental/back_chevron.png and /dev/null differ diff --git a/Libraries/NavigationExperimental/NavigationAnimatedView.js b/Libraries/NavigationExperimental/NavigationAnimatedView.js index 4d1f5b68f..918307210 100644 --- a/Libraries/NavigationExperimental/NavigationAnimatedView.js +++ b/Libraries/NavigationExperimental/NavigationAnimatedView.js @@ -21,14 +21,9 @@ var View = require('View'); import type { NavigationState, NavigationParentState, + NavigationScene, } from 'NavigationStateUtils'; -type NavigationScene = { - index: number, - state: NavigationState, - isStale: boolean, -}; - /** * Helper function to compare route keys (e.g. "9", "11"). */ @@ -70,6 +65,13 @@ type Layout = { height: Animated.Value; }; +type OverlayRenderer = ( + scenes: Array, + index: number, + position: Animated.Value, + layout: Layout +) => ReactElement; + type Position = Animated.Value; /** @@ -251,6 +253,11 @@ class NavigationAnimatedView extends React.Component { } } +NavigationAnimatedView.propTypes = { + navigationState: React.PropTypes.instanceOf(NavigationStateUtils), + style: View.propTypes.style, +}; + function setDefaultTiming(position, navigationState) { Animated.spring( position, diff --git a/Libraries/NavigationExperimental/NavigationExperimental.js b/Libraries/NavigationExperimental/NavigationExperimental.js index ca6bb627d..0f4e9fa63 100644 --- a/Libraries/NavigationExperimental/NavigationExperimental.js +++ b/Libraries/NavigationExperimental/NavigationExperimental.js @@ -16,6 +16,8 @@ const NavigationCard = require('NavigationCard'); const NavigationCardStack = require('NavigationCardStack'); const NavigationContainer = require('NavigationContainer'); const NavigationHeader = require('NavigationHeader'); +const NavigationHeaderTitle = require('NavigationHeaderTitle'); +const NavigationHeaderBackButton = require('NavigationHeaderBackButton'); const NavigationLegacyNavigator = require('NavigationLegacyNavigator'); const NavigationReducer = require('NavigationReducer'); const NavigationRootContainer = require('NavigationRootContainer'); @@ -39,6 +41,8 @@ const NavigationExperimental = { Card: NavigationCard, CardStack: NavigationCardStack, Header: NavigationHeader, + HeaderTitle: NavigationHeaderTitle, + HeaderBackButton: NavigationHeaderBackButton, LegacyNavigator: NavigationLegacyNavigator, }; diff --git a/Libraries/NavigationExperimental/NavigationStateUtils.js b/Libraries/NavigationExperimental/NavigationStateUtils.js index d457ebea8..28d5c85bd 100644 --- a/Libraries/NavigationExperimental/NavigationStateUtils.js +++ b/Libraries/NavigationExperimental/NavigationStateUtils.js @@ -23,6 +23,12 @@ export type NavigationParentState = { children: Array; }; +export type NavigationScene = { + index: number; + state: NavigationState; + isStale: boolean; +}; + export type NavigationAction = { type: string; };