@linktr.ee/messaging-react 1.18.1 → 1.19.1
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.d.ts +7 -0
- package/dist/index.js +877 -869
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelList/CustomChannelPreview.stories.tsx +93 -1
- package/src/components/ChannelList/CustomChannelPreview.tsx +19 -5
- package/src/components/ChannelList/index.tsx +3 -1
- package/src/components/MessagingShell/index.tsx +2 -0
- package/src/types.ts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Channel,
|
|
5
|
+
CustomMessageData,
|
|
6
|
+
LocalMessage,
|
|
7
|
+
StreamChat,
|
|
8
|
+
} from 'stream-chat'
|
|
4
9
|
|
|
5
10
|
import CustomChannelPreview from './CustomChannelPreview'
|
|
6
11
|
|
|
@@ -220,6 +225,93 @@ ChatbotMessage.args = {
|
|
|
220
225
|
}),
|
|
221
226
|
}
|
|
222
227
|
|
|
228
|
+
const renderMessagePreviewWithMarkers = (
|
|
229
|
+
message: LocalMessage | undefined,
|
|
230
|
+
defaultPreview?: string
|
|
231
|
+
): React.ReactNode => {
|
|
232
|
+
if (!message) return defaultPreview
|
|
233
|
+
|
|
234
|
+
const metadata = (
|
|
235
|
+
message as LocalMessage & { metadata?: Record<string, unknown> }
|
|
236
|
+
).metadata
|
|
237
|
+
|
|
238
|
+
if (metadata?.custom_type === 'MESSAGE_URGENT') {
|
|
239
|
+
return (
|
|
240
|
+
<>
|
|
241
|
+
<span className="text-red-600 font-bold">🔥 URGENT:</span>{' '}
|
|
242
|
+
{defaultPreview}
|
|
243
|
+
</>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (metadata?.is_priority) {
|
|
248
|
+
return `⚡ ${defaultPreview}`
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (metadata?.is_vip) {
|
|
252
|
+
return `👑 ${defaultPreview}`
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return defaultPreview
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const WithCustomMessagePreview: StoryFn = () => {
|
|
259
|
+
const channels = [
|
|
260
|
+
createMockChannel({
|
|
261
|
+
id: 'channel-urgent',
|
|
262
|
+
participantName: 'Alice Urgent',
|
|
263
|
+
participantId: 'participant-urgent',
|
|
264
|
+
participantImage: 'https://i.pravatar.cc/150?img=36',
|
|
265
|
+
lastMessageText: 'Critical issue needs immediate attention!',
|
|
266
|
+
lastMessageTime: new Date(Date.now() - 1000 * 60), // 1 minute ago
|
|
267
|
+
lastMessageMetadata: { custom_type: 'MESSAGE_URGENT' },
|
|
268
|
+
unreadCount: 3,
|
|
269
|
+
}),
|
|
270
|
+
createMockChannel({
|
|
271
|
+
id: 'channel-priority',
|
|
272
|
+
participantName: 'Bob Priority',
|
|
273
|
+
participantId: 'participant-priority',
|
|
274
|
+
participantImage: 'https://i.pravatar.cc/150?img=37',
|
|
275
|
+
lastMessageText: 'High priority message',
|
|
276
|
+
lastMessageTime: new Date(Date.now() - 1000 * 60 * 5), // 5 minutes ago
|
|
277
|
+
lastMessageMetadata: { is_priority: true },
|
|
278
|
+
unreadCount: 1,
|
|
279
|
+
}),
|
|
280
|
+
createMockChannel({
|
|
281
|
+
id: 'channel-vip',
|
|
282
|
+
participantName: 'Carol VIP',
|
|
283
|
+
participantId: 'participant-vip',
|
|
284
|
+
participantImage: 'https://i.pravatar.cc/150?img=38',
|
|
285
|
+
lastMessageText: 'Thanks for the support!',
|
|
286
|
+
lastMessageTime: new Date(Date.now() - 1000 * 60 * 10), // 10 minutes ago
|
|
287
|
+
lastMessageMetadata: { is_vip: true },
|
|
288
|
+
}),
|
|
289
|
+
createMockChannel({
|
|
290
|
+
id: 'channel-normal',
|
|
291
|
+
participantName: 'David Normal',
|
|
292
|
+
participantId: 'participant-normal',
|
|
293
|
+
participantImage: 'https://i.pravatar.cc/150?img=39',
|
|
294
|
+
lastMessageText: 'Hey, how are you?',
|
|
295
|
+
lastMessageTime: new Date(Date.now() - 1000 * 60 * 30), // 30 minutes ago
|
|
296
|
+
}),
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<div className="w-[360px] bg-chalk border border-sand overflow-hidden">
|
|
301
|
+
{channels.map((channel) => (
|
|
302
|
+
<CustomChannelPreview
|
|
303
|
+
key={channel.id}
|
|
304
|
+
channel={channel}
|
|
305
|
+
onChannelSelect={(channel) => {
|
|
306
|
+
console.log('Channel selected:', channel.id)
|
|
307
|
+
}}
|
|
308
|
+
renderMessagePreview={renderMessagePreviewWithMarkers}
|
|
309
|
+
/>
|
|
310
|
+
))}
|
|
311
|
+
</div>
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
|
|
223
315
|
export const SelectedWithUnread: StoryFn<ComponentProps> = Template.bind({})
|
|
224
316
|
SelectedWithUnread.args = {
|
|
225
317
|
channel: createMockChannel({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import { Channel } from 'stream-chat'
|
|
3
|
+
import { Channel, LocalMessage } from 'stream-chat'
|
|
4
4
|
import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
|
|
5
5
|
|
|
6
6
|
import { formatRelativeTime } from '../../utils/formatRelativeTime'
|
|
@@ -11,13 +11,24 @@ type CustomChannelPreviewProps = ChannelPreviewUIComponentProps & {
|
|
|
11
11
|
selectedChannel?: Channel | null
|
|
12
12
|
onChannelSelect: (channel: Channel) => void
|
|
13
13
|
debug?: boolean
|
|
14
|
+
renderMessagePreview?: (
|
|
15
|
+
message: LocalMessage | undefined,
|
|
16
|
+
defaultPreview?: string
|
|
17
|
+
) => React.ReactNode
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
21
|
* Custom channel preview that handles selection
|
|
18
22
|
*/
|
|
19
23
|
const CustomChannelPreview = React.memo<CustomChannelPreviewProps>(
|
|
20
|
-
({
|
|
24
|
+
({
|
|
25
|
+
channel,
|
|
26
|
+
selectedChannel,
|
|
27
|
+
onChannelSelect,
|
|
28
|
+
debug = false,
|
|
29
|
+
unread,
|
|
30
|
+
renderMessagePreview,
|
|
31
|
+
}) => {
|
|
21
32
|
const isSelected = selectedChannel?.id === channel?.id
|
|
22
33
|
|
|
23
34
|
const handleClick = () => {
|
|
@@ -67,6 +78,10 @@ const CustomChannelPreview = React.memo<CustomChannelPreviewProps>(
|
|
|
67
78
|
? isChatbotMessage(lastMessage)
|
|
68
79
|
: false
|
|
69
80
|
|
|
81
|
+
const messagePreview = renderMessagePreview
|
|
82
|
+
? renderMessagePreview(lastMessage, lastMessageText)
|
|
83
|
+
: `${isLastMessageFromChatbot ? '✨ ' : ''}${lastMessageText}`
|
|
84
|
+
|
|
70
85
|
// Use the unread prop passed by Stream Chat (reactive and updates automatically)
|
|
71
86
|
const unreadCount = unread ?? 0
|
|
72
87
|
|
|
@@ -123,9 +138,8 @@ const CustomChannelPreview = React.memo<CustomChannelPreviewProps>(
|
|
|
123
138
|
|
|
124
139
|
{/* Message and unread badge row */}
|
|
125
140
|
<div className="flex items-center justify-between gap-2 min-w-0">
|
|
126
|
-
<p className="text-xs text-stone
|
|
127
|
-
{
|
|
128
|
-
{lastMessageText}
|
|
141
|
+
<p className="text-xs text-stone flex-1 line-clamp-1">
|
|
142
|
+
{messagePreview}
|
|
129
143
|
</p>
|
|
130
144
|
{unreadCount > 0 && (
|
|
131
145
|
<span className="bg-[#7f22fe] text-white text-xs px-2 py-0.5 rounded-full min-w-[20px] text-center flex-shrink-0">
|
|
@@ -18,6 +18,7 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
18
18
|
filters,
|
|
19
19
|
className,
|
|
20
20
|
customEmptyStateIndicator,
|
|
21
|
+
renderMessagePreview,
|
|
21
22
|
}) => {
|
|
22
23
|
// Track renders
|
|
23
24
|
const renderCountRef = React.useRef(0)
|
|
@@ -42,10 +43,11 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
42
43
|
selectedChannel={selectedChannel}
|
|
43
44
|
onChannelSelect={onChannelSelect}
|
|
44
45
|
debug={debug}
|
|
46
|
+
renderMessagePreview={renderMessagePreview}
|
|
45
47
|
/>
|
|
46
48
|
)
|
|
47
49
|
return Preview
|
|
48
|
-
}, [selectedChannel, onChannelSelect, debug])
|
|
50
|
+
}, [selectedChannel, onChannelSelect, debug, renderMessagePreview])
|
|
49
51
|
|
|
50
52
|
return (
|
|
51
53
|
<div
|
|
@@ -34,6 +34,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
34
34
|
messageMetadata,
|
|
35
35
|
onMessageSent,
|
|
36
36
|
showStarButton = false,
|
|
37
|
+
renderMessagePreview,
|
|
37
38
|
}) => {
|
|
38
39
|
const {
|
|
39
40
|
service,
|
|
@@ -449,6 +450,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
449
450
|
selectedChannel={selectedChannel || undefined}
|
|
450
451
|
filters={channelFilters}
|
|
451
452
|
customEmptyStateIndicator={channelListCustomEmptyStateIndicator}
|
|
453
|
+
renderMessagePreview={renderMessagePreview}
|
|
452
454
|
/>
|
|
453
455
|
</div>
|
|
454
456
|
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
import type {
|
|
6
6
|
Channel,
|
|
7
7
|
ChannelFilters,
|
|
8
|
+
LocalMessage,
|
|
8
9
|
SendMessageAPIResponse,
|
|
9
10
|
} from 'stream-chat'
|
|
10
11
|
import { EmptyStateIndicatorProps } from 'stream-chat-react'
|
|
@@ -67,6 +68,10 @@ export interface ChannelListProps {
|
|
|
67
68
|
className?: string
|
|
68
69
|
filters: ChannelFilters
|
|
69
70
|
customEmptyStateIndicator?: React.ComponentType<EmptyStateIndicatorProps>
|
|
71
|
+
renderMessagePreview?: (
|
|
72
|
+
message: LocalMessage | undefined,
|
|
73
|
+
defaultPreview?: string
|
|
74
|
+
) => React.ReactNode
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
/**
|
|
@@ -198,6 +203,15 @@ export interface MessagingShellProps extends ChannelViewPassthroughProps {
|
|
|
198
203
|
* Useful for showing a custom empty state indicator when the channel list is empty.
|
|
199
204
|
*/
|
|
200
205
|
channelListCustomEmptyStateIndicator?: React.ComponentType<EmptyStateIndicatorProps>
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Custom render function for message preview text in channel list.
|
|
209
|
+
* Receives the message and a default preview string.
|
|
210
|
+
*/
|
|
211
|
+
renderMessagePreview?: (
|
|
212
|
+
message: LocalMessage | undefined,
|
|
213
|
+
defaultPreview?: string
|
|
214
|
+
) => React.ReactNode
|
|
201
215
|
}
|
|
202
216
|
|
|
203
217
|
/**
|