Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hide suggestion box #45076

Merged
merged 18 commits into from
Jul 18, 2024
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, {useCallback} from 'react';
import {View} from 'react-native';
import type {PointerEvent} from 'react-native';
import type PressableProps from '@components/Pressable/GenericPressable/types';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';

type TransparentOverlayProps = {
resetSuggestions: () => void;
};

type OnPressHandler = PressableProps['onPress'];

function TransparentOverlay({resetSuggestions}: TransparentOverlayProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();

const onResetSuggestions = useCallback<NonNullable<OnPressHandler>>(
(event) => {
event?.preventDefault();
resetSuggestions();
},
[resetSuggestions],
);

const handlePointerDown = useCallback((e: PointerEvent) => {
e?.preventDefault();
}, []);

return (
<View
onPointerDown={handlePointerDown}
style={styles.fullScreen}
>
<PressableWithoutFeedback
onPress={onResetSuggestions}
style={[styles.flex1, styles.cursorDefault]}
accessibilityLabel={translate('common.close')}
role={CONST.ROLE.BUTTON}
/>
</View>
);
}

export default TransparentOverlay;
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import {View} from 'react-native';
import BaseAutoCompleteSuggestions from '@components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions';
import useStyleUtils from '@hooks/useStyleUtils';
import getBottomSuggestionPadding from './getBottomSuggestionPadding';
import TransparentOverlay from './TransparentOverlay/TransparentOverlay';
import type {AutoCompleteSuggestionsPortalProps} from './types';

function AutoCompleteSuggestionsPortal<TSuggestion>({left = 0, width = 0, bottom = 0, ...props}: AutoCompleteSuggestionsPortalProps<TSuggestion>) {
function AutoCompleteSuggestionsPortal<TSuggestion>({left = 0, width = 0, bottom = 0, resetSuggestions = () => {}, ...props}: AutoCompleteSuggestionsPortalProps<TSuggestion>) {
const StyleUtils = useStyleUtils();
const styles = useMemo(() => StyleUtils.getBaseAutoCompleteSuggestionContainerStyle({left, width, bottom: bottom + getBottomSuggestionPadding()}), [StyleUtils, left, width, bottom]);

Expand All @@ -16,6 +17,7 @@ function AutoCompleteSuggestionsPortal<TSuggestion>({left = 0, width = 0, bottom

return (
<Portal hostName="suggestions">
<TransparentOverlay resetSuggestions={resetSuggestions} />
<View style={styles}>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<BaseAutoCompleteSuggestions<TSuggestion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {View} from 'react-native';
import BaseAutoCompleteSuggestions from '@components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions';
import useStyleUtils from '@hooks/useStyleUtils';
import getBottomSuggestionPadding from './getBottomSuggestionPadding';
import TransparentOverlay from './TransparentOverlay/TransparentOverlay';
import type {AutoCompleteSuggestionsPortalProps} from './types';

/**
Expand All @@ -14,7 +15,13 @@ import type {AutoCompleteSuggestionsPortalProps} from './types';
* On the native platform, tapping on auto-complete suggestions will not blur the main input.
*/

function AutoCompleteSuggestionsPortal<TSuggestion>({left = 0, width = 0, bottom = 0, ...props}: AutoCompleteSuggestionsPortalProps<TSuggestion>): ReactElement | null | false {
function AutoCompleteSuggestionsPortal<TSuggestion>({
left = 0,
width = 0,
bottom = 0,
resetSuggestions = () => {},
...props
}: AutoCompleteSuggestionsPortalProps<TSuggestion>): ReactElement | null | false {
const StyleUtils = useStyleUtils();

const bodyElement = document.querySelector('body');
Expand All @@ -31,7 +38,10 @@ function AutoCompleteSuggestionsPortal<TSuggestion>({left = 0, width = 0, bottom
!!width &&
bodyElement &&
ReactDOM.createPortal(
<View style={StyleUtils.getBaseAutoCompleteSuggestionContainerStyle({left, width, bottom: bottom - getBottomSuggestionPadding()})}>{componentToRender}</View>,
<>
<TransparentOverlay resetSuggestions={resetSuggestions} />
<View style={StyleUtils.getBaseAutoCompleteSuggestionContainerStyle({left, width, bottom: bottom - getBottomSuggestionPadding()})}>{componentToRender}</View>
</>,
bodyElement,
)
);
Expand Down
19 changes: 13 additions & 6 deletions src/components/AutoCompleteSuggestions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ function isEnoughSpaceToRenderMenuAboveCursor({y, cursorCoordinates, scrollValue
return y + (cursorCoordinates.y - scrollValue) > contentHeight + topInset + CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_BOX_MAX_SAFE_DISTANCE;
}

const initialContainerState = {
width: 0,
left: 0,
bottom: 0,
cursorCoordinates: {x: 0, y: 0},
};

/**
* On the mobile-web platform, when long-pressing on auto-complete suggestions,
* we need to prevent focus shifting to avoid blurring the main input (which makes the suggestions picker close and fires the onSelect callback).
Expand All @@ -48,12 +55,7 @@ function AutoCompleteSuggestions<TSuggestion>({measureParentContainerAndReportCu
const prevLeftValue = React.useRef<number>(0);
const {windowHeight, windowWidth, isSmallScreenWidth} = useWindowDimensions();
const [suggestionHeight, setSuggestionHeight] = React.useState(0);
const [containerState, setContainerState] = React.useState({
width: 0,
left: 0,
bottom: 0,
cursorCoordinates: {x: 0, y: 0},
});
const [containerState, setContainerState] = React.useState(initialContainerState);
const StyleUtils = useStyleUtils();
const insets = useSafeAreaInsets();
const {keyboardHeight} = useKeyboardState();
Expand All @@ -80,6 +82,11 @@ function AutoCompleteSuggestions<TSuggestion>({measureParentContainerAndReportCu
return;
}

if (!windowHeight || !windowWidth || !suggestionsLength) {
setContainerState(initialContainerState);
return;
}

measureParentContainerAndReportCursor(({x, y, width, scrollValue, cursorCoordinates}: MeasureParentContainerAndCursor) => {
const xCoordinatesOfCursor = x + cursorCoordinates.x;
const bigScreenLeftOffset =
Expand Down
3 changes: 3 additions & 0 deletions src/components/AutoCompleteSuggestions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ type AutoCompleteSuggestionsProps<TSuggestion> = {

/** Measures the parent container's position and dimensions. Also add a cursor coordinates */
measureParentContainerAndReportCursor?: (props: MeasureParentContainerAndCursorCallback) => void;

/** Reset the emoji suggestions */
resetSuggestions?: () => void;
};

export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps, MeasureParentContainerAndCursorCallback, MeasureParentContainerAndCursor};
5 changes: 5 additions & 0 deletions src/components/EmojiSuggestions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ type EmojiSuggestionsProps = {

/** Measures the parent container's position and dimensions. Also add cursor coordinates */
measureParentContainerAndReportCursor: (callback: MeasureParentContainerAndCursorCallback) => void;

/** Reset the emoji suggestions */
resetSuggestions: () => void;
};

/**
Expand All @@ -49,6 +52,7 @@ function EmojiSuggestions({
preferredSkinToneIndex,
highlightedEmojiIndex = 0,
measureParentContainerAndReportCursor = () => {},
resetSuggestions,
}: EmojiSuggestionsProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
Expand Down Expand Up @@ -93,6 +97,7 @@ function EmojiSuggestions({
isSuggestionPickerLarge={isEmojiPickerLarge}
accessibilityLabelExtractor={keyExtractor}
measureParentContainerAndReportCursor={measureParentContainerAndReportCursor}
resetSuggestions={resetSuggestions}
/>
);
}
Expand Down
14 changes: 13 additions & 1 deletion src/components/MentionSuggestions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,25 @@ type MentionSuggestionsProps = {

/** Measures the parent container's position and dimensions. Also add cursor coordinates */
measureParentContainerAndReportCursor: (callback: MeasureParentContainerAndCursorCallback) => void;

/** Reset the emoji suggestions */
resetSuggestions: () => void;
};

/**
* Create unique keys for each mention item
*/
const keyExtractor = (item: Mention) => item.alternateText;

function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSelect, isMentionPickerLarge, measureParentContainerAndReportCursor = () => {}}: MentionSuggestionsProps) {
function MentionSuggestions({
prefix,
mentions,
highlightedMentionIndex = 0,
onSelect,
isMentionPickerLarge,
measureParentContainerAndReportCursor = () => {},
resetSuggestions,
}: MentionSuggestionsProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
Expand Down Expand Up @@ -149,6 +160,7 @@ function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSe
isSuggestionPickerLarge={isMentionPickerLarge}
accessibilityLabelExtractor={keyExtractor}
measureParentContainerAndReportCursor={measureParentContainerAndReportCursor}
resetSuggestions={resetSuggestions}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ function SuggestionEmoji(
preferredSkinToneIndex={preferredSkinTone}
isEmojiPickerLarge={!!isAutoSuggestionPickerLarge}
measureParentContainerAndReportCursor={measureParentContainerAndReportCursor}
resetSuggestions={resetSuggestions}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ function SuggestionMention(
onSelect={insertSelectedMention}
isMentionPickerLarge={!!isAutoSuggestionPickerLarge}
measureParentContainerAndReportCursor={measureParentContainerAndReportCursor}
resetSuggestions={resetSuggestions}
/>
);
}
Expand Down
23 changes: 9 additions & 14 deletions src/pages/home/report/ReportActionItemMessageEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ function ReportActionItemMessageEdit(
}

// Show the main composer when the focused message is deleted from another client
// to prevent the main composer stays hidden until we swtich to another chat.
// to prevent the main composer stays hidden until we switch to another chat.
setShouldShowComposeInputKeyboardAware(true);
};
},
Expand Down Expand Up @@ -354,7 +354,7 @@ function ReportActionItemMessageEdit(
const keyEvent = e as KeyboardEvent;
const isSuggestionsMenuVisible = suggestionsRef.current?.getIsSuggestionsMenuVisible();

if (isSuggestionsMenuVisible && keyEvent.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) {
if (isSuggestionsMenuVisible) {
suggestionsRef.current?.triggerHotkeyActions(keyEvent);
return;
}
Expand All @@ -374,16 +374,12 @@ function ReportActionItemMessageEdit(
[deleteDraft, hideSuggestionMenu, isKeyboardShown, isSmallScreenWidth, publishDraft],
);

const measureContainer = useCallback(
(callback: MeasureInWindowOnSuccessCallback) => {
if (!containerRef.current) {
return;
}
containerRef.current.measureInWindow(callback);
},
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
[isFocused],
);
const measureContainer = useCallback((callback: MeasureInWindowOnSuccessCallback) => {
if (!containerRef.current) {
return;
}
containerRef.current.measureInWindow(callback);
}, []);

const measureParentContainerAndReportCursor = useCallback(
(callback: MeasureParentContainerAndCursorCallback) => {
Expand All @@ -408,8 +404,7 @@ function ReportActionItemMessageEdit(

// eslint-disable-next-line react-compiler/react-compiler
tag.value = findNodeHandle(textInputRef.current) ?? -1;
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, []);
}, [tag]);
useFocusedInputHandler(
{
onSelectionChange: (event) => {
Expand Down
Loading