Skip to content

Commit

Permalink
Merge pull request #33537 from bernhardoj/fix/31155-fix-emoji-picker-…
Browse files Browse the repository at this point in the history
…position-after-resize

Fix emoji picker position moves to top left after resizing window
  • Loading branch information
neil-marcellini authored Dec 26, 2023
2 parents 92501dc + ea6cc4c commit 64a88e4
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 15 deletions.
28 changes: 20 additions & 8 deletions src/components/EmojiPicker/EmojiPicker.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react';
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
import {Dimensions} from 'react-native';
import _ from 'underscore';
import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent';
Expand Down Expand Up @@ -30,26 +30,37 @@ const EmojiPicker = forwardRef((props, ref) => {
});
const [emojiPopoverAnchorOrigin, setEmojiPopoverAnchorOrigin] = useState(DEFAULT_ANCHOR_ORIGIN);
const [activeID, setActiveID] = useState();
const emojiPopoverAnchor = useRef(null);
const emojiPopoverAnchorRef = useRef(null);
const onModalHide = useRef(() => {});
const onEmojiSelected = useRef(() => {});
const emojiSearchInput = useRef();
const {isSmallScreenWidth, windowHeight} = useWindowDimensions();

/**
* Get the popover anchor ref
*
* emojiPopoverAnchorRef contains either null or the ref object of the anchor element.
* { current: { current: anchorElement } }
*
* Don't directly get the ref from emojiPopoverAnchorRef, instead use getEmojiPopoverAnchor()
*/
const getEmojiPopoverAnchor = useCallback(() => emojiPopoverAnchorRef.current || emojiPopoverAnchorRef, []);

/**
* Show the emoji picker menu.
*
* @param {Function} [onModalHideValue=() => {}] - Run a callback when Modal hides.
* @param {Function} [onEmojiSelectedValue=() => {}] - Run a callback when Emoji selected.
* @param {Element} emojiPopoverAnchorValue - Element to which Popover is anchored
* @param {React.MutableRefObject} emojiPopoverAnchorValue - Element to which Popover is anchored
* @param {Object} [anchorOrigin=DEFAULT_ANCHOR_ORIGIN] - Anchor origin for Popover
* @param {Function} [onWillShow=() => {}] - Run a callback when Popover will show
* @param {String} id - Unique id for EmojiPicker
*/
const showEmojiPicker = (onModalHideValue, onEmojiSelectedValue, emojiPopoverAnchorValue, anchorOrigin, onWillShow = () => {}, id) => {
onModalHide.current = onModalHideValue;
onEmojiSelected.current = onEmojiSelectedValue;
emojiPopoverAnchor.current = emojiPopoverAnchorValue;
emojiPopoverAnchorRef.current = emojiPopoverAnchorValue;
const emojiPopoverAnchor = getEmojiPopoverAnchor();
if (emojiPopoverAnchor.current && emojiPopoverAnchor.current.blur) {
// Drop focus to avoid blue focus ring.
emojiPopoverAnchor.current.blur();
Expand All @@ -75,7 +86,7 @@ const EmojiPicker = forwardRef((props, ref) => {
if (isNavigating) {
onModalHide.current = () => {};
}
emojiPopoverAnchor.current = null;
emojiPopoverAnchorRef.current = null;
setIsEmojiPickerVisible(false);
};

Expand Down Expand Up @@ -118,12 +129,13 @@ const EmojiPicker = forwardRef((props, ref) => {

const clearActive = () => setActiveID(null);

const resetEmojiPopoverAnchor = () => (emojiPopoverAnchor.current = null);
const resetEmojiPopoverAnchor = () => (emojiPopoverAnchorRef.current = null);

useImperativeHandle(ref, () => ({showEmojiPicker, isActive, clearActive, hideEmojiPicker, isEmojiPickerVisible, resetEmojiPopoverAnchor}));

useEffect(() => {
const emojiPopoverDimensionListener = Dimensions.addEventListener('change', () => {
const emojiPopoverAnchor = getEmojiPopoverAnchor();
if (!emojiPopoverAnchor.current) {
// In small screen width, the window size change might be due to keyboard open/hide, we should avoid hide EmojiPicker in those cases
if (isEmojiPickerVisible && !isSmallScreenWidth) {
Expand All @@ -141,7 +153,7 @@ const EmojiPicker = forwardRef((props, ref) => {
}
emojiPopoverDimensionListener.remove();
};
}, [isEmojiPickerVisible, isSmallScreenWidth, emojiPopoverAnchorOrigin]);
}, [isEmojiPickerVisible, isSmallScreenWidth, emojiPopoverAnchorOrigin, getEmojiPopoverAnchor]);

// There is no way to disable animations, and they are really laggy, because there are so many
// emojis. The best alternative is to set it to 1ms so it just "pops" in and out
Expand All @@ -159,7 +171,7 @@ const EmojiPicker = forwardRef((props, ref) => {
vertical: emojiPopoverAnchorPosition.vertical,
horizontal: emojiPopoverAnchorPosition.horizontal,
}}
anchorRef={emojiPopoverAnchor}
anchorRef={getEmojiPopoverAnchor()}
withoutOverlay
popoverDimensions={{
width: CONST.EMOJI_PICKER_SIZE.WIDTH,
Expand Down
2 changes: 1 addition & 1 deletion src/components/EmojiPicker/EmojiPickerButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function EmojiPickerButton(props) {
return;
}
if (!EmojiPickerAction.emojiPickerRef.current.isEmojiPickerVisible) {
EmojiPickerAction.showEmojiPicker(props.onModalHide, props.onEmojiSelected, emojiPopoverAnchor.current, undefined, () => {}, props.emojiPickerID);
EmojiPickerAction.showEmojiPicker(props.onModalHide, props.onEmojiSelected, emojiPopoverAnchor, undefined, () => {}, props.emojiPickerID);
} else {
EmojiPickerAction.emojiPickerRef.current.hideEmojiPicker();
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/EmojiPicker/EmojiPickerButtonDropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function EmojiPickerButtonDropdown(props) {
return;
}

EmojiPickerAction.showEmojiPicker(props.onModalHide, (emoji) => props.onInputChange(emoji), emojiPopoverAnchor.current, {
EmojiPickerAction.showEmojiPicker(props.onModalHide, (emoji) => props.onInputChange(emoji), emojiPopoverAnchor, {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
shiftVertical: 4,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Reactions/AddReactionBubble.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function AddReactionBubble(props) {
(emojiCode, emojiObject) => {
props.onSelectEmoji(emojiObject);
},
refParam || ref.current,
refParam || ref,
anchorOrigin,
props.onWillShowPicker,
props.reportAction.reportActionID,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Reactions/MiniQuickEmojiReactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function MiniQuickEmojiReactions(props) {
(emojiCode, emojiObject) => {
props.onEmojiSelected(emojiObject, props.emojiReactions);
},
ref.current,
ref,
undefined,
() => {},
props.reportAction.reportActionID,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Reactions/QuickEmojiReactions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const propTypes = {

function QuickEmojiReactions(props) {
const onPressOpenPicker = (openPicker) => {
openPicker(contextMenuRef.current.contentRef.current, {
openPicker(contextMenuRef.current.contentRef, {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function QuickEmojiReactions(props) {
// As the menu which includes the button to open the emoji picker
// gets closed, before the picker actually opens, we pass the composer
// ref as anchor for the emoji picker popover.
openPicker(ReportActionComposeFocusManager.composerRef.current);
openPicker(ReportActionComposeFocusManager.composerRef);
});
};

Expand Down
9 changes: 8 additions & 1 deletion src/libs/actions/EmojiPickerAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ type AnchorOrigin = {

// TODO: Move this type to src/components/EmojiPicker/EmojiPicker.js once it is converted to TS
type EmojiPickerRef = {
showEmojiPicker: (onModalHideValue?: () => void, onEmojiSelectedValue?: () => void, emojiPopoverAnchor?: View, anchorOrigin?: AnchorOrigin, onWillShow?: () => void, id?: string) => void;
showEmojiPicker: (
onModalHideValue?: () => void,
onEmojiSelectedValue?: () => void,
emojiPopoverAnchor?: React.MutableRefObject<View | HTMLElement | null>,
anchorOrigin?: AnchorOrigin,
onWillShow?: () => void,
id?: string,
) => void;
isActive: (id: string) => boolean;
clearActive: () => void;
hideEmojiPicker: (isNavigating: boolean) => void;
Expand Down

0 comments on commit 64a88e4

Please sign in to comment.