Full support for React Navigation v2

This commit is contained in:
SteffeyDev
2018-05-19 16:21:11 -04:00
parent 8b11a76c0e
commit 6f254c9481
5 changed files with 141 additions and 78 deletions

View File

@@ -35,9 +35,15 @@ The `<Popover>` is able to handle dynamic content and adapt to screen size chang
* Moves to stay visible on orientation change or when entering split-screen mode
* (Optional) Integration with [React Navigation](https://reactnavigation.org)
## <a name="upgrading" />Upgrading from 0.5.x
## <a name="upgrading" />Upgrading
Version 0.6 brought some large changes, increasing efficiency, stability, and flexibility. For React Navigation users, there is a new prop, `showInPopover`, that you might want to pass to `PopoverStackNavigator` if you want to customize when to show stack views in a Popover. This replaces `PopoverNavigation.shouldShowInPopover`. See the new [setup](#setup) instructions below for details.
### `0.7` to `1.0`
The only breaking change in version 1.0 was renaming `PopoverStackNavigator` to `createPopoverStackNavigator`, to match the `react-navigation` other navigation functions.
### `0.5` to `0.6`
Version 0.6 brought some large changes, increasing efficiency, stability, and flexibility. For React Navigation users, there is a new prop, `showInPopover`, that you might want to pass to `createPopoverStackNavigator` if you want to customize when to show stack views in a Popover. This replaces `PopoverNavigation.shouldShowInPopover`. See the new [setup](#setup) instructions below for details.
## <a name="demo"/>Demo App
@@ -171,9 +177,9 @@ This can also be integrated with react-navigation's StackNavigator, so that on t
### <a name="setup"/>Basic Setup
#### 1) Change `StackNavigator` to `PopoverStackNavigator`
#### 1) Change `StackNavigator` to `createPopoverStackNavigator`
`PopoverStackNavigator` is a drop-in replacement for react-navigation's `StackNavigator`. It assumes the first view in your `RouteConfigs` is the base view, and every other view should be shown in a Popover when the `showInPopover` prop is `true` (see step #2).
`createPopoverStackNavigator` is a drop-in replacement for react-navigation's `StackNavigator`. It assumes the first view in your `RouteConfigs` is the base view, and every other view should be shown in a Popover when the `showInPopover` prop is `true` (see step #2).
You can pass a few (optional) per-screen options through your `RouteConfigs` or globally through your `StackNavigatorConfig`:
Option | Type | Default | Description
@@ -189,9 +195,9 @@ Note: If you pass a value through the `StackNavigatorConfig`, and pass the same
Example:
```jsx
import Popover, { PopoverStackNavigator } from 'react-native-popover-view';
import Popover, { createPopoverStackNavigator } from 'react-native-popover-view';
let stack = PopoverStackNavigator({
let stack = createPopoverStackNavigator({
BaseView: {
screen: BaseView,
navigationOptions: {
@@ -225,10 +231,10 @@ let stack = PopoverStackNavigator({
#### 2) Define when Popover should be shown
By default, views will be shown in a Popover view on tablets, and normally on phones. To override this behavior, you can pass the `showInPopover` prop to the class returned by `PopoverStackNavigator`:
By default, views will be shown in a Popover view on tablets, and normally on phones. To override this behavior, you can pass the `showInPopover` prop to the class returned by `createPopoverStackNavigator`:
```jsx
let Stack = PopoverStackNavigator(...);
let Stack = createPopoverStackNavigator(...);
...
render() {
let smallScreen = this.props.width < 500;
@@ -248,13 +254,13 @@ You can register the button as the source of the `Popover` for a particular rout
We first register the ref for a view:
```jsx
<TouchableHighlight ref={ref => PopoverStackNavigator.registerRefForView(ref, 'View1')} {...otherProps} />
<TouchableHighlight ref={ref => createPopoverStackNavigator.registerRefForView(ref, 'View1')} {...otherProps} />
```
Then, if `View1` is a route name in a `PopoverStackNavigator`...
Then, if `View1` is a route name in a `createPopoverStackNavigator`...
```jsx
import View1 from './views/View1';
...
let stack = PopoverStackNavigator({
let stack = createPopoverStackNavigator({
View1: {
screen: View1,
navigationOptions: navOptions
@@ -269,7 +275,7 @@ this.props.navigation.navigate('View1', params);
You can register any type of view, not only a `TouchableHighlight`, and the `Popover` will point to the outside of the bounds of that view.
Note: The map is stored statically, so you cannot register two views with the same name, even if they are in different `PopoverStackNavigator`'s.
Note: The map is stored statically, so you cannot register two views with the same name, even if they are in different `createPopoverStackNavigator`'s.
##### II. Send showFromView
@@ -287,7 +293,7 @@ See "Show Popover from custom rect" in the Advanced Usage section below.
```jsx
import React, { Component } from 'react';
import { PopoverStackNavigator } from 'react-native-popover-view';
import { createPopoverStackNavigator } from 'react-native-popover-view';
import { MoreHeaderView, ExtraInfoView, MoreOptionView } from './myOtherViews';
import { Colors } from './Colors';
import DeviceInfo from 'react-native-device-info';
@@ -300,19 +306,19 @@ class MoreView extends Component {
<View>
<TouchableHighlight
style={styles.buttonStyle}
ref={touchable => PopoverStackNavigator.registerRefForView(touchable, 'About')}
ref={touchable => createPopoverStackNavigator.registerRefForView(touchable, 'About')}
onPress={() => this.props.navigation.navigate('About')}>
<Text>About the App</Text>
</TouchableHighlight>
<TouchableHighlight
style={styles.buttonStyle}
ref={touchable => PopoverStackNavigator.registerRefForView(touchable, 'Settings')}
ref={touchable => createPopoverStackNavigator.registerRefForView(touchable, 'Settings')}
onPress={() => this.props.navigation.navigate('Settings')}>
<Text>Content Settings</Text>
</TouchableHighlight>
<TouchableHighlight
style={styles.buttonStyle}
ref={touchable => PopoverStackNavigator.registerRefForView(touchable, 'Account')}
ref={touchable => createPopoverStackNavigator.registerRefForView(touchable, 'Account')}
onPress={() => this.props.navigation.navigate('Account')}>
<Text>Account Details</Text>
</TouchableHighlight>
@@ -323,7 +329,7 @@ class MoreView extends Component {
}
}
let MoreStack = PopoverStackNavigator({
let MoreStack = createPopoverStackNavigator({
MoreView: {
screen: MoreView,
navigationOptions: {title: 'More'}
@@ -388,10 +394,10 @@ let styles = {
#### Custumize Display Area used by Popovers
By default, Popover's will query RN's `SafeAreaView` to get the allowed display area on the device, and then add a 10pt padding around all the edges, and set this as the display area. If you want to inject a custum display area to a specific popover, you can do so either through the `PopoverStackNavigator`'s `RouteConfigs` or through params in the `navigate` call:
By default, Popover's will query RN's `SafeAreaView` to get the allowed display area on the device, and then add a 10pt padding around all the edges, and set this as the display area. If you want to inject a custum display area to a specific popover, you can do so either through the `createPopoverStackNavigator`'s `RouteConfigs` or through params in the `navigate` call:
```jsx
let Stack = PopoverStackNavigator({
let Stack = createPopoverStackNavigator({
View1: {
screen: 'View1',
popoverOptions: {
@@ -409,7 +415,7 @@ this.props.navigation.navigate('View1', {displayArea: new Rect(0, 0, 50,50)});
#### Show Popover from custom rect
There may be situations in which you want to show a `Popover` with a custom fromRect, not tied to any view. Instead of using `PopoverStackNavigator.registerRefForView`, you can pass in a custom `fromRect` as params to the `navigate()` call. For example:
There may be situations in which you want to show a `Popover` with a custom fromRect, not tied to any view. Instead of using `createPopoverStackNavigator.registerRefForView`, you can pass in a custom `fromRect` as params to the `navigate()` call. For example:
```jsx
import { Rect } from 'react-native-popover-view';
...

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-popover-view",
"version": "0.7.1",
"version": "1.0.0",
"description": "A <Popover /> component for react-native",
"main": "src/index.js",
"author": "Peter Steffey <steffeydev@icloud.com> (https://github.com/steffeydev)",

View File

@@ -19,7 +19,7 @@ export default class PopoverNavigation extends Component {
}
goBack() {
if (this.props.showInPopover())
if (this.props.showInPopover)
this.setState({visible: false});
else
this.props.children.props.navigation.goBack();
@@ -43,7 +43,7 @@ export default class PopoverNavigation extends Component {
const child = React.cloneElement(this.props.children, {goBack: () => this.goBack()});
const { contentContainerStyle, popoverStyle, arrowStyle, placement, showInModal, layoutRtl, showBackground, getRegisteredView, displayArea, showInPopover, backgroundColor } = this.props;
if (showInPopover()) {
if (showInPopover) {
return (
<Popover
arrowStyle={arrowStyle}
@@ -72,7 +72,7 @@ export default class PopoverNavigation extends Component {
PopoverNavigation.defaultProps = {
contentContainerStyle: {width: 380},
showInPopover: () => true
showInPopover: true
}
PopoverNavigation.propTypes = {
@@ -83,7 +83,7 @@ PopoverNavigation.propTypes = {
showBackground: PropTypes.bool,
contentContainerStyle: PropTypes.object,
displayArea: PropTypes.objectOf(PropTypes.number),
showInPopover: PropTypes.func,
showInPopover: PropTypes.bool,
popoverStyle: PropTypes.object,
arrowStyle: PropTypes.object
}

View File

@@ -1,32 +1,109 @@
/* @flow */
// React Imports
import * as React from 'react';
import createNavigationContainer from '../../react-navigation/src/createNavigationContainer';
import createNavigator from '../../react-navigation/src/navigators/createNavigator';
import CardStackTransitioner from '.../../react-navigation/src/views/CardStack/CardStackTransitioner';
import StackRouter from '../../react-navigation/src/routers/StackRouter';
import NavigationActions from '../../react-navigation/src/NavigationActions';
import { NativeModules } from 'react-native';
// Popover Imports
import { popoverTransitionConfig, isTablet } from './Utility'
import PopoverNavigation from './PopoverNavigation'
export let withPopoverNavigation = (Comp, popoverOptions) => props => <PopoverNavigation {...popoverOptions}><Comp {...props} /></PopoverNavigation>;
// RN Imports
import createNavigationContainer from '../../react-navigation/src/createNavigationContainer';
import StackRouter from '../../react-navigation/src/routers/StackRouter';
import createKeyboardAwareNavigator from '../../react-navigation/src/navigators/createKeyboardAwareNavigator';
import createNavigator from '../../react-navigation/src/navigators/createNavigator';
import StackView from '../../react-navigation/src/views/StackView/StackView';
import StackViewLayout from '../../react-navigation/src/views/StackView/StackViewLayout';
import Transitioner from '../../react-navigation/src/views/Transitioner';
import NavigationActions from '../../react-navigation/src/NavigationActions';
import StackActions from '../../react-navigation/src/routers/StackActions';
import TransitionConfigs from '../../react-navigation/src/views/StackView/StackViewTransitionConfigs';
const PopoverStackNavigator = (routeConfigMap, stackConfig = {}) => {
export let withPopoverNavigation = (Comp, popoverOptions) => props => <PopoverNavigation {...popoverOptions} showInPopover={popoverOptions.isFirstView ? false : props.screenProps.showInPopover}><Comp {...props} /></PopoverNavigation>;
const NativeAnimatedModule =
NativeModules && NativeModules.NativeAnimatedModule;
class PopoverStackView extends React.Component {
static defaultProps = {
navigationConfig: {
mode: 'card',
},
};
render() {
return (
<Transitioner
render={this._render}
configureTransition={this._configureTransition}
navigation={this.props.navigation}
descriptors={this.props.descriptors}
onTransitionStart={this.props.onTransitionStart}
onTransitionEnd={(transition, lastTransition) => {
const { onTransitionEnd, navigation } = this.props;
if (transition.navigation.state.isTransitioning) {
navigation.dispatch(
StackActions.completeTransition({
key: navigation.state.key,
})
);
}
onTransitionEnd && onTransitionEnd(transition, lastTransition);
}}
/>
);
}
_shouldShowInPopover = () => this.props.screenProps && this.props.screenProps.showInPopover !== undefined
? this.props.screenProps.showInPopover
: isTablet()
_configureTransition = (transitionProps, prevTransitionProps) => {
return {
...TransitionConfigs.getTransitionConfig(
this._shouldShowInPopover() ? popoverTransitionConfig : this.props.navigationConfig.transitionConfig,
transitionProps,
prevTransitionProps,
this.props.navigationConfig.mode === 'modal'
).transitionSpec,
useNativeDriver: !!NativeAnimatedModule,
};
};
_render = (transitionProps, lastTransitionProps) => {
const { screenProps, navigationConfig } = this.props;
const { headerMode, cardStyle, transitionConfig, ...remainingNavigationConfig } = navigationConfig;
return (
<StackViewLayout
{...remainingNavigationConfig}
headerMode={this._shouldShowInPopover() ? 'screen' : headerMode}
cardStyle={this._shouldShowInPopover() ? {...cardStyle, backgroundColor: 'transparent'} : cardStyle}
transitionConfig={this._shouldShowInPopover() ? popoverTransitionConfig : transitionConfig}
onGestureBegin={this.props.onGestureBegin}
onGestureCanceled={this.props.onGestureCanceled}
onGestureEnd={this.props.onGestureEnd}
screenProps={screenProps}
descriptors={this.props.descriptors}
transitionProps={transitionProps}
lastTransitionProps={lastTransitionProps}
/>
);
};
}
function createPopoverStackNavigator(routeConfigMap, stackConfig = {}) {
const {
initialRouteKey,
initialRouteName,
initialRouteParams,
paths,
headerMode,
headerTransitionPreset,
mode,
cardStyle,
transitionConfig,
onTransitionStart,
onTransitionEnd,
navigationOptions,
disableKeyboardHandling,
} = stackConfig;
const stackRouterConfig = {
initialRouteKey,
initialRouteName,
initialRouteParams,
paths,
@@ -39,9 +116,9 @@ const PopoverStackNavigator = (routeConfigMap, stackConfig = {}) => {
width: 0,
height: 0
};
let shouldShowInPopover = isTablet();
routeKeys.forEach((route, i) => {
let getRegisteredView = () => PopoverStackNavigator.registeredViews.hasOwnProperty(route) ? PopoverStackNavigator.registeredViews[route] : null;
let getRegisteredView = () => createPopoverStackNavigator.registeredViews.hasOwnProperty(route) ? createPopoverStackNavigator.registeredViews[route] : null;
newRouteConfigMap[route] = Object.assign({},
routeConfigMap[route],
{
@@ -49,7 +126,7 @@ const PopoverStackNavigator = (routeConfigMap, stackConfig = {}) => {
stackConfig.popoverOptions,
routeConfigMap[route].popoverOptions,
{
showInPopover: i > 0 ? () => shouldShowInPopover : () => false,
isFirstView: i === 0,
getRegisteredView,
backgroundColor: stackConfig.cardStyle ? stackConfig.cardStyle.backgroundColor : (i > 0 ? 'white' : '#E9E9EF')
}
@@ -63,7 +140,7 @@ const PopoverStackNavigator = (routeConfigMap, stackConfig = {}) => {
else if (typeof userNavigationOptions === "object")
additionalNavigationOptions = userNavigationOptions;
}
return i > 0 && shouldShowInPopover
return i > 0 && ops.screenProps && ops.screenProps.showInPopover
? Object.assign({}, additionalNavigationOptions, {header: null})
: additionalNavigationOptions;
}
@@ -73,40 +150,20 @@ const PopoverStackNavigator = (routeConfigMap, stackConfig = {}) => {
const router = StackRouter(newRouteConfigMap, stackRouterConfig);
// Create a navigator with CardStackTransitioner as the view
const navigator = createNavigator(
router,
routeConfigMap,
stackConfig
)(props => {
const { showInPopover, screenProps, ...otherProps } = props;
shouldShowInPopover = showInPopover !== undefined ? showInPopover : isTablet();
return (
<CardStackTransitioner
{...otherProps}
screenProps={{shouldShowInPopover, ...screenProps}}
headerMode={shouldShowInPopover ? 'screen' : headerMode}
headerTransitionPreset={headerTransitionPreset}
mode={mode}
cardStyle={shouldShowInPopover ? {...cardStyle, backgroundColor: 'transparent'} : cardStyle}
transitionConfig={shouldShowInPopover ? popoverTransitionConfig : transitionConfig}
onTransitionStart={onTransitionStart}
onTransitionEnd={(lastTransition, transition) => {
const { state, dispatch } = props.navigation;
dispatch(NavigationActions.completeTransition({ key: state.key }));
onTransitionEnd && onTransitionEnd();
}}
/>
)
});
return createNavigationContainer(navigator);
};
PopoverStackNavigator.registeredViews = {};
PopoverStackNavigator.registerRefForView = (ref, view) => {
PopoverStackNavigator.registeredViews[view] = ref;
// Create a navigator with StackView as the view
let Navigator = createNavigator(PopoverStackView, router, stackConfig);
if (!disableKeyboardHandling) {
Navigator = createKeyboardAwareNavigator(Navigator);
}
export default PopoverStackNavigator;
// HOC to provide the navigation prop for the top-level navigator (when the prop is missing)
return createNavigationContainer(Navigator);
}
createPopoverStackNavigator.registeredViews = {};
createPopoverStackNavigator.registerRefForView = (ref, view) => {
createPopoverStackNavigator.registeredViews[view] = ref;
}
export default createPopoverStackNavigator;

View File

@@ -1,4 +1,4 @@
export { default as default } from './Popover.js'
export { default as PopoverNavigation } from './PopoverNavigation.js'
export { default as PopoverStackNavigator, withPopoverNavigation as withPopoverNavigation } from './PopoverStackNavigator.js'
export { default as createPopoverStackNavigator, withPopoverNavigation as withPopoverNavigation } from './PopoverStackNavigator.js'
export { Rect as Rect, Size as Size, popoverTransitionConfig as popoverTransitionConfig } from './Utility.js'