Skip to content

Commit 32c0180

Browse files
authored
fix: get rid of positioning wrapper for message actions box (#2246)
### 🎯 Goal In #2241 we've added a new positioning wrapper for `MessageActionsBox`. That was not the greatest idea, since adding new wrappers can break custom CSS for the users. Having a wrapper is not necessary in this case anyway. This PR implements the same positioning as #2241, but without additional wrappers.
1 parent d07861f commit 32c0180

File tree

3 files changed

+136
-169
lines changed

3 files changed

+136
-169
lines changed

src/components/MessageActions/MessageActions.tsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -134,24 +134,20 @@ export const MessageActions = <
134134
inline={inline}
135135
setActionsBoxOpen={setActionsBoxOpen}
136136
>
137-
<div
137+
<MessageActionsBox
138138
{...attributes.popper}
139-
className='str-chat__message-actions-box-wrapper'
139+
getMessageActions={getMessageActions}
140+
handleDelete={handleDelete}
141+
handleEdit={setEditingState}
142+
handleFlag={handleFlag}
143+
handleMute={handleMute}
144+
handlePin={handlePin}
145+
isUserMuted={isMuted}
146+
mine={isMine}
147+
open={actionsBoxOpen}
140148
ref={popperElementRef}
141149
style={styles.popper}
142-
>
143-
<MessageActionsBox
144-
getMessageActions={getMessageActions}
145-
handleDelete={handleDelete}
146-
handleEdit={setEditingState}
147-
handleFlag={handleFlag}
148-
handleMute={handleMute}
149-
handlePin={handlePin}
150-
isUserMuted={isMuted}
151-
mine={isMine}
152-
open={actionsBoxOpen}
153-
/>
154-
</div>
150+
/>
155151
<button
156152
aria-expanded={actionsBoxOpen}
157153
aria-haspopup='true'

src/components/MessageActions/MessageActionsBox.tsx

Lines changed: 121 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { ComponentProps } from 'react';
22
import clsx from 'clsx';
33

44
import { MESSAGE_ACTIONS } from '../Message/utils';
@@ -29,123 +29,127 @@ export type MessageActionsBoxProps<
2929
isUserMuted: () => boolean;
3030
mine: boolean;
3131
open: boolean;
32-
};
33-
34-
const UnMemoizedMessageActionsBox = <
35-
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
36-
>(
37-
props: MessageActionsBoxProps<StreamChatGenerics>,
38-
) => {
39-
const {
40-
getMessageActions,
41-
handleDelete,
42-
handleEdit,
43-
handleFlag,
44-
handleMute,
45-
handlePin,
46-
isUserMuted,
47-
open = false,
48-
} = props;
49-
50-
const {
51-
CustomMessageActionsList = DefaultCustomMessageActionsList,
52-
} = useComponentContext<StreamChatGenerics>('MessageActionsBox');
53-
const { setQuotedMessage } = useChannelActionContext<StreamChatGenerics>('MessageActionsBox');
54-
const { customMessageActions, message } = useMessageContext<StreamChatGenerics>(
55-
'MessageActionsBox',
56-
);
57-
58-
const { t } = useTranslationContext('MessageActionsBox');
59-
60-
const messageActions = getMessageActions();
61-
62-
const handleQuote = () => {
63-
setQuotedMessage(message);
64-
65-
const elements = message.parent_id
66-
? document.querySelectorAll('.str-chat__thread .str-chat__textarea__textarea')
67-
: document.getElementsByClassName('str-chat__textarea__textarea');
68-
const textarea = elements.item(0);
69-
70-
if (textarea instanceof HTMLTextAreaElement) {
71-
textarea.focus();
72-
}
73-
};
74-
75-
const rootClassName = clsx('str-chat__message-actions-box', {
76-
'str-chat__message-actions-box--open': open,
77-
});
78-
const buttonClassName =
79-
'str-chat__message-actions-list-item str-chat__message-actions-list-item-button';
80-
81-
return (
82-
<div className={rootClassName} data-testid='message-actions-box'>
83-
<div aria-label='Message Options' className='str-chat__message-actions-list' role='listbox'>
84-
<CustomMessageActionsList customMessageActions={customMessageActions} message={message} />
85-
{messageActions.indexOf(MESSAGE_ACTIONS.quote) > -1 && (
86-
<button
87-
aria-selected='false'
88-
className={buttonClassName}
89-
onClick={handleQuote}
90-
role='option'
91-
>
92-
{t<string>('Reply')}
93-
</button>
94-
)}
95-
{messageActions.indexOf(MESSAGE_ACTIONS.pin) > -1 && !message.parent_id && (
96-
<button
97-
aria-selected='false'
98-
className={buttonClassName}
99-
onClick={handlePin}
100-
role='option'
101-
>
102-
{!message.pinned ? t<string>('Pin') : t<string>('Unpin')}
103-
</button>
104-
)}
105-
{messageActions.indexOf(MESSAGE_ACTIONS.flag) > -1 && (
106-
<button
107-
aria-selected='false'
108-
className={buttonClassName}
109-
onClick={handleFlag}
110-
role='option'
111-
>
112-
{t<string>('Flag')}
113-
</button>
114-
)}
115-
{messageActions.indexOf(MESSAGE_ACTIONS.mute) > -1 && (
116-
<button
117-
aria-selected='false'
118-
className={buttonClassName}
119-
onClick={handleMute}
120-
role='option'
121-
>
122-
{isUserMuted() ? t<string>('Unmute') : t<string>('Mute')}
123-
</button>
124-
)}
125-
{messageActions.indexOf(MESSAGE_ACTIONS.edit) > -1 && (
126-
<button
127-
aria-selected='false'
128-
className={buttonClassName}
129-
onClick={handleEdit}
130-
role='option'
131-
>
132-
{t<string>('Edit Message')}
133-
</button>
134-
)}
135-
{messageActions.indexOf(MESSAGE_ACTIONS.delete) > -1 && (
136-
<button
137-
aria-selected='false'
138-
className={buttonClassName}
139-
onClick={handleDelete}
140-
role='option'
141-
>
142-
{t<string>('Delete')}
143-
</button>
144-
)}
32+
} & ComponentProps<'div'>;
33+
34+
const UnMemoizedMessageActionsBox = React.forwardRef(
35+
<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(
36+
props: MessageActionsBoxProps<StreamChatGenerics>,
37+
ref: React.ForwardedRef<HTMLDivElement | null>,
38+
) => {
39+
const {
40+
getMessageActions,
41+
handleDelete,
42+
handleEdit,
43+
handleFlag,
44+
handleMute,
45+
handlePin,
46+
isUserMuted,
47+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
48+
mine,
49+
open = false,
50+
...restDivProps
51+
} = props;
52+
53+
const {
54+
CustomMessageActionsList = DefaultCustomMessageActionsList,
55+
} = useComponentContext<StreamChatGenerics>('MessageActionsBox');
56+
const { setQuotedMessage } = useChannelActionContext<StreamChatGenerics>('MessageActionsBox');
57+
const { customMessageActions, message } = useMessageContext<StreamChatGenerics>(
58+
'MessageActionsBox',
59+
);
60+
61+
const { t } = useTranslationContext('MessageActionsBox');
62+
63+
const messageActions = getMessageActions();
64+
65+
const handleQuote = () => {
66+
setQuotedMessage(message);
67+
68+
const elements = message.parent_id
69+
? document.querySelectorAll('.str-chat__thread .str-chat__textarea__textarea')
70+
: document.getElementsByClassName('str-chat__textarea__textarea');
71+
const textarea = elements.item(0);
72+
73+
if (textarea instanceof HTMLTextAreaElement) {
74+
textarea.focus();
75+
}
76+
};
77+
78+
const rootClassName = clsx('str-chat__message-actions-box', {
79+
'str-chat__message-actions-box--open': open,
80+
});
81+
const buttonClassName =
82+
'str-chat__message-actions-list-item str-chat__message-actions-list-item-button';
83+
84+
return (
85+
<div {...restDivProps} className={rootClassName} data-testid='message-actions-box' ref={ref}>
86+
<div aria-label='Message Options' className='str-chat__message-actions-list' role='listbox'>
87+
<CustomMessageActionsList customMessageActions={customMessageActions} message={message} />
88+
{messageActions.indexOf(MESSAGE_ACTIONS.quote) > -1 && (
89+
<button
90+
aria-selected='false'
91+
className={buttonClassName}
92+
onClick={handleQuote}
93+
role='option'
94+
>
95+
{t<string>('Reply')}
96+
</button>
97+
)}
98+
{messageActions.indexOf(MESSAGE_ACTIONS.pin) > -1 && !message.parent_id && (
99+
<button
100+
aria-selected='false'
101+
className={buttonClassName}
102+
onClick={handlePin}
103+
role='option'
104+
>
105+
{!message.pinned ? t<string>('Pin') : t<string>('Unpin')}
106+
</button>
107+
)}
108+
{messageActions.indexOf(MESSAGE_ACTIONS.flag) > -1 && (
109+
<button
110+
aria-selected='false'
111+
className={buttonClassName}
112+
onClick={handleFlag}
113+
role='option'
114+
>
115+
{t<string>('Flag')}
116+
</button>
117+
)}
118+
{messageActions.indexOf(MESSAGE_ACTIONS.mute) > -1 && (
119+
<button
120+
aria-selected='false'
121+
className={buttonClassName}
122+
onClick={handleMute}
123+
role='option'
124+
>
125+
{isUserMuted() ? t<string>('Unmute') : t<string>('Mute')}
126+
</button>
127+
)}
128+
{messageActions.indexOf(MESSAGE_ACTIONS.edit) > -1 && (
129+
<button
130+
aria-selected='false'
131+
className={buttonClassName}
132+
onClick={handleEdit}
133+
role='option'
134+
>
135+
{t<string>('Edit Message')}
136+
</button>
137+
)}
138+
{messageActions.indexOf(MESSAGE_ACTIONS.delete) > -1 && (
139+
<button
140+
aria-selected='false'
141+
className={buttonClassName}
142+
onClick={handleDelete}
143+
role='option'
144+
>
145+
{t<string>('Delete')}
146+
</button>
147+
)}
148+
</div>
145149
</div>
146-
</div>
147-
);
148-
};
150+
);
151+
},
152+
);
149153

150154
/**
151155
* A popup box that displays the available actions on a message, such as edit, delete, pin, etc.

src/components/MessageActions/__tests__/MessageActions.test.js

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,7 @@ describe('<MessageActions /> component', () => {
7272
data-testid="message-actions"
7373
onClick={[Function]}
7474
>
75-
<div
76-
className="str-chat__message-actions-box-wrapper"
77-
style={
78-
Object {
79-
"left": "0",
80-
"position": "absolute",
81-
"top": "0",
82-
}
83-
}
84-
>
85-
<div />
86-
</div>
75+
<div />
8776
<button
8877
aria-expanded={false}
8978
aria-haspopup="true"
@@ -117,7 +106,7 @@ describe('<MessageActions /> component', () => {
117106

118107
it('should open message actions box on click', () => {
119108
const { getByTestId } = renderMessageActions();
120-
expect(MessageActionsBoxMock).toHaveBeenLastCalledWith(
109+
expect(MessageActionsBoxMock).toHaveBeenCalledWith(
121110
expect.objectContaining({ open: false }),
122111
{},
123112
);
@@ -254,18 +243,7 @@ describe('<MessageActions /> component', () => {
254243
data-testid="message-actions"
255244
onClick={[Function]}
256245
>
257-
<div
258-
className="str-chat__message-actions-box-wrapper"
259-
style={
260-
Object {
261-
"left": "0",
262-
"position": "absolute",
263-
"top": "0",
264-
}
265-
}
266-
>
267-
<div />
268-
</div>
246+
<div />
269247
<button
270248
aria-expanded={false}
271249
aria-haspopup="true"
@@ -305,18 +283,7 @@ describe('<MessageActions /> component', () => {
305283
data-testid="message-actions"
306284
onClick={[Function]}
307285
>
308-
<div
309-
className="str-chat__message-actions-box-wrapper"
310-
style={
311-
Object {
312-
"left": "0",
313-
"position": "absolute",
314-
"top": "0",
315-
}
316-
}
317-
>
318-
<div />
319-
</div>
286+
<div />
320287
<button
321288
aria-expanded={false}
322289
aria-haspopup="true"

0 commit comments

Comments
 (0)