diff --git a/package-lock.json b/package-lock.json
index cd763dffefbe..c27a35204518 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -48,6 +48,7 @@
"domhandler": "^4.3.0",
"expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#e63d06e239fe5b2f221e86eab71ae8a4e3b8bee3",
"fbjs": "^3.0.2",
+ "focus-trap-react": "^10.2.1",
"htmlparser2": "^7.2.0",
"idb-keyval": "^6.2.1",
"jest-when": "^3.5.2",
@@ -28261,6 +28262,28 @@
"readable-stream": "^2.3.6"
}
},
+ "node_modules/focus-trap": {
+ "version": "7.5.2",
+ "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz",
+ "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==",
+ "dependencies": {
+ "tabbable": "^6.2.0"
+ }
+ },
+ "node_modules/focus-trap-react": {
+ "version": "10.2.1",
+ "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.2.1.tgz",
+ "integrity": "sha512-UrAKOn52lvfHF6lkUMfFhlQxFgahyNW5i6FpHWkDxAeD4FSk3iwx9n4UEA4Sims0G5WiGIi0fAyoq3/UVeNCYA==",
+ "dependencies": {
+ "focus-trap": "^7.5.2",
+ "tabbable": "^6.2.0"
+ },
+ "peerDependencies": {
+ "prop-types": "^15.8.1",
+ "react": ">=16.3.0",
+ "react-dom": ">=16.3.0"
+ }
+ },
"node_modules/follow-redirects": {
"version": "1.15.1",
"dev": true,
@@ -45036,6 +45059,11 @@
"dev": true,
"license": "BSD-3-Clause"
},
+ "node_modules/tabbable": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
+ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
+ },
"node_modules/table": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
@@ -67995,6 +68023,23 @@
"readable-stream": "^2.3.6"
}
},
+ "focus-trap": {
+ "version": "7.5.2",
+ "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz",
+ "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==",
+ "requires": {
+ "tabbable": "^6.2.0"
+ }
+ },
+ "focus-trap-react": {
+ "version": "10.2.1",
+ "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.2.1.tgz",
+ "integrity": "sha512-UrAKOn52lvfHF6lkUMfFhlQxFgahyNW5i6FpHWkDxAeD4FSk3iwx9n4UEA4Sims0G5WiGIi0fAyoq3/UVeNCYA==",
+ "requires": {
+ "focus-trap": "^7.5.2",
+ "tabbable": "^6.2.0"
+ }
+ },
"follow-redirects": {
"version": "1.15.1",
"dev": true
@@ -79433,6 +79478,11 @@
"version": "2.0.15",
"dev": true
},
+ "tabbable": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
+ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
+ },
"table": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
diff --git a/package.json b/package.json
index 6666fd19cf7a..a3a1f9663542 100644
--- a/package.json
+++ b/package.json
@@ -88,6 +88,7 @@
"domhandler": "^4.3.0",
"expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#e63d06e239fe5b2f221e86eab71ae8a4e3b8bee3",
"fbjs": "^3.0.2",
+ "focus-trap-react": "^10.2.1",
"htmlparser2": "^7.2.0",
"idb-keyval": "^6.2.1",
"jest-when": "^3.5.2",
diff --git a/src/components/FocusTrapView/index.js b/src/components/FocusTrapView/index.js
new file mode 100644
index 000000000000..2dcab7b9d998
--- /dev/null
+++ b/src/components/FocusTrapView/index.js
@@ -0,0 +1,75 @@
+/*
+ * The FocusTrap is only used on web and desktop
+ */
+import React, {useEffect, useRef} from 'react';
+import FocusTrap from 'focus-trap-react';
+import {View} from 'react-native';
+import {PropTypes} from 'prop-types';
+import {useIsFocused} from '@react-navigation/native';
+
+const propTypes = {
+ /** Children to wrap with FocusTrap */
+ children: PropTypes.node.isRequired,
+
+ /** Whether to enable the FocusTrap */
+ enabled: PropTypes.bool,
+
+ /**
+ * Whether to disable auto focus
+ * It is used when the component inside the FocusTrap have their own auto focus logic
+ */
+ shouldEnableAutoFocus: PropTypes.bool,
+};
+
+const defaultProps = {
+ enabled: true,
+ shouldEnableAutoFocus: false,
+};
+
+function FocusTrapView({enabled, shouldEnableAutoFocus, ...props}) {
+ const isFocused = useIsFocused();
+
+ /**
+ * Focus trap always needs a focusable element.
+ * In case that we don't have any focusable elements in the modal,
+ * the FocusTrap will use fallback View element using this ref.
+ */
+ const ref = useRef(null);
+
+ /**
+ * We have to set the 'tabindex' attribute to 0 to make the View focusable.
+ * Currently, it is not possible to set this through props.
+ * After the upgrade of 'react-native-web' to version 0.19 we can use 'tabIndex={0}' prop instead.
+ */
+ useEffect(() => {
+ if (!ref.current) {
+ return;
+ }
+ ref.current.setAttribute('tabindex', '0');
+ }, []);
+
+ return enabled ? (
+ shouldEnableAutoFocus && ref.current,
+ fallbackFocus: () => ref.current,
+ clickOutsideDeactivates: true,
+ }}
+ >
+
+
+ ) : (
+ props.children
+ );
+}
+
+FocusTrapView.displayName = 'FocusTrapView';
+FocusTrapView.propTypes = propTypes;
+FocusTrapView.defaultProps = defaultProps;
+
+export default FocusTrapView;
diff --git a/src/components/FocusTrapView/index.native.js b/src/components/FocusTrapView/index.native.js
new file mode 100644
index 000000000000..5720601f5a2b
--- /dev/null
+++ b/src/components/FocusTrapView/index.native.js
@@ -0,0 +1,11 @@
+/*
+ * The FocusTrap is only used on web and desktop
+ */
+
+function FocusTrapView({children}) {
+ return children;
+}
+
+FocusTrapView.displayName = 'FocusTrapView';
+
+export default FocusTrapView;
diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js
index f760e5d5aeb4..f0f8b8a4b09b 100644
--- a/src/components/ScreenWrapper/index.js
+++ b/src/components/ScreenWrapper/index.js
@@ -3,6 +3,7 @@ import React from 'react';
import _ from 'underscore';
import lodashGet from 'lodash/get';
import {PickerAvoidingView} from 'react-native-picker-select';
+import FocusTrapView from '../FocusTrapView';
import KeyboardAvoidingView from '../KeyboardAvoidingView';
import CONST from '../../CONST';
import styles from '../../styles/styles';
@@ -124,20 +125,26 @@ class ScreenWrapper extends React.Component {
style={styles.flex1}
enabled={this.props.shouldEnablePickerAvoiding}
>
-
- {this.props.environment === CONST.ENVIRONMENT.DEV && }
- {this.props.environment === CONST.ENVIRONMENT.DEV && }
- {
- // If props.children is a function, call it to provide the insets to the children.
- _.isFunction(this.props.children)
- ? this.props.children({
- insets,
- safeAreaPaddingBottomStyle,
- didScreenTransitionEnd: this.state.didScreenTransitionEnd,
- })
- : this.props.children
- }
- {this.props.isSmallScreenWidth && this.props.shouldShowOfflineIndicator && }
+
+
+ {this.props.environment === CONST.ENVIRONMENT.DEV && }
+ {this.props.environment === CONST.ENVIRONMENT.DEV && }
+ {
+ // If props.children is a function, call it to provide the insets to the children.
+ _.isFunction(this.props.children)
+ ? this.props.children({
+ insets,
+ safeAreaPaddingBottomStyle,
+ didScreenTransitionEnd: this.state.didScreenTransitionEnd,
+ })
+ : this.props.children
+ }
+ {this.props.isSmallScreenWidth && this.props.shouldShowOfflineIndicator && }
+
diff --git a/src/components/ScreenWrapper/propTypes.js b/src/components/ScreenWrapper/propTypes.js
index 83033d9e97b7..c3538b3c026d 100644
--- a/src/components/ScreenWrapper/propTypes.js
+++ b/src/components/ScreenWrapper/propTypes.js
@@ -48,6 +48,12 @@ const propTypes = {
/** Styles for the offline indicator */
offlineIndicatorStyle: stylePropTypes,
+
+ /** Whether to disable the focus trap */
+ shouldDisableFocusTrap: PropTypes.bool,
+
+ /** Whether to disable auto focus of the focus trap */
+ shouldEnableAutoFocus: PropTypes.bool,
};
const defaultProps = {
@@ -63,6 +69,8 @@ const defaultProps = {
shouldShowOfflineIndicator: true,
offlineIndicatorStyle: [],
headerGapStyles: [],
+ shouldDisableFocusTrap: false,
+ shouldEnableAutoFocus: false,
};
export {propTypes, defaultProps};
diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js
index b5ef85e14cbb..22cac40cf29c 100755
--- a/src/pages/ProfilePage.js
+++ b/src/pages/ProfilePage.js
@@ -141,7 +141,7 @@ function ProfilePage(props) {
const navigateBackTo = lodashGet(props.route, 'params.backTo', '');
return (
-
+
Navigation.goBack(navigateBackTo)}
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index 5d0cb5ab9bf6..0e0f5944d3d8 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -298,6 +298,7 @@ function ReportScreen({
{({insets}) => (
<>