Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c4242cc
Improve useSidePane logic
blazejkustra Mar 26, 2025
28481ae
Remove focus trap on large screen
blazejkustra Mar 26, 2025
6fa8374
Auto focus inputs when side pane is closed
blazejkustra Mar 26, 2025
ea330ed
Remove unnecessary blur on composer
blazejkustra Mar 26, 2025
bb293cf
Refactor and create useSidePaneDisplayStatus
blazejkustra Mar 26, 2025
3ab8c60
Rename isPaneHidden to shouldHideSidePane
blazejkustra Mar 26, 2025
5fbdeea
Refactor HelpButton to use useSidePaneDisplayStatus
blazejkustra Mar 26, 2025
fc71e35
Prevent composer focus when side pane is visible
blazejkustra Mar 26, 2025
42b7eaf
Fix logic
blazejkustra Mar 26, 2025
8a81b7d
Fix focusing on large screen
blazejkustra Mar 26, 2025
f2e3379
Merge branch 'main' of github.com:Expensify/App into side-pane-fix/au…
blazejkustra Mar 26, 2025
5378b85
Refactor useThreeDotsAnchorPosition to use updated side pane display …
blazejkustra Mar 26, 2025
f30d898
Fix 3-dot menu position on attachment view
blazejkustra Mar 26, 2025
0d02f6c
Merge branch 'main' into side-pane-fix/auto-focus
blazejkustra Mar 27, 2025
d98fec3
Fix isModalCenteredVisible logic
blazejkustra Mar 27, 2025
067252b
Fix prettier
blazejkustra Mar 27, 2025
22f7166
Fix logic for tooltips
blazejkustra Mar 28, 2025
d94e63c
Merge branch 'main' of github.com:Expensify/App into side-pane-fix/au…
blazejkustra Mar 28, 2025
4bccfa2
Refactor side pane tooltip visibility, hide it only on animation on w…
blazejkustra Mar 28, 2025
db0ce4d
Fix lint
blazejkustra Mar 28, 2025
6f7aec9
Remove unnecessary dependencies from side pane animation effect
blazejkustra Mar 28, 2025
d3e4116
Merge branch 'main' of github.com:Expensify/App into side-pane-fix/au…
blazejkustra Mar 28, 2025
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
5 changes: 5 additions & 0 deletions src/components/AttachmentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,11 @@ function AttachmentModal({
onCloseButtonPress={closeModal}
shouldShowThreeDotsButton={shouldShowThreeDotsButton}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)}
threeDotsAnchorAlignment={{
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
}}
shouldSetModalVisibility={false}
threeDotsMenuItems={threeDotsMenuItems}
shouldOverlayDots
subTitleLink={currentAttachmentLink ?? ''}
Expand Down
14 changes: 2 additions & 12 deletions src/components/SidePane/HelpComponents/HelpButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ import * as Expensicons from '@components/Icon/Expensicons';
import {PressableWithoutFeedback} from '@components/Pressable';
import Tooltip from '@components/Tooltip';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSidePane from '@hooks/useSidePane';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {triggerSidePane} from '@libs/actions/SidePane';
import KeyboardUtils from '@src/utils/keyboard';

type HelpButtonProps = {
style?: StyleProp<ViewStyle>;
Expand All @@ -20,8 +17,7 @@ function HelpButton({style}: HelpButtonProps) {
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
const {isExtraLargeScreenWidth} = useResponsiveLayout();
const {sidePane, shouldHideHelpButton} = useSidePane();
const {openSidePane, shouldHideHelpButton} = useSidePane();

if (shouldHideHelpButton) {
return null;
Expand All @@ -32,13 +28,7 @@ function HelpButton({style}: HelpButtonProps) {
<PressableWithoutFeedback
accessibilityLabel={translate('common.help')}
style={[styles.flexRow, styles.touchableButtonImage, style]}
onPress={() => {
KeyboardUtils.dismiss();
triggerSidePane({
isOpen: isExtraLargeScreenWidth ? !sidePane?.open : !sidePane?.openNarrowScreen,
isOpenNarrowScreen: isExtraLargeScreenWidth ? undefined : !sidePane?.openNarrowScreen,
});
}}
onPress={openSidePane}
>
<Icon
src={Expensicons.QuestionMark}
Expand Down
4 changes: 2 additions & 2 deletions src/components/SidePane/HelpModal/index.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import HelpContent from '@components/SidePane/HelpComponents/HelpContent';
import CONST from '@src/CONST';
import type HelpProps from './types';

function Help({isPaneHidden, closeSidePane}: HelpProps) {
function Help({shouldHideSidePane, closeSidePane}: HelpProps) {
// SidePane isn't a native screen, this handles the back button press on Android
useFocusEffect(
useCallback(() => {
Expand All @@ -23,7 +23,7 @@ function Help({isPaneHidden, closeSidePane}: HelpProps) {
return (
<Modal
onClose={() => closeSidePane()}
isVisible={!isPaneHidden}
isVisible={!shouldHideSidePane}
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
shouldHandleNavigationBack
>
Expand Down
4 changes: 2 additions & 2 deletions src/components/SidePane/HelpModal/index.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import HelpContent from '@components/SidePane/HelpComponents/HelpContent';
import CONST from '@src/CONST';
import type HelpProps from './types';

function Help({isPaneHidden, closeSidePane}: HelpProps) {
function Help({shouldHideSidePane, closeSidePane}: HelpProps) {
return (
<Modal
onClose={() => closeSidePane()}
isVisible={!isPaneHidden}
isVisible={!shouldHideSidePane}
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
shouldHandleNavigationBack
propagateSwipe
Expand Down
2 changes: 1 addition & 1 deletion src/components/SidePane/HelpModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function Help({sidePaneTranslateX, closeSidePane, shouldHideSidePaneBackdrop}: H

return (
<ModalPortal>
<FocusTrapForModal active>
<FocusTrapForModal active={!isExtraLargeScreenWidth}>
<View style={styles.sidePaneContainer}>
<View>
{!shouldHideSidePaneBackdrop && (
Expand Down
2 changes: 1 addition & 1 deletion src/components/SidePane/HelpModal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {MutableRefObject} from 'react';
import type {Animated} from 'react-native';

type HelpProps = {
isPaneHidden: boolean;
shouldHideSidePane: boolean;
sidePaneTranslateX: MutableRefObject<Animated.Value>;
shouldHideSidePaneBackdrop: boolean;
closeSidePane: (shouldUpdateNarrow?: boolean) => void;
Expand Down
6 changes: 3 additions & 3 deletions src/components/SidePane/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import useSidePane from '@hooks/useSidePane';
import Help from './HelpModal';

function SidePane() {
const {shouldHideSidePane, isPaneHidden, sidePaneTranslateX, shouldHideSidePaneBackdrop, closeSidePane} = useSidePane();
const {isSidePaneTransitionEnded, shouldHideSidePane, sidePaneTranslateX, shouldHideSidePaneBackdrop, closeSidePane} = useSidePane();

if (shouldHideSidePane) {
if (isSidePaneTransitionEnded && shouldHideSidePane) {
return null;
}

return (
<Help
isPaneHidden={isPaneHidden}
shouldHideSidePane={shouldHideSidePane}
sidePaneTranslateX={sidePaneTranslateX}
closeSidePane={closeSidePane}
shouldHideSidePaneBackdrop={shouldHideSidePaneBackdrop}
Expand Down
11 changes: 11 additions & 0 deletions src/hooks/useAutoFocusInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {InteractionManager} from 'react-native';
import {moveSelectionToEnd, scrollToBottom} from '@libs/InputUtils';
import CONST from '@src/CONST';
import {useSplashScreenStateContext} from '@src/SplashScreenStateContext';
import useSidePane from './useSidePane';

type UseAutoFocusInput = {
inputCallbackRef: (ref: TextInput | null) => void;
Expand Down Expand Up @@ -53,6 +54,16 @@ export default function useAutoFocusInput(isMultiline = false): UseAutoFocusInpu
}, []),
);

// Trigger focus when side pane transition ends
const {isSidePaneTransitionEnded, shouldHideSidePane} = useSidePane();
useEffect(() => {
if (!shouldHideSidePane) {
return;
}

setIsScreenTransitionEnded(isSidePaneTransitionEnded);
}, [isSidePaneTransitionEnded, shouldHideSidePane]);

const inputCallbackRef = (ref: TextInput | null) => {
inputRef.current = ref;
if (isInputInitialized) {
Expand Down
93 changes: 54 additions & 39 deletions src/hooks/useSidePane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,70 @@ import {useCallback, useEffect, useRef, useState} from 'react';
// Import Animated directly from 'react-native' as animations are used with navigation.
// eslint-disable-next-line no-restricted-imports
import {Animated} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import {triggerSidePane} from '@libs/actions/SidePane';
import focusComposerWithDelay from '@libs/focusComposerWithDelay';
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type * as OnyxTypes from '@src/types/onyx';
import KeyboardUtils from '@src/utils/keyboard';
import useResponsiveLayout from './useResponsiveLayout';
import useWindowDimensions from './useWindowDimensions';

function isSidePaneHidden(sidePane: OnyxEntry<OnyxTypes.SidePane>, isExtraLargeScreenWidth: boolean) {
if (!isExtraLargeScreenWidth && !sidePane?.openNarrowScreen) {
return true;
}

return isExtraLargeScreenWidth && !sidePane?.open;
}

/**
* Hook to get the animated position of the side pane and the margin of the navigator
* Hook to get the display status of the side pane
*/
function useSidePane() {
function useSidePaneDisplayStatus() {
const {isExtraLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout();
const {windowWidth} = useWindowDimensions();

const [sidePaneNVP] = useOnyx(ONYXKEYS.NVP_SIDE_PANE);
const [language] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE);
const [isModalCenteredVisible = false] = useOnyx(ONYXKEYS.MODAL, {
selector: (modal) =>
modal?.type === CONST.MODAL.MODAL_TYPE.CENTERED_SWIPABLE_TO_RIGHT ||
modal?.type === CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE ||
modal?.type === CONST.MODAL.MODAL_TYPE.CENTERED_SMALL,
modal?.type === CONST.MODAL.MODAL_TYPE.CENTERED_SMALL ||
modal?.type === CONST.MODAL.MODAL_TYPE.CENTERED,
});
const isLanguageUnsupported = language !== CONST.LOCALES.EN;
const isPaneHidden = isSidePaneHidden(sidePaneNVP, isExtraLargeScreenWidth) || isLanguageUnsupported || isModalCenteredVisible;

const sidePaneWidth = shouldUseNarrowLayout ? windowWidth : variables.sideBarWidth;
const shouldApplySidePaneOffset = isExtraLargeScreenWidth && !isPaneHidden;

const [shouldHideSidePane, setShouldHideSidePane] = useState(true);
const [isAnimatingExtraLargeScree, setIsAnimatingExtraLargeScreen] = useState(false);
const isLanguageUnsupported = language !== CONST.LOCALES.EN;
const isSidePaneVisible = isExtraLargeScreenWidth ? sidePaneNVP?.open : sidePaneNVP?.openNarrowScreen;

const shouldHideSidePaneBackdrop = isPaneHidden || isExtraLargeScreenWidth || shouldUseNarrowLayout;
const shouldHideToolTip = isExtraLargeScreenWidth ? isAnimatingExtraLargeScree : !shouldHideSidePane;
// The side pane is hidden when:
// - NVP is not set or it is false
// - language is unsupported
// - modal centered is visible
const shouldHideSidePane = !isSidePaneVisible || isLanguageUnsupported || isModalCenteredVisible;
const isSidePaneHiddenOrLargeScreen = !isSidePaneVisible || isLanguageUnsupported || isExtraLargeScreenWidth;

// The help button is hidden when:
// - side pane nvp is not set
// - side pane is displayed currently
// - language is unsupported
const shouldHideHelpButton = !sidePaneNVP || !isPaneHidden || isLanguageUnsupported;
const shouldHideHelpButton = !sidePaneNVP || !shouldHideSidePane || isLanguageUnsupported;
const shouldHideSidePaneBackdrop = shouldHideSidePane || isExtraLargeScreenWidth || shouldUseNarrowLayout;

return {shouldHideSidePane, isSidePaneHiddenOrLargeScreen, shouldHideHelpButton, shouldHideSidePaneBackdrop, sidePaneNVP};
}

/**
* Hook to get the animated position of the side pane and the margin of the navigator
*/
function useSidePane() {
const {isExtraLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout();
const {windowWidth} = useWindowDimensions();
const sidePaneWidth = shouldUseNarrowLayout ? windowWidth : variables.sideBarWidth;

const [isSidePaneTransitionEnded, setIsSidePaneTransitionEnded] = useState(true);
const {shouldHideSidePane, shouldHideSidePaneBackdrop, shouldHideHelpButton, sidePaneNVP} = useSidePaneDisplayStatus();
const shouldHideToolTip = isExtraLargeScreenWidth ? !isSidePaneTransitionEnded : !shouldHideSidePane;

const shouldApplySidePaneOffset = isExtraLargeScreenWidth && !shouldHideSidePane;
const sidePaneOffset = useRef(new Animated.Value(shouldApplySidePaneOffset ? variables.sideBarWidth : 0));
const sidePaneTranslateX = useRef(new Animated.Value(isPaneHidden ? sidePaneWidth : 0));
const sidePaneTranslateX = useRef(new Animated.Value(shouldHideSidePane ? sidePaneWidth : 0));

useEffect(() => {
if (!isPaneHidden) {
setShouldHideSidePane(false);
}
if (isExtraLargeScreenWidth) {
setIsAnimatingExtraLargeScreen(true);
Comment on lines -65 to -66

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed here a little change in hiding tooltip logic. I've checked and tooltips no longer hide during side pane hiding animation. I can't find any tooltip that can be affected by this (it should bo located on the right side of the screen on Extra large). But I remember that there was w tooltip next to advanced filters buttons that I can't find anymore.
If that's no longer a problem please ignore this comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tooltip I was talking about was removed in #58539
But it's still possible there are similar ones in the app

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed with latest commits!

Screen.Recording.2025-03-28.at.11.45.15.mov

}
setIsSidePaneTransitionEnded(false);

Animated.parallel([
Animated.timing(sidePaneOffset.current, {
Expand All @@ -73,22 +74,34 @@ function useSidePane() {
useNativeDriver: true,
}),
Animated.timing(sidePaneTranslateX.current, {
toValue: isPaneHidden ? sidePaneWidth : 0,
toValue: shouldHideSidePane ? sidePaneWidth : 0,
duration: CONST.ANIMATED_TRANSITION,
useNativeDriver: true,
}),
]).start(() => {
setShouldHideSidePane(isPaneHidden);
setIsAnimatingExtraLargeScreen(false);
]).start(() => setIsSidePaneTransitionEnded(true));
}, [shouldHideSidePane, shouldApplySidePaneOffset, sidePaneWidth]);

const openSidePane = useCallback(() => {
if (!sidePaneNVP) {
return;
}

setIsSidePaneTransitionEnded(false);
KeyboardUtils.dismiss();

triggerSidePane({
isOpen: true,
isOpenNarrowScreen: isExtraLargeScreenWidth ? undefined : true,
});
}, [isPaneHidden, shouldApplySidePaneOffset, shouldUseNarrowLayout, sidePaneWidth, isExtraLargeScreenWidth]);
}, [isExtraLargeScreenWidth, sidePaneNVP]);

const closeSidePane = useCallback(
(shouldUpdateNarrow = false) => {
if (!sidePaneNVP) {
return;
}

setIsSidePaneTransitionEnded(false);
const shouldOnlyUpdateNarrowLayout = !isExtraLargeScreenWidth || shouldUpdateNarrow;
triggerSidePane({
isOpen: shouldOnlyUpdateNarrowLayout ? undefined : false,
Expand All @@ -103,15 +116,17 @@ function useSidePane() {

return {
sidePane: sidePaneNVP,
isPaneHidden,
isSidePaneTransitionEnded,
shouldHideSidePane,
shouldHideSidePaneBackdrop,
shouldHideHelpButton,
shouldHideToolTip,
sidePaneOffset,
sidePaneTranslateX,
shouldHideToolTip,
openSidePane,
closeSidePane,
};
}

export default useSidePane;
export {useSidePaneDisplayStatus};
6 changes: 3 additions & 3 deletions src/hooks/useThreeDotsAnchorPosition.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {AnchorPosition} from '@styles/index';
import variables from '@styles/variables';
import useSidePane from './useSidePane';
import {useSidePaneDisplayStatus} from './useSidePane';
import useWindowDimensions from './useWindowDimensions';

/**
Expand All @@ -9,9 +9,9 @@ import useWindowDimensions from './useWindowDimensions';
*/
function useThreeDotsAnchorPosition(anchorPositionStyle: (screenWidth: number) => AnchorPosition) {
const {windowWidth} = useWindowDimensions();
const {isPaneHidden} = useSidePane();
const {shouldHideSidePane} = useSidePaneDisplayStatus();

return anchorPositionStyle(isPaneHidden ? windowWidth : windowWidth - variables.sideBarWidth);
return anchorPositionStyle(shouldHideSidePane ? windowWidth : windowWidth - variables.sideBarWidth);
}

export default useThreeDotsAnchorPosition;
Loading