diff --git a/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js b/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js index a68dc340f6..ac9f3061d9 100644 --- a/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js +++ b/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js @@ -50,6 +50,24 @@ const ChatInputFormattingToolbar = ({ const [isInsertLinkOpen, setInsertLinkOpen] = useState(false); const [isPopoverOpen, setPopoverOpen] = useState(false); const popoverRef = useRef(null); + const emojiTimeoutRef = useRef(null); + + const handleEmojiClose = () => { + if (emojiTimeoutRef.current) { + clearTimeout(emojiTimeoutRef.current); + } + emojiTimeoutRef.current = setTimeout(() => { + setEmojiOpen(false); + }, 300); + }; + + useEffect(() => { + return () => { + if (emojiTimeoutRef.current) { + clearTimeout(emojiTimeoutRef.current); + } + }; + }, []); const handleClickToOpenFiles = () => { inputRef.current.click(); @@ -296,9 +314,8 @@ const ChatInputFormattingToolbar = ({ if (itemInFormatter) { return ( @@ -341,10 +358,10 @@ const ChatInputFormattingToolbar = ({ { - setEmojiOpen(false); handleEmojiClick(emoji); + handleEmojiClose(); }} - onClose={() => setEmojiOpen(false)} + onClose={handleEmojiClose} positionStyles={css` position: absolute; bottom: 7rem; diff --git a/packages/react/src/views/EmojiPicker/EmojiPicker.js b/packages/react/src/views/EmojiPicker/EmojiPicker.js index 6501eb8320..6c93188775 100644 --- a/packages/react/src/views/EmojiPicker/EmojiPicker.js +++ b/packages/react/src/views/EmojiPicker/EmojiPicker.js @@ -1,53 +1,71 @@ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; +import { createPortal } from 'react-dom'; import EmojiPicker from 'emoji-picker-react'; -import { css } from '@emotion/react'; -import PropTypes from 'prop-types'; -import { Box, Popup, useTheme } from '@embeddedchat/ui-elements'; +import { Box, useTheme } from '@embeddedchat/ui-elements'; import getEmojiPickerStyles from './EmojiPicker.styles'; const CustomEmojiPicker = ({ handleEmojiClick, - positionStyles = css` - position: absolute; - top: 0; - right: 0; - `, - wrapperId = 'emoji-popup', - onClose = () => {}, + onClose = () => { }, }) => { const theme = useTheme(); const styles = getEmojiPickerStyles(theme); - const previewConfig = { - defaultEmoji: '1f60d', - defaultCaption: 'None', - showPreview: true, + const isMountedRef = useRef(true); + + useEffect(() => { + isMountedRef.current = true; + return () => { + isMountedRef.current = false; + }; + }, []); + + const internalHandleEmojiClick = (emojiData, event) => { + if (isMountedRef.current) { + handleEmojiClick(emojiData, event); + } }; - return ( - - - - - - ); -}; + const portalStyles = { + position: 'fixed', + bottom: '100px', + left: '50%', + transform: 'translateX(-50%)', + zIndex: 99999, + backgroundColor: '#fff', + borderRadius: '12px', + boxShadow: '0px 8px 30px rgba(0,0,0,0.2)', + border: '1px solid #ebebeb', + width: 'min(350px, 90vw)', + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + }; + + const pickerUI = ( + <> +
-export default CustomEmojiPicker; +
+ + + +
+ + ); -CustomEmojiPicker.propTypes = { - handleEmojiClick: PropTypes.func, + return createPortal(pickerUI, document.body); }; + +export default CustomEmojiPicker; \ No newline at end of file diff --git a/packages/react/src/views/Message/MessageToolbox.js b/packages/react/src/views/Message/MessageToolbox.js index 75bdc7467d..212b33fc7f 100644 --- a/packages/react/src/views/Message/MessageToolbox.js +++ b/packages/react/src/views/Message/MessageToolbox.js @@ -1,4 +1,4 @@ -import React, { useState, useContext, useMemo } from 'react'; +import React, { useState, useContext, useMemo, useRef, useEffect } from 'react'; import { Box, Modal, @@ -76,6 +76,24 @@ export const MessageToolbox = ({ const [isEmojiOpen, setEmojiOpen] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); + const emojiTimeoutRef = useRef(null); + + const handleEmojiClose = () => { + if (emojiTimeoutRef.current) { + clearTimeout(emojiTimeoutRef.current); + } + emojiTimeoutRef.current = setTimeout(() => { + setEmojiOpen(false); + }, 300); + }; + + useEffect(() => { + return () => { + if (emojiTimeoutRef.current) { + clearTimeout(emojiTimeoutRef.current); + } + }; + }, []); const handleOnClose = () => { setShowDeleteModal(false); @@ -108,10 +126,10 @@ export const MessageToolbox = ({ const canDeleteMessage = isAllowedToForceDeleteMessage ? true : isAllowedToDeleteMessage - ? true - : isAllowedToDeleteOwnMessage - ? message.u._id === authenticatedUserId - : false; + ? true + : isAllowedToDeleteOwnMessage + ? message.u._id === authenticatedUserId + : false; const options = useMemo( () => ({ @@ -132,14 +150,14 @@ export const MessageToolbox = ({ star: { label: message.starred && - message.starred.find((u) => u._id === authenticatedUserId) + message.starred.find((u) => u._id === authenticatedUserId) ? 'Unstar' : 'Star', id: 'star', onClick: () => handleStarMessage(message), iconName: message.starred && - message.starred.find((u) => u._id === authenticatedUserId) + message.starred.find((u) => u._id === authenticatedUserId) ? 'star-filled' : 'star', visible: true, @@ -268,10 +286,10 @@ export const MessageToolbox = ({ {isEmojiOpen && ( { - setEmojiOpen(false); handleEmojiClick(emoji, message, true); + handleEmojiClose(); }} - onClose={() => setEmojiOpen(false)} + onClose={handleEmojiClose} positionStyles={ variantStyles.emojiPickerStyles || styles.emojiPickerStyles }