Skip to content

Commit c1fe251

Browse files
Merge pull request Expensify#67045 from thelullabyy/fix/66693/blue-border
Workspace - Usd currency changed displays blue border.
2 parents acd0021 + 90f8b0d commit c1fe251

4 files changed

Lines changed: 83 additions & 25 deletions

File tree

src/components/Modal/BaseModal.tsx

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,77 @@ import ModalContext from './ModalContext';
3131
import ReanimatedModal from './ReanimatedModal';
3232
import type ReanimatedModalProps from './ReanimatedModal/types';
3333
import type BaseModalProps from './types';
34+
import type {FocusTrapOptions} from './types';
3435

3536
const REANIMATED_MODAL_TYPES: Array<ValueOf<typeof CONST.MODAL.MODAL_TYPE>> = [CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED, CONST.MODAL.MODAL_TYPE.FULLSCREEN];
3637

3738
type ModalComponentProps = (ReactNativeModalProps | ReanimatedModalProps) & {
3839
type?: ValueOf<typeof CONST.MODAL.MODAL_TYPE>;
3940
shouldUseReanimatedModal?: boolean;
41+
shouldPreventScrollOnFocus?: boolean;
42+
initialFocus?: FocusTrapOptions['initialFocus'];
43+
isVisible: boolean;
44+
isKeyboardActive: boolean;
45+
saveFocusState: () => void;
4046
};
4147

42-
function ModalComponent({type, shouldUseReanimatedModal, ...props}: ModalComponentProps) {
48+
function ModalComponent({
49+
type,
50+
shouldUseReanimatedModal,
51+
isVisible,
52+
shouldPreventScrollOnFocus,
53+
initialFocus,
54+
children,
55+
saveFocusState,
56+
onDismiss = () => {},
57+
isKeyboardActive,
58+
...props
59+
}: ModalComponentProps) {
4360
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
4461
if ((type && REANIMATED_MODAL_TYPES.includes(type)) || shouldUseReanimatedModal) {
4562
return (
4663
<ReanimatedModal
4764
// eslint-disable-next-line react/jsx-props-no-spreading
4865
{...(props as ReanimatedModalProps)}
4966
type={type}
50-
/>
67+
isVisible={isVisible}
68+
shouldPreventScrollOnFocus={shouldPreventScrollOnFocus}
69+
initialFocus={initialFocus}
70+
onDismiss={onDismiss}
71+
>
72+
<ModalContent
73+
onModalWillShow={saveFocusState}
74+
onDismiss={onDismiss}
75+
>
76+
{children}
77+
</ModalContent>
78+
{!isKeyboardActive && <NavigationBar />}
79+
</ReanimatedModal>
5180
);
5281
}
5382

54-
// eslint-disable-next-line react/jsx-props-no-spreading
55-
return <ReactNativeModal {...(props as ReactNativeModalProps)} />;
83+
return (
84+
<ReactNativeModal
85+
// eslint-disable-next-line react/jsx-props-no-spreading
86+
{...(props as ReactNativeModalProps)}
87+
isVisible={isVisible}
88+
onDismiss={onDismiss}
89+
>
90+
<ModalContent
91+
onModalWillShow={saveFocusState}
92+
onDismiss={onDismiss}
93+
>
94+
<FocusTrapForModal
95+
active={isVisible}
96+
initialFocus={initialFocus}
97+
shouldPreventScroll={shouldPreventScrollOnFocus}
98+
>
99+
{children}
100+
</FocusTrapForModal>
101+
</ModalContent>
102+
{!isKeyboardActive && <NavigationBar />}
103+
</ReactNativeModal>
104+
);
56105
}
57106

58107
function BaseModal(
@@ -379,6 +428,8 @@ function BaseModal(
379428
onDismiss={handleDismissModal}
380429
onSwipeComplete={onClose}
381430
swipeDirection={swipeDirection}
431+
shouldPreventScrollOnFocus={shouldPreventScrollOnFocus}
432+
initialFocus={initialFocus}
382433
swipeThreshold={swipeThreshold}
383434
isVisible={isVisible}
384435
backdropColor={theme.overlay}
@@ -403,26 +454,16 @@ function BaseModal(
403454
customBackdrop={shouldUseCustomBackdrop ? <Overlay onPress={handleBackdropPress} /> : undefined}
404455
type={type}
405456
shouldUseReanimatedModal={shouldUseReanimatedModal}
457+
isKeyboardActive={keyboardStateContextValue?.isKeyboardActive}
458+
saveFocusState={saveFocusState}
406459
>
407-
<ModalContent
408-
onModalWillShow={saveFocusState}
409-
onDismiss={handleDismissModal}
460+
<Animated.View
461+
onLayout={onViewLayout}
462+
style={[styles.defaultModalContainer, modalContainerStyle, modalPaddingStyles, !isVisible && styles.pointerEventsNone, sidePanelReanimatedStyle]}
463+
ref={ref}
410464
>
411-
<FocusTrapForModal
412-
active={isVisible}
413-
initialFocus={initialFocus}
414-
shouldPreventScroll={shouldPreventScrollOnFocus}
415-
>
416-
<Animated.View
417-
onLayout={onViewLayout}
418-
style={[styles.defaultModalContainer, modalContainerStyle, modalPaddingStyles, !isVisible && styles.pointerEventsNone, sidePanelReanimatedStyle]}
419-
ref={ref}
420-
>
421-
<ColorSchemeWrapper>{children}</ColorSchemeWrapper>
422-
</Animated.View>
423-
</FocusTrapForModal>
424-
</ModalContent>
425-
{!keyboardStateContextValue?.isKeyboardActive && <NavigationBar />}
465+
<ColorSchemeWrapper>{children}</ColorSchemeWrapper>
466+
</Animated.View>
426467
</ModalComponent>
427468
</View>
428469
</ScreenWrapperOfflineIndicatorContext.Provider>

src/components/Modal/ReanimatedModal/index.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
33
import type {NativeEventSubscription, ViewStyle} from 'react-native';
44
import {BackHandler, Dimensions, InteractionManager, Modal, View} from 'react-native';
55
import {LayoutAnimationConfig} from 'react-native-reanimated';
6+
import FocusTrapForModal from '@components/FocusTrap/FocusTrapForModal';
67
import KeyboardAvoidingView from '@components/KeyboardAvoidingView';
78
import useThemeStyles from '@hooks/useThemeStyles';
89
import getPlatform from '@libs/getPlatform';
@@ -41,6 +42,8 @@ function ReanimatedModal({
4142
onSwipeComplete,
4243
swipeDirection,
4344
swipeThreshold,
45+
shouldPreventScrollOnFocus,
46+
initialFocus,
4447
...props
4548
}: ReanimatedModalProps) {
4649
const [isVisibleState, setIsVisibleState] = useState(isVisible);
@@ -184,13 +187,14 @@ function ReanimatedModal({
184187
);
185188
}
186189
const isBackdropMounted = isVisibleState || ((isTransitioning || isContainerOpen !== isVisibleState) && getPlatform() === CONST.PLATFORM.WEB);
190+
const modalVisibility = isVisibleState || isTransitioning || isContainerOpen !== isVisibleState;
187191
return (
188192
<LayoutAnimationConfig skipExiting={getPlatform() !== CONST.PLATFORM.WEB}>
189193
<Modal
190194
transparent
191195
animationType="none"
192196
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
193-
visible={isVisibleState || isTransitioning || isContainerOpen !== isVisibleState}
197+
visible={modalVisibility}
194198
onRequestClose={onBackButtonPress}
195199
statusBarTranslucent={statusBarTranslucent}
196200
testID={testID}
@@ -213,7 +217,13 @@ function ReanimatedModal({
213217
{isVisibleState && containerView}
214218
</KeyboardAvoidingView>
215219
) : (
216-
isVisibleState && containerView
220+
<FocusTrapForModal
221+
active={modalVisibility}
222+
initialFocus={initialFocus}
223+
shouldPreventScroll={shouldPreventScrollOnFocus}
224+
>
225+
{isVisibleState && containerView}
226+
</FocusTrapForModal>
217227
)}
218228
</Modal>
219229
</LayoutAnimationConfig>

src/components/Modal/ReanimatedModal/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {NativeSyntheticEvent, StyleProp, ViewProps, ViewStyle} from 'react-
33
import type {ModalProps as ReactNativeModalProps} from 'react-native-modal';
44
import type {SharedValue} from 'react-native-reanimated';
55
import type {ValueOf} from 'type-fest';
6+
import type {FocusTrapOptions} from '@components/Modal/types';
67
import type CONST from '@src/CONST';
78

89
type GestureProps = {
@@ -136,6 +137,12 @@ type ReanimatedModalProps = ViewProps &
136137

137138
/** Modal type */
138139
type?: ValueOf<typeof CONST.MODAL.MODAL_TYPE>;
140+
141+
/** Whether to prevent scroll on focus */
142+
shouldPreventScrollOnFocus?: boolean;
143+
144+
/** Whether to use a custom backdrop for the modal? (This prevents focus issues on desktop) */
145+
initialFocus?: FocusTrapOptions['initialFocus'];
139146
};
140147

141148
type BackdropProps = {

src/components/Modal/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,4 @@ type BaseModalProps = Partial<ReactNativeModalProps> &
136136
};
137137

138138
export default BaseModalProps;
139-
export type {PopoverAnchorPosition, WindowState};
139+
export type {PopoverAnchorPosition, FocusTrapOptions, WindowState};

0 commit comments

Comments
 (0)