@linktr.ee/messaging-react 3.5.0-rc-1782408966 → 3.5.0-rc-1782792813
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/{Card-iekRe643.js → Card-BWWBaNKF.js} +2 -2
- package/dist/{Card-iekRe643.js.map → Card-BWWBaNKF.js.map} +1 -1
- package/dist/{Card-DJtDI03Y.cjs → Card-Bj4YaHav.cjs} +2 -2
- package/dist/{Card-DJtDI03Y.cjs.map → Card-Bj4YaHav.cjs.map} +1 -1
- package/dist/{Card-CUHEDe8O.js → Card-BnGeMt4f.js} +2 -2
- package/dist/{Card-CUHEDe8O.js.map → Card-BnGeMt4f.js.map} +1 -1
- package/dist/{Card-BNzC_bp8.js → Card-DkXzpFLA.js} +3 -3
- package/dist/{Card-BNzC_bp8.js.map → Card-DkXzpFLA.js.map} +1 -1
- package/dist/{Card-DuMr5KMP.cjs → Card-DpCwDviq.cjs} +2 -2
- package/dist/{Card-DuMr5KMP.cjs.map → Card-DpCwDviq.cjs.map} +1 -1
- package/dist/{Card-DEa50m9p.cjs → Card-qrDaFHtJ.cjs} +2 -2
- package/dist/{Card-DEa50m9p.cjs.map → Card-qrDaFHtJ.cjs.map} +1 -1
- package/dist/{LockedThumbnail-CvpRZFi-.js → LockedThumbnail-DJR--PAi.js} +2 -2
- package/dist/{LockedThumbnail-CvpRZFi-.js.map → LockedThumbnail-DJR--PAi.js.map} +1 -1
- package/dist/{LockedThumbnail-aME0f_ZF.cjs → LockedThumbnail-DhXEk-NX.cjs} +2 -2
- package/dist/{LockedThumbnail-aME0f_ZF.cjs.map → LockedThumbnail-DhXEk-NX.cjs.map} +1 -1
- package/dist/index-DzOA7Yu8.cjs +2 -0
- package/dist/index-DzOA7Yu8.cjs.map +1 -0
- package/dist/{index-BlpDX5fl.js → index-ubCRSgQk.js} +1423 -1328
- package/dist/index-ubCRSgQk.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +40 -16
- package/dist/index.js +19 -19
- package/package.json +2 -2
- package/src/components/ChannelList/ChannelList.stories.tsx +5 -3
- package/src/components/ChannelList/CustomChannelPreview.stories.tsx +111 -1
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +58 -46
- package/src/components/ChannelList/CustomChannelPreview.tsx +12 -13
- package/src/components/ChannelList/index.test.tsx +25 -2
- package/src/components/ChannelList/index.tsx +2 -1
- package/src/components/ChannelView.stories.tsx +45 -12
- package/src/components/ChannelView.test.tsx +64 -62
- package/src/components/ChannelView.tsx +123 -68
- package/src/components/CustomMessage/index.tsx +1 -0
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +34 -0
- package/src/components/CustomMessageInput/index.tsx +27 -2
- package/src/components/MediaMessage/index.tsx +13 -5
- package/src/components/MessagingShell/index.tsx +2 -0
- package/src/components/ParticipantBadges.tsx +42 -0
- package/src/hooks/useChannelStar.ts +9 -6
- package/src/index.ts +1 -2
- package/src/stories/mocks.tsx +30 -5
- package/src/stream-custom-data.ts +12 -5
- package/src/types.ts +27 -1
- package/dist/index-BIWS3Inh.cjs +0 -2
- package/dist/index-BIWS3Inh.cjs.map +0 -1
- package/dist/index-BlpDX5fl.js.map +0 -1
- package/src/components/VerifiedBadge/index.tsx +0 -25
|
@@ -198,6 +198,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
198
198
|
className={rootClassName}
|
|
199
199
|
key={message.id}
|
|
200
200
|
data-message-id={message.id}
|
|
201
|
+
data-dd-privacy="mask"
|
|
201
202
|
>
|
|
202
203
|
{PinIndicator && <PinIndicator />}
|
|
203
204
|
{!!reminder && <ReminderNotification reminder={reminder} />}
|
|
@@ -204,6 +204,40 @@ describe('CustomMessageInput', () => {
|
|
|
204
204
|
).toBeInTheDocument()
|
|
205
205
|
})
|
|
206
206
|
|
|
207
|
+
it('renders a custom composerInput in place of the default composer', () => {
|
|
208
|
+
mockChannelData = {}
|
|
209
|
+
|
|
210
|
+
renderWithProviders(
|
|
211
|
+
<CustomMessageInput
|
|
212
|
+
composerInput={() => (
|
|
213
|
+
<div data-testid="custom-composer">custom composer</div>
|
|
214
|
+
)}
|
|
215
|
+
/>
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
// The host-owned composer renders instead of the built-in textarea/send.
|
|
219
|
+
expect(screen.getByTestId('custom-composer')).toBeInTheDocument()
|
|
220
|
+
expect(screen.queryByTestId('textarea-composer')).not.toBeInTheDocument()
|
|
221
|
+
// It is still mounted inside Stream's <MessageInput> so it keeps the
|
|
222
|
+
// message-input context (send pipeline, attachments, previews).
|
|
223
|
+
expect(screen.getByTestId('stream-message-input')).toBeInTheDocument()
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('keeps the frozen wrapper around a custom composerInput', () => {
|
|
227
|
+
mockChannelData = { frozen: true }
|
|
228
|
+
|
|
229
|
+
const { container } = renderWithProviders(
|
|
230
|
+
<CustomMessageInput
|
|
231
|
+
composerInput={() => <div data-testid="custom-composer" />}
|
|
232
|
+
/>
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
const messageInput = container.querySelector('.message-input')
|
|
236
|
+
expect(messageInput).toHaveAttribute('aria-disabled', 'true')
|
|
237
|
+
expect(messageInput).toHaveAttribute('inert')
|
|
238
|
+
expect(screen.getByTestId('custom-composer')).toBeInTheDocument()
|
|
239
|
+
})
|
|
240
|
+
|
|
207
241
|
it('does not render the locked panel when the composer is not disabled', () => {
|
|
208
242
|
mockChannelData = {}
|
|
209
243
|
|
|
@@ -23,6 +23,15 @@ import { CustomLinkPreviewList } from '../CustomLinkPreviewList'
|
|
|
23
23
|
*/
|
|
24
24
|
const ComposerLockedContext = React.createContext(false)
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Read the composer's locked (frozen-channel) state from inside a custom
|
|
28
|
+
* composer supplied via `composerInput`. Returns true when the channel is
|
|
29
|
+
* frozen; mirror the default composer by rendering the textarea read-only and
|
|
30
|
+
* disabling the send control.
|
|
31
|
+
*/
|
|
32
|
+
export const useComposerLocked = (): boolean =>
|
|
33
|
+
useContext(ComposerLockedContext)
|
|
34
|
+
|
|
26
35
|
const DefaultSendButton: React.FC<{
|
|
27
36
|
sendMessage: () => void
|
|
28
37
|
disabled?: boolean
|
|
@@ -52,7 +61,10 @@ const CustomMessageInputInner: React.FC = () => {
|
|
|
52
61
|
} = useComponentContext('CustomMessageInput')
|
|
53
62
|
|
|
54
63
|
return (
|
|
55
|
-
<div
|
|
64
|
+
<div
|
|
65
|
+
className="central-container flex flex-col gap-2 min-w-0 w-full p-2 bg-white rounded-[1.5rem] shadow-[0_4px_16px_0_rgba(0,0,0,0.08),0_1px_2px_0_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.04)]"
|
|
66
|
+
data-dd-privacy="mask"
|
|
67
|
+
>
|
|
56
68
|
<QuotedMessagePreview />
|
|
57
69
|
<CustomLinkPreviewList />
|
|
58
70
|
<AttachmentPreviewList />
|
|
@@ -101,6 +113,18 @@ export interface CustomMessageInputProps {
|
|
|
101
113
|
* `disabled` is true.
|
|
102
114
|
*/
|
|
103
115
|
disabledReason?: string
|
|
116
|
+
/**
|
|
117
|
+
* Replace the inner composer — the textarea + send arrangement rendered
|
|
118
|
+
* inside Stream's `<MessageInput>` — with a host-owned component. When
|
|
119
|
+
* provided it is passed to Stream as the `Input` component, so it runs inside
|
|
120
|
+
* the message-input context and can drive sending, attachments, and previews
|
|
121
|
+
* via the Stream hooks (`useMessageInputContext`,
|
|
122
|
+
* `useMessageComposerHasSendableData`, `TextareaComposer`, …). The outer
|
|
123
|
+
* frozen/`renderActions`/`renderFooter` wrapper is unchanged; read
|
|
124
|
+
* `useComposerLocked()` to honour the frozen state. Defaults to the built-in
|
|
125
|
+
* composer.
|
|
126
|
+
*/
|
|
127
|
+
composerInput?: React.ComponentType
|
|
104
128
|
}
|
|
105
129
|
|
|
106
130
|
export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
|
|
@@ -108,6 +132,7 @@ export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
|
|
|
108
132
|
renderFooter,
|
|
109
133
|
disabled = false,
|
|
110
134
|
disabledReason,
|
|
135
|
+
composerInput,
|
|
111
136
|
}) => {
|
|
112
137
|
const { channel } = useChannelStateContext()
|
|
113
138
|
const isFrozen = channel?.data?.frozen === true
|
|
@@ -143,7 +168,7 @@ export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
|
|
|
143
168
|
</div>
|
|
144
169
|
)}
|
|
145
170
|
<ComposerLockedContext.Provider value={isFrozen}>
|
|
146
|
-
<MessageInput Input={CustomMessageInputInner} />
|
|
171
|
+
<MessageInput Input={composerInput ?? CustomMessageInputInner} />
|
|
147
172
|
</ComposerLockedContext.Provider>
|
|
148
173
|
</div>
|
|
149
174
|
{renderFooter?.()}
|
|
@@ -302,7 +302,7 @@ const MediaMessageRoot: React.FC<MediaMessageProps> = ({
|
|
|
302
302
|
: 'str-chat__message str-chat__message-simple str-chat__message--other'
|
|
303
303
|
|
|
304
304
|
return (
|
|
305
|
-
<div className={messageClass}>
|
|
305
|
+
<div className={messageClass} data-dd-privacy="mask">
|
|
306
306
|
{!isMyMessage && message.user && (
|
|
307
307
|
<Avatar
|
|
308
308
|
className="str-chat__avatar str-chat__message-sender-avatar"
|
|
@@ -342,14 +342,18 @@ const MediaMessageCreatorEntry: React.FC<{ message: LocalMessage }> = ({
|
|
|
342
342
|
const linkAttachment = resolveLinkAttachment(message)
|
|
343
343
|
if (linkAttachment) {
|
|
344
344
|
return (
|
|
345
|
-
<div className={linkCardShellClass(true)}>
|
|
345
|
+
<div className={linkCardShellClass(true)} data-dd-privacy="mask">
|
|
346
346
|
<LinkCard attachment={linkAttachment} isMyMessage={true} />
|
|
347
347
|
</div>
|
|
348
348
|
)
|
|
349
349
|
}
|
|
350
350
|
const resolved = resolveMediaFromMessage(message)
|
|
351
351
|
if (!resolved) return null
|
|
352
|
-
return
|
|
352
|
+
return (
|
|
353
|
+
<div style={{ display: 'contents' }} data-dd-privacy="mask">
|
|
354
|
+
<Creator {...resolved} />
|
|
355
|
+
</div>
|
|
356
|
+
)
|
|
353
357
|
}
|
|
354
358
|
|
|
355
359
|
const MediaMessageVisitorEntry: React.FC<{ message: LocalMessage }> = ({
|
|
@@ -358,14 +362,18 @@ const MediaMessageVisitorEntry: React.FC<{ message: LocalMessage }> = ({
|
|
|
358
362
|
const linkAttachment = resolveLinkAttachment(message)
|
|
359
363
|
if (linkAttachment) {
|
|
360
364
|
return (
|
|
361
|
-
<div className={linkCardShellClass(false)}>
|
|
365
|
+
<div className={linkCardShellClass(false)} data-dd-privacy="mask">
|
|
362
366
|
<LinkCard attachment={linkAttachment} isMyMessage={false} />
|
|
363
367
|
</div>
|
|
364
368
|
)
|
|
365
369
|
}
|
|
366
370
|
const resolved = resolveMediaFromMessage(message)
|
|
367
371
|
if (!resolved) return null
|
|
368
|
-
return
|
|
372
|
+
return (
|
|
373
|
+
<div style={{ display: 'contents' }} data-dd-privacy="mask">
|
|
374
|
+
<Visitor {...resolved} />
|
|
375
|
+
</div>
|
|
376
|
+
)
|
|
369
377
|
}
|
|
370
378
|
|
|
371
379
|
export const MediaMessage = Object.assign(MediaMessageRoot, {
|
|
@@ -39,6 +39,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
39
39
|
renderMessage,
|
|
40
40
|
onMessageLinkClick,
|
|
41
41
|
showChannelInfo,
|
|
42
|
+
composerInput,
|
|
42
43
|
}) => {
|
|
43
44
|
const {
|
|
44
45
|
client,
|
|
@@ -264,6 +265,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
264
265
|
renderMessage={renderMessage}
|
|
265
266
|
onMessageLinkClick={onMessageLinkClick}
|
|
266
267
|
showChannelInfo={showChannelInfo}
|
|
268
|
+
composerInput={composerInput}
|
|
267
269
|
/>
|
|
268
270
|
</div>
|
|
269
271
|
</div>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CurrencyDollarIcon, StarIcon } from '@phosphor-icons/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import type { Channel, ChannelMemberResponse } from 'stream-chat'
|
|
4
|
+
|
|
5
|
+
import { useChannelStar } from '../hooks/useChannelStar'
|
|
6
|
+
|
|
7
|
+
export const ParticipantBadges: React.FC<{
|
|
8
|
+
channel?: Channel
|
|
9
|
+
participant?: ChannelMemberResponse
|
|
10
|
+
}> = ({ channel, participant }) => {
|
|
11
|
+
const isCustomer = participant?.customer === true
|
|
12
|
+
const isStarred = useChannelStar(channel)
|
|
13
|
+
|
|
14
|
+
if (!isCustomer && !isStarred) return null
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<span className="inline-flex shrink-0 items-center gap-1">
|
|
18
|
+
{isCustomer && (
|
|
19
|
+
<span
|
|
20
|
+
role="img"
|
|
21
|
+
aria-label="Customer"
|
|
22
|
+
className="inline-flex size-4 items-center justify-center rounded-full bg-[#22C55E] text-white"
|
|
23
|
+
>
|
|
24
|
+
<CurrencyDollarIcon
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
className="size-3"
|
|
27
|
+
weight="bold"
|
|
28
|
+
/>
|
|
29
|
+
</span>
|
|
30
|
+
)}
|
|
31
|
+
{isStarred && (
|
|
32
|
+
<span
|
|
33
|
+
role="img"
|
|
34
|
+
aria-label="Starred conversation"
|
|
35
|
+
className="inline-flex size-4 items-center justify-center rounded-full bg-[#F6C343] text-white"
|
|
36
|
+
>
|
|
37
|
+
<StarIcon aria-hidden="true" className="size-3" weight="fill" />
|
|
38
|
+
</span>
|
|
39
|
+
)}
|
|
40
|
+
</span>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
|
-
import { Channel, Event } from 'stream-chat'
|
|
2
|
+
import type { Channel, Event } from 'stream-chat'
|
|
3
3
|
|
|
4
4
|
export const useChannelStar = (channel?: Channel) => {
|
|
5
5
|
const [isChannelStarred, setIsChannelStarred] = useState(
|
|
@@ -15,15 +15,18 @@ export const useChannelStar = (channel?: Channel) => {
|
|
|
15
15
|
setIsChannelStarred(!!channel.state.membership?.pinned_at)
|
|
16
16
|
|
|
17
17
|
const handleMemberUpdate = (event: Event) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const currentUserId = channel._client?.userID
|
|
19
|
+
const memberUserId = event.member?.user_id ?? event.member?.user?.id
|
|
20
|
+
|
|
21
|
+
if (!currentUserId || memberUserId !== currentUserId) return
|
|
22
|
+
|
|
23
|
+
setIsChannelStarred(!!event.member?.pinned_at)
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
channel.on('member.updated', handleMemberUpdate)
|
|
26
|
+
const subscription = channel.on('member.updated', handleMemberUpdate)
|
|
24
27
|
|
|
25
28
|
return () => {
|
|
26
|
-
|
|
29
|
+
subscription.unsubscribe()
|
|
27
30
|
}
|
|
28
31
|
}, [channel])
|
|
29
32
|
|
package/src/index.ts
CHANGED
|
@@ -10,8 +10,6 @@ export { default as LockedAttachment } from './components/LockedAttachment'
|
|
|
10
10
|
export { default as LinkAttachment } from './components/LinkAttachment'
|
|
11
11
|
export { default as MessageAttachment } from './components/MessageAttachment'
|
|
12
12
|
export { Avatar } from './components/Avatar'
|
|
13
|
-
export { VerifiedBadge } from './components/VerifiedBadge'
|
|
14
|
-
export type { VerifiedBadgeProps } from './components/VerifiedBadge'
|
|
15
13
|
export { FaqList } from './components/FaqList'
|
|
16
14
|
export { FaqListItem } from './components/FaqList/FaqListItem'
|
|
17
15
|
export { ChannelEmptyState } from './components/MessagingShell/ChannelEmptyState'
|
|
@@ -32,6 +30,7 @@ export { MessagingProvider } from './providers/MessagingProvider'
|
|
|
32
30
|
export { CustomMessageProvider } from './components/CustomMessage/context'
|
|
33
31
|
|
|
34
32
|
// Hooks
|
|
33
|
+
export { useComposerLocked } from './components/CustomMessageInput'
|
|
35
34
|
export { useMessaging } from './hooks/useMessaging'
|
|
36
35
|
export { useMessageVote } from './hooks/useMessageVote'
|
|
37
36
|
export { useCustomMessage } from './components/CustomMessage/context'
|
package/src/stories/mocks.tsx
CHANGED
|
@@ -54,6 +54,14 @@ export const MockChatProviderWithChannels: React.FC<{
|
|
|
54
54
|
// Create mock channels
|
|
55
55
|
const mockChannels = Array.from({ length: channelCount }, (_, i) => {
|
|
56
56
|
const participant = mockParticipants[i % mockParticipants.length]
|
|
57
|
+
const eventListeners = new Map<string, Set<(...args: unknown[]) => void>>()
|
|
58
|
+
|
|
59
|
+
const latestMessage = {
|
|
60
|
+
id: `msg-${i}-1`,
|
|
61
|
+
text: `Hey! This is message ${i + 1}`,
|
|
62
|
+
created_at: new Date(Date.now() - 1000 * 60 * (i + 1)),
|
|
63
|
+
user: participant,
|
|
64
|
+
}
|
|
57
65
|
|
|
58
66
|
const mockChannel = {
|
|
59
67
|
id: `channel-${i + 1}`,
|
|
@@ -71,18 +79,35 @@ export const MockChatProviderWithChannels: React.FC<{
|
|
|
71
79
|
user_id: participant.id,
|
|
72
80
|
},
|
|
73
81
|
},
|
|
74
|
-
|
|
82
|
+
latestMessages: [latestMessage],
|
|
83
|
+
messages: [latestMessage],
|
|
84
|
+
messageSets: [
|
|
75
85
|
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
user: participant,
|
|
86
|
+
isCurrent: true,
|
|
87
|
+
isLatest: true,
|
|
88
|
+
messages: [latestMessage],
|
|
80
89
|
},
|
|
81
90
|
],
|
|
82
91
|
unreadCount: i === 0 ? 2 : 0,
|
|
83
92
|
read: {},
|
|
84
93
|
},
|
|
85
94
|
// Mock channel methods
|
|
95
|
+
countUnread: () => (i === 0 ? 2 : 0),
|
|
96
|
+
muteStatus: () => false,
|
|
97
|
+
on: (eventName: string, listener: (...args: unknown[]) => void) => {
|
|
98
|
+
if (!eventListeners.has(eventName)) {
|
|
99
|
+
eventListeners.set(eventName, new Set())
|
|
100
|
+
}
|
|
101
|
+
eventListeners.get(eventName)?.add(listener)
|
|
102
|
+
return {
|
|
103
|
+
unsubscribe: () => {
|
|
104
|
+
eventListeners.get(eventName)?.delete(listener)
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
off: (eventName: string, listener: (...args: unknown[]) => void) => {
|
|
109
|
+
eventListeners.get(eventName)?.delete(listener)
|
|
110
|
+
},
|
|
86
111
|
query: async () => ({ messages: [] }),
|
|
87
112
|
watch: async () => ({ messages: [] }),
|
|
88
113
|
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Stream Chat custom data type augmentation.
|
|
3
3
|
*
|
|
4
4
|
* This file extends Stream Chat's type system by augmenting their empty
|
|
5
|
-
* CustomMessageData
|
|
5
|
+
* CustomMessageData, CustomChannelData, CustomMemberData, and CustomUserData
|
|
6
|
+
* interfaces with our custom properties.
|
|
6
7
|
*
|
|
7
8
|
* @see https://getstream.io/chat/docs/sdk/react/guides/typescript_and_custom_data_types/
|
|
8
9
|
*
|
|
@@ -98,12 +99,12 @@ declare module 'stream-chat' {
|
|
|
98
99
|
chatbot_paused?: boolean
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
interface
|
|
102
|
+
interface CustomMemberData {
|
|
102
103
|
/**
|
|
103
|
-
* Whether the
|
|
104
|
-
*
|
|
104
|
+
* Whether the member should be identified as a customer in participant UI.
|
|
105
|
+
* Stored on the channel member so it is scoped to this conversation.
|
|
105
106
|
*/
|
|
106
|
-
|
|
107
|
+
customer?: boolean
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
interface CustomMessageData {
|
|
@@ -123,4 +124,10 @@ declare module 'stream-chat' {
|
|
|
123
124
|
*/
|
|
124
125
|
dm_agent_system_type?: DmAgentSystemType
|
|
125
126
|
}
|
|
127
|
+
|
|
128
|
+
interface CustomUserData {
|
|
129
|
+
is_account?: boolean
|
|
130
|
+
is_follower?: boolean
|
|
131
|
+
username?: string
|
|
132
|
+
}
|
|
126
133
|
}
|
package/src/types.ts
CHANGED
|
@@ -11,7 +11,10 @@ import type {
|
|
|
11
11
|
LocalMessage,
|
|
12
12
|
SendMessageAPIResponse,
|
|
13
13
|
} from 'stream-chat'
|
|
14
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
ChannelPreviewUIComponentProps,
|
|
16
|
+
EmptyStateIndicatorProps,
|
|
17
|
+
} from 'stream-chat-react'
|
|
15
18
|
|
|
16
19
|
import type { MessageMetadata } from './stream-custom-data'
|
|
17
20
|
|
|
@@ -89,6 +92,11 @@ export interface ChannelListProps {
|
|
|
89
92
|
*/
|
|
90
93
|
sort?: ChannelSort
|
|
91
94
|
customEmptyStateIndicator?: React.ComponentType<EmptyStateIndicatorProps>
|
|
95
|
+
/**
|
|
96
|
+
* Custom component rendered for each channel row in the conversation list.
|
|
97
|
+
* Defaults to the package's built-in CustomChannelPreview.
|
|
98
|
+
*/
|
|
99
|
+
channelPreview?: ComponentType<ChannelPreviewUIComponentProps>
|
|
92
100
|
renderMessagePreview?: (
|
|
93
101
|
message: LocalMessage | undefined,
|
|
94
102
|
defaultPreview?: string
|
|
@@ -324,6 +332,23 @@ export interface ChannelViewProps {
|
|
|
324
332
|
*/
|
|
325
333
|
attachmentPreviewList?: ComponentType
|
|
326
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Replace the entire inner composer — the textarea + send arrangement — with
|
|
337
|
+
* a host-owned component. Passed to Stream's `<MessageInput>` as the `Input`
|
|
338
|
+
* component, so it runs inside the message-input context and reuses the
|
|
339
|
+
* library's send pipeline (`doSendMessageRequest`, message metadata, DM-agent
|
|
340
|
+
* suppression), attachments, and previews via the Stream hooks
|
|
341
|
+
* (`useMessageInputContext`, `useMessageComposerHasSendableData`,
|
|
342
|
+
* `TextareaComposer`, …). Use it when a surface needs a different composer
|
|
343
|
+
* layout (e.g. a full-width textarea above a toolbar row) than the default.
|
|
344
|
+
*
|
|
345
|
+
* The outer wrapper — frozen handling and the `renderMessageInputActions` /
|
|
346
|
+
* `renderMessageInputFooter` slots — is unchanged. Read the exported
|
|
347
|
+
* `useComposerLocked()` hook to honour the frozen state. Defaults to the
|
|
348
|
+
* built-in composer when omitted.
|
|
349
|
+
*/
|
|
350
|
+
composerInput?: ComponentType
|
|
351
|
+
|
|
327
352
|
/**
|
|
328
353
|
* Fired when the participant's name in the channel header is clicked. When
|
|
329
354
|
* provided, the name renders as a button with a trailing caret (an affordance
|
|
@@ -359,6 +384,7 @@ export type ChannelViewPassthroughProps = Pick<
|
|
|
359
384
|
| 'renderMessage'
|
|
360
385
|
| 'onMessageLinkClick'
|
|
361
386
|
| 'showChannelInfo'
|
|
387
|
+
| 'composerInput'
|
|
362
388
|
>
|
|
363
389
|
|
|
364
390
|
/**
|