Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 57 additions & 4 deletions src/components/Modal/BaseModal.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import type {LayoutChangeEvent} from 'react-native';
import type {GestureResponderEvent, LayoutChangeEvent} from 'react-native';
// Animated required for side panel navigation
// eslint-disable-next-line no-restricted-imports
import {Animated, DeviceEventEmitter, View} from 'react-native';
import ColorSchemeWrapper from '@components/ColorSchemeWrapper';
import NavigationBar from '@components/NavigationBar';
import {PressableWithoutFeedback} from '@components/Pressable';
import ScreenWrapperOfflineIndicatorContext from '@components/ScreenWrapper/ScreenWrapperOfflineIndicatorContext';
import useKeyboardState from '@hooks/useKeyboardState';
import useLocalize from '@hooks/useLocalize';
import usePrevious from '@hooks/usePrevious';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
Expand All @@ -15,8 +17,10 @@ import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Accessibility from '@libs/Accessibility';
import ComposerFocusManager from '@libs/ComposerFocusManager';
import {canUseTouchScreen as canUseTouchScreenCheck} from '@libs/DeviceCapabilities';
import getPlatform from '@libs/getPlatform';
import NarrowPaneContext from '@libs/Navigation/AppNavigator/Navigators/NarrowPaneContext';
import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay';
import Navigation from '@libs/Navigation/Navigation';
Expand Down Expand Up @@ -75,7 +79,10 @@ function BaseModal({
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const isScreenReaderEnabled = Accessibility.useScreenReaderStatus();
const {windowWidth, windowHeight} = useWindowDimensions();
const isWeb = getPlatform() === CONST.PLATFORM.WEB;
// We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to apply correct modal width
const canUseTouchScreen = canUseTouchScreenCheck();
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
Expand All @@ -92,6 +99,7 @@ function BaseModal({

const shouldCallHideModalOnUnmount = useRef(false);
const hideModalCallbackRef = useRef<(callHideCallback: boolean) => void>(undefined);
const dismissButtonRef = useRef<HTMLDivElement | View | null>(null);

const wasVisible = usePrevious(isVisible);

Expand Down Expand Up @@ -176,8 +184,8 @@ function BaseModal({
onModalShow();
}, [onModalShow, shouldSetModalVisibility, type]);

const handleBackdropPress = (e?: KeyboardEvent) => {
if (e?.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) {
const handleBackdropPress = (e?: KeyboardEvent | GestureResponderEvent) => {
if (e && 'key' in e && e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) {
return;
}

Expand Down Expand Up @@ -261,6 +269,38 @@ function BaseModal({
],
);

const shouldShowBottomDockedDismissButton = isSmallScreenWidth && type === CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED && !!(onBackdropPress ?? onClose) && isScreenReaderEnabled;

const initialFocusTarget = useMemo(() => {
if (!isWeb || !shouldShowBottomDockedDismissButton) {
return initialFocus;
}
return () => dismissButtonRef.current ?? document.body;
}, [initialFocus, isWeb, shouldShowBottomDockedDismissButton]);

useEffect(() => {
if (!isWeb || !isVisible || !shouldShowBottomDockedDismissButton) {
return;
}

let retries = 0;
const focusDismissButton = () => {
const target = dismissButtonRef.current;
if (target && 'focus' in target && typeof target.focus === 'function') {
target.focus();
return;
}

if (retries >= 5) {
return;
}
retries++;
requestAnimationFrame(focusDismissButton);
};

requestAnimationFrame(focusDismissButton);
}, [isWeb, isVisible, shouldShowBottomDockedDismissButton]);

const modalPaddingStyles = useMemo(() => {
const paddings = StyleUtils.getModalPaddingStyles({
shouldAddBottomSafeAreaMargin,
Expand Down Expand Up @@ -343,7 +383,7 @@ function BaseModal({
onSwipeComplete={onClose}
swipeDirection={swipeDirection}
shouldPreventScrollOnFocus={shouldPreventScrollOnFocus}
initialFocus={initialFocus}
initialFocus={initialFocusTarget}
swipeThreshold={swipeThreshold}
isVisible={isVisible}
backdropColor={theme.overlay}
Expand Down Expand Up @@ -375,6 +415,19 @@ function BaseModal({
ref={ref}
fsClass={forwardedFSClass}
>
{shouldShowBottomDockedDismissButton && (
<PressableWithoutFeedback
ref={dismissButtonRef}
onPress={handleBackdropPress}
accessibilityRole={CONST.ROLE.BUTTON}
accessibilityLabel={translate('modal.dismissDialog')}
sentryLabel="Modal-DismissDialog"
style={styles.bottomDockedModalDismissButton}
shouldUseAutoHitSlop
>
<View />
</PressableWithoutFeedback>
)}
<ColorSchemeWrapper>{children}</ColorSchemeWrapper>
</Animated.View>
{!keyboardStateContextValue?.isKeyboardActive && <NavigationBar />}
Expand Down
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1666,6 +1666,7 @@ const translations: TranslationDeepObject<typeof en> = {
},
modal: {
backdropLabel: 'Modal-Hintergrund',
dismissDialog: 'Dialog schließen',
},
nextStep: {
message: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1681,6 +1681,7 @@ const translations = {
},
modal: {
backdropLabel: 'Modal Backdrop',
dismissDialog: 'Dismiss dialog',
},
nextStep: {
message: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,7 @@ const translations: TranslationDeepObject<typeof en> = {
},
modal: {
backdropLabel: 'Fondo del Modal',
dismissDialog: 'Cerrar diálogo',
},
nextStep: {
message: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,7 @@ const translations: TranslationDeepObject<typeof en> = {
},
modal: {
backdropLabel: 'Arrière-plan de la fenêtre modale',
dismissDialog: 'Fermer la boîte de dialogue',
},
nextStep: {
message: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1663,6 +1663,7 @@ const translations: TranslationDeepObject<typeof en> = {
},
modal: {
backdropLabel: 'Sfondo modale',
dismissDialog: 'Chiudi finestra di dialogo',
},
nextStep: {
message: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1658,6 +1658,7 @@ const translations: TranslationDeepObject<typeof en> = {
},
modal: {
backdropLabel: 'モーダルの背景',
dismissDialog: 'ダイアログを閉じる',
},
nextStep: {
message: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1663,6 +1663,7 @@ const translations: TranslationDeepObject<typeof en> = {
},
modal: {
backdropLabel: 'Modale achtergrond',
dismissDialog: 'Dialoog sluiten',
},
nextStep: {
message: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,7 @@ const translations: TranslationDeepObject<typeof en> = {
},
modal: {
backdropLabel: 'Tło modalu',
dismissDialog: 'Zamknij okno dialogowe',
},
nextStep: {
message: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1660,6 +1660,7 @@ const translations: TranslationDeepObject<typeof en> = {
},
modal: {
backdropLabel: 'Pano de fundo do modal',
dismissDialog: 'Fechar diálogo',
},
nextStep: {
message: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1634,6 +1634,7 @@ const translations: TranslationDeepObject<typeof en> = {
},
modal: {
backdropLabel: '模态背景',
dismissDialog: '关闭对话框',
},
nextStep: {
message: {
Expand Down
22 changes: 21 additions & 1 deletion src/libs/Accessibility/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,29 @@ type HitSlop = {x: number; y: number};
const useScreenReaderStatus = (): boolean => {
const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false);
useEffect(() => {
const subscription = AccessibilityInfo.addEventListener('screenReaderChanged', setIsScreenReaderEnabled);
let isMounted = true;
const isScreenReaderEnabledAsync = AccessibilityInfo.isScreenReaderEnabled;
if (isScreenReaderEnabledAsync) {
isScreenReaderEnabledAsync()
.then((enabled) => {
if (!isMounted) {
return;
}

setIsScreenReaderEnabled(enabled);
})
.catch(() => {});
}
const subscription = AccessibilityInfo.addEventListener('screenReaderChanged', (enabled) => {
if (!isMounted) {
return;
}

setIsScreenReaderEnabled(enabled);
});

return () => {
isMounted = false;
subscription?.remove();
};
}, []);
Expand Down
10 changes: 10 additions & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3575,6 +3575,16 @@ const staticStyles = (theme: ThemeColors) =>
backgroundColor: theme.overlay,
},

bottomDockedModalDismissButton: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: variables.iconSizeXSmall,
backgroundColor: 'transparent',
zIndex: 1,
},

invisibleOverlay: {
backgroundColor: theme.transparent,
zIndex: 1000,
Expand Down
Loading