@@ -30,6 +30,7 @@ import { useMessageComposerController } from '../MessageComposer/hooks/useMessag
3030import { savePreEditSnapshot } from '../MessageComposer/preEditSnapshot' ;
3131import { useNotificationApi } from '../Notifications' ;
3232import { useMessageReminder } from '../Message/hooks/useMessageReminder' ;
33+ import { ReactionSelector as DefaultReactionSelector } from '../Reactions' ;
3334import { ReactionSelectorWithButton } from '../Reactions/ReactionSelectorWithButton' ;
3435import {
3536 useChatContext ,
@@ -40,6 +41,7 @@ import {
4041import { RemindMeSubmenu , RemindMeSubmenuHeader } from './RemindMeSubmenu' ;
4142import {
4243 ContextMenuButton ,
44+ DialogAnchor ,
4345 useContextMenuContext ,
4446 useDialogIsOpen ,
4547 useDialogOnNearestManager ,
@@ -67,6 +69,61 @@ const getNotificationError = (error: unknown): Error | undefined => {
6769
6870const DefaultMessageActionComponents = {
6971 dropdown : {
72+ React ( ) {
73+ const { ReactionSelector = DefaultReactionSelector } = useComponentContext ( ) ;
74+ const { anchorReferenceElement } = useContextMenuContext ( ) ;
75+ const { isMyMessage, message, threadList } = useMessageContext ( ) ;
76+ const { t } = useTranslationContext ( ) ;
77+ const [ referenceElement , setReferenceElement ] = useState < HTMLElement | null > ( null ) ;
78+ const dialogId = `${ DefaultReactionSelector . getDialogId ( {
79+ messageId : message . id ,
80+ threadList,
81+ } ) } -dropdown`;
82+ const { dialog, dialogManager } = useDialogOnNearestManager ( {
83+ id : dialogId ,
84+ } ) ;
85+ const dialogIsOpen = useDialogIsOpen ( dialogId , dialogManager ?. id ) ;
86+
87+ return (
88+ < >
89+ < DialogAnchor
90+ dialogManagerId = { dialogManager ?. id }
91+ id = { dialogId }
92+ offset = { 8 }
93+ placement = { isMyMessage ( ) ? 'top-end' : 'top-start' }
94+ referenceElement = { referenceElement }
95+ trapFocus
96+ updatePositionOnContentResize
97+ >
98+ < ReactionSelector dialogId = { dialogId } />
99+ </ DialogAnchor >
100+ < ContextMenuButton
101+ aria-expanded = { dialogIsOpen }
102+ aria-label = { t ( 'aria/Open Reaction Selector' ) }
103+ className = { clsx (
104+ msgActionsBoxButtonClassName ,
105+ 'str-chat__message-actions-list-item-button--react' ,
106+ ) }
107+ data-testid = 'dropdown-react-action'
108+ Icon = { IconEmoji }
109+ onClick = { ( event ) => {
110+ if ( dialogIsOpen ) {
111+ dialog . close ( ) ;
112+ return ;
113+ }
114+ setReferenceElement (
115+ anchorReferenceElement instanceof HTMLElement
116+ ? anchorReferenceElement
117+ : event . currentTarget ,
118+ ) ;
119+ dialog . open ( ) ;
120+ } }
121+ >
122+ { t ( 'Add reaction' ) }
123+ </ ContextMenuButton >
124+ </ >
125+ ) ;
126+ } ,
70127 ThreadReply ( ) {
71128 const { closeMenu } = useContextMenuContext ( ) ;
72129 const { handleOpenThread } = useMessageContext ( ) ;
@@ -586,13 +643,20 @@ const DefaultMessageActionComponents = {
586643 // eslint-disable-next-line react/display-name
587644 DropdownToggle : forwardRef < HTMLButtonElement > ( ( _ , ref ) => {
588645 const { t } = useTranslationContext ( ) ;
589- const { message } = useMessageContext ( ) ;
646+ const { message, threadList } = useMessageContext ( ) ;
590647 const dropdownDialogIsOpen = useDialogIsOpen (
591648 MessageActions . getDialogId ( { messageId : message . id } ) ,
592649 ) ;
593650 const { dialog } = useDialogOnNearestManager ( {
594651 id : MessageActions . getDialogId ( { messageId : message . id } ) ,
595652 } ) ;
653+ const reactionSelectorDialogId = DefaultReactionSelector . getDialogId ( {
654+ messageId : message . id ,
655+ threadList,
656+ } ) ;
657+ const { dialog : dropdownReactionSelectorDialog } = useDialogOnNearestManager ( {
658+ id : `${ reactionSelectorDialogId } -dropdown` ,
659+ } ) ;
596660
597661 return (
598662 < QuickMessageActionsButton
@@ -602,6 +666,9 @@ const DefaultMessageActionComponents = {
602666 className = 'str-chat__message-actions-box-button'
603667 data-testid = 'message-actions-toggle-button'
604668 onClick = { ( ) => {
669+ // Close dropdown-anchored reaction selectors before toggling actions menu
670+ // to avoid stale selector re-anchoring.
671+ dropdownReactionSelectorDialog ?. close ( ) ;
605672 dialog ?. toggle ( ) ;
606673 } }
607674 ref = { ref }
@@ -646,6 +713,11 @@ export const defaultMessageActionSet: MessageActionSetItem[] = [
646713 placement : 'quick' ,
647714 type : 'react' ,
648715 } ,
716+ {
717+ Component : DefaultMessageActionComponents . dropdown . React ,
718+ placement : 'dropdown' ,
719+ type : 'react' ,
720+ } ,
649721 {
650722 Component : DefaultMessageActionComponents . dropdown . ThreadReply ,
651723 placement : 'dropdown' ,
0 commit comments