Skip to content

Commit

Permalink
Merge pull request #1960 from Expensify/marcaaron-fixModalClosing
Browse files Browse the repository at this point in the history
Fix modal closing animation on web/desktop
  • Loading branch information
jasperhuangg authored Mar 24, 2021
2 parents 9b282f1 + 0db97f4 commit f96ebbd
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 156 deletions.
14 changes: 10 additions & 4 deletions src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import {createStackNavigator} from '@react-navigation/stack';

import {getNavigationModalCardStyle} from '../../../styles/styles';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import CONST from '../../../CONST';
Expand All @@ -25,6 +23,8 @@ import KeyboardShortcut from '../../KeyboardShortcut';
import Navigation from '../Navigation';
import * as User from '../../actions/User';
import NameValuePair from '../../actions/NameValuePair';
import modalCardStyleInterpolator from './modalCardStyleInterpolator';
import createCustomModalStackNavigator from './createCustomModalStackNavigator';

// Main drawer navigator
import MainDrawerNavigator from './MainDrawerNavigator';
Expand All @@ -40,7 +40,7 @@ import {
SettingsModalStackNavigator,
} from './ModalStackNavigators';

const RootStack = createStackNavigator();
const RootStack = createCustomModalStackNavigator();

const propTypes = {
network: PropTypes.shape({isOffline: PropTypes.bool}),
Expand Down Expand Up @@ -116,8 +116,14 @@ class AuthScreens extends React.Component {
const modalScreenOptions = {
headerShown: false,
cardStyle: getNavigationModalCardStyle(this.props.isSmallScreenWidth),
};
cardStyleInterpolator: modalCardStyleInterpolator,
animationEnabled: true,
gestureDirection: 'horizontal',

// This is a custom prop we are passing to custom navigator so that we will know to add a Pressable overlay
// when displaying a modal. This allows us to dismiss by clicking outside on web / large screens.
isModal: true,
};
return (
<RootStack.Navigator
mode="modal"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Pressable} from 'react-native';
import Navigation from '../../Navigation';
import styles from '../../../../styles/styles';

const propTypes = {
isDisplayingModal: PropTypes.bool.isRequired,
};

const ClickAwayHandler = (props) => {
if (!props.isDisplayingModal) {
return null;
}

return (
<Pressable
style={styles.navigationModalOverlay}
onPress={Navigation.dismissModal}
/>
);
};

ClickAwayHandler.propTypes = propTypes;
ClickAwayHandler.displayName = 'ClickAwayHandler';
export default ClickAwayHandler;
3 changes: 3 additions & 0 deletions src/libs/Navigation/AppNavigator/ClickAwayHandler/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ClickAwayHandler from './ClickAwayHandler';

export default ClickAwayHandler;
20 changes: 20 additions & 0 deletions src/libs/Navigation/AppNavigator/ClickAwayHandler/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions';
import ClickAwayHandler from './ClickAwayHandler';

const propTypes = {
...windowDimensionsPropTypes,
};

const ClickAwayHandlerWithWindowDimensions = (props) => {
if (props.isSmallScreenWidth) {
return null;
}

// eslint-disable-next-line react/jsx-props-no-spreading
return <ClickAwayHandler {...props} />;
};

ClickAwayHandlerWithWindowDimensions.propTypes = propTypes;
ClickAwayHandlerWithWindowDimensions.displayName = 'ClickAwayHandlerWithWindowDimensions';
export default withWindowDimensions(ClickAwayHandlerWithWindowDimensions);
20 changes: 10 additions & 10 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import React from 'react';

import {createStackNavigator} from '@react-navigation/stack';
import styles from '../../../styles/styles';
import ROUTES from '../../../ROUTES';
import {
SettingsModalStack,
NewChatModalStack,
NewGroupModalStack,
SearchModalStack,
DetailsModalStack,
IOURequestModalStack,
IOUBillModalStack,
} from './ModalStacks';
import NewChatPage from '../../../pages/NewChatPage';
import NewGroupPage from '../../../pages/NewGroupPage';
import SearchPage from '../../../pages/SearchPage';
Expand All @@ -23,6 +14,15 @@ import SettingsPreferencesPage from '../../../pages/settings/PreferencesPage';
import SettingsPasswordPage from '../../../pages/settings/PasswordPage';
import SettingsPaymentsPage from '../../../pages/settings/PaymentsPage';

// Setup the modal stack navigators so we only have to create them once
const SettingsModalStack = createStackNavigator();
const NewChatModalStack = createStackNavigator();
const NewGroupModalStack = createStackNavigator();
const SearchModalStack = createStackNavigator();
const DetailsModalStack = createStackNavigator();
const IOURequestModalStack = createStackNavigator();
const IOUBillModalStack = createStackNavigator();

const defaultSubRouteOptions = {
cardStyle: styles.navigationScreenCardStyle,
headerShown: false,
Expand Down
20 changes: 0 additions & 20 deletions src/libs/Navigation/AppNavigator/ModalStacks/index.js

This file was deleted.

26 changes: 0 additions & 26 deletions src/libs/Navigation/AppNavigator/ModalStacks/index.native.js

This file was deleted.

118 changes: 22 additions & 96 deletions src/libs/Navigation/AppNavigator/createCustomModalStackNavigator.js
Original file line number Diff line number Diff line change
@@ -1,114 +1,40 @@
import _ from 'underscore';
import React from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import {createNavigatorFactory, useNavigationBuilder} from '@react-navigation/core';
import {StackRouter} from '@react-navigation/routers';
import withWindowDimensions from '../../../components/withWindowDimensions';
import Modal from '../../../components/Modal';
import themeColors from '../../../styles/themes/default';
import ONYXKEYS from '../../../ONYXKEYS';
import Navigation from '../Navigation';
import compose from '../../compose';
import CONST from '../../../CONST';
import {StackView} from '@react-navigation/stack';
import ClickAwayHandler from './ClickAwayHandler';

const propTypes = {
// Navigation state for this navigator
// See: https://reactnavigation.org/docs/navigation-state/
state: PropTypes.shape({
// Index of the focused route object in the routes array
index: PropTypes.number,

// List of route objects (screens) which are rendered in the navigator. It also represents the history in a
// stack navigator. There should be at least one item present in this array.
routes: PropTypes.arrayOf(PropTypes.shape({

// A unique key name for a screen. Created automatically by react-nav.
key: PropTypes.string,
})),
}).isRequired,

// Object containing descriptors for each route with the route keys as its properties
// See: https://reactnavigation.org/docs/custom-navigators/#usenavigationbuilder
// eslint-disable-next-line react/no-unused-prop-types
descriptors: PropTypes.objectOf(PropTypes.shape({

// A function which can be used to render the actual screen. Calling descriptors[route.key].render() will return
// a React element containing the screen content.
render: PropTypes.func,
})).isRequired,

// Current url we are navigated to
currentURL: PropTypes.string,

// Path for the modal parent to match on
path: PropTypes.string.isRequired,
};

const defaultProps = {
currentURL: '',
};

/**
* Returns the current descriptor for the focused screen in this navigators state. The descriptor has a function
* called render() that we must call each time this navigator updates. It's important to use this method to render
* a screen, otherwise any child navigators won't be connected to the navigation tree properly.
*
* @param {Object} props
* @returns {Object}
*/
function getCurrentViewDescriptor(props) {
const currentRoute = props.state.routes[props.state.index];
const currentRouteKey = currentRoute.key;
const currentDescriptor = props.descriptors[currentRouteKey];
return currentDescriptor;
}

const ResponsiveView = props => (
<Modal
isVisible={props.currentURL
&& props.currentURL.includes(props.path)}
backgroundColor={themeColors.componentBG}
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
onClose={Navigation.dismissModal}
>
{getCurrentViewDescriptor(props).render()}
</Modal>
);

ResponsiveView.propTypes = propTypes;
ResponsiveView.defaultProps = defaultProps;
ResponsiveView.displayName = 'ResponsiveView';

const ResponsiveViewWithHOCs = compose(
withWindowDimensions,
withOnyx({
currentURL: {
key: ONYXKEYS.CURRENT_URL,
},
}),
)(ResponsiveView);

const ResponsiveNavigator = ({
const CustomRootStackNavigator = ({
children,
...rest
}) => {
const {state, navigation, descriptors} = useNavigationBuilder(StackRouter, {
children,
});

const isDisplayingModal = Boolean(_.find(descriptors, descriptor => descriptor.options.isModal));
return (
<ResponsiveViewWithHOCs
state={state}
navigation={navigation}
descriptors={descriptors}
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
/>
<>
<StackView
state={state}
navigation={navigation}
descriptors={descriptors}
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
/>

{/* We need to superimpose a clickaway handler when showing modals so that they can be dismissed. Capturing
press events on the cardOverlay element in react-navigation is not yet supported on web */}
<ClickAwayHandler
isDisplayingModal={isDisplayingModal}
/>
</>
);
};

ResponsiveNavigator.propTypes = {
CustomRootStackNavigator.propTypes = {
children: PropTypes.node.isRequired,
};

export default createNavigatorFactory(ResponsiveNavigator);
export default createNavigatorFactory(CustomRootStackNavigator);
31 changes: 31 additions & 0 deletions src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {Animated} from 'react-native';

export default ({
current: {progress},
inverted,
layouts: {
screen,
},
}) => {
const translateX = Animated.multiply(progress.interpolate({
inputRange: [0, 1],
outputRange: [screen.width, 0],
extrapolate: 'clamp',
}), inverted);

return ({
containerStyle: {
overflow: 'hidden',
},
cardStyle: {
transform: [{translateX}],
},
overlayStyle: {
opacity: progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 0.3],
extrapolate: 'clamp',
}),
},
});
};
9 changes: 9 additions & 0 deletions src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,15 @@ const styles = {
zIndex: 2,
},

navigationModalOverlay: {
position: 'absolute',
width: '100%',
height: '100%',
transform: [{
translateX: -variables.sideBarWidth,
}],
},

sidebarVisible: {
borderRightWidth: 1,
},
Expand Down

0 comments on commit f96ebbd

Please sign in to comment.