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

fix: move emoji suggestions to portals #24541

Merged
merged 10 commits into from
Aug 24, 2023
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import PropTypes from 'prop-types';
import refPropType from '../refPropTypes';

const propTypes = {
/** Array of suggestions */
Expand Down Expand Up @@ -27,8 +28,17 @@ const propTypes = {

/** create accessibility label for each item */
accessibilityLabelExtractor: PropTypes.func.isRequired,

/** Ref of the container enclosing the menu.
* This is needed to render the menu in correct position inside a portal
*/
parentContainerRef: refPropType,
};

const defaultProps = {};
const defaultProps = {
parentContainerRef: {
current: null,
},
};

export {propTypes, defaultProps};
27 changes: 25 additions & 2 deletions src/components/AutoCompleteSuggestions/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React from 'react';
import {View} from 'react-native';
import ReactDOM from 'react-dom';
import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
import {propTypes} from './autoCompleteSuggestionsPropTypes';
import * as StyleUtils from '../../styles/StyleUtils';
import useWindowDimensions from '../../hooks/useWindowDimensions';

/**
* On the mobile-web platform, when long-pressing on auto-complete suggestions,
Expand All @@ -10,8 +14,14 @@ import {propTypes} from './autoCompleteSuggestionsPropTypes';
* On the native platform, tapping on auto-complete suggestions will not blur the main input.
*/

function AutoCompleteSuggestions(props) {
function AutoCompleteSuggestions({parentContainerRef, ...props}) {
const containerRef = React.useRef(null);
const {windowHeight, windowWidth} = useWindowDimensions();
const [{width, left, bottom}, setContainerState] = React.useState({
width: 0,
left: 0,
bottom: 0,
});
React.useEffect(() => {
const container = containerRef.current;
if (!container) {
Expand All @@ -26,13 +36,26 @@ function AutoCompleteSuggestions(props) {
return () => (container.onpointerdown = null);
}, []);

return (
React.useEffect(() => {
if (!parentContainerRef || !parentContainerRef.current) {
return;
}
parentContainerRef.current.measureInWindow((x, y, w) => setContainerState({left: x, bottom: windowHeight - y, width: w}));
}, [parentContainerRef, windowHeight, windowWidth]);

const componentToRender = (
<BaseAutoCompleteSuggestions
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
ref={containerRef}
/>
);

if (!width) {
return componentToRender;
}

return ReactDOM.createPortal(<View style={StyleUtils.getBaseAutoCompleteSuggestionContainerStyle({left, width, bottom})}>{componentToRender}</View>, document.querySelector('body'));
Copy link
Contributor

@roryabraham roryabraham Nov 21, 2023

Choose a reason for hiding this comment

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

Not a huge deal, but I think we should strive to use the context-based @gorhom/portal instead of ReactDOM.createPortal wherever possible, to reduce unnecessary differences between code across platforms.

If we can keep our code in pure React Native (w/ react-native-web of course), we should

}

AutoCompleteSuggestions.propTypes = propTypes;
Expand Down
2 changes: 1 addition & 1 deletion src/components/AutoCompleteSuggestions/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions';
import {propTypes} from './autoCompleteSuggestionsPropTypes';

function AutoCompleteSuggestions(props) {
function AutoCompleteSuggestions({parentContainerRef, ...props}) {
// eslint-disable-next-line react/jsx-props-no-spreading
return <BaseAutoCompleteSuggestions {...props} />;
}
Expand Down
9 changes: 8 additions & 1 deletion src/components/EmojiSuggestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as EmojiUtils from '../libs/EmojiUtils';
import Text from './Text';
import getStyledTextArray from '../libs/GetStyledTextArray';
import AutoCompleteSuggestions from './AutoCompleteSuggestions';
import refPropType from './refPropTypes';

const propTypes = {
/** The index of the highlighted emoji */
Expand Down Expand Up @@ -45,9 +46,14 @@ const propTypes = {

/** Stores user's preferred skin tone */
preferredSkinToneIndex: PropTypes.number.isRequired,

/** Ref of the container enclosing the menu.
* This is needed to render the menu in correct position inside a portal
*/
containerRef: refPropType,
};

const defaultProps = {highlightedEmojiIndex: 0};
const defaultProps = {highlightedEmojiIndex: 0, containerRef: {current: null}};

/**
* Create unique keys for each emoji item
Expand Down Expand Up @@ -98,6 +104,7 @@ function EmojiSuggestions(props) {
isSuggestionPickerLarge={props.isEmojiPickerLarge}
shouldIncludeReportRecipientLocalTimeHeight={props.shouldIncludeReportRecipientLocalTimeHeight}
accessibilityLabelExtractor={keyExtractor}
parentContainerRef={props.containerRef}
/>
);
}
Expand Down
10 changes: 10 additions & 0 deletions src/components/MentionSuggestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Avatar from './Avatar';
import AutoCompleteSuggestions from './AutoCompleteSuggestions';
import getStyledTextArray from '../libs/GetStyledTextArray';
import avatarPropTypes from './avatarPropTypes';
import refPropType from './refPropTypes';

const propTypes = {
/** The index of the highlighted mention */
Expand Down Expand Up @@ -42,10 +43,18 @@ const propTypes = {

/** Show that we should include ReportRecipientLocalTime view height */
shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired,

/** Ref of the container enclosing the menu.
* This is needed to render the menu in correct position inside a portal
*/
containerRef: refPropType,
};

const defaultProps = {
highlightedMentionIndex: 0,
containerRef: {
current: null,
},
};

/**
Expand Down Expand Up @@ -122,6 +131,7 @@ function MentionSuggestions(props) {
isSuggestionPickerLarge={props.isMentionPickerLarge}
shouldIncludeReportRecipientLocalTimeHeight={props.shouldIncludeReportRecipientLocalTimeHeight}
accessibilityLabelExtractor={keyExtractor}
parentContainerRef={props.containerRef}
/>
);
}
Expand Down
9 changes: 8 additions & 1 deletion src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ function ReportActionCompose({

const insertedEmojisRef = useRef([]);

const containerRef = useRef(null);

/**
* Update frequently used emojis list. We debounce this method in the constructor so that UpdateFrequentlyUsedEmojis
* API is not called too often.
Expand Down Expand Up @@ -1030,7 +1032,10 @@ function ReportActionCompose({
});

return (
<View style={[shouldShowReportRecipientLocalTime && !lodashGet(network, 'isOffline') && styles.chatItemComposeWithFirstRow, isComposerFullSize && styles.chatItemFullComposeRow]}>
<View
style={[shouldShowReportRecipientLocalTime && !lodashGet(network, 'isOffline') && styles.chatItemComposeWithFirstRow, isComposerFullSize && styles.chatItemFullComposeRow]}
ref={containerRef}
>
<OfflineWithFeedback
pendingAction={pendingAction}
style={isComposerFullSize ? styles.chatItemFullComposeRow : {}}
Expand Down Expand Up @@ -1293,6 +1298,7 @@ function ReportActionCompose({
isEmojiPickerLarge={suggestionValues.isAutoSuggestionPickerLarge}
composerHeight={composerHeight}
shouldIncludeReportRecipientLocalTimeHeight={shouldShowReportRecipientLocalTime}
containerRef={containerRef}
/>
)}
{isMentionSuggestionsMenuVisible && (
Expand All @@ -1309,6 +1315,7 @@ function ReportActionCompose({
isMentionPickerLarge={suggestionValues.isAutoSuggestionPickerLarge}
composerHeight={composerHeight}
shouldIncludeReportRecipientLocalTimeHeight={shouldShowReportRecipientLocalTime}
containerRef={containerRef}
/>
)}
</View>
Expand Down
12 changes: 12 additions & 0 deletions src/styles/StyleUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,17 @@ function getAutoCompleteSuggestionItemStyle(highlightedEmojiIndex, rowHeight, ho
];
}

/**
* Gets the correct position for the base auto complete suggestion container
*
* @param {Object} parentContainerLayout
* @returns {Object}
*/

function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}) {
return {position: 'fixed', bottom, left, width};
}

/**
* Gets the correct position for auto complete suggestion container
*
Expand Down Expand Up @@ -1351,6 +1362,7 @@ export {
getReportWelcomeBackgroundImageStyle,
getReportWelcomeTopMarginStyle,
getReportWelcomeContainerStyle,
getBaseAutoCompleteSuggestionContainerStyle,
getAutoCompleteSuggestionItemStyle,
getAutoCompleteSuggestionContainerStyle,
getColoredBackgroundStyle,
Expand Down