@linktr.ee/messaging-react 1.12.7 → 1.12.8-rc-1765974383
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/assets/index.css +1 -1
- package/dist/index.d.ts +20 -0
- package/dist/index.js +919 -713
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelView.tsx +34 -23
- package/src/components/CustomMessage/CustomMessage.stories.tsx +220 -0
- package/src/components/CustomMessage/MessageTag.stories.tsx +80 -0
- package/src/components/CustomMessage/MessageTag.tsx +68 -0
- package/src/components/CustomMessage/index.tsx +260 -0
- package/src/components/CustomSystemMessage/index.tsx +10 -1
- package/src/stream-custom-data.ts +20 -0
- package/src/styles.css +34 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import React, { useMemo, useState } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
Attachment as DefaultAttachment,
|
|
5
|
+
Avatar as DefaultAvatar,
|
|
6
|
+
EditMessageModal as DefaultEditMessageModal,
|
|
7
|
+
MessageBounceModal,
|
|
8
|
+
MessageBouncePrompt as DefaultMessageBouncePrompt,
|
|
9
|
+
MessageBlocked as DefaultMessageBlocked,
|
|
10
|
+
MessageDeleted as DefaultMessageDeleted,
|
|
11
|
+
MessageEditedTimestamp,
|
|
12
|
+
MessageErrorIcon,
|
|
13
|
+
MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator,
|
|
14
|
+
MessageOptions as DefaultMessageOptions,
|
|
15
|
+
MessageRepliesCountButton as DefaultMessageRepliesCountButton,
|
|
16
|
+
MessageStatus as DefaultMessageStatus,
|
|
17
|
+
MessageText,
|
|
18
|
+
MessageTimestamp as DefaultMessageTimestamp,
|
|
19
|
+
Poll,
|
|
20
|
+
ReactionsList as DefaultReactionList,
|
|
21
|
+
ReminderNotification as DefaultReminderNotification,
|
|
22
|
+
StreamedMessageText as DefaultStreamedMessageText,
|
|
23
|
+
areMessageUIPropsEqual,
|
|
24
|
+
isDateSeparatorMessage,
|
|
25
|
+
isMessageBlocked,
|
|
26
|
+
isMessageBounced,
|
|
27
|
+
isMessageEdited,
|
|
28
|
+
messageHasAttachments,
|
|
29
|
+
messageHasReactions,
|
|
30
|
+
useComponentContext,
|
|
31
|
+
useChatContext,
|
|
32
|
+
useMessageContext,
|
|
33
|
+
useMessageReminder,
|
|
34
|
+
useTranslationContext,
|
|
35
|
+
type MessageContextValue,
|
|
36
|
+
type MessageUIComponentProps,
|
|
37
|
+
} from 'stream-chat-react'
|
|
38
|
+
|
|
39
|
+
import { MessageTag } from './MessageTag'
|
|
40
|
+
|
|
41
|
+
type CustomMessageWithContextProps = MessageContextValue
|
|
42
|
+
|
|
43
|
+
const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
44
|
+
const {
|
|
45
|
+
additionalMessageInputProps,
|
|
46
|
+
editing,
|
|
47
|
+
endOfGroup,
|
|
48
|
+
firstOfGroup,
|
|
49
|
+
groupedByUser,
|
|
50
|
+
handleAction,
|
|
51
|
+
handleOpenThread,
|
|
52
|
+
handleRetry,
|
|
53
|
+
highlighted,
|
|
54
|
+
isMessageAIGenerated,
|
|
55
|
+
isMyMessage,
|
|
56
|
+
message,
|
|
57
|
+
onUserClick,
|
|
58
|
+
onUserHover,
|
|
59
|
+
renderText,
|
|
60
|
+
threadList,
|
|
61
|
+
} = props
|
|
62
|
+
|
|
63
|
+
const { client } = useChatContext('CustomMessage')
|
|
64
|
+
const { t } = useTranslationContext('CustomMessage')
|
|
65
|
+
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false)
|
|
66
|
+
const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false)
|
|
67
|
+
const reminder = useMessageReminder(message.id)
|
|
68
|
+
|
|
69
|
+
const {
|
|
70
|
+
Attachment = DefaultAttachment,
|
|
71
|
+
Avatar = DefaultAvatar,
|
|
72
|
+
EditMessageModal = DefaultEditMessageModal,
|
|
73
|
+
MessageOptions = DefaultMessageOptions,
|
|
74
|
+
MessageActions = MessageOptions,
|
|
75
|
+
MessageBlocked = DefaultMessageBlocked,
|
|
76
|
+
MessageBouncePrompt = DefaultMessageBouncePrompt,
|
|
77
|
+
MessageDeleted = DefaultMessageDeleted,
|
|
78
|
+
MessageIsThreadReplyInChannelButtonIndicator = DefaultMessageIsThreadReplyInChannelButtonIndicator,
|
|
79
|
+
MessageRepliesCountButton = DefaultMessageRepliesCountButton,
|
|
80
|
+
MessageStatus = DefaultMessageStatus,
|
|
81
|
+
MessageTimestamp = DefaultMessageTimestamp,
|
|
82
|
+
ReactionsList = DefaultReactionList,
|
|
83
|
+
ReminderNotification = DefaultReminderNotification,
|
|
84
|
+
StreamedMessageText = DefaultStreamedMessageText,
|
|
85
|
+
PinIndicator,
|
|
86
|
+
} = useComponentContext('CustomMessage')
|
|
87
|
+
|
|
88
|
+
const hasAttachment = messageHasAttachments(message)
|
|
89
|
+
const hasReactions = messageHasReactions(message)
|
|
90
|
+
const isAIGenerated = useMemo(
|
|
91
|
+
() => isMessageAIGenerated?.(message),
|
|
92
|
+
[isMessageAIGenerated, message]
|
|
93
|
+
)
|
|
94
|
+
const finalAttachments = useMemo(
|
|
95
|
+
() =>
|
|
96
|
+
!message.shared_location && !message.attachments
|
|
97
|
+
? []
|
|
98
|
+
: !message.shared_location
|
|
99
|
+
? message.attachments
|
|
100
|
+
: [message.shared_location, ...(message.attachments ?? [])],
|
|
101
|
+
[message]
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if (isDateSeparatorMessage(message)) {
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (message.deleted_at || message.type === 'deleted') {
|
|
109
|
+
return <MessageDeleted message={message} />
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isMessageBlocked(message)) {
|
|
113
|
+
return <MessageBlocked />
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const showMetadata = !groupedByUser || endOfGroup
|
|
117
|
+
const showReplyCountButton = !threadList && !!message.reply_count
|
|
118
|
+
const showIsReplyInChannel =
|
|
119
|
+
!threadList && message.show_in_channel && message.parent_id
|
|
120
|
+
const allowRetry =
|
|
121
|
+
message.status === 'failed' && message.error?.status !== 403
|
|
122
|
+
const isBounced = isMessageBounced(message)
|
|
123
|
+
const isEdited = isMessageEdited(message) && !isAIGenerated
|
|
124
|
+
|
|
125
|
+
let handleClick: (() => void) | undefined = undefined
|
|
126
|
+
|
|
127
|
+
if (allowRetry) {
|
|
128
|
+
handleClick = () => handleRetry(message)
|
|
129
|
+
} else if (isBounced) {
|
|
130
|
+
handleClick = () => setIsBounceDialogOpen(true)
|
|
131
|
+
} else if (isEdited) {
|
|
132
|
+
handleClick = () => setEditedTimestampOpen((prev) => !prev)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const rootClassName = classNames(
|
|
136
|
+
'str-chat__message str-chat__message-simple',
|
|
137
|
+
`str-chat__message--${message.type}`,
|
|
138
|
+
`str-chat__message--${message.status}`,
|
|
139
|
+
isMyMessage()
|
|
140
|
+
? 'str-chat__message--me str-chat__message-simple--me'
|
|
141
|
+
: 'str-chat__message--other',
|
|
142
|
+
message.text ? 'str-chat__message--has-text' : 'has-no-text',
|
|
143
|
+
{
|
|
144
|
+
'str-chat__message--has-attachment': hasAttachment,
|
|
145
|
+
'str-chat__message--highlighted': highlighted,
|
|
146
|
+
'str-chat__message--pinned pinned-message': message.pinned,
|
|
147
|
+
'str-chat__message--with-reactions': hasReactions,
|
|
148
|
+
'str-chat__message-send-can-be-retried':
|
|
149
|
+
message?.status === 'failed' && message?.error?.status !== 403,
|
|
150
|
+
'str-chat__message-with-thread-link':
|
|
151
|
+
showReplyCountButton || showIsReplyInChannel,
|
|
152
|
+
'str-chat__virtual-message__wrapper--end': endOfGroup,
|
|
153
|
+
'str-chat__virtual-message__wrapper--first': firstOfGroup,
|
|
154
|
+
'str-chat__virtual-message__wrapper--group': groupedByUser,
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
const poll = message.poll_id && client.polls.fromState(message.poll_id)
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
{editing && (
|
|
163
|
+
<EditMessageModal
|
|
164
|
+
additionalMessageInputProps={additionalMessageInputProps}
|
|
165
|
+
/>
|
|
166
|
+
)}
|
|
167
|
+
{isBounceDialogOpen && (
|
|
168
|
+
<MessageBounceModal
|
|
169
|
+
MessageBouncePrompt={MessageBouncePrompt}
|
|
170
|
+
onClose={() => setIsBounceDialogOpen(false)}
|
|
171
|
+
open={isBounceDialogOpen}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
<div className={rootClassName} key={message.id}>
|
|
175
|
+
{PinIndicator && <PinIndicator />}
|
|
176
|
+
{!!reminder && <ReminderNotification reminder={reminder} />}
|
|
177
|
+
{message.user && (
|
|
178
|
+
<Avatar
|
|
179
|
+
image={message.user.image}
|
|
180
|
+
name={message.user.name || message.user.id}
|
|
181
|
+
onClick={onUserClick}
|
|
182
|
+
onMouseOver={onUserHover}
|
|
183
|
+
user={message.user}
|
|
184
|
+
/>
|
|
185
|
+
)}
|
|
186
|
+
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
|
187
|
+
<div
|
|
188
|
+
className={classNames('str-chat__message-inner', {
|
|
189
|
+
'str-chat__simple-message--error-failed': allowRetry || isBounced,
|
|
190
|
+
})}
|
|
191
|
+
data-testid="message-inner"
|
|
192
|
+
onClick={handleClick}
|
|
193
|
+
onKeyDown={handleClick}
|
|
194
|
+
role={handleClick ? 'button' : undefined}
|
|
195
|
+
tabIndex={handleClick ? 0 : undefined}
|
|
196
|
+
>
|
|
197
|
+
<MessageActions />
|
|
198
|
+
<div className="str-chat__message-reactions-host">
|
|
199
|
+
{hasReactions && <ReactionsList reverse />}
|
|
200
|
+
</div>
|
|
201
|
+
<div className="str-chat__message-bubble">
|
|
202
|
+
{poll && <Poll poll={poll} />}
|
|
203
|
+
{finalAttachments?.length && !message.quoted_message ? (
|
|
204
|
+
<Attachment
|
|
205
|
+
actionHandler={handleAction}
|
|
206
|
+
attachments={finalAttachments}
|
|
207
|
+
/>
|
|
208
|
+
) : null}
|
|
209
|
+
{/* Custom tag component - positioned above message text */}
|
|
210
|
+
<MessageTag message={message} />
|
|
211
|
+
{isAIGenerated ? (
|
|
212
|
+
<StreamedMessageText message={message} renderText={renderText} />
|
|
213
|
+
) : (
|
|
214
|
+
<MessageText message={message} renderText={renderText} />
|
|
215
|
+
)}
|
|
216
|
+
<MessageErrorIcon />
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
{showReplyCountButton && (
|
|
220
|
+
<MessageRepliesCountButton
|
|
221
|
+
onClick={handleOpenThread}
|
|
222
|
+
reply_count={message.reply_count}
|
|
223
|
+
/>
|
|
224
|
+
)}
|
|
225
|
+
{showIsReplyInChannel && (
|
|
226
|
+
<MessageIsThreadReplyInChannelButtonIndicator />
|
|
227
|
+
)}
|
|
228
|
+
{showMetadata && (
|
|
229
|
+
<div className="str-chat__message-metadata">
|
|
230
|
+
<MessageStatus />
|
|
231
|
+
{!isMyMessage() && !!message.user && (
|
|
232
|
+
<span className="str-chat__message-simple-name">
|
|
233
|
+
{message.user.name || message.user.id}
|
|
234
|
+
</span>
|
|
235
|
+
)}
|
|
236
|
+
<MessageTimestamp customClass="str-chat__message-simple-timestamp" />
|
|
237
|
+
{isEdited && (
|
|
238
|
+
<span className="str-chat__mesage-simple-edited">
|
|
239
|
+
{t('Edited')}
|
|
240
|
+
</span>
|
|
241
|
+
)}
|
|
242
|
+
{isEdited && (
|
|
243
|
+
<MessageEditedTimestamp calendar open={isEditedTimestampOpen} />
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
</>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const MemoizedCustomMessage = React.memo(
|
|
253
|
+
CustomMessageWithContext,
|
|
254
|
+
areMessageUIPropsEqual
|
|
255
|
+
) as typeof CustomMessageWithContext
|
|
256
|
+
|
|
257
|
+
export const CustomMessage = (props: MessageUIComponentProps) => {
|
|
258
|
+
const messageContext = useMessageContext('CustomMessage')
|
|
259
|
+
return <MemoizedCustomMessage {...messageContext} {...props} />
|
|
260
|
+
}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import { StarFourIcon } from '@phosphor-icons/react'
|
|
2
|
+
import classNames from 'classnames'
|
|
1
3
|
import { MessageTimestamp, type EventComponentProps } from 'stream-chat-react'
|
|
2
4
|
|
|
3
5
|
export const CustomSystemMessage: React.FC<EventComponentProps> = (props) => {
|
|
4
6
|
const isDateHidden = props.message.hide_date === true
|
|
7
|
+
const isPriority = props.message.system_type === 'priority'
|
|
5
8
|
|
|
6
9
|
return (
|
|
7
|
-
<div
|
|
10
|
+
<div
|
|
11
|
+
className={classNames('str-chat__message--system', {
|
|
12
|
+
'str-chat__message--system--priority': isPriority,
|
|
13
|
+
})}
|
|
14
|
+
data-testid="message-system"
|
|
15
|
+
>
|
|
16
|
+
{isPriority && <StarFourIcon size={18} className="mx-auto mb-2 mt-3" />}
|
|
8
17
|
<div className="str-chat__message--system__text">
|
|
9
18
|
<div className="str-chat__message--system__line"></div>
|
|
10
19
|
<p>{props.message.text}</p>
|
|
@@ -21,6 +21,26 @@ declare module 'stream-chat' {
|
|
|
21
21
|
* Used by CustomSystemMessage component.
|
|
22
22
|
*/
|
|
23
23
|
hide_date?: boolean
|
|
24
|
+
/**
|
|
25
|
+
* Type of system message for special styling.
|
|
26
|
+
* Used by CustomSystemMessage component.
|
|
27
|
+
*/
|
|
28
|
+
system_type?: 'priority'
|
|
29
|
+
/**
|
|
30
|
+
* Type of tag to display on the message.
|
|
31
|
+
* Used by MessageTag component.
|
|
32
|
+
*/
|
|
33
|
+
tag_type?: 'paid-priority' | 'tip'
|
|
34
|
+
/**
|
|
35
|
+
* Monetary amount for tip messages.
|
|
36
|
+
* Used by MessageTag component.
|
|
37
|
+
*/
|
|
38
|
+
amount?: number
|
|
39
|
+
/**
|
|
40
|
+
* Currency code (e.g. 'USD', 'EUR') for tip messages.
|
|
41
|
+
* Used by MessageTag component.
|
|
42
|
+
*/
|
|
43
|
+
currency?: string
|
|
24
44
|
}
|
|
25
45
|
}
|
|
26
46
|
|
package/src/styles.css
CHANGED
|
@@ -102,3 +102,37 @@
|
|
|
102
102
|
.str-chat__list {
|
|
103
103
|
background: transparent;
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
/* Custom message tag styles */
|
|
107
|
+
.message-tag {
|
|
108
|
+
display: inline-flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
gap: 4px;
|
|
111
|
+
margin: var(--str-chat__spacing-4);
|
|
112
|
+
margin-bottom: 0;
|
|
113
|
+
padding: 7px 8px;
|
|
114
|
+
border-radius: 20px;
|
|
115
|
+
font-size: 12px;
|
|
116
|
+
font-weight: 500;
|
|
117
|
+
color: white;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.message-tag__icon {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.message-tag--tip {
|
|
126
|
+
background-color: #008236;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.message-tag--paid-priority {
|
|
130
|
+
background-color: #7f22fe;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.str-chat__message--system--priority {
|
|
134
|
+
margin-top: var(--str-chat__spacing-3);
|
|
135
|
+
margin-bottom: var(--str-chat__spacing-2);
|
|
136
|
+
font-size: 12px;
|
|
137
|
+
color: var(--text-secondary, #0000008c);
|
|
138
|
+
}
|