From 256a839c49353ccd8096b22ed580053065c59a5a Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 18 Sep 2023 08:37:50 +0200 Subject: [PATCH 01/20] Revert "Revert "Add focus trap to the RHP"" This reverts commit 6a9f592efdd4c6008acdebbe33823eab19cf68d5. --- package-lock.json | 50 +++++++++++++ package.json | 1 + src/components/FocusTrapView/index.js | 75 +++++++++++++++++++ src/components/FocusTrapView/index.native.js | 11 +++ src/components/ScreenWrapper/index.js | 35 +++++---- src/components/ScreenWrapper/propTypes.js | 8 ++ src/pages/ProfilePage.js | 2 +- src/pages/home/ReportScreen.js | 1 + .../SidebarScreen/BaseSidebarScreen.js | 1 + 9 files changed, 169 insertions(+), 15 deletions(-) create mode 100644 src/components/FocusTrapView/index.js create mode 100644 src/components/FocusTrapView/index.native.js diff --git a/package-lock.json b/package-lock.json index 382dcf45f55e..216a447fda82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "domhandler": "^4.3.0", "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#35bff866a8d345b460ea6256f0a0f0a8a7f81086", "fbjs": "^3.0.2", + "focus-trap-react": "^10.2.1", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", "jest-when": "^3.5.2", @@ -28294,6 +28295,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.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -44671,6 +44694,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", @@ -67682,6 +67710,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.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -78809,6 +78854,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 0073dedb741c..a2615415d080 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "domhandler": "^4.3.0", "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#35bff866a8d345b460ea6256f0a0f0a8a7f81086", "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 19f2b1fdc0c6..b306164a8ba0 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -144,7 +144,7 @@ function ProfilePage(props) { const chatReportWithCurrentUser = !isCurrentUser && !Session.isAnonymousUser() ? ReportUtils.getChatByParticipants([accountID]) : 0; return ( - + Navigation.goBack(navigateBackTo)} diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index a4145843ab87..5e41e33b18a4 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -319,6 +319,7 @@ function ReportScreen({ {({insets}) => ( <> From 8f1c89252af275b33ca430cd205029e99f358ee9 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 18 Sep 2023 09:15:18 +0200 Subject: [PATCH 02/20] fix initial state in the TwoFactorAuthSteps --- .../TwoFactorAuth/TwoFactorAuthSteps.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js index e0094267742b..d06612967ff9 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js @@ -13,22 +13,21 @@ import {defaultAccount, TwoFactorAuthPropTypes} from './TwoFactorAuthPropTypes'; import useAnimatedStepContext from '../../../../components/AnimatedStep/useAnimatedStepContext'; function TwoFactorAuthSteps({account = defaultAccount}) { - const [currentStep, setCurrentStep] = useState(CONST.TWO_FACTOR_AUTH_STEPS.CODES); + const calculateCurrentStep = () => { + if (account.twoFactorAuthStep) { + return account.twoFactorAuthStep; + } + return account.requiresTwoFactorAuth ? CONST.TWO_FACTOR_AUTH_STEPS.ENABLED : CONST.TWO_FACTOR_AUTH_STEPS.CODES; + }; + + const [currentStep, setCurrentStep] = useState(calculateCurrentStep); const {setAnimationDirection} = useAnimatedStepContext(); useEffect(() => () => TwoFactorAuthActions.clearTwoFactorAuthData(), []); useEffect(() => { - if (account.twoFactorAuthStep) { - setCurrentStep(account.twoFactorAuthStep); - return; - } - if (account.requiresTwoFactorAuth) { - setCurrentStep(CONST.TWO_FACTOR_AUTH_STEPS.ENABLED); - } else { - setCurrentStep(CONST.TWO_FACTOR_AUTH_STEPS.CODES); - } + setCurrentStep(calculateCurrentStep); // we don't want to trigger the hook every time the step changes, only when the requiresTwoFactorAuth changes // eslint-disable-next-line react-hooks/exhaustive-deps }, [account.requiresTwoFactorAuth]); From b8ca73d9219dd9b64db8d713e152c977ec5ed774 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:37:33 +0200 Subject: [PATCH 03/20] fix focus issue --- src/components/FocusTrapView/index.js | 11 ++++++----- src/components/KeyboardShortcutsModal.js | 1 + src/components/Modal/index.web.js | 10 +++++++++- src/components/Modal/modalPropTypes.js | 3 +++ src/components/ScreenWrapper/index.js | 1 + 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/FocusTrapView/index.js b/src/components/FocusTrapView/index.js index 2dcab7b9d998..38d7ac5730f8 100644 --- a/src/components/FocusTrapView/index.js +++ b/src/components/FocusTrapView/index.js @@ -5,7 +5,6 @@ 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 */ @@ -19,16 +18,18 @@ const propTypes = { * It is used when the component inside the FocusTrap have their own auto focus logic */ shouldEnableAutoFocus: PropTypes.bool, + + /** Whether the FocusTrap is active */ + active: PropTypes.bool, }; const defaultProps = { enabled: true, shouldEnableAutoFocus: false, + active: false, }; -function FocusTrapView({enabled, shouldEnableAutoFocus, ...props}) { - const isFocused = useIsFocused(); - +function FocusTrapView({enabled = true, active = true, shouldEnableAutoFocus, ...props}) { /** * Focus trap always needs a focusable element. * In case that we don't have any focusable elements in the modal, @@ -50,7 +51,7 @@ function FocusTrapView({enabled, shouldEnableAutoFocus, ...props}) { return enabled ? ( shouldEnableAutoFocus && ref.current, fallbackFocus: () => ref.current, diff --git a/src/components/KeyboardShortcutsModal.js b/src/components/KeyboardShortcutsModal.js index 6ca3cce6412c..f262f69a40d9 100644 --- a/src/components/KeyboardShortcutsModal.js +++ b/src/components/KeyboardShortcutsModal.js @@ -158,6 +158,7 @@ function KeyboardShortcutsModal({isShortcutsModalOpen = false, isSmallScreenWidt type={modalType} innerContainerStyle={{...styles.keyboardShortcutModalContainer, ...StyleUtils.getKeyboardShortcutsModalWidth(isSmallScreenWidth)}} onClose={KeyboardShortcutsActions.hideKeyboardShortcutModal} + shouldEnableFocusTrap > - {props.children} + + {props.children} + ); } diff --git a/src/components/Modal/modalPropTypes.js b/src/components/Modal/modalPropTypes.js index 58de5a6c57ca..a5ebcab89fab 100644 --- a/src/components/Modal/modalPropTypes.js +++ b/src/components/Modal/modalPropTypes.js @@ -66,6 +66,8 @@ const propTypes = { * */ hideModalContentWhileAnimating: PropTypes.bool, + shouldEnableFocusTrap: PropTypes.bool, + ...windowDimensionsPropTypes, }; @@ -84,6 +86,7 @@ const defaultProps = { statusBarTranslucent: true, avoidKeyboard: false, hideModalContentWhileAnimating: false, + shouldEnableFocusTrap: false, }; export {propTypes, defaultProps}; diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index f0f8b8a4b09b..c44e9f9c1b51 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -129,6 +129,7 @@ class ScreenWrapper extends React.Component { style={[styles.flex1, styles.noSelect]} enabled={!this.props.shouldDisableFocusTrap} shouldEnableAutoFocus={this.props.shouldEnableAutoFocus} + active={this.props.navigation.isFocused()} > {this.props.environment === CONST.ENVIRONMENT.DEV && } From 550d3852436593a945b3f6ec5b8fc6a56f9b3538 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:01:49 +0200 Subject: [PATCH 04/20] Refactor --- src/components/Modal/modalPropTypes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Modal/modalPropTypes.js b/src/components/Modal/modalPropTypes.js index a5ebcab89fab..f02414a76704 100644 --- a/src/components/Modal/modalPropTypes.js +++ b/src/components/Modal/modalPropTypes.js @@ -66,6 +66,7 @@ const propTypes = { * */ hideModalContentWhileAnimating: PropTypes.bool, + /** Should the modal use custom focus trap logic */ shouldEnableFocusTrap: PropTypes.bool, ...windowDimensionsPropTypes, From 9a0673542daeac40d2ecff339d63478b67d761d1 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 9 Oct 2023 10:16:07 +0200 Subject: [PATCH 05/20] refactor calculateCurrentStep --- .../Security/TwoFactorAuth/TwoFactorAuthSteps.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js index b93d14510c5d..dad0e5f5007e 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useState, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import CodesStep from './Steps/CodesStep'; import DisabledStep from './Steps/DisabledStep'; @@ -13,12 +13,12 @@ import {defaultAccount, TwoFactorAuthPropTypes} from './TwoFactorAuthPropTypes'; import useAnimatedStepContext from '../../../../components/AnimatedStep/useAnimatedStepContext'; function TwoFactorAuthSteps({account = defaultAccount}) { - const calculateCurrentStep = () => { + const calculateCurrentStep = useMemo(() => { if (account.twoFactorAuthStep) { return account.twoFactorAuthStep; } return account.requiresTwoFactorAuth ? CONST.TWO_FACTOR_AUTH_STEPS.ENABLED : CONST.TWO_FACTOR_AUTH_STEPS.CODES; - }; + }, [account.requiresTwoFactorAuth, account.twoFactorAuthStep]); const [currentStep, setCurrentStep] = useState(calculateCurrentStep); @@ -28,8 +28,7 @@ function TwoFactorAuthSteps({account = defaultAccount}) { useEffect(() => { setCurrentStep(calculateCurrentStep); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [account.requiresTwoFactorAuth, account.twoFactorAuthStep]); + }, [calculateCurrentStep]); const handleSetStep = useCallback( (step, animationDirection = CONST.ANIMATION_DIRECTION.IN) => { From be7a36826f4dd6d56b1ecc39b5c089a420338ccd Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 10 Oct 2023 13:36:37 +0200 Subject: [PATCH 06/20] use useIsFocused hook --- src/components/ScreenWrapper/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index 607ca4fef7da..be57ed44f4bc 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -3,7 +3,7 @@ import React, {useEffect, useRef, useState} from 'react'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {PickerAvoidingView} from 'react-native-picker-select'; -import {useNavigation} from '@react-navigation/native'; +import {useNavigation, useIsFocused} from '@react-navigation/native'; import FocusTrapView from '../FocusTrapView'; import KeyboardAvoidingView from '../KeyboardAvoidingView'; import CONST from '../../CONST'; @@ -44,6 +44,7 @@ function ScreenWrapper({ const {isDevelopment} = useEnvironment(); const {isOffline} = useNetwork(); const navigation = useNavigation(); + const isFocused = useIsFocused(); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const maxHeight = shouldEnableMaxHeight ? windowHeight : undefined; const isKeyboardShown = lodashGet(keyboardState, 'isKeyboardShown', false); @@ -140,7 +141,7 @@ function ScreenWrapper({ style={[styles.flex1, styles.noSelect]} enabled={!shouldDisableFocusTrap} shouldEnableAutoFocus={shouldEnableAutoFocus} - active={navigation.isFocused()} + active={isFocused} > {isDevelopment && } From 9fb1ff75e914e1316693b20e4af14a453eb63196 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:10:29 +0200 Subject: [PATCH 07/20] enable focus trap in the ConfirmModal --- src/components/ConfirmModal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ConfirmModal.js b/src/components/ConfirmModal.js index 705a05ec2058..491c6b834ce4 100755 --- a/src/components/ConfirmModal.js +++ b/src/components/ConfirmModal.js @@ -98,6 +98,7 @@ function ConfirmModal(props) { shouldSetModalVisibility={props.shouldSetModalVisibility} onModalHide={props.onModalHide} type={props.isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM} + shouldEnableFocusTrap > Date: Thu, 2 Nov 2023 10:21:27 +0100 Subject: [PATCH 08/20] prettier --- src/components/FocusTrapView/index.js | 4 ++-- src/components/ScreenWrapper/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/FocusTrapView/index.js b/src/components/FocusTrapView/index.js index 38d7ac5730f8..647f037af805 100644 --- a/src/components/FocusTrapView/index.js +++ b/src/components/FocusTrapView/index.js @@ -1,10 +1,10 @@ /* * 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 React, {useEffect, useRef} from 'react'; +import {View} from 'react-native'; const propTypes = { /** Children to wrap with FocusTrap */ diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index 11669cdf001e..889c898a6bf6 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -5,6 +5,7 @@ import {Keyboard, PanResponder, View} from 'react-native'; import {PickerAvoidingView} from 'react-native-picker-select'; import _ from 'underscore'; import CustomDevMenu from '@components/CustomDevMenu'; +import FocusTrapView from '@components/FocusTrapView'; import HeaderGap from '@components/HeaderGap'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; @@ -19,7 +20,6 @@ import * as Browser from '@libs/Browser'; import styles from '@styles/styles'; import toggleTestToolsModal from '@userActions/TestTool'; import CONST from '@src/CONST'; -import FocusTrapView from '../FocusTrapView'; import {defaultProps, propTypes} from './propTypes'; function ScreenWrapper({ From 18a0aea2d13dabaf2b94ec941c2376bc93cc3b51 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:48:35 +0100 Subject: [PATCH 09/20] refactor --- src/components/FocusTrapView/index.js | 14 +++++++------- src/components/Modal/index.web.js | 8 +------- src/components/ScreenWrapper/index.js | 4 ++-- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/components/FocusTrapView/index.js b/src/components/FocusTrapView/index.js index 647f037af805..384f531aa6ed 100644 --- a/src/components/FocusTrapView/index.js +++ b/src/components/FocusTrapView/index.js @@ -11,7 +11,7 @@ const propTypes = { children: PropTypes.node.isRequired, /** Whether to enable the FocusTrap */ - enabled: PropTypes.bool, + isEnabled: PropTypes.bool, /** * Whether to disable auto focus @@ -20,16 +20,16 @@ const propTypes = { shouldEnableAutoFocus: PropTypes.bool, /** Whether the FocusTrap is active */ - active: PropTypes.bool, + isActive: PropTypes.bool, }; const defaultProps = { - enabled: true, + isEnabled: true, shouldEnableAutoFocus: false, - active: false, + isActive: false, }; -function FocusTrapView({enabled = true, active = true, shouldEnableAutoFocus, ...props}) { +function FocusTrapView({isEnabled = true, isActive = true, shouldEnableAutoFocus, ...props}) { /** * Focus trap always needs a focusable element. * In case that we don't have any focusable elements in the modal, @@ -49,9 +49,9 @@ function FocusTrapView({enabled = true, active = true, shouldEnableAutoFocus, .. ref.current.setAttribute('tabindex', '0'); }, []); - return enabled ? ( + return isEnabled ? ( shouldEnableAutoFocus && ref.current, fallbackFocus: () => ref.current, diff --git a/src/components/Modal/index.web.js b/src/components/Modal/index.web.js index 1383d5488964..1344bc994ff4 100644 --- a/src/components/Modal/index.web.js +++ b/src/components/Modal/index.web.js @@ -43,13 +43,7 @@ function Modal(props) { onModalShow={showModal} avoidKeyboard={false} > - - {props.children} - + {props.children} ); } diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index 889c898a6bf6..4df3eba05551 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -143,9 +143,9 @@ function ScreenWrapper({ > {isDevelopment && } From 68905314d4b1d547d1071908510748e6c6c1684e Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:56:11 +0100 Subject: [PATCH 10/20] refactor TwoFactorAuthSteps --- src/components/FocusTrapView/index.js | 7 +++++-- .../Security/TwoFactorAuth/TwoFactorAuthSteps.js | 12 ++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/components/FocusTrapView/index.js b/src/components/FocusTrapView/index.js index 384f531aa6ed..72efb396cf20 100644 --- a/src/components/FocusTrapView/index.js +++ b/src/components/FocusTrapView/index.js @@ -10,7 +10,10 @@ const propTypes = { /** Children to wrap with FocusTrap */ children: PropTypes.node.isRequired, - /** Whether to enable the FocusTrap */ + /** + * Whether to enable the FocusTrap. + * If the FocusTrap is disabled, we just pass the children through. + */ isEnabled: PropTypes.bool, /** @@ -19,7 +22,7 @@ const propTypes = { */ shouldEnableAutoFocus: PropTypes.bool, - /** Whether the FocusTrap is active */ + /** Whether the FocusTrap is active (listening for events) */ isActive: PropTypes.bool, }; diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js index 116d44e9e334..9a9e42f75576 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.js @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import useAnimatedStepContext from '@components/AnimatedStep/useAnimatedStepContext'; import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; @@ -13,28 +13,20 @@ import TwoFactorAuthContext from './TwoFactorAuthContext'; import {defaultAccount, TwoFactorAuthPropTypes} from './TwoFactorAuthPropTypes'; function TwoFactorAuthSteps({account = defaultAccount}) { - const calculateCurrentStep = useMemo(() => { + const currentStep = useMemo(() => { if (account.twoFactorAuthStep) { return account.twoFactorAuthStep; } return account.requiresTwoFactorAuth ? CONST.TWO_FACTOR_AUTH_STEPS.ENABLED : CONST.TWO_FACTOR_AUTH_STEPS.CODES; }, [account.requiresTwoFactorAuth, account.twoFactorAuthStep]); - const [currentStep, setCurrentStep] = useState(calculateCurrentStep); - const {setAnimationDirection} = useAnimatedStepContext(); useEffect(() => () => TwoFactorAuthActions.clearTwoFactorAuthData(), []); - - useEffect(() => { - setCurrentStep(calculateCurrentStep); - }, [calculateCurrentStep]); - const handleSetStep = useCallback( (step, animationDirection = CONST.ANIMATION_DIRECTION.IN) => { setAnimationDirection(animationDirection); TwoFactorAuthActions.setTwoFactorAuthStep(step); - setCurrentStep(step); }, [setAnimationDirection], ); From f0ec2ca0e945e81f6592f02ce905038c96280ee9 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:01:00 +0100 Subject: [PATCH 11/20] fix confirmation modal issue --- src/components/Modal/index.web.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Modal/index.web.js b/src/components/Modal/index.web.js index 1344bc994ff4..1383d5488964 100644 --- a/src/components/Modal/index.web.js +++ b/src/components/Modal/index.web.js @@ -43,7 +43,13 @@ function Modal(props) { onModalShow={showModal} avoidKeyboard={false} > - {props.children} + + {props.children} + ); } From 3f51f98720cbbc2abf91189be36a0659910f593c Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:07:03 +0100 Subject: [PATCH 12/20] fix --- src/components/Modal/index.web.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Modal/index.web.js b/src/components/Modal/index.web.js index 1383d5488964..0627cb4d5c0b 100644 --- a/src/components/Modal/index.web.js +++ b/src/components/Modal/index.web.js @@ -44,9 +44,9 @@ function Modal(props) { avoidKeyboard={false} > {props.children} From 49c8699f433ecbad212a20dd65b43fe5d809cd8e Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:19:53 +0100 Subject: [PATCH 13/20] use tabIndex property --- src/components/FocusTrapView/index.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/components/FocusTrapView/index.js b/src/components/FocusTrapView/index.js index 72efb396cf20..361fb60f3f9e 100644 --- a/src/components/FocusTrapView/index.js +++ b/src/components/FocusTrapView/index.js @@ -3,7 +3,7 @@ */ import FocusTrap from 'focus-trap-react'; import {PropTypes} from 'prop-types'; -import React, {useEffect, useRef} from 'react'; +import React, {useRef} from 'react'; import {View} from 'react-native'; const propTypes = { @@ -40,18 +40,6 @@ function FocusTrapView({isEnabled = true, isActive = true, shouldEnableAutoFocus */ 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 isEnabled ? ( From 7135f065a1254b0f98a9788e45767f980ad2c91b Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 9 Nov 2023 12:15:11 +0100 Subject: [PATCH 14/20] prettier --- src/components/ScreenWrapper/index.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index 386fb01911f5..01c28b3b8463 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -40,8 +40,8 @@ const ScreenWrapper = React.forwardRef( shouldDismissKeyboardBeforeClose, onEntryTransitionEnd, testID, - shouldDisableFocusTrap, - shouldEnableAutoFocus, + shouldDisableFocusTrap, + shouldEnableAutoFocus, }, ref, ) => { @@ -52,7 +52,7 @@ const ScreenWrapper = React.forwardRef( const {isOffline} = useNetwork(); const navigation = useNavigation(); const isFocused = useIsFocused(); - const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); + const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const maxHeight = shouldEnableMaxHeight ? windowHeight : undefined; const minHeight = shouldEnableMinHeight ? initialHeight : undefined; const isKeyboardShown = lodashGet(keyboardState, 'isKeyboardShown', false); @@ -150,12 +150,12 @@ const ScreenWrapper = React.forwardRef( style={styles.flex1} enabled={shouldEnablePickerAvoiding} > - + {isDevelopment && } {isDevelopment && } @@ -171,7 +171,7 @@ const ScreenWrapper = React.forwardRef( } {isSmallScreenWidth && shouldShowOfflineIndicator && } - + From b78323a56e11785839eff737e7667dade6283df0 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:36:52 +0100 Subject: [PATCH 15/20] Refactor FocusTrapView component --- src/components/FocusTrapView/index.js | 68 ------------------- .../{index.native.js => index.native.tsx} | 4 +- src/components/FocusTrapView/index.tsx | 40 +++++++++++ src/components/FocusTrapView/types.ts | 23 +++++++ 4 files changed, 66 insertions(+), 69 deletions(-) delete mode 100644 src/components/FocusTrapView/index.js rename src/components/FocusTrapView/{index.native.js => index.native.tsx} (61%) create mode 100644 src/components/FocusTrapView/index.tsx create mode 100644 src/components/FocusTrapView/types.ts diff --git a/src/components/FocusTrapView/index.js b/src/components/FocusTrapView/index.js deleted file mode 100644 index 361fb60f3f9e..000000000000 --- a/src/components/FocusTrapView/index.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * The FocusTrap is only used on web and desktop - */ -import FocusTrap from 'focus-trap-react'; -import {PropTypes} from 'prop-types'; -import React, {useRef} from 'react'; -import {View} from 'react-native'; - -const propTypes = { - /** Children to wrap with FocusTrap */ - children: PropTypes.node.isRequired, - - /** - * Whether to enable the FocusTrap. - * If the FocusTrap is disabled, we just pass the children through. - */ - isEnabled: 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, - - /** Whether the FocusTrap is active (listening for events) */ - isActive: PropTypes.bool, -}; - -const defaultProps = { - isEnabled: true, - shouldEnableAutoFocus: false, - isActive: false, -}; - -function FocusTrapView({isEnabled = true, isActive = true, shouldEnableAutoFocus, ...props}) { - /** - * 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); - - return isEnabled ? ( - 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.tsx similarity index 61% rename from src/components/FocusTrapView/index.native.js rename to src/components/FocusTrapView/index.native.tsx index 5720601f5a2b..e16e65ff3d1e 100644 --- a/src/components/FocusTrapView/index.native.js +++ b/src/components/FocusTrapView/index.native.tsx @@ -2,7 +2,9 @@ * The FocusTrap is only used on web and desktop */ -function FocusTrapView({children}) { +import FocusTrapViewProps from "./types"; + +function FocusTrapView({children}: FocusTrapViewProps) { return children; } diff --git a/src/components/FocusTrapView/index.tsx b/src/components/FocusTrapView/index.tsx new file mode 100644 index 000000000000..4e1a3c29643a --- /dev/null +++ b/src/components/FocusTrapView/index.tsx @@ -0,0 +1,40 @@ +/* + * The FocusTrap is only used on web and desktop + */ +import FocusTrap from 'focus-trap-react'; +import React, {useRef} from 'react'; +import {View} from 'react-native'; +import FocusTrapViewProps from './types'; + +function FocusTrapView({isEnabled = true, isActive = true, shouldEnableAutoFocus = false, ...props}: FocusTrapViewProps) { + /** + * 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(); + + return isEnabled ? ( + + } + tabIndex={0} + // eslint-disable-next-line react/jsx-props-no-spreading + {...props} + /> + + ) : ( + props.children + ); +} + +FocusTrapView.displayName = 'FocusTrapView'; + +export default FocusTrapView; diff --git a/src/components/FocusTrapView/types.ts b/src/components/FocusTrapView/types.ts new file mode 100644 index 000000000000..027da4052958 --- /dev/null +++ b/src/components/FocusTrapView/types.ts @@ -0,0 +1,23 @@ +import { ReactNode } from "react"; + +type FocusTrapViewProps = { + /** Children to wrap with FocusTrap */ + children: ReactNode, + + /** + * Whether to enable the FocusTrap. + * If the FocusTrap is disabled, we just pass the children through. + */ + isEnabled: boolean, + + /** + * Whether to disable auto focus + * It is used when the component inside the FocusTrap have their own auto focus logic + */ + shouldEnableAutoFocus: boolean, + + /** Whether the FocusTrap is active (listening for events) */ + isActive: boolean, +}; + +export default FocusTrapViewProps; \ No newline at end of file From d00081472eb41a77a8b4a85028d7ae26a440bada Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:02:26 +0100 Subject: [PATCH 16/20] fix types --- src/components/FocusTrapView/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/FocusTrapView/index.tsx b/src/components/FocusTrapView/index.tsx index 4e1a3c29643a..2697b1fe43c9 100644 --- a/src/components/FocusTrapView/index.tsx +++ b/src/components/FocusTrapView/index.tsx @@ -18,8 +18,9 @@ function FocusTrapView({isEnabled = true, isActive = true, shouldEnableAutoFocus (shouldEnableAutoFocus && ref.current) ?? false, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + fallbackFocus: () => ref.current!, clickOutsideDeactivates: true, }} > From 3a1cf2e27fee206855bf5f3f2aa8f0ca26a434f7 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:02:41 +0100 Subject: [PATCH 17/20] new ref type --- src/components/FocusTrapView/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/FocusTrapView/index.tsx b/src/components/FocusTrapView/index.tsx index 2697b1fe43c9..fb4350f1502b 100644 --- a/src/components/FocusTrapView/index.tsx +++ b/src/components/FocusTrapView/index.tsx @@ -6,13 +6,15 @@ import React, {useRef} from 'react'; import {View} from 'react-native'; import FocusTrapViewProps from './types'; +const viewRef = (ref: React.RefObject) => ref as React.RefObject; + function FocusTrapView({isEnabled = true, isActive = true, shouldEnableAutoFocus = false, ...props}: FocusTrapViewProps) { /** * 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(); + const ref = useRef(null); return isEnabled ? ( } + ref={viewRef(ref)} tabIndex={0} // eslint-disable-next-line react/jsx-props-no-spreading {...props} From 4a12485d6e51b4724baae298602bc21fccb1d471 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:49:16 +0100 Subject: [PATCH 18/20] fix attachment focus trap issue --- src/components/Modal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index d1039d45f092..710ecd79b375 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -9,7 +9,7 @@ import CONST from '@src/CONST'; import BaseModal from './BaseModal'; import BaseModalProps from './types'; -function Modal({fullscreen = true, onModalHide = () => {}, type, onModalShow = () => {}, children, shouldEnableFocusTrap, ...rest}: BaseModalProps) { +function Modal({fullscreen = true, onModalHide = () => {}, type, onModalShow = () => {}, children, shouldEnableFocusTrap = false, ...rest}: BaseModalProps) { const styles = useThemeStyles(); const theme = useTheme(); const [previousStatusBarColor, setPreviousStatusBarColor] = useState(); From 237685352526ad184b49d5f9e195c46a4ba4f384 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:19:52 +0100 Subject: [PATCH 19/20] address review --- src/components/FocusTrapView/index.tsx | 3 +-- src/components/FocusTrapView/types.ts | 7 ++----- src/types/utils/viewRef.ts | 5 +++++ 3 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 src/types/utils/viewRef.ts diff --git a/src/components/FocusTrapView/index.tsx b/src/components/FocusTrapView/index.tsx index fb4350f1502b..6b52512c2e63 100644 --- a/src/components/FocusTrapView/index.tsx +++ b/src/components/FocusTrapView/index.tsx @@ -4,10 +4,9 @@ import FocusTrap from 'focus-trap-react'; import React, {useRef} from 'react'; import {View} from 'react-native'; +import viewRef from '@src/types/utils/viewRef'; import FocusTrapViewProps from './types'; -const viewRef = (ref: React.RefObject) => ref as React.RefObject; - function FocusTrapView({isEnabled = true, isActive = true, shouldEnableAutoFocus = false, ...props}: FocusTrapViewProps) { /** * Focus trap always needs a focusable element. diff --git a/src/components/FocusTrapView/types.ts b/src/components/FocusTrapView/types.ts index b5fb0ede3e79..500b4b4315d9 100644 --- a/src/components/FocusTrapView/types.ts +++ b/src/components/FocusTrapView/types.ts @@ -1,10 +1,7 @@ -import {ReactNode} from 'react'; import {ViewProps} from 'react-native'; +import ChildrenProps from '@src/types/utils/ChildrenProps'; -type FocusTrapViewProps = { - /** Children to wrap with FocusTrap */ - children: ReactNode; - +type FocusTrapViewProps = ChildrenProps & { /** * Whether to enable the FocusTrap. * If the FocusTrap is disabled, we just pass the children through. diff --git a/src/types/utils/viewRef.ts b/src/types/utils/viewRef.ts new file mode 100644 index 000000000000..015d88cc5a8b --- /dev/null +++ b/src/types/utils/viewRef.ts @@ -0,0 +1,5 @@ +import {View} from 'react-native'; + +const viewRef = (ref: React.RefObject) => ref as React.RefObject; + +export default viewRef; From 25c51254906543bdb73bcef2c57922a627f63616 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:26:48 +0100 Subject: [PATCH 20/20] fix split details and details --- src/pages/ReportDetailsPage.js | 5 ++++- src/pages/iou/SplitBillDetailsPage.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js index f3f55dee3253..df9aea868d87 100644 --- a/src/pages/ReportDetailsPage.js +++ b/src/pages/ReportDetailsPage.js @@ -176,7 +176,10 @@ function ReportDetailsPage(props) { ) : null; return ( - + +