@linktr.ee/messaging-react 1.32.1 → 1.33.0-rc-1777272812
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-1CQEn-OT.js → Card-DEe10CiS.js} +2 -2
- package/dist/{Card-1CQEn-OT.js.map → Card-DEe10CiS.js.map} +1 -1
- package/dist/{Card-ClE_iExA.js → Card-Ddi8bg90.js} +2 -2
- package/dist/{Card-ClE_iExA.js.map → Card-Ddi8bg90.js.map} +1 -1
- package/dist/index-BePLvyvi.js +2868 -0
- package/dist/index-BePLvyvi.js.map +1 -0
- package/dist/index.d.ts +19 -1
- package/dist/index.js +20 -2477
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelView.stories.tsx +38 -0
- package/src/components/ChannelView.test.tsx +25 -6
- package/src/components/ChannelView.tsx +2 -0
- package/src/components/CustomMessageInput/CustomMessageInput.stories.tsx +180 -0
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +63 -1
- package/src/components/CustomMessageInput/index.tsx +24 -5
- package/src/components/MediaMessage/MediaMessage.stories.tsx +170 -0
- package/src/components/MediaMessage/MediaMessage.test.tsx +261 -0
- package/src/components/MediaMessage/index.tsx +165 -0
- package/src/components/MessagingShell/index.tsx +2 -0
- package/src/index.ts +2 -0
- package/src/types.ts +13 -0
- package/dist/MediaPlayer-B9Ws2NeE.js +0 -292
- package/dist/MediaPlayer-B9Ws2NeE.js.map +0 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { LocalMessage } from 'stream-chat'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { renderWithProviders, screen } from '../../test/utils'
|
|
6
|
+
|
|
7
|
+
import { MediaMessage } from '.'
|
|
8
|
+
|
|
9
|
+
vi.mock('../Avatar', () => ({
|
|
10
|
+
Avatar: ({ id }: { id: string }) => (
|
|
11
|
+
<div data-testid="avatar" data-user-id={id} />
|
|
12
|
+
),
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
vi.mock('../LockedAttachment/components/MediaPlayer', () => ({
|
|
16
|
+
default: ({
|
|
17
|
+
source,
|
|
18
|
+
mimeType,
|
|
19
|
+
}: {
|
|
20
|
+
source: string
|
|
21
|
+
mimeType: string
|
|
22
|
+
}) => (
|
|
23
|
+
<div
|
|
24
|
+
data-testid="media-player"
|
|
25
|
+
data-source={source}
|
|
26
|
+
data-mime-type={mimeType}
|
|
27
|
+
/>
|
|
28
|
+
),
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
vi.mock('../LockedAttachment/utils/icons', () => ({
|
|
32
|
+
renderTypeIcon: () => <span data-testid="type-icon" />,
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
vi.mock('../LockedAttachment/utils/mimeType', () => ({
|
|
36
|
+
getSourceType: (mimeType: string) => {
|
|
37
|
+
if (mimeType.startsWith('video/')) return 'video'
|
|
38
|
+
if (mimeType.startsWith('image/')) return 'image'
|
|
39
|
+
if (mimeType.startsWith('audio/')) return 'audio'
|
|
40
|
+
return 'document'
|
|
41
|
+
},
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
// Cast through unknown to avoid satisfying every optional field of LocalMessage
|
|
45
|
+
const msg = (overrides: Record<string, unknown> = {}): LocalMessage =>
|
|
46
|
+
({
|
|
47
|
+
id: 'msg-1',
|
|
48
|
+
text: '',
|
|
49
|
+
type: 'regular',
|
|
50
|
+
created_at: new Date(),
|
|
51
|
+
updated_at: new Date(),
|
|
52
|
+
deleted_at: null,
|
|
53
|
+
pinned_at: null,
|
|
54
|
+
status: 'received',
|
|
55
|
+
user: { id: 'user-1', name: 'Alice' },
|
|
56
|
+
attachments: [],
|
|
57
|
+
...overrides,
|
|
58
|
+
}) as unknown as LocalMessage
|
|
59
|
+
|
|
60
|
+
describe('MediaMessage', () => {
|
|
61
|
+
it('renders nothing when no media URL is resolvable', () => {
|
|
62
|
+
const { container } = renderWithProviders(<MediaMessage message={msg()} />)
|
|
63
|
+
expect(container.firstChild).toBeNull()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('renders MediaPlayer for a video attachment', () => {
|
|
67
|
+
renderWithProviders(
|
|
68
|
+
<MediaMessage
|
|
69
|
+
message={msg({
|
|
70
|
+
attachments: [
|
|
71
|
+
{
|
|
72
|
+
type: 'video',
|
|
73
|
+
asset_url: 'https://cdn.example.com/clip.mp4',
|
|
74
|
+
mime_type: 'video/mp4',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
})}
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const player = screen.getByTestId('media-player')
|
|
82
|
+
expect(player).toBeInTheDocument()
|
|
83
|
+
expect(player).toHaveAttribute(
|
|
84
|
+
'data-source',
|
|
85
|
+
'https://cdn.example.com/clip.mp4'
|
|
86
|
+
)
|
|
87
|
+
expect(player).toHaveAttribute('data-mime-type', 'video/mp4')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('renders MediaPlayer for an audio attachment', () => {
|
|
91
|
+
renderWithProviders(
|
|
92
|
+
<MediaMessage
|
|
93
|
+
message={msg({
|
|
94
|
+
attachments: [
|
|
95
|
+
{
|
|
96
|
+
type: 'audio',
|
|
97
|
+
asset_url: 'https://cdn.example.com/track.mp3',
|
|
98
|
+
mime_type: 'audio/mpeg',
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
})}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
expect(screen.getByTestId('media-player')).toHaveAttribute(
|
|
106
|
+
'data-source',
|
|
107
|
+
'https://cdn.example.com/track.mp3'
|
|
108
|
+
)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('renders an img for an image attachment', () => {
|
|
112
|
+
renderWithProviders(
|
|
113
|
+
<MediaMessage
|
|
114
|
+
message={msg({
|
|
115
|
+
attachments: [
|
|
116
|
+
{
|
|
117
|
+
type: 'image',
|
|
118
|
+
image_url: 'https://cdn.example.com/photo.jpg',
|
|
119
|
+
mime_type: 'image/jpeg',
|
|
120
|
+
title: 'My Photo',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
})}
|
|
124
|
+
/>
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const image = screen.getByRole('img', { name: 'My Photo' })
|
|
128
|
+
expect(image).toHaveAttribute('src', 'https://cdn.example.com/photo.jpg')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('renders a download link for a document attachment', () => {
|
|
132
|
+
renderWithProviders(
|
|
133
|
+
<MediaMessage
|
|
134
|
+
message={msg({
|
|
135
|
+
attachments: [
|
|
136
|
+
{
|
|
137
|
+
type: 'file',
|
|
138
|
+
asset_url: 'https://cdn.example.com/report.pdf',
|
|
139
|
+
mime_type: 'application/pdf',
|
|
140
|
+
title: 'Annual Report',
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
})}
|
|
144
|
+
/>
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
const link = screen.getByRole('link')
|
|
148
|
+
expect(link).toHaveAttribute('href', 'https://cdn.example.com/report.pdf')
|
|
149
|
+
expect(screen.getByText('Annual Report')).toBeInTheDocument()
|
|
150
|
+
expect(screen.getByTestId('type-icon')).toBeInTheDocument()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('shows title and file size below video', () => {
|
|
154
|
+
renderWithProviders(
|
|
155
|
+
<MediaMessage
|
|
156
|
+
message={msg({
|
|
157
|
+
attachments: [
|
|
158
|
+
{
|
|
159
|
+
type: 'video',
|
|
160
|
+
asset_url: 'https://cdn.example.com/clip.mp4',
|
|
161
|
+
mime_type: 'video/mp4',
|
|
162
|
+
title: 'Clip',
|
|
163
|
+
file_size: 2048,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
})}
|
|
167
|
+
/>
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
expect(screen.getByText('Clip')).toBeInTheDocument()
|
|
171
|
+
expect(screen.getByText('2.0 KB')).toBeInTheDocument()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('renders Avatar for a message from another user', () => {
|
|
175
|
+
renderWithProviders(
|
|
176
|
+
<MediaMessage
|
|
177
|
+
isMyMessage={false}
|
|
178
|
+
message={msg({
|
|
179
|
+
attachments: [
|
|
180
|
+
{
|
|
181
|
+
type: 'image',
|
|
182
|
+
image_url: 'https://cdn.example.com/photo.jpg',
|
|
183
|
+
mime_type: 'image/jpeg',
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
})}
|
|
187
|
+
/>
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
expect(screen.getByTestId('avatar')).toBeInTheDocument()
|
|
191
|
+
expect(screen.getByTestId('avatar')).toHaveAttribute(
|
|
192
|
+
'data-user-id',
|
|
193
|
+
'user-1'
|
|
194
|
+
)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('does not render Avatar for own messages', () => {
|
|
198
|
+
renderWithProviders(
|
|
199
|
+
<MediaMessage
|
|
200
|
+
isMyMessage={true}
|
|
201
|
+
message={msg({
|
|
202
|
+
attachments: [
|
|
203
|
+
{
|
|
204
|
+
type: 'image',
|
|
205
|
+
image_url: 'https://cdn.example.com/photo.jpg',
|
|
206
|
+
mime_type: 'image/jpeg',
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
})}
|
|
210
|
+
/>
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
expect(screen.queryByTestId('avatar')).not.toBeInTheDocument()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('applies the --me class for own messages', () => {
|
|
217
|
+
const { container } = renderWithProviders(
|
|
218
|
+
<MediaMessage
|
|
219
|
+
isMyMessage={true}
|
|
220
|
+
message={msg({
|
|
221
|
+
attachments: [
|
|
222
|
+
{
|
|
223
|
+
type: 'image',
|
|
224
|
+
image_url: 'https://cdn.example.com/photo.jpg',
|
|
225
|
+
mime_type: 'image/jpeg',
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
})}
|
|
229
|
+
/>
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
expect(container.firstChild).toHaveClass('str-chat__message--me')
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('applies the --other class for messages from other users', () => {
|
|
236
|
+
const { container } = renderWithProviders(
|
|
237
|
+
<MediaMessage
|
|
238
|
+
isMyMessage={false}
|
|
239
|
+
message={msg({
|
|
240
|
+
attachments: [
|
|
241
|
+
{
|
|
242
|
+
type: 'image',
|
|
243
|
+
image_url: 'https://cdn.example.com/photo.jpg',
|
|
244
|
+
mime_type: 'image/jpeg',
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
})}
|
|
248
|
+
/>
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
expect(container.firstChild).toHaveClass('str-chat__message--other')
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('renders nothing when no attachments', () => {
|
|
255
|
+
const { container } = renderWithProviders(
|
|
256
|
+
<MediaMessage message={msg({ attachments: [] })} />
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
expect(container.firstChild).toBeNull()
|
|
260
|
+
})
|
|
261
|
+
})
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { LocalMessage } from 'stream-chat'
|
|
3
|
+
|
|
4
|
+
import { Avatar } from '../Avatar'
|
|
5
|
+
import MediaPlayer from '../LockedAttachment/components/MediaPlayer'
|
|
6
|
+
import { renderTypeIcon } from '../LockedAttachment/utils/icons'
|
|
7
|
+
import { getSourceType } from '../LockedAttachment/utils/mimeType'
|
|
8
|
+
|
|
9
|
+
function formatBytes(bytes: number): string {
|
|
10
|
+
if (bytes < 1024) return `${bytes} B`
|
|
11
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
12
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CARD_CLASS =
|
|
16
|
+
'w-[280px] select-none overflow-hidden rounded-[24px] bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]'
|
|
17
|
+
|
|
18
|
+
const MediaMeta: React.FC<{ mimeType: string; title?: string; fileSize?: number }> = ({
|
|
19
|
+
mimeType,
|
|
20
|
+
title,
|
|
21
|
+
fileSize,
|
|
22
|
+
}) => {
|
|
23
|
+
if (!title && fileSize === undefined) return null
|
|
24
|
+
return (
|
|
25
|
+
<div className="px-4 pb-3 pt-3">
|
|
26
|
+
{title && (
|
|
27
|
+
<p className="mb-1.5 truncate text-base font-medium text-black">{title}</p>
|
|
28
|
+
)}
|
|
29
|
+
{fileSize !== undefined && (
|
|
30
|
+
<div className="flex items-center gap-1">
|
|
31
|
+
{renderTypeIcon(mimeType, { className: 'size-5 shrink-0 text-black/55', weight: 'regular' })}
|
|
32
|
+
<span className="text-xs font-medium text-black/55">{formatBytes(fileSize)}</span>
|
|
33
|
+
</div>
|
|
34
|
+
)}
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const FileCard: React.FC<{
|
|
40
|
+
url: string
|
|
41
|
+
mimeType: string
|
|
42
|
+
title?: string
|
|
43
|
+
fileSize?: number
|
|
44
|
+
}> = ({ url, mimeType, title, fileSize }) => (
|
|
45
|
+
<a href={url} target="_blank" rel="noopener noreferrer" className="block no-underline">
|
|
46
|
+
<div className="aspect-video w-full bg-black/5 flex items-center justify-center">
|
|
47
|
+
{renderTypeIcon(mimeType, { className: 'size-12 text-black/20', weight: 'regular' })}
|
|
48
|
+
</div>
|
|
49
|
+
<div className="px-4 pb-3 pt-3">
|
|
50
|
+
{title && (
|
|
51
|
+
<p className="mb-1.5 truncate text-base font-medium text-black">{title}</p>
|
|
52
|
+
)}
|
|
53
|
+
{fileSize !== undefined && (
|
|
54
|
+
<div className="flex items-center gap-1">
|
|
55
|
+
{renderTypeIcon(mimeType, { className: 'size-5 shrink-0 text-black/55', weight: 'regular' })}
|
|
56
|
+
<span className="text-xs font-medium text-black/55">{formatBytes(fileSize)}</span>
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
</a>
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
export interface MediaMessageProps {
|
|
64
|
+
message: LocalMessage
|
|
65
|
+
isMyMessage?: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const MediaMessage: React.FC<MediaMessageProps> = ({
|
|
69
|
+
message,
|
|
70
|
+
isMyMessage = false,
|
|
71
|
+
}) => {
|
|
72
|
+
const videoAttachment = message.attachments?.find(
|
|
73
|
+
(a) => a.type === 'video' && a.asset_url
|
|
74
|
+
)
|
|
75
|
+
const imageAttachment = message.attachments?.find(
|
|
76
|
+
(a) => a.type === 'image' && (a as { image_url?: string }).image_url
|
|
77
|
+
)
|
|
78
|
+
const audioAttachment = message.attachments?.find(
|
|
79
|
+
(a) => a.type === 'audio' && a.asset_url
|
|
80
|
+
)
|
|
81
|
+
const fileAttachment = message.attachments?.find(
|
|
82
|
+
(a) => a.type === 'file' && a.asset_url
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const activeAttachment =
|
|
86
|
+
videoAttachment ?? imageAttachment ?? audioAttachment ?? fileAttachment
|
|
87
|
+
|
|
88
|
+
const resolvedUrl =
|
|
89
|
+
videoAttachment?.asset_url ??
|
|
90
|
+
(imageAttachment as { image_url?: string } | undefined)?.image_url ??
|
|
91
|
+
audioAttachment?.asset_url ??
|
|
92
|
+
fileAttachment?.asset_url
|
|
93
|
+
|
|
94
|
+
const resolvedType =
|
|
95
|
+
activeAttachment?.mime_type ??
|
|
96
|
+
(imageAttachment
|
|
97
|
+
? 'image/jpeg'
|
|
98
|
+
: videoAttachment
|
|
99
|
+
? 'video/mp4'
|
|
100
|
+
: audioAttachment
|
|
101
|
+
? 'audio/mpeg'
|
|
102
|
+
: 'application/octet-stream')
|
|
103
|
+
|
|
104
|
+
if (!resolvedUrl) return null
|
|
105
|
+
|
|
106
|
+
const sourceType = getSourceType(resolvedType)
|
|
107
|
+
const title = (activeAttachment as { title?: string } | undefined)?.title
|
|
108
|
+
const fileSize = (activeAttachment as { file_size?: number } | undefined)?.file_size
|
|
109
|
+
const thumbnailUrl = (videoAttachment as { thumb_url?: string } | undefined)?.thumb_url
|
|
110
|
+
|
|
111
|
+
const messageClass = isMyMessage
|
|
112
|
+
? 'str-chat__message str-chat__message-simple str-chat__message--me str-chat__message-simple--me'
|
|
113
|
+
: 'str-chat__message str-chat__message-simple str-chat__message--other'
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className={messageClass}>
|
|
117
|
+
{!isMyMessage && message.user && (
|
|
118
|
+
<Avatar
|
|
119
|
+
className="str-chat__avatar str-chat__message-sender-avatar"
|
|
120
|
+
id={message.user.id}
|
|
121
|
+
image={message.user.image}
|
|
122
|
+
name={message.user.name ?? message.user.id}
|
|
123
|
+
/>
|
|
124
|
+
)}
|
|
125
|
+
<div
|
|
126
|
+
className="str-chat__message-inner"
|
|
127
|
+
style={{ marginInlineEnd: 0, marginInlineStart: 0 }}
|
|
128
|
+
>
|
|
129
|
+
<div className="str-chat__message-bubble-wrapper">
|
|
130
|
+
<div className="str-chat__message-bubble" style={{ padding: 0, borderRadius: 0, overflow: 'visible', background: 'transparent' }}>
|
|
131
|
+
<div className={CARD_CLASS}>
|
|
132
|
+
{sourceType === 'image' ? (
|
|
133
|
+
<>
|
|
134
|
+
<img
|
|
135
|
+
src={resolvedUrl}
|
|
136
|
+
alt={title ?? ''}
|
|
137
|
+
className="block w-full"
|
|
138
|
+
/>
|
|
139
|
+
<MediaMeta mimeType={resolvedType} title={title} fileSize={fileSize} />
|
|
140
|
+
</>
|
|
141
|
+
) : sourceType === 'document' ? (
|
|
142
|
+
<FileCard
|
|
143
|
+
url={resolvedUrl}
|
|
144
|
+
mimeType={resolvedType}
|
|
145
|
+
title={title}
|
|
146
|
+
fileSize={fileSize}
|
|
147
|
+
/>
|
|
148
|
+
) : (
|
|
149
|
+
<>
|
|
150
|
+
<MediaPlayer
|
|
151
|
+
source={resolvedUrl}
|
|
152
|
+
mimeType={resolvedType}
|
|
153
|
+
poster={thumbnailUrl}
|
|
154
|
+
controls
|
|
155
|
+
/>
|
|
156
|
+
<MediaMeta mimeType={resolvedType} title={title} fileSize={fileSize} />
|
|
157
|
+
</>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
@@ -42,6 +42,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
42
42
|
customProfileContent,
|
|
43
43
|
customChannelActions,
|
|
44
44
|
renderMessage,
|
|
45
|
+
sendButton,
|
|
45
46
|
}) => {
|
|
46
47
|
const {
|
|
47
48
|
service,
|
|
@@ -505,6 +506,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
505
506
|
customProfileContent={customProfileContent}
|
|
506
507
|
customChannelActions={customChannelActions}
|
|
507
508
|
renderMessage={renderMessage}
|
|
509
|
+
sendButton={sendButton}
|
|
508
510
|
/>
|
|
509
511
|
</div>
|
|
510
512
|
) : initialParticipantFilter ? (
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,8 @@ export { FaqList } from './components/FaqList'
|
|
|
13
13
|
export { FaqListItem } from './components/FaqList/FaqListItem'
|
|
14
14
|
export { ChannelEmptyState } from './components/MessagingShell/ChannelEmptyState'
|
|
15
15
|
export { MessageVoteButtons } from './components/CustomMessage/MessageVoteButtons'
|
|
16
|
+
export { MediaMessage } from './components/MediaMessage'
|
|
17
|
+
export type { MediaMessageProps } from './components/MediaMessage'
|
|
16
18
|
|
|
17
19
|
// Providers
|
|
18
20
|
export { MessagingProvider } from './providers/MessagingProvider'
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
MessagingUser,
|
|
3
3
|
StreamChatServiceConfig,
|
|
4
4
|
} from '@linktr.ee/messaging-core'
|
|
5
|
+
import type { ComponentType } from 'react'
|
|
5
6
|
import type {
|
|
6
7
|
Channel,
|
|
7
8
|
ChannelFilters,
|
|
@@ -234,6 +235,17 @@ export interface ChannelViewProps {
|
|
|
234
235
|
message: LocalMessage
|
|
235
236
|
) => React.ReactNode
|
|
236
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Passed to Stream `Channel` as `SendButton`. Required for hosts that replace
|
|
240
|
+
* the send control: `Channel` merges this into `ComponentContext` and an
|
|
241
|
+
* explicit `SendButton: undefined` would otherwise override outer
|
|
242
|
+
* `WithComponents` overrides.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* sendButton={MediaSendButton}
|
|
246
|
+
*/
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
248
|
+
sendButton?: ComponentType<any>
|
|
237
249
|
}
|
|
238
250
|
|
|
239
251
|
/**
|
|
@@ -257,6 +269,7 @@ export type ChannelViewPassthroughProps = Pick<
|
|
|
257
269
|
| 'customProfileContent'
|
|
258
270
|
| 'customChannelActions'
|
|
259
271
|
| 'renderMessage'
|
|
272
|
+
| 'sendButton'
|
|
260
273
|
>
|
|
261
274
|
|
|
262
275
|
/**
|