1- import React , { useContext , useRef , useState } from 'react' ;
1+ import React , { ReactNode , useContext , useRef , useState } from 'react' ;
22import { EmojiContainer } from '@sendbird/chat' ;
33import { FileMessage , MultipleFilesMessage , UserMessage } from '@sendbird/chat/message' ;
44import { GroupChannel } from '@sendbird/chat/groupChannel' ;
55
66import './ThreadListItemContent.scss' ;
77
88import { ReplyType } from '../../../../types' ;
9- import ContextMenu , { MenuItems } from '../../../../ui/ContextMenu' ;
9+ import ContextMenu , { EMOJI_MENU_ROOT_ID , getObservingId , MENU_OBSERVING_CLASS_NAME , MENU_ROOT_ID , MenuItems } from '../../../../ui/ContextMenu' ;
1010import Avatar from '../../../../ui/Avatar' ;
1111import { UserProfileContext } from '../../../../lib/UserProfileContext' ;
1212import UserProfile from '../../../../ui/UserProfile' ;
13- import MessageItemMenu from '../../../../ui/MessageItemMenu' ;
14- import MessageItemReactionMenu from '../../../../ui/MessageItemReactionMenu' ;
13+ import { MessageEmojiMenu , MessageEmojiMenuProps } from '../../../../ui/MessageItemReactionMenu' ;
1514import Label , { LabelTypography , LabelColors } from '../../../../ui/Label' ;
1615import {
1716 getClassName ,
@@ -44,12 +43,15 @@ import { useThreadMessageKindKeySelector } from '../../../Channel/context/hooks/
4443import { useFileInfoListWithUploaded } from '../../../Channel/context/hooks/useFileInfoListWithUploaded' ;
4544import { useThreadContext } from '../../context/ThreadProvider' ;
4645import { classnames } from '../../../../utils/utils' ;
46+ import { MessageMenu , MessageMenuProps } from '../../../../ui/MessageMenu' ;
47+ import useElementObserver from '../../../../hooks/useElementObserver' ;
4748
4849export interface ThreadListItemContentProps {
4950 className ?: string ;
5051 userId : string ;
5152 channel : GroupChannel ;
5253 message : SendableMessageType ;
54+ /** @deprecated This prop is deprecated and no longer in use. */
5355 disabled ?: boolean ;
5456 chainTop ?: boolean ;
5557 chainBottom ?: boolean ;
@@ -65,14 +67,15 @@ export interface ThreadListItemContentProps {
6567 resendMessage ?: ( message : SendableMessageType ) => void ;
6668 toggleReaction ?: ( message : SendableMessageType , reactionKey : string , isReacted : boolean ) => void ;
6769 onReplyInThread ?: ( props : { message : SendableMessageType } ) => void ;
70+ renderEmojiMenu ?: ( props : MessageEmojiMenuProps ) => ReactNode ;
71+ renderMessageMenu ?: ( props : MessageMenuProps ) => ReactNode ;
6872}
6973
7074export default function ThreadListItemContent ( {
7175 className,
7276 userId,
7377 channel,
7478 message,
75- disabled = false ,
7679 chainTop = false ,
7780 chainBottom = false ,
7881 isMentionEnabled = false ,
@@ -87,14 +90,22 @@ export default function ThreadListItemContent({
8790 resendMessage,
8891 toggleReaction,
8992 onReplyInThread,
93+ renderEmojiMenu = ( props ) => < MessageEmojiMenu { ...props } /> ,
94+ renderMessageMenu = ( props ) => < MessageMenu { ...props } /> ,
9095} : ThreadListItemContentProps ) : React . ReactElement {
9196 const messageTypes = getUIKitMessageTypes ( ) ;
9297 const { isMobile } = useMediaQueryContext ( ) ;
9398 const { dateLocale } = useLocalization ( ) ;
9499 const { config, eventHandlers } = useSendbirdStateContext ?.( ) || { } ;
95100 const { logger } = config ;
96101 const onPressUserProfileHandler = eventHandlers ?. reaction ?. onPressUserProfile ;
97- const [ supposedHover , setSupposedHover ] = useState ( false ) ;
102+ const isMenuMounted = useElementObserver (
103+ `#${ getObservingId ( message . messageId ) } .${ MENU_OBSERVING_CLASS_NAME } ` ,
104+ [
105+ document . getElementById ( MENU_ROOT_ID ) ,
106+ document . getElementById ( EMOJI_MENU_ROOT_ID ) ,
107+ ] ,
108+ ) ;
98109 const { disableUserProfile, renderUserProfile } = useContext ( UserProfileContext ) ;
99110 const { deleteMessage, onBeforeDownloadFileMessage } = useThreadContext ( ) ;
100111 const avatarRef = useRef ( null ) ;
@@ -106,7 +117,7 @@ export default function ThreadListItemContent({
106117 && message ?. parentMessageId && message ?. parentMessage
107118 && ! disableQuoteMessage
108119 ) ;
109- const supposedHoverClassName = supposedHover ? 'sendbird-mouse-hover' : '' ;
120+ const supposedHoverClassName = isMenuMounted ? 'sendbird-mouse-hover' : '' ;
110121 const isReactionEnabledInChannel = isReactionEnabled && ! channel ?. isEphemeral ;
111122 const isOgMessageEnabledInGroupChannel = channel . isGroupChannel ( ) && config . groupChannel . enableOgtag ;
112123
@@ -185,28 +196,28 @@ export default function ThreadListItemContent({
185196 supposedHoverClassName ,
186197 ) }
187198 >
188- < MessageItemMenu
189- className = "sendbird-thread-list-item-content-menu__normal-menu"
190- channel = { channel }
191- message = { message as SendableMessageType }
192- isByMe = { isByMe }
193- replyType = { replyType }
194- disabled = { disabled }
195- showEdit = { showEdit }
196- showRemove = { showRemove }
197- resendMessage = { resendMessage }
198- setSupposedHover = { setSupposedHover }
199- onReplyInThread = { onReplyInThread }
200- deleteMessage = { deleteMessage }
201- />
199+ { renderMessageMenu ( {
200+ className : 'sendbird-thread-list-item-content-menu__normal-menu' ,
201+ channel : channel ,
202+ message : message as SendableMessageType ,
203+ isByMe : isByMe ,
204+ replyType : replyType ,
205+ showEdit : showEdit ,
206+ showRemove : showRemove ,
207+ resendMessage : resendMessage ,
208+ onReplyInThread : onReplyInThread ,
209+ deleteMessage : deleteMessage ,
210+ } ) }
202211 { isReactionEnabledInChannel && (
203- < MessageItemReactionMenu
204- className = "sendbird-thread-list-item-content-menu__reaction-menu"
205- message = { message as SendableMessageType }
206- userId = { userId }
207- emojiContainer = { emojiContainer }
208- toggleReaction = { toggleReaction }
209- />
212+ < >
213+ { renderEmojiMenu ( {
214+ className : 'sendbird-thread-list-item-content-menu__reaction-menu' ,
215+ message : message as SendableMessageType ,
216+ userId : userId ,
217+ emojiContainer : emojiContainer ,
218+ toggleReaction : toggleReaction ,
219+ } ) }
220+ </ >
210221 ) }
211222 </ div >
212223 ) }
@@ -352,27 +363,25 @@ export default function ThreadListItemContent({
352363 { ( ! isByMe && ! isMobile ) && (
353364 < div className = { `sendbird-thread-list-item-content-menu ${ supposedHoverClassName } ` } >
354365 { isReactionEnabledInChannel && (
355- < MessageItemReactionMenu
356- className = " sendbird-thread-list-item-content-menu__reaction-menu"
357- message = { message as SendableMessageType }
358- userId = { userId }
359- emojiContainer = { emojiContainer }
360- toggleReaction = { toggleReaction }
361- />
366+ renderEmojiMenu ( {
367+ className : ' sendbird-thread-list-item-content-menu__reaction-menu' ,
368+ message : message as SendableMessageType ,
369+ userId : userId ,
370+ emojiContainer : emojiContainer ,
371+ toggleReaction : toggleReaction ,
372+ } )
362373 ) }
363- < MessageItemMenu
364- className = "sendbird-thread-list-item-content-menu__normal-menu"
365- channel = { channel }
366- message = { message as SendableMessageType }
367- isByMe = { isByMe }
368- replyType = { replyType }
369- disabled = { disabled }
370- showRemove = { showRemove }
371- resendMessage = { resendMessage }
372- setSupposedHover = { setSupposedHover }
373- onReplyInThread = { onReplyInThread }
374- deleteMessage = { deleteMessage }
375- />
374+ { renderMessageMenu ( {
375+ className : 'sendbird-thread-list-item-content-menu__normal-menu' ,
376+ channel : channel ,
377+ message : message as SendableMessageType ,
378+ isByMe : isByMe ,
379+ replyType : replyType ,
380+ showRemove : showRemove ,
381+ resendMessage : resendMessage ,
382+ onReplyInThread : onReplyInThread ,
383+ deleteMessage : deleteMessage ,
384+ } ) }
376385 </ div >
377386 ) }
378387 </ div >
0 commit comments