@ermis-network/ermis-chat-react 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +6593 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +3375 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +1138 -0
- package/dist/index.d.ts +1138 -0
- package/dist/index.mjs +6500 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -0
- package/src/components/Avatar.tsx +102 -0
- package/src/components/Channel.tsx +77 -0
- package/src/components/ChannelHeader.tsx +85 -0
- package/src/components/ChannelInfo/AddMemberModal.tsx +204 -0
- package/src/components/ChannelInfo/ChannelInfo.tsx +455 -0
- package/src/components/ChannelInfo/ChannelInfoTabs.tsx +282 -0
- package/src/components/ChannelInfo/ChannelSettingsPanel.tsx +479 -0
- package/src/components/ChannelInfo/EditChannelModal.tsx +272 -0
- package/src/components/ChannelInfo/FileListItem.tsx +49 -0
- package/src/components/ChannelInfo/LinkListItem.tsx +62 -0
- package/src/components/ChannelInfo/MediaGridItem.tsx +90 -0
- package/src/components/ChannelInfo/MemberListItem.tsx +85 -0
- package/src/components/ChannelInfo/MessageSearchPanel.tsx +333 -0
- package/src/components/ChannelInfo/States.tsx +36 -0
- package/src/components/ChannelInfo/index.ts +10 -0
- package/src/components/ChannelInfo/utils.tsx +49 -0
- package/src/components/ChannelList.tsx +395 -0
- package/src/components/Dropdown.tsx +120 -0
- package/src/components/EditPreview.tsx +102 -0
- package/src/components/FilesPreview.tsx +108 -0
- package/src/components/ForwardMessageModal.tsx +234 -0
- package/src/components/MentionSuggestions.tsx +59 -0
- package/src/components/MessageActionsBox.tsx +186 -0
- package/src/components/MessageInput.tsx +513 -0
- package/src/components/MessageInputDefaults.tsx +50 -0
- package/src/components/MessageItem.tsx +218 -0
- package/src/components/MessageQuickReactions.tsx +73 -0
- package/src/components/MessageReactions.tsx +59 -0
- package/src/components/MessageRenderers.tsx +565 -0
- package/src/components/Modal.tsx +58 -0
- package/src/components/Panel.tsx +64 -0
- package/src/components/PinnedMessages.tsx +165 -0
- package/src/components/QuotedMessagePreview.tsx +55 -0
- package/src/components/ReadReceipts.tsx +80 -0
- package/src/components/ReplyPreview.tsx +98 -0
- package/src/components/TypingIndicator.tsx +57 -0
- package/src/components/VirtualMessageList.tsx +425 -0
- package/src/context/ChatProvider.tsx +73 -0
- package/src/hooks/useBannedState.ts +48 -0
- package/src/hooks/useBlockedState.ts +55 -0
- package/src/hooks/useChannel.ts +18 -0
- package/src/hooks/useChannelCapabilities.ts +42 -0
- package/src/hooks/useChannelData.ts +55 -0
- package/src/hooks/useChannelListUpdates.ts +224 -0
- package/src/hooks/useChannelMessages.ts +159 -0
- package/src/hooks/useChannelRowUpdates.ts +78 -0
- package/src/hooks/useChatClient.ts +11 -0
- package/src/hooks/useEmojiPicker.ts +53 -0
- package/src/hooks/useFileUpload.ts +128 -0
- package/src/hooks/useLoadMessages.ts +178 -0
- package/src/hooks/useMentions.ts +287 -0
- package/src/hooks/useMessageActions.ts +87 -0
- package/src/hooks/useMessageSend.ts +164 -0
- package/src/hooks/usePendingState.ts +63 -0
- package/src/hooks/useScrollToMessage.ts +155 -0
- package/src/hooks/useTypingIndicator.ts +86 -0
- package/src/index.ts +129 -0
- package/src/styles/_add-member-modal.css +122 -0
- package/src/styles/_base.css +32 -0
- package/src/styles/_channel-info.css +941 -0
- package/src/styles/_channel-list.css +217 -0
- package/src/styles/_dropdown.css +69 -0
- package/src/styles/_forward-modal.css +191 -0
- package/src/styles/_mentions.css +102 -0
- package/src/styles/_message-actions.css +61 -0
- package/src/styles/_message-bubble.css +656 -0
- package/src/styles/_message-input.css +389 -0
- package/src/styles/_message-list.css +416 -0
- package/src/styles/_message-quick-reactions.css +62 -0
- package/src/styles/_message-reactions.css +67 -0
- package/src/styles/_modal.css +113 -0
- package/src/styles/_panel.css +69 -0
- package/src/styles/_pinned-messages.css +140 -0
- package/src/styles/_search-panel.css +219 -0
- package/src/styles/_tokens.css +92 -0
- package/src/styles/_typing-indicator.css +59 -0
- package/src/styles/index.css +24 -0
- package/src/types.ts +955 -0
- package/src/utils.ts +242 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { MessageItemProps, SystemMessageItemProps } from '../types';
|
|
3
|
+
import { QuotedMessagePreview } from './QuotedMessagePreview';
|
|
4
|
+
import { MessageActionsBox } from './MessageActionsBox';
|
|
5
|
+
import { MessageReactions } from './MessageReactions';
|
|
6
|
+
import { MessageQuickReactions } from './MessageQuickReactions';
|
|
7
|
+
import { useChannelCapabilities } from '../hooks/useChannelCapabilities';
|
|
8
|
+
import { useChatClient } from '../hooks/useChatClient';
|
|
9
|
+
import { formatTime } from '../utils';
|
|
10
|
+
|
|
11
|
+
export type { MessageItemProps, SystemMessageItemProps } from '../types';
|
|
12
|
+
|
|
13
|
+
/* ----------------------------------------------------------
|
|
14
|
+
MessageItem — single regular/signal message row
|
|
15
|
+
---------------------------------------------------------- */
|
|
16
|
+
/* Inline status icon for own messages (sent / sending / error) */
|
|
17
|
+
const InlineStatusIcon: React.FC<{ status?: string; isOwnMessage: boolean; isLastInGroup: boolean }> = React.memo(({
|
|
18
|
+
status,
|
|
19
|
+
isOwnMessage,
|
|
20
|
+
isLastInGroup,
|
|
21
|
+
}) => {
|
|
22
|
+
if (!isOwnMessage) return null;
|
|
23
|
+
|
|
24
|
+
const isError = status === 'error' || status === 'failed_offline';
|
|
25
|
+
if (!isLastInGroup && !isError) return null;
|
|
26
|
+
|
|
27
|
+
if (isError) {
|
|
28
|
+
return (
|
|
29
|
+
<span className="ermis-message-status-icon ermis-message-status-icon--failed" title="Failed to send">
|
|
30
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
31
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
32
|
+
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
33
|
+
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
34
|
+
</svg>
|
|
35
|
+
</span>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (status === 'sending') {
|
|
40
|
+
return (
|
|
41
|
+
<span className="ermis-message-status-icon ermis-message-status-icon--sending" title="Sending...">
|
|
42
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
43
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
44
|
+
<polyline points="12 6 12 12 16 14"></polyline>
|
|
45
|
+
</svg>
|
|
46
|
+
</span>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<span className="ermis-message-status-icon ermis-message-status-icon--sent" title="Sent">
|
|
52
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
53
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
54
|
+
</svg>
|
|
55
|
+
</span>
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
InlineStatusIcon.displayName = 'InlineStatusIcon';
|
|
59
|
+
|
|
60
|
+
export const MessageItem: React.FC<MessageItemProps> = React.memo(({
|
|
61
|
+
message,
|
|
62
|
+
isOwnMessage,
|
|
63
|
+
isFirstInGroup,
|
|
64
|
+
isLastInGroup,
|
|
65
|
+
isHighlighted,
|
|
66
|
+
AvatarComponent,
|
|
67
|
+
MessageBubble,
|
|
68
|
+
MessageRenderer,
|
|
69
|
+
onClickQuote,
|
|
70
|
+
QuotedMessagePreviewComponent = QuotedMessagePreview,
|
|
71
|
+
MessageActionsBoxComponent = MessageActionsBox,
|
|
72
|
+
MessageReactionsComponent = MessageReactions,
|
|
73
|
+
forwardedLabel = 'Forwarded',
|
|
74
|
+
editedLabel = 'Edited',
|
|
75
|
+
}) => {
|
|
76
|
+
const { activeChannel, client } = useChatClient();
|
|
77
|
+
const { hasCapability } = useChannelCapabilities();
|
|
78
|
+
|
|
79
|
+
const canReact = hasCapability('send-reaction');
|
|
80
|
+
|
|
81
|
+
const userName = message.user?.name || message.user_id;
|
|
82
|
+
const userAvatar = message.user?.avatar;
|
|
83
|
+
|
|
84
|
+
const quotedMessage = (message as any).quoted_message;
|
|
85
|
+
const isForwarded = !!(message as any).forward_cid;
|
|
86
|
+
const oldTexts = (message as any).old_texts;
|
|
87
|
+
const isEdited = oldTexts && oldTexts.length > 0;
|
|
88
|
+
const hasAttachments = message.attachments && message.attachments.length > 0;
|
|
89
|
+
|
|
90
|
+
const handleReactionToggle = React.useCallback(async (type: string) => {
|
|
91
|
+
if (!activeChannel || !canReact) return;
|
|
92
|
+
const currentUserId = client?.userID;
|
|
93
|
+
const isOwn =
|
|
94
|
+
(message as any).own_reactions?.some((r: any) => r.type === type) ||
|
|
95
|
+
(message as any).latest_reactions?.some((r: any) => r.type === type && (r.user?.id === currentUserId || (r as any).user_id === currentUserId));
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
if (isOwn) {
|
|
99
|
+
await activeChannel.deleteReaction(message.id!, type);
|
|
100
|
+
} else {
|
|
101
|
+
await activeChannel.sendReaction(message.id!, type);
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error('Failed to toggle reaction', err);
|
|
105
|
+
}
|
|
106
|
+
}, [activeChannel, message, client?.userID]);
|
|
107
|
+
|
|
108
|
+
const statusClass =
|
|
109
|
+
message.status === 'sending'
|
|
110
|
+
? 'ermis-message--sending'
|
|
111
|
+
: (message.status === 'error' || message.status === 'failed_offline')
|
|
112
|
+
? 'ermis-message--error'
|
|
113
|
+
: '';
|
|
114
|
+
|
|
115
|
+
const isNewMessage = React.useMemo(() => {
|
|
116
|
+
if (!message.created_at) return false;
|
|
117
|
+
return Date.now() - new Date(message.created_at).getTime() < 1000;
|
|
118
|
+
}, [message.created_at]);
|
|
119
|
+
|
|
120
|
+
const itemClass = [
|
|
121
|
+
'ermis-message-list__item',
|
|
122
|
+
isOwnMessage ? 'ermis-message-list__item--own' : 'ermis-message-list__item--other',
|
|
123
|
+
isFirstInGroup ? 'ermis-message-list__item--group-start' : 'ermis-message-list__item--group-cont',
|
|
124
|
+
isHighlighted ? 'ermis-message-list__item--highlighted' : '',
|
|
125
|
+
isNewMessage ? 'ermis-message-list__item--new' : '',
|
|
126
|
+
statusClass,
|
|
127
|
+
].filter(Boolean).join(' ');
|
|
128
|
+
|
|
129
|
+
const contentClass = [
|
|
130
|
+
'ermis-message-list__item-content',
|
|
131
|
+
hasAttachments ? 'ermis-message-list__item-content--has-attachments' : '',
|
|
132
|
+
].filter(Boolean).join(' ');
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className={itemClass} data-message-id={message.id}>
|
|
136
|
+
{/* Avatar area: show avatar only on first message, otherwise placeholder for alignment */}
|
|
137
|
+
{!isOwnMessage && (
|
|
138
|
+
<div className="ermis-message-list__item-avatar">
|
|
139
|
+
{isFirstInGroup
|
|
140
|
+
? <AvatarComponent image={userAvatar} name={userName} size={28} />
|
|
141
|
+
: <div style={{ width: 28 }} />
|
|
142
|
+
}
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
<div className={contentClass}>
|
|
146
|
+
{!isOwnMessage && isFirstInGroup && (
|
|
147
|
+
<span className="ermis-message-list__item-user">{userName}</span>
|
|
148
|
+
)}
|
|
149
|
+
{/* Quoted message preview */}
|
|
150
|
+
{quotedMessage && onClickQuote && (
|
|
151
|
+
<QuotedMessagePreviewComponent
|
|
152
|
+
quotedMessage={quotedMessage}
|
|
153
|
+
isOwnMessage={isOwnMessage}
|
|
154
|
+
onClick={onClickQuote}
|
|
155
|
+
/>
|
|
156
|
+
)}
|
|
157
|
+
<div className="ermis-message-list__bubble-wrapper">
|
|
158
|
+
<div style={!canReact ? { opacity: 0.5, pointerEvents: 'none' } : {}}>
|
|
159
|
+
<MessageQuickReactions message={message} isOwnMessage={isOwnMessage} />
|
|
160
|
+
</div>
|
|
161
|
+
<MessageBubble message={message} isOwnMessage={isOwnMessage}>
|
|
162
|
+
{isForwarded && (
|
|
163
|
+
<span className="ermis-message-list__forwarded-indicator">{forwardedLabel}</span>
|
|
164
|
+
)}
|
|
165
|
+
<MessageRenderer message={message} isOwnMessage={isOwnMessage} />
|
|
166
|
+
<span className="ermis-message-list__item-time">
|
|
167
|
+
{isEdited && (
|
|
168
|
+
<span
|
|
169
|
+
className="ermis-message-list__edited-indicator"
|
|
170
|
+
// data-tooltip={oldTexts.map((ot: any) => `[${formatTime(ot.created_at)}] ${ot.text}`).join('\n')}
|
|
171
|
+
>
|
|
172
|
+
{editedLabel}
|
|
173
|
+
</span>
|
|
174
|
+
)}
|
|
175
|
+
{formatTime(message.created_at)}
|
|
176
|
+
<InlineStatusIcon status={message.status} isOwnMessage={isOwnMessage} isLastInGroup={isLastInGroup} />
|
|
177
|
+
</span>
|
|
178
|
+
</MessageBubble>
|
|
179
|
+
|
|
180
|
+
{/* Actions: hover buttons + dropdown menu */}
|
|
181
|
+
{message.type !== 'system' && (
|
|
182
|
+
<MessageActionsBoxComponent
|
|
183
|
+
message={message}
|
|
184
|
+
isOwnMessage={isOwnMessage}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
{/* Message Reactions */}
|
|
189
|
+
{MessageReactionsComponent && (
|
|
190
|
+
<div style={!canReact ? { opacity: 0.8, pointerEvents: 'none' } : {}}>
|
|
191
|
+
<MessageReactionsComponent
|
|
192
|
+
reactionCounts={(message as any).reaction_counts}
|
|
193
|
+
ownReactions={(message as any).own_reactions}
|
|
194
|
+
latestReactions={(message as any).latest_reactions}
|
|
195
|
+
onClickReaction={handleReactionToggle}
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
MessageItem.displayName = 'MessageItem';
|
|
205
|
+
|
|
206
|
+
/* ----------------------------------------------------------
|
|
207
|
+
SystemMessageItem — system/notification message row
|
|
208
|
+
---------------------------------------------------------- */
|
|
209
|
+
export const SystemMessageItem: React.FC<SystemMessageItemProps> = React.memo(({
|
|
210
|
+
message,
|
|
211
|
+
isOwnMessage,
|
|
212
|
+
SystemRenderer,
|
|
213
|
+
}) => (
|
|
214
|
+
<div className="ermis-message-list__system">
|
|
215
|
+
<SystemRenderer message={message} isOwnMessage={isOwnMessage} />
|
|
216
|
+
</div>
|
|
217
|
+
));
|
|
218
|
+
SystemMessageItem.displayName = 'SystemMessageItem';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import type { FormatMessageResponse } from '@ermis-network/ermis-chat-sdk';
|
|
3
|
+
import { useChatClient } from '../hooks/useChatClient';
|
|
4
|
+
|
|
5
|
+
const QUICK_REACTIONS = ['like', 'love', 'haha', 'sad', 'fire'];
|
|
6
|
+
const EMOJI_MAP: Record<string, string> = {
|
|
7
|
+
like: '👍',
|
|
8
|
+
love: '❤️',
|
|
9
|
+
haha: '😂',
|
|
10
|
+
sad: '😢',
|
|
11
|
+
fire: '🔥',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const MessageQuickReactions: React.FC<{
|
|
15
|
+
message: FormatMessageResponse;
|
|
16
|
+
isOwnMessage: boolean;
|
|
17
|
+
}> = React.memo(({ message, isOwnMessage }) => {
|
|
18
|
+
const { activeChannel, client } = useChatClient();
|
|
19
|
+
const currentUserId = client?.userID;
|
|
20
|
+
|
|
21
|
+
const handleReactionToggle = useCallback(
|
|
22
|
+
async (type: string) => {
|
|
23
|
+
if (!activeChannel) return;
|
|
24
|
+
const isOwn =
|
|
25
|
+
(message as any).own_reactions?.some((r: any) => r.type === type) ||
|
|
26
|
+
(message as any).latest_reactions?.some(
|
|
27
|
+
(r: any) => r.type === type && (r.user?.id === currentUserId || (r as any).user_id === currentUserId)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
if (isOwn) {
|
|
32
|
+
await activeChannel.deleteReaction(message.id!, type);
|
|
33
|
+
} else {
|
|
34
|
+
await activeChannel.sendReaction(message.id!, type);
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error('Failed to toggle reaction', err);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
[activeChannel, message, currentUserId]
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className={`ermis-message-quick-reactions ${isOwnMessage ? 'ermis-message-quick-reactions--own' : ''}`}>
|
|
45
|
+
{QUICK_REACTIONS.map((type) => {
|
|
46
|
+
const isOwn =
|
|
47
|
+
(message as any).own_reactions?.some((r: any) => r.type === type) ||
|
|
48
|
+
(message as any).latest_reactions?.some(
|
|
49
|
+
(r: any) => r.type === type && (r.user?.id === currentUserId || (r as any).user_id === currentUserId)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<button
|
|
54
|
+
key={type}
|
|
55
|
+
className={`ermis-message-quick-reactions__btn ${
|
|
56
|
+
isOwn ? 'ermis-message-quick-reactions__btn--active' : ''
|
|
57
|
+
}`}
|
|
58
|
+
title={type}
|
|
59
|
+
onClick={(e) => {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
e.stopPropagation();
|
|
62
|
+
handleReactionToggle(type);
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
{EMOJI_MAP[type]}
|
|
66
|
+
</button>
|
|
67
|
+
);
|
|
68
|
+
})}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
MessageQuickReactions.displayName = 'MessageQuickReactions';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { MessageReactionsProps } from '../types';
|
|
3
|
+
|
|
4
|
+
import { useChatClient } from '../hooks/useChatClient';
|
|
5
|
+
|
|
6
|
+
const defaultReactionEmojiMap: Record<string, string> = {
|
|
7
|
+
like: '👍',
|
|
8
|
+
love: '❤️',
|
|
9
|
+
haha: '😂',
|
|
10
|
+
sad: '😢',
|
|
11
|
+
fire: '🔥',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const MessageReactions: React.FC<MessageReactionsProps> = React.memo(({
|
|
15
|
+
reactionCounts,
|
|
16
|
+
ownReactions,
|
|
17
|
+
latestReactions,
|
|
18
|
+
onClickReaction,
|
|
19
|
+
}) => {
|
|
20
|
+
const { client } = useChatClient();
|
|
21
|
+
const currentUserId = client?.userID;
|
|
22
|
+
|
|
23
|
+
if (!reactionCounts || Object.keys(reactionCounts).length === 0) return null;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="ermis-message-reactions">
|
|
27
|
+
{Object.entries(reactionCounts).map(([type, count]) => {
|
|
28
|
+
const isOwn =
|
|
29
|
+
ownReactions?.some((r) => r.type === type) ||
|
|
30
|
+
latestReactions?.some((r) => r.type === type && (r.user?.id === currentUserId || (r as any).user_id === currentUserId));
|
|
31
|
+
|
|
32
|
+
// Find users who reacted with this type for the tooltip
|
|
33
|
+
const userNames = latestReactions
|
|
34
|
+
?.filter((r) => r.type === type)
|
|
35
|
+
.map((r: any) => r.user?.name || r.user?.id || r.user_id || 'Someone');
|
|
36
|
+
|
|
37
|
+
const tooltip = userNames && userNames.length > 0 ? userNames.join('\n') : type;
|
|
38
|
+
const emoji = defaultReactionEmojiMap[type] || type;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<button
|
|
42
|
+
key={type}
|
|
43
|
+
className={`ermis-message-reactions__item ${
|
|
44
|
+
isOwn ? 'ermis-message-reactions__item--active' : ''
|
|
45
|
+
}`}
|
|
46
|
+
data-tooltip={tooltip}
|
|
47
|
+
onClick={() => onClickReaction?.(type)}
|
|
48
|
+
type="button"
|
|
49
|
+
>
|
|
50
|
+
<span className="ermis-message-reactions__emoji">{emoji}</span>
|
|
51
|
+
<span className="ermis-message-reactions__count">{count}</span>
|
|
52
|
+
</button>
|
|
53
|
+
);
|
|
54
|
+
})}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
MessageReactions.displayName = 'MessageReactions';
|