diff --git a/src/components/ScreenWrapper/BaseScreenWrapper.js b/src/components/ScreenWrapper/BaseScreenWrapper.js
index a3ebdd4eb6a2..83408e6a36fe 100644
--- a/src/components/ScreenWrapper/BaseScreenWrapper.js
+++ b/src/components/ScreenWrapper/BaseScreenWrapper.js
@@ -43,6 +43,18 @@ class BaseScreenWrapper extends React.Component {
});
}
+ /**
+ * We explicitly want to ignore if props.modal changes, and only want to rerender if
+ * any of the other props **used for the rendering output** is changed.
+ * @param {Object} nextProps
+ * @param {Object} nextState
+ * @returns {boolean}
+ */
+ shouldComponentUpdate(nextProps, nextState) {
+ return !_.isEqual(this.state, nextState)
+ || !_.isEqual(_.omit(this.props, 'modal'), _.omit(nextProps, 'modal'));
+ }
+
componentWillUnmount() {
if (this.unsubscribeEscapeKey) {
this.unsubscribeEscapeKey();
diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
index 9f200ec9356d..14ca7bc531e5 100644
--- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
+++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
@@ -1,145 +1,42 @@
-import lodashGet from 'lodash/get';
-import _ from 'underscore';
import React, {Component} from 'react';
import {View} from 'react-native';
-import PropTypes from 'prop-types';
import styles from '../../../../styles/styles';
import SidebarLinks from '../SidebarLinks';
-import PopoverMenu from '../../../../components/PopoverMenu';
-import FloatingActionButton from '../../../../components/FloatingActionButton';
import ScreenWrapper from '../../../../components/ScreenWrapper';
-import compose from '../../../../libs/compose';
import Navigation from '../../../../libs/Navigation/Navigation';
import ROUTES from '../../../../ROUTES';
import Timing from '../../../../libs/actions/Timing';
import CONST from '../../../../CONST';
-import * as Expensicons from '../../../../components/Icon/Expensicons';
-import Permissions from '../../../../libs/Permissions';
-import * as Policy from '../../../../libs/actions/Policy';
import Performance from '../../../../libs/Performance';
-import * as Welcome from '../../../../libs/actions/Welcome';
-import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes';
import withDrawerState from '../../../../components/withDrawerState';
-import withNavigationFocus from '../../../../components/withNavigationFocus';
import KeyboardShortcutsModal from '../../../../components/KeyboardShortcutsModal';
+import withWindowDimensions, {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions';
+import compose from '../../../../libs/compose';
+import sidebarPropTypes from './sidebarPropTypes';
const propTypes = {
-
- /** Callback function when the menu is shown */
- onShowCreateMenu: PropTypes.func,
-
- /** Callback function before the menu is hidden */
- onHideCreateMenu: PropTypes.func,
-
- /** reportID in the current navigation state */
- reportIDFromRoute: PropTypes.string,
-
...sidebarPropTypes,
-};
-const defaultProps = {
- onHideCreateMenu: () => {},
- onShowCreateMenu: () => {},
- ...sidebarDefaultProps,
+ ...windowDimensionsPropTypes,
};
class BaseSidebarScreen extends Component {
constructor(props) {
super(props);
- this.hideCreateMenu = this.hideCreateMenu.bind(this);
this.startTimer = this.startTimer.bind(this);
- this.showCreateMenu = this.showCreateMenu.bind(this);
-
- this.state = {
- isCreateMenuActive: false,
- };
+ this.navigateToSettings = this.navigateToSettings.bind(this);
}
componentDidMount() {
Performance.markStart(CONST.TIMING.SIDEBAR_LOADED);
Timing.start(CONST.TIMING.SIDEBAR_LOADED, true);
-
- const routes = lodashGet(this.props.navigation.getState(), 'routes', []);
- Welcome.show({routes, showCreateMenu: this.showCreateMenu});
- }
-
- componentDidUpdate(prevProps) {
- if (!this.didScreenBecomeInactive(prevProps)) {
- return;
- }
-
- // Hide menu manually when other pages are opened using shortcut key
- this.hideCreateMenu();
}
/**
- * Check if LHN status changed from active to inactive.
- * Used to close already opened FAB menu when open any other pages (i.e. Press Command + K on web).
- *
- * @param {Object} prevProps
- * @return {Boolean}
+ * Method called when avatar is clicked
*/
- didScreenBecomeInactive(prevProps) {
- // When the Drawer gets closed and ReportScreen is shown
- if (!this.props.isDrawerOpen && prevProps.isDrawerOpen) {
- return true;
- }
-
- // When any other page is opened over LHN
- if (!this.props.isFocused && prevProps.isFocused) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Check if LHN is inactive.
- * Used to prevent FAB menu showing after opening any other pages.
- *
- * @return {Boolean}
- */
- isScreenInactive() {
- // When drawer is closed and Report page is open
- if (this.props.isSmallScreenWidth && !this.props.isDrawerOpen) {
- return true;
- }
-
- // When any other page is open
- if (!this.props.isFocused) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Method called when we click the floating action button
- */
- showCreateMenu() {
- if (this.isScreenInactive()) {
- // Prevent showing menu when click FAB icon quickly after opening other pages
- return;
- }
- this.setState({
- isCreateMenuActive: true,
- });
- this.props.onShowCreateMenu();
- }
-
- /**
- * Method called either when:
- * Pressing the floating action button to open the CreateMenu modal
- * Selecting an item on CreateMenu or closing it by clicking outside of the modal component
- */
- hideCreateMenu() {
- if (!this.state.isCreateMenuActive) {
- return;
- }
- this.props.onHideCreateMenu();
- this.setState({
- isCreateMenuActive: false,
- });
+ navigateToSettings() {
+ Navigation.navigate(ROUTES.SETTINGS);
}
/**
@@ -151,8 +48,6 @@ class BaseSidebarScreen extends Component {
}
render() {
- // Workspaces are policies with type === 'free'
- const workspaces = _.filter(this.props.allPolicies, policy => policy && policy.type === CONST.POLICY.TYPE.FREE);
return (
-
- Navigation.navigate(ROUTES.NEW_CHAT),
- },
- {
- icon: Expensicons.Users,
- text: this.props.translate('sidebarScreen.newGroup'),
- onSelected: () => Navigation.navigate(ROUTES.NEW_GROUP),
- },
- ...(Permissions.canUsePolicyRooms(this.props.betas) && workspaces.length ? [
- {
- icon: Expensicons.Hashtag,
- text: this.props.translate('sidebarScreen.newRoom'),
- onSelected: () => Navigation.navigate(ROUTES.WORKSPACE_NEW_ROOM),
- },
- ] : []),
- ...(Permissions.canUseIOUSend(this.props.betas) ? [
- {
- icon: Expensicons.Send,
- text: this.props.translate('iou.sendMoney'),
- onSelected: () => Navigation.navigate(ROUTES.IOU_SEND),
- },
- ] : []),
- ...(Permissions.canUseIOU(this.props.betas) ? [
- {
- icon: Expensicons.MoneyCircle,
- text: this.props.translate('iou.requestMoney'),
- onSelected: () => Navigation.navigate(ROUTES.IOU_REQUEST),
- },
- ] : []),
- ...(Permissions.canUseIOU(this.props.betas) ? [
- {
- icon: Expensicons.Receipt,
- text: this.props.translate('iou.splitBill'),
- onSelected: () => Navigation.navigate(ROUTES.IOU_BILL),
- },
- ] : []),
- ...(!Policy.hasActiveFreePolicy(this.props.allPolicies) ? [
- {
- icon: Expensicons.NewWorkspace,
- iconWidth: 46,
- iconHeight: 40,
- text: this.props.translate('workspace.new.newWorkspace'),
- description: this.props.translate('workspace.new.getTheExpensifyCardAndMore'),
- onSelected: () => Policy.createWorkspace(),
- },
- ] : []),
- ]}
- />
+ {this.props.children}
>
)}
@@ -242,9 +75,8 @@ class BaseSidebarScreen extends Component {
}
BaseSidebarScreen.propTypes = propTypes;
-BaseSidebarScreen.defaultProps = defaultProps;
export default compose(
+ withWindowDimensions,
withDrawerState,
- withNavigationFocus,
)(BaseSidebarScreen);
diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
new file mode 100644
index 000000000000..465902df23d0
--- /dev/null
+++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
@@ -0,0 +1,252 @@
+import React from 'react';
+import _ from 'underscore';
+import {withOnyx} from 'react-native-onyx';
+import PropTypes from 'prop-types';
+import lodashGet from 'lodash/get';
+import {View} from 'react-native';
+import styles from '../../../../styles/styles';
+import * as Expensicons from '../../../../components/Icon/Expensicons';
+import Navigation from '../../../../libs/Navigation/Navigation';
+import ROUTES from '../../../../ROUTES';
+import Permissions from '../../../../libs/Permissions';
+import * as Policy from '../../../../libs/actions/Policy';
+import PopoverMenu from '../../../../components/PopoverMenu';
+import CONST from '../../../../CONST';
+import FloatingActionButton from '../../../../components/FloatingActionButton';
+import compose from '../../../../libs/compose';
+import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
+import withWindowDimensions from '../../../../components/withWindowDimensions';
+import ONYXKEYS from '../../../../ONYXKEYS';
+import withNavigation from '../../../../components/withNavigation';
+import * as Welcome from '../../../../libs/actions/Welcome';
+import withNavigationFocus from '../../../../components/withNavigationFocus';
+import withDrawerState from '../../../../components/withDrawerState';
+
+/**
+ * @param {Object} [policy]
+ * @returns {Object|undefined}
+ */
+const policySelector = policy => policy && ({
+ type: policy.type,
+ role: policy.role,
+});
+
+const propTypes = {
+ /* Callback function when the menu is shown */
+ onShowCreateMenu: PropTypes.func,
+
+ /* Callback function before the menu is hidden */
+ onHideCreateMenu: PropTypes.func,
+
+ /** The list of policies the user has access to. */
+ allPolicies: PropTypes.shape({
+ /** The policy name */
+ name: PropTypes.string,
+ }),
+
+ /* Beta features list */
+ betas: PropTypes.arrayOf(PropTypes.string),
+
+ ...withLocalizePropTypes,
+};
+const defaultProps = {
+ onHideCreateMenu: () => {},
+ onShowCreateMenu: () => {},
+ allPolicies: {},
+ betas: [],
+};
+
+/**
+ * Responsible for rendering the {@link PopoverMenu}, and the accompanying
+ * FAB that can open or close the menu.
+ */
+class FloatingActionButtonAndPopover extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.showCreateMenu = this.showCreateMenu.bind(this);
+ this.hideCreateMenu = this.hideCreateMenu.bind(this);
+
+ this.state = {
+ isCreateMenuActive: false,
+ };
+ }
+
+ componentDidMount() {
+ const routes = lodashGet(this.props.navigation.getState(), 'routes', []);
+ Welcome.show({routes, showCreateMenu: this.showCreateMenu});
+ }
+
+ componentDidUpdate(prevProps) {
+ if (!this.didScreenBecomeInactive(prevProps)) {
+ return;
+ }
+
+ // Hide menu manually when other pages are opened using shortcut key
+ this.hideCreateMenu();
+ }
+
+ /**
+ * Check if LHN status changed from active to inactive.
+ * Used to close already opened FAB menu when open any other pages (i.e. Press Command + K on web).
+ *
+ * @param {Object} prevProps
+ * @return {Boolean}
+ */
+ didScreenBecomeInactive(prevProps) {
+ // When the Drawer gets closed and ReportScreen is shown
+ if (!this.props.isDrawerOpen && prevProps.isDrawerOpen) {
+ return true;
+ }
+
+ // When any other page is opened over LHN
+ if (!this.props.isFocused && prevProps.isFocused) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if LHN is inactive.
+ * Used to prevent FAB menu showing after opening any other pages.
+ *
+ * @return {Boolean}
+ */
+ isScreenInactive() {
+ // When drawer is closed and Report page is open
+ if (this.props.isSmallScreenWidth && !this.props.isDrawerOpen) {
+ return true;
+ }
+
+ // When any other page is open
+ if (!this.props.isFocused) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Method called when we click the floating action button
+ */
+ showCreateMenu() {
+ if (this.isScreenInactive()) {
+ // Prevent showing menu when click FAB icon quickly after opening other pages
+ return;
+ }
+ this.setState({
+ isCreateMenuActive: true,
+ });
+ this.props.onShowCreateMenu();
+ }
+
+ /**
+ * Method called either when:
+ * - Pressing the floating action button to open the CreateMenu modal
+ * - Selecting an item on CreateMenu or closing it by clicking outside of the modal component
+ */
+ hideCreateMenu() {
+ if (this.isScreenInactive()) {
+ // Prevent showing menu when click FAB icon quickly after opening other pages
+ return;
+ }
+ this.props.onHideCreateMenu();
+ this.setState({
+ isCreateMenuActive: false,
+ });
+ }
+
+ render() {
+ // Workspaces are policies with type === 'free'
+ const workspaces = _.filter(this.props.allPolicies, policy => policy && policy.type === CONST.POLICY.TYPE.FREE);
+
+ return (
+
+ Navigation.navigate(ROUTES.NEW_CHAT),
+ },
+ {
+ icon: Expensicons.Users,
+ text: this.props.translate('sidebarScreen.newGroup'),
+ onSelected: () => Navigation.navigate(ROUTES.NEW_GROUP),
+ },
+ ...(Permissions.canUsePolicyRooms(this.props.betas) && workspaces.length ? [
+ {
+ icon: Expensicons.Hashtag,
+ text: this.props.translate('sidebarScreen.newRoom'),
+ onSelected: () => Navigation.navigate(ROUTES.WORKSPACE_NEW_ROOM),
+ },
+ ] : []),
+ ...(Permissions.canUseIOUSend(this.props.betas) ? [
+ {
+ icon: Expensicons.Send,
+ text: this.props.translate('iou.sendMoney'),
+ onSelected: () => Navigation.navigate(ROUTES.IOU_SEND),
+ },
+ ] : []),
+ ...(Permissions.canUseIOU(this.props.betas) ? [
+ {
+ icon: Expensicons.MoneyCircle,
+ text: this.props.translate('iou.requestMoney'),
+ onSelected: () => Navigation.navigate(ROUTES.IOU_REQUEST),
+ },
+ ] : []),
+ ...(Permissions.canUseIOU(this.props.betas) ? [
+ {
+ icon: Expensicons.Receipt,
+ text: this.props.translate('iou.splitBill'),
+ onSelected: () => Navigation.navigate(ROUTES.IOU_BILL),
+ },
+ ] : []),
+ ...(!Policy.hasActiveFreePolicy(this.props.allPolicies) ? [
+ {
+ icon: Expensicons.NewWorkspace,
+ iconWidth: 46,
+ iconHeight: 40,
+ text: this.props.translate('workspace.new.newWorkspace'),
+ description: this.props.translate('workspace.new.getTheExpensifyCardAndMore'),
+ onSelected: () => Policy.createWorkspace(),
+ },
+ ] : []),
+ ]}
+ />
+
+
+ );
+ }
+}
+
+FloatingActionButtonAndPopover.propTypes = propTypes;
+FloatingActionButtonAndPopover.defaultProps = defaultProps;
+
+export default compose(
+ withLocalize,
+ withNavigation,
+ withNavigationFocus,
+ withDrawerState,
+ withWindowDimensions,
+ withOnyx({
+ allPolicies: {
+ key: ONYXKEYS.COLLECTION.POLICY,
+ selector: policySelector,
+ },
+ betas: {
+ key: ONYXKEYS.BETAS,
+ },
+ }),
+)(FloatingActionButtonAndPopover);
diff --git a/src/pages/home/sidebar/SidebarScreen/PopoverModal.js b/src/pages/home/sidebar/SidebarScreen/PopoverModal.js
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.js
index 584bcc49f8e4..552b0e2dc180 100755
--- a/src/pages/home/sidebar/SidebarScreen/index.js
+++ b/src/pages/home/sidebar/SidebarScreen/index.js
@@ -1,52 +1,40 @@
-import React from 'react';
-import {withOnyx} from 'react-native-onyx';
-import compose from '../../../../libs/compose';
-import withWindowDimensions from '../../../../components/withWindowDimensions';
-import withLocalize from '../../../../components/withLocalize';
-import ONYXKEYS from '../../../../ONYXKEYS';
-import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes';
+import React, {useRef} from 'react';
+import sidebarPropTypes from './sidebarPropTypes';
import BaseSidebarScreen from './BaseSidebarScreen';
+import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover';
const SidebarScreen = (props) => {
- let baseSidebarScreen = null;
+ const popoverModal = useRef(null);
/**
* Method create event listener
*/
const createDragoverListener = () => {
- document.addEventListener('dragover', baseSidebarScreen.hideCreateMenu);
+ document.addEventListener('dragover', popoverModal.current.hideCreateMenu);
};
/**
* Method remove event listener.
*/
const removeDragoverListener = () => {
- document.removeEventListener('dragover', baseSidebarScreen.hideCreateMenu);
+ document.removeEventListener('dragover', popoverModal.current.hideCreateMenu);
};
+
return (
baseSidebarScreen = el}
- onShowCreateMenu={createDragoverListener}
- onHideCreateMenu={removeDragoverListener}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
- />
+ >
+
+
);
};
SidebarScreen.propTypes = sidebarPropTypes;
-SidebarScreen.defaultProps = sidebarDefaultProps;
SidebarScreen.displayName = 'SidebarScreen';
-export default compose(
- withLocalize,
- withWindowDimensions,
- withOnyx({
- allPolicies: {
- key: ONYXKEYS.COLLECTION.POLICY,
- },
- betas: {
- key: ONYXKEYS.BETAS,
- },
- }),
-)(SidebarScreen);
+export default SidebarScreen;
diff --git a/src/pages/home/sidebar/SidebarScreen/index.native.js b/src/pages/home/sidebar/SidebarScreen/index.native.js
index e2cb2838efe8..145c841b1d2f 100755
--- a/src/pages/home/sidebar/SidebarScreen/index.native.js
+++ b/src/pages/home/sidebar/SidebarScreen/index.native.js
@@ -1,28 +1,18 @@
import React from 'react';
-import {withOnyx} from 'react-native-onyx';
-import compose from '../../../../libs/compose';
-import withWindowDimensions from '../../../../components/withWindowDimensions';
-import withLocalize from '../../../../components/withLocalize';
-import ONYXKEYS from '../../../../ONYXKEYS';
-import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes';
+import sidebarPropTypes from './sidebarPropTypes';
import BaseSidebarScreen from './BaseSidebarScreen';
+import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover';
-// eslint-disable-next-line react/jsx-props-no-spreading
-const SidebarScreen = props => ;
+const SidebarScreen = props => (
+
+
+
+);
SidebarScreen.propTypes = sidebarPropTypes;
-SidebarScreen.defaultProps = sidebarDefaultProps;
SidebarScreen.displayName = 'SidebarScreen';
-export default compose(
- withLocalize,
- withWindowDimensions,
- withOnyx({
- allPolicies: {
- key: ONYXKEYS.COLLECTION.POLICY,
- },
- betas: {
- key: ONYXKEYS.BETAS,
- },
- }),
-)(SidebarScreen);
+export default SidebarScreen;
diff --git a/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js b/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js
index 996bba9d676b..3affaa2d00be 100644
--- a/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js
+++ b/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js
@@ -1,26 +1,8 @@
import PropTypes from 'prop-types';
-import {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions';
-import {withLocalizePropTypes} from '../../../../components/withLocalize';
const sidebarPropTypes = {
- /** The list of policies the user has access to. */
- allPolicies: PropTypes.shape({
- /** The policy name */
- name: PropTypes.string,
- }),
-
- /* Beta features list */
- betas: PropTypes.arrayOf(PropTypes.string),
-
- ...windowDimensionsPropTypes,
-
- ...withLocalizePropTypes,
+ /** reportID in the current navigation state */
+ reportIDFromRoute: PropTypes.string,
};
-
-const sidebarDefaultProps = {
- allPolicies: {},
- betas: [],
-};
-
-export {sidebarPropTypes, sidebarDefaultProps};
+export default sidebarPropTypes;