2016-02-25 02:51:09 +00:00
|
|
|
/**
|
2016-03-15 22:58:38 +00:00
|
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
2016-02-25 02:51:09 +00:00
|
|
|
*
|
|
|
|
* 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 NavigationCardStack
|
|
|
|
* @flow
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
2016-10-19 22:20:05 +00:00
|
|
|
const NativeAnimatedModule = require('NativeModules').NativeAnimatedModule;
|
2016-03-04 22:56:37 +00:00
|
|
|
const NavigationCard = require('NavigationCard');
|
2016-03-23 19:49:28 +00:00
|
|
|
const NavigationCardStackPanResponder = require('NavigationCardStackPanResponder');
|
2016-10-19 22:20:05 +00:00
|
|
|
const NavigationCardStackStyleInterpolator = require('NavigationCardStackStyleInterpolator');
|
2016-03-04 22:56:37 +00:00
|
|
|
const NavigationPropTypes = require('NavigationPropTypes');
|
2016-10-19 22:20:05 +00:00
|
|
|
const NavigationTransitioner = require('NavigationTransitioner');
|
2016-02-25 02:51:09 +00:00
|
|
|
const React = require('React');
|
|
|
|
const StyleSheet = require('StyleSheet');
|
2016-06-21 22:46:15 +00:00
|
|
|
const View = require('View');
|
2016-02-25 02:51:09 +00:00
|
|
|
|
|
|
|
const {PropTypes} = React;
|
2016-03-23 19:49:28 +00:00
|
|
|
const {Directions} = NavigationCardStackPanResponder;
|
2016-02-25 02:51:09 +00:00
|
|
|
|
|
|
|
import type {
|
2016-05-20 21:24:24 +00:00
|
|
|
NavigationState,
|
2016-03-04 22:56:37 +00:00
|
|
|
NavigationSceneRenderer,
|
|
|
|
NavigationSceneRendererProps,
|
2016-06-21 22:46:15 +00:00
|
|
|
NavigationTransitionProps,
|
2016-03-04 22:56:37 +00:00
|
|
|
} from 'NavigationTypeDefinition';
|
2016-02-25 02:51:09 +00:00
|
|
|
|
|
|
|
import type {
|
2016-03-04 22:56:37 +00:00
|
|
|
NavigationGestureDirection,
|
2016-03-23 19:49:28 +00:00
|
|
|
} from 'NavigationCardStackPanResponder';
|
2016-02-25 02:51:09 +00:00
|
|
|
|
|
|
|
type Props = {
|
2016-03-04 22:56:37 +00:00
|
|
|
direction: NavigationGestureDirection,
|
2016-05-20 21:24:24 +00:00
|
|
|
navigationState: NavigationState,
|
2016-06-16 22:34:51 +00:00
|
|
|
onNavigateBack?: Function,
|
NavigationExperimental: Rename `renderOverlay` to `renderHeader`
Summary:
NavigationCardStack is a custom component, and its API should be explicit, not
too generic..
In NavigationCardStack, the prop `renderOverlay` is actually used to render
the NavigationHeader, and we uses absolute position to build the layout for
the header and the body.
One of the problem with using absolute postion and fixed height to build the
layout that contains the header is that the header can't have variant height
easily.
Ideally, if the layout for the header used flex-box, we'd ve able to be more
adaptive to deal with the header that has variant height.
That said, let's rename `renderOverlay` to `renderHeader`, then build the
proper layout that explicitly works better with the header.
If we to need to support overlay in navigation, we may consider add
`renderOverlay` later, if it's really necessary.
Reviewed By: ericvicenti
Differential Revision: D3670224
fbshipit-source-id: ff04acfe9dc995cb57117b3fd9b07d5f97b9c6ee
2016-08-04 18:16:40 +00:00
|
|
|
renderHeader: ?NavigationSceneRenderer,
|
2016-03-04 22:56:37 +00:00
|
|
|
renderScene: NavigationSceneRenderer,
|
2016-07-01 11:30:59 +00:00
|
|
|
cardStyle?: any,
|
2016-06-10 00:48:39 +00:00
|
|
|
style: any,
|
2016-07-25 18:46:47 +00:00
|
|
|
gestureResponseDistance?: ?number,
|
2016-08-21 20:02:58 +00:00
|
|
|
enableGestures: ?boolean
|
2016-03-04 22:56:37 +00:00
|
|
|
};
|
|
|
|
|
2016-04-22 02:46:36 +00:00
|
|
|
type DefaultProps = {
|
|
|
|
direction: NavigationGestureDirection,
|
2016-08-21 20:02:58 +00:00
|
|
|
enableGestures: boolean
|
2016-04-22 02:46:36 +00:00
|
|
|
};
|
|
|
|
|
2016-02-25 02:51:09 +00:00
|
|
|
/**
|
2016-03-23 19:49:28 +00:00
|
|
|
* A controlled navigation view that renders a stack of cards.
|
|
|
|
*
|
2016-08-15 06:15:26 +00:00
|
|
|
* ```html
|
2016-03-23 19:49:28 +00:00
|
|
|
* +------------+
|
2016-08-04 22:17:39 +00:00
|
|
|
* +-| Header |
|
|
|
|
* +-+ |------------|
|
2016-03-23 19:49:28 +00:00
|
|
|
* | | | |
|
|
|
|
* | | | Focused |
|
|
|
|
* | | | Card |
|
|
|
|
* | | | |
|
|
|
|
* +-+ | |
|
|
|
|
* +-+ |
|
|
|
|
* +------------+
|
2016-08-15 06:15:26 +00:00
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* ```js
|
|
|
|
*
|
|
|
|
* class App extends React.Component {
|
|
|
|
* constructor(props, context) {
|
|
|
|
* this.state = {
|
|
|
|
* navigation: {
|
|
|
|
* index: 0,
|
|
|
|
* routes: [
|
|
|
|
* {key: 'page 1'},
|
|
|
|
* },
|
|
|
|
* },
|
|
|
|
* };
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* render() {
|
|
|
|
* return (
|
|
|
|
* <NavigationCardStack
|
|
|
|
* navigationState={this.state.navigation}
|
|
|
|
* renderScene={this._renderScene}
|
|
|
|
* />
|
|
|
|
* );
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* _renderScene: (props) => {
|
|
|
|
* return (
|
|
|
|
* <View>
|
|
|
|
* <Text>{props.scene.route.key}</Text>
|
|
|
|
* </View>
|
|
|
|
* );
|
|
|
|
* };
|
|
|
|
* ```
|
2016-02-25 02:51:09 +00:00
|
|
|
*/
|
2016-04-22 02:46:36 +00:00
|
|
|
class NavigationCardStack extends React.Component<DefaultProps, Props, void> {
|
2016-06-21 22:46:15 +00:00
|
|
|
_render : NavigationSceneRenderer;
|
2016-03-04 22:56:37 +00:00
|
|
|
_renderScene : NavigationSceneRenderer;
|
2016-02-25 02:51:09 +00:00
|
|
|
|
2016-03-28 23:45:18 +00:00
|
|
|
static propTypes = {
|
2016-08-15 06:15:26 +00:00
|
|
|
/**
|
|
|
|
* Custom style applied to the card.
|
|
|
|
*/
|
|
|
|
cardStyle: View.propTypes.style,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Direction of the cards movement. Value could be `horizontal` or
|
|
|
|
* `vertical`. Default value is `horizontal`.
|
|
|
|
*/
|
2016-03-28 23:45:18 +00:00
|
|
|
direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
|
2016-08-15 06:15:26 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The distance from the edge of the card which gesture response can start
|
|
|
|
* for. Defaults value is `30`.
|
|
|
|
*/
|
|
|
|
gestureResponseDistance: PropTypes.number,
|
|
|
|
|
2016-08-21 20:02:58 +00:00
|
|
|
/**
|
2016-10-19 22:20:05 +00:00
|
|
|
* Enable gestures. Default value is true.
|
|
|
|
*
|
|
|
|
* When disabled, transition animations will be handled natively, which
|
|
|
|
* improves performance of the animation. In future iterations, gestures
|
|
|
|
* will also work with native-driven animation.
|
2016-08-21 20:02:58 +00:00
|
|
|
*/
|
|
|
|
enableGestures: PropTypes.bool,
|
|
|
|
|
2016-08-15 06:15:26 +00:00
|
|
|
/**
|
|
|
|
* The controlled navigation state. Typically, the navigation state
|
|
|
|
* look like this:
|
|
|
|
*
|
|
|
|
* ```js
|
|
|
|
* const navigationState = {
|
|
|
|
* index: 0, // the index of the selected route.
|
|
|
|
* routes: [ // A list of routes.
|
|
|
|
* {key: 'page 1'}, // The 1st route.
|
|
|
|
* {key: 'page 2'}, // The second route.
|
|
|
|
* ],
|
|
|
|
* };
|
|
|
|
* ```
|
|
|
|
*/
|
2016-05-21 01:09:57 +00:00
|
|
|
navigationState: NavigationPropTypes.navigationState.isRequired,
|
2016-08-15 06:15:26 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback that is called when the "back" action is performed.
|
|
|
|
* This happens when the back button is pressed or the back gesture is
|
|
|
|
* performed.
|
|
|
|
*/
|
2016-06-10 00:48:39 +00:00
|
|
|
onNavigateBack: PropTypes.func,
|
2016-08-15 06:15:26 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Function that renders the header.
|
|
|
|
*/
|
NavigationExperimental: Rename `renderOverlay` to `renderHeader`
Summary:
NavigationCardStack is a custom component, and its API should be explicit, not
too generic..
In NavigationCardStack, the prop `renderOverlay` is actually used to render
the NavigationHeader, and we uses absolute position to build the layout for
the header and the body.
One of the problem with using absolute postion and fixed height to build the
layout that contains the header is that the header can't have variant height
easily.
Ideally, if the layout for the header used flex-box, we'd ve able to be more
adaptive to deal with the header that has variant height.
That said, let's rename `renderOverlay` to `renderHeader`, then build the
proper layout that explicitly works better with the header.
If we to need to support overlay in navigation, we may consider add
`renderOverlay` later, if it's really necessary.
Reviewed By: ericvicenti
Differential Revision: D3670224
fbshipit-source-id: ff04acfe9dc995cb57117b3fd9b07d5f97b9c6ee
2016-08-04 18:16:40 +00:00
|
|
|
renderHeader: PropTypes.func,
|
2016-08-15 06:15:26 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Function that renders the a scene for a route.
|
|
|
|
*/
|
2016-03-28 23:45:18 +00:00
|
|
|
renderScene: PropTypes.func.isRequired,
|
2016-08-15 06:15:26 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Custom style applied to the cards stack.
|
|
|
|
*/
|
|
|
|
style: View.propTypes.style,
|
2016-03-28 23:45:18 +00:00
|
|
|
};
|
|
|
|
|
2016-04-22 02:46:36 +00:00
|
|
|
static defaultProps: DefaultProps = {
|
2016-03-28 23:45:18 +00:00
|
|
|
direction: Directions.HORIZONTAL,
|
2016-08-21 20:02:58 +00:00
|
|
|
enableGestures: true,
|
2016-03-28 23:45:18 +00:00
|
|
|
};
|
|
|
|
|
2016-02-25 02:51:09 +00:00
|
|
|
constructor(props: Props, context: any) {
|
|
|
|
super(props, context);
|
2016-03-04 22:56:37 +00:00
|
|
|
}
|
|
|
|
|
2016-03-09 07:31:27 +00:00
|
|
|
componentWillMount(): void {
|
2016-06-21 22:46:15 +00:00
|
|
|
this._render = this._render.bind(this);
|
2016-02-25 02:51:09 +00:00
|
|
|
this._renderScene = this._renderScene.bind(this);
|
2016-03-01 17:43:35 +00:00
|
|
|
}
|
|
|
|
|
2016-10-16 11:11:59 +00:00
|
|
|
render(): React.Element<any> {
|
2016-02-25 02:51:09 +00:00
|
|
|
return (
|
2016-06-21 22:46:15 +00:00
|
|
|
<NavigationTransitioner
|
2016-10-19 22:20:05 +00:00
|
|
|
configureTransition={this._configureTransition}
|
2016-02-25 02:51:09 +00:00
|
|
|
navigationState={this.props.navigationState}
|
2016-06-21 22:46:15 +00:00
|
|
|
render={this._render}
|
|
|
|
style={this.props.style}
|
2016-02-25 02:51:09 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-10-19 22:20:05 +00:00
|
|
|
_configureTransition = () => {
|
|
|
|
const isVertical = this.props.direction === 'vertical';
|
|
|
|
const animationConfig = {};
|
|
|
|
if (
|
|
|
|
!!NativeAnimatedModule
|
|
|
|
|
|
|
|
// Gestures do not work with the current iteration of native animation
|
|
|
|
// driving. When gestures are disabled, we can drive natively.
|
|
|
|
&& !this.props.enableGestures
|
|
|
|
|
|
|
|
// Native animation support also depends on the transforms used:
|
|
|
|
&& NavigationCardStackStyleInterpolator.canUseNativeDriver(isVertical)
|
|
|
|
) {
|
|
|
|
animationConfig.useNativeDriver = true;
|
|
|
|
}
|
|
|
|
return animationConfig;
|
|
|
|
}
|
|
|
|
|
2016-10-16 11:11:59 +00:00
|
|
|
_render(props: NavigationTransitionProps): React.Element<any> {
|
2016-06-21 22:46:15 +00:00
|
|
|
const {
|
2016-10-19 22:20:05 +00:00
|
|
|
renderHeader,
|
2016-07-08 03:59:44 +00:00
|
|
|
} = this.props;
|
2016-06-21 22:46:15 +00:00
|
|
|
|
2016-08-04 22:17:39 +00:00
|
|
|
const header = renderHeader ? <View>{renderHeader(props)}</View> : null;
|
2016-06-21 22:46:15 +00:00
|
|
|
|
|
|
|
const scenes = props.scenes.map(
|
|
|
|
scene => this._renderScene({
|
|
|
|
...props,
|
|
|
|
scene,
|
2016-07-08 03:59:44 +00:00
|
|
|
})
|
2016-06-21 22:46:15 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
2016-08-04 22:17:39 +00:00
|
|
|
<View style={styles.container}>
|
2016-06-21 22:46:15 +00:00
|
|
|
<View
|
|
|
|
style={styles.scenes}>
|
|
|
|
{scenes}
|
|
|
|
</View>
|
2016-08-04 22:17:39 +00:00
|
|
|
{header}
|
2016-06-21 22:46:15 +00:00
|
|
|
</View>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-10-16 11:11:59 +00:00
|
|
|
_renderScene(props: NavigationSceneRendererProps): React.Element<any> {
|
2016-03-15 22:58:38 +00:00
|
|
|
const isVertical = this.props.direction === 'vertical';
|
|
|
|
|
|
|
|
const style = isVertical ?
|
|
|
|
NavigationCardStackStyleInterpolator.forVertical(props) :
|
|
|
|
NavigationCardStackStyleInterpolator.forHorizontal(props);
|
|
|
|
|
2016-08-21 20:02:58 +00:00
|
|
|
let panHandlers = null;
|
|
|
|
|
|
|
|
if (this.props.enableGestures) {
|
|
|
|
const panHandlersProps = {
|
|
|
|
...props,
|
|
|
|
onNavigateBack: this.props.onNavigateBack,
|
|
|
|
gestureResponseDistance: this.props.gestureResponseDistance,
|
|
|
|
};
|
|
|
|
panHandlers = isVertical ?
|
|
|
|
NavigationCardStackPanResponder.forVertical(panHandlersProps) :
|
|
|
|
NavigationCardStackPanResponder.forHorizontal(panHandlersProps);
|
|
|
|
}
|
2016-03-15 22:58:38 +00:00
|
|
|
|
2016-02-25 02:51:09 +00:00
|
|
|
return (
|
2016-03-04 22:56:37 +00:00
|
|
|
<NavigationCard
|
|
|
|
{...props}
|
2016-03-21 18:13:59 +00:00
|
|
|
key={'card_' + props.scene.key}
|
2016-03-15 22:58:38 +00:00
|
|
|
panHandlers={panHandlers}
|
2016-03-01 17:43:35 +00:00
|
|
|
renderScene={this.props.renderScene}
|
2016-07-01 11:30:59 +00:00
|
|
|
style={[style, this.props.cardStyle]}
|
2016-03-01 17:43:35 +00:00
|
|
|
/>
|
2016-02-25 02:51:09 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
2016-06-21 22:46:15 +00:00
|
|
|
container: {
|
|
|
|
flex: 1,
|
2016-08-04 22:17:39 +00:00
|
|
|
// Header is physically rendered after scenes so that Header won't be
|
|
|
|
// covered by the shadows of the scenes.
|
|
|
|
// That said, we'd have use `flexDirection: 'column-reverse'` to move
|
|
|
|
// Header above the scenes.
|
|
|
|
flexDirection: 'column-reverse',
|
2016-06-21 22:46:15 +00:00
|
|
|
},
|
|
|
|
scenes: {
|
2016-02-25 02:51:09 +00:00
|
|
|
flex: 1,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2016-05-05 23:51:34 +00:00
|
|
|
module.exports = NavigationCardStack;
|