@linktr.ee/messaging-react 3.37.0-rc-1786190741 → 3.37.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/README.md +4 -4
- package/dist/Card-415OfRhw.cjs.map +1 -1
- package/dist/{Card-DVMzYquF.js → Card-D4PPD5qa.js} +8 -11
- package/dist/Card-D4PPD5qa.js.map +1 -0
- package/dist/Card-Dxk_Ggxw.cjs.map +1 -1
- package/dist/{Card-DaJtDL7m.js → Card-qS17qnN0.js} +6 -15
- package/dist/Card-qS17qnN0.js.map +1 -0
- package/dist/LoadingDots-Ct3WmVlT.js.map +1 -1
- package/dist/LoadingDots-D9GGphgM.cjs.map +1 -1
- package/dist/index-D7A8U8GQ.cjs.map +1 -1
- package/dist/{index-BOBIiAuD.js → index-DGmVyxHy.js} +62 -87
- package/dist/index-DGmVyxHy.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/components/ActionButton/ActionButton.stories.tsx +2 -0
- package/src/components/ActionButton/ActionButton.test.tsx +55 -60
- package/src/components/ActionButton/index.tsx +14 -13
- package/src/components/AttachmentCard/AttachmentCard.stories.tsx +3 -1
- package/src/components/AttachmentCard/MediaPlayer.tsx +7 -9
- package/src/components/AttachmentCard/index.tsx +2 -6
- package/src/components/Avatar/Avatar.stories.tsx +5 -17
- package/src/components/Avatar/index.tsx +23 -13
- package/src/components/ChannelView.test.tsx +10 -6
- package/src/components/CustomMessage/CustomMessage.stories.tsx +2 -7
- package/src/components/CustomMessage/CustomMessage.test.tsx +3 -5
- package/src/components/CustomMessage/LockedAttachment/LockedAttachment.test.tsx +4 -5
- package/src/components/CustomMessage/LockedAttachment/components/Text/Card.tsx +4 -2
- package/src/components/CustomMessage/LockedAttachment/components/_shared/CardBody.tsx +6 -9
- package/src/components/CustomMessage/LockedAttachment/components/_shared/CardThumbnail.tsx +3 -1
- package/src/components/CustomMessage/LockedAttachment/components/_shared/GalleryThumbnail.tsx +4 -1
- package/src/components/CustomMessage/LockedAttachment/components/_shared/PurchaseStatusBadge.tsx +1 -0
- package/src/components/CustomMessage/LockedAttachment/components/_shared/SingleThumbnail.tsx +4 -1
- package/src/components/CustomMessage/LockedAttachment/components/_shared/SkeletonTextLines.tsx +2 -7
- package/src/components/CustomMessage/LockedAttachment/types.ts +3 -4
- package/src/components/CustomMessage/MessageAttachmentConversations.stories.tsx +86 -89
- package/src/components/CustomMessage/MessageVoteButtons.tsx +1 -4
- package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +10 -13
- package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +4 -1
- package/src/components/CustomMessage/StreamAttachmentMessage.stories.tsx +4 -4
- package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +7 -6
- package/src/components/CustomMessage/context.tsx +2 -7
- package/src/components/CustomMessage/index.tsx +4 -1
- package/src/components/CustomMessageInput/CustomMessageInput.stories.tsx +11 -33
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +3 -3
- package/src/components/CustomSystemMessage/CustomSystemMessage.test.tsx +9 -6
- package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +1 -4
- package/src/components/IconButton/index.tsx +17 -21
- package/src/components/LinkAttachment/LinkAttachment.test.tsx +1 -4
- package/src/components/LinkAttachment/components/_shared/CardCta.tsx +1 -4
- package/src/components/Loading/Loading.stories.tsx +4 -1
- package/src/components/MediaMessage/MediaMessage.stories.tsx +18 -6
- package/src/components/MediaMessage/MediaMessage.test.tsx +16 -30
- package/src/components/MediaMessage/index.tsx +14 -51
- package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +2 -4
- package/src/components/MessageAttachment/File/FileAttachment.stories.tsx +1 -3
- package/src/components/MessageAttachment/Image/ImageAttachment.stories.tsx +4 -15
- package/src/components/MessageAttachment/Image/index.tsx +12 -6
- package/src/components/MessageAttachment/MessageAttachment.test.tsx +27 -59
- package/src/components/MessageAttachment/Pdf/PdfAttachment.stories.tsx +1 -3
- package/src/components/MessageAttachment/Pdf/index.tsx +3 -2
- package/src/components/MessageAttachment/Video/VideoAttachment.stories.tsx +3 -11
- package/src/components/MessageAttachment/Video/index.tsx +14 -9
- package/src/components/MessageAttachment/_shared/CompactDocumentRow.tsx +3 -1
- package/src/components/MessageAttachment/_shared/ImageViewer.tsx +3 -1
- package/src/components/MessageAttachment/_shared/MediaStackGrid.tsx +1 -5
- package/src/components/MessageAttachment/_shared/PdfViewer.tsx +3 -1
- package/src/components/MessageAttachment/_shared/VideoViewer.tsx +3 -1
- package/src/components/MessageAttachment/_shared/fileMeta.test.ts +6 -4
- package/src/components/MessageAttachment/stories/StoryTable.tsx +3 -1
- package/src/components/MessageBubble/MessageBubble.stories.tsx +1 -5
- package/src/components/MessageBubble/MessageBubble.test.tsx +3 -1
- package/src/components/MessageBubble/components/_shared/bubbleTail.tsx +1 -2
- package/src/components/MessagingShell/LoadingState.stories.tsx +1 -0
- package/src/components/RichCard/RichCard.stories.tsx +3 -14
- package/src/components/RichCard/RichCard.test.tsx +4 -16
- package/src/components/RichCard/RichCardBody.tsx +3 -11
- package/src/components/RichCard/RichCardCard.tsx +3 -1
- package/src/components/RichCard/RichCardImages.tsx +4 -2
- package/src/components/SearchInput/SearchInput.stories.tsx +1 -0
- package/src/components/SearchInput/SearchInput.test.tsx +65 -94
- package/src/hooks/useChannelStar.ts +1 -3
- package/src/hooks/useMessaging.ts +4 -4
- package/src/stories/decorators/storyUser.tsx +2 -2
- package/src/stories/mocks.tsx +1 -4
- package/src/styles.css +8 -23
- package/src/types.ts +2 -1
- package/src/utils/cdnImageUrl.test.ts +1 -3
- package/dist/Card-DVMzYquF.js.map +0 -1
- package/dist/Card-DaJtDL7m.js.map +0 -1
- package/dist/index-BOBIiAuD.js.map +0 -1
|
@@ -13,7 +13,13 @@ vi.mock('../Avatar', () => ({
|
|
|
13
13
|
}))
|
|
14
14
|
|
|
15
15
|
vi.mock('../AttachmentCard/MediaPlayer', () => ({
|
|
16
|
-
default: ({
|
|
16
|
+
default: ({
|
|
17
|
+
source,
|
|
18
|
+
mimeType,
|
|
19
|
+
}: {
|
|
20
|
+
source: string
|
|
21
|
+
mimeType: string
|
|
22
|
+
}) => (
|
|
17
23
|
<div
|
|
18
24
|
role="button"
|
|
19
25
|
tabIndex={0}
|
|
@@ -76,10 +82,7 @@ describe('MediaMessage', () => {
|
|
|
76
82
|
|
|
77
83
|
const player = screen.getByTestId('media-player')
|
|
78
84
|
expect(player).toBeInTheDocument()
|
|
79
|
-
expect(player).toHaveAttribute(
|
|
80
|
-
'data-source',
|
|
81
|
-
'https://cdn.example.com/clip.mp4'
|
|
82
|
-
)
|
|
85
|
+
expect(player).toHaveAttribute('data-source', 'https://cdn.example.com/clip.mp4')
|
|
83
86
|
})
|
|
84
87
|
|
|
85
88
|
it('renders MediaPlayer for an audio attachment', () => {
|
|
@@ -182,10 +185,7 @@ describe('MediaMessage', () => {
|
|
|
182
185
|
)
|
|
183
186
|
|
|
184
187
|
expect(screen.getByTestId('avatar')).toBeInTheDocument()
|
|
185
|
-
expect(screen.getByTestId('avatar')).toHaveAttribute(
|
|
186
|
-
'data-user-id',
|
|
187
|
-
'user-1'
|
|
188
|
-
)
|
|
188
|
+
expect(screen.getByTestId('avatar')).toHaveAttribute('data-user-id', 'user-1')
|
|
189
189
|
})
|
|
190
190
|
|
|
191
191
|
it('does not render Avatar for own messages', () => {
|
|
@@ -308,9 +308,7 @@ describe('MediaMessage', () => {
|
|
|
308
308
|
/>
|
|
309
309
|
)
|
|
310
310
|
|
|
311
|
-
expect(
|
|
312
|
-
screen.queryByRole('button', { name: 'Download' })
|
|
313
|
-
).not.toBeInTheDocument()
|
|
311
|
+
expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument()
|
|
314
312
|
})
|
|
315
313
|
|
|
316
314
|
it('shows Download action for received audio attachment', () => {
|
|
@@ -392,12 +390,8 @@ describe('MediaMessage', () => {
|
|
|
392
390
|
/>
|
|
393
391
|
)
|
|
394
392
|
|
|
395
|
-
expect(
|
|
396
|
-
|
|
397
|
-
).not.toBeInTheDocument()
|
|
398
|
-
expect(
|
|
399
|
-
screen.queryByRole('button', { name: 'View full screen' })
|
|
400
|
-
).not.toBeInTheDocument()
|
|
393
|
+
expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument()
|
|
394
|
+
expect(screen.queryByRole('button', { name: 'View full screen' })).not.toBeInTheDocument()
|
|
401
395
|
})
|
|
402
396
|
|
|
403
397
|
it('MediaMessage.Visitor renders card without chat shell', () => {
|
|
@@ -416,9 +410,7 @@ describe('MediaMessage', () => {
|
|
|
416
410
|
/>
|
|
417
411
|
)
|
|
418
412
|
|
|
419
|
-
expect(
|
|
420
|
-
container.querySelector('.str-chat__message')
|
|
421
|
-
).not.toBeInTheDocument()
|
|
413
|
+
expect(container.querySelector('.str-chat__message')).not.toBeInTheDocument()
|
|
422
414
|
expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument()
|
|
423
415
|
})
|
|
424
416
|
|
|
@@ -437,12 +429,8 @@ describe('MediaMessage', () => {
|
|
|
437
429
|
/>
|
|
438
430
|
)
|
|
439
431
|
|
|
440
|
-
expect(
|
|
441
|
-
|
|
442
|
-
).not.toBeInTheDocument()
|
|
443
|
-
expect(
|
|
444
|
-
screen.queryByRole('button', { name: 'Download' })
|
|
445
|
-
).not.toBeInTheDocument()
|
|
432
|
+
expect(container.querySelector('.str-chat__message')).not.toBeInTheDocument()
|
|
433
|
+
expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument()
|
|
446
434
|
})
|
|
447
435
|
|
|
448
436
|
it('resolveLinkAttachment ignores empty-string og_scrape_url without asset_url', () => {
|
|
@@ -460,9 +448,7 @@ describe('MediaMessage', () => {
|
|
|
460
448
|
})
|
|
461
449
|
|
|
462
450
|
it('Download button triggers fetch and uses fallback on failure', async () => {
|
|
463
|
-
const fetchSpy = vi
|
|
464
|
-
.spyOn(global, 'fetch')
|
|
465
|
-
.mockRejectedValue(new Error('fail'))
|
|
451
|
+
const fetchSpy = vi.spyOn(global, 'fetch').mockRejectedValue(new Error('fail'))
|
|
466
452
|
const openSpy = vi.spyOn(window, 'open').mockReturnValue({
|
|
467
453
|
location: { href: '' },
|
|
468
454
|
close: vi.fn(),
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CircleNotchIcon,
|
|
3
|
-
DownloadSimpleIcon,
|
|
4
|
-
LinkIcon,
|
|
5
|
-
} from '@phosphor-icons/react'
|
|
1
|
+
import { CircleNotchIcon, DownloadSimpleIcon, LinkIcon } from '@phosphor-icons/react'
|
|
6
2
|
import React, { useState } from 'react'
|
|
7
3
|
import type { Attachment, LocalMessage } from 'stream-chat'
|
|
8
4
|
|
|
@@ -73,23 +69,15 @@ const LinkCard: React.FC<{
|
|
|
73
69
|
</div>
|
|
74
70
|
<div className="px-3 pb-3">
|
|
75
71
|
{title && (
|
|
76
|
-
<p
|
|
77
|
-
className={`truncate text-[14px] font-medium leading-5 ${linkPrimaryText(isMyMessage)}`}
|
|
78
|
-
>
|
|
72
|
+
<p className={`truncate text-[14px] font-medium leading-5 ${linkPrimaryText(isMyMessage)}`}>
|
|
79
73
|
{title}
|
|
80
74
|
</p>
|
|
81
75
|
)}
|
|
82
76
|
{text && (
|
|
83
|
-
<p
|
|
84
|
-
className={`truncate text-[12px] leading-4 ${linkSecondaryText(isMyMessage)}`}
|
|
85
|
-
>
|
|
86
|
-
{text}
|
|
87
|
-
</p>
|
|
77
|
+
<p className={`truncate text-[12px] leading-4 ${linkSecondaryText(isMyMessage)}`}>{text}</p>
|
|
88
78
|
)}
|
|
89
79
|
{url && (
|
|
90
|
-
<p
|
|
91
|
-
className={`mt-1 truncate text-[12px] leading-4 ${linkTertiaryText(isMyMessage)}`}
|
|
92
|
-
>
|
|
80
|
+
<p className={`mt-1 truncate text-[12px] leading-4 ${linkTertiaryText(isMyMessage)}`}>
|
|
93
81
|
{url}
|
|
94
82
|
</p>
|
|
95
83
|
)}
|
|
@@ -99,12 +87,7 @@ const LinkCard: React.FC<{
|
|
|
99
87
|
|
|
100
88
|
if (url) {
|
|
101
89
|
return (
|
|
102
|
-
<a
|
|
103
|
-
href={url}
|
|
104
|
-
target="_blank"
|
|
105
|
-
rel="noopener noreferrer"
|
|
106
|
-
className="block no-underline"
|
|
107
|
-
>
|
|
90
|
+
<a href={url} target="_blank" rel="noopener noreferrer" className="block no-underline">
|
|
108
91
|
{body}
|
|
109
92
|
</a>
|
|
110
93
|
)
|
|
@@ -125,11 +108,8 @@ export function resolveLinkAttachment(
|
|
|
125
108
|
|
|
126
109
|
async function triggerDownload(url: string, filename?: string): Promise<void> {
|
|
127
110
|
let name: string
|
|
128
|
-
try {
|
|
129
|
-
|
|
130
|
-
} catch {
|
|
131
|
-
name = filename ?? 'download'
|
|
132
|
-
}
|
|
111
|
+
try { name = filename ?? new URL(url).pathname.split('/').pop() ?? 'download' }
|
|
112
|
+
catch { name = filename ?? 'download' }
|
|
133
113
|
const res = await fetch(url, { mode: 'cors' })
|
|
134
114
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
135
115
|
const blob = await res.blob()
|
|
@@ -155,12 +135,8 @@ const DownloadAction: React.FC<{ url: string; filename?: string }> = ({
|
|
|
155
135
|
const fallback = window.open('', '_blank', 'noopener,noreferrer')
|
|
156
136
|
setBusy(true)
|
|
157
137
|
triggerDownload(url, filename)
|
|
158
|
-
.then(() => {
|
|
159
|
-
|
|
160
|
-
})
|
|
161
|
-
.catch(() => {
|
|
162
|
-
if (fallback) fallback.location.href = url
|
|
163
|
-
})
|
|
138
|
+
.then(() => { fallback?.close() })
|
|
139
|
+
.catch(() => { if (fallback) fallback.location.href = url })
|
|
164
140
|
.finally(() => setBusy(false))
|
|
165
141
|
}
|
|
166
142
|
|
|
@@ -172,10 +148,7 @@ const DownloadAction: React.FC<{ url: string; filename?: string }> = ({
|
|
|
172
148
|
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-[#121110] px-4 text-sm font-medium leading-none text-white hover:bg-[#2a2928] disabled:opacity-70"
|
|
173
149
|
>
|
|
174
150
|
{busy ? (
|
|
175
|
-
<CircleNotchIcon
|
|
176
|
-
className="size-4 animate-spin text-white"
|
|
177
|
-
weight="bold"
|
|
178
|
-
/>
|
|
151
|
+
<CircleNotchIcon className="size-4 animate-spin text-white" weight="bold" />
|
|
179
152
|
) : (
|
|
180
153
|
<React.Fragment>
|
|
181
154
|
<DownloadSimpleIcon className="size-4 text-white" weight="bold" />
|
|
@@ -232,10 +205,8 @@ export function resolveMediaFromMessage(
|
|
|
232
205
|
: 'application/octet-stream')
|
|
233
206
|
|
|
234
207
|
const title = (activeAttachment as { title?: string } | undefined)?.title
|
|
235
|
-
const fileSize = (activeAttachment as { file_size?: number } | undefined)
|
|
236
|
-
|
|
237
|
-
const thumbnailUrl = (videoAttachment as { thumb_url?: string } | undefined)
|
|
238
|
-
?.thumb_url
|
|
208
|
+
const fileSize = (activeAttachment as { file_size?: number } | undefined)?.file_size
|
|
209
|
+
const thumbnailUrl = (videoAttachment as { thumb_url?: string } | undefined)?.thumb_url
|
|
239
210
|
|
|
240
211
|
return {
|
|
241
212
|
resolvedUrl,
|
|
@@ -355,19 +326,11 @@ const MediaMessageRoot: React.FC<MediaMessageProps> = ({
|
|
|
355
326
|
// tail tinted from the text-bubble surface (#f2f1ef) hangs off
|
|
356
327
|
// a white card and reads as a detached grey nub.
|
|
357
328
|
data-attachment-bubble="true"
|
|
358
|
-
style={{
|
|
359
|
-
padding: 0,
|
|
360
|
-
borderRadius: 0,
|
|
361
|
-
overflow: 'visible',
|
|
362
|
-
background: 'transparent',
|
|
363
|
-
}}
|
|
329
|
+
style={{ padding: 0, borderRadius: 0, overflow: 'visible', background: 'transparent' }}
|
|
364
330
|
>
|
|
365
331
|
{linkAttachment ? (
|
|
366
332
|
<div className={linkCardShellClass(isMyMessage)}>
|
|
367
|
-
<LinkCard
|
|
368
|
-
attachment={linkAttachment}
|
|
369
|
-
isMyMessage={isMyMessage}
|
|
370
|
-
/>
|
|
333
|
+
<LinkCard attachment={linkAttachment} isMyMessage={isMyMessage} />
|
|
371
334
|
</div>
|
|
372
335
|
) : isMyMessage ? (
|
|
373
336
|
<Creator {...resolved!} />
|
|
@@ -145,8 +145,7 @@ export const Stacked: StoryFn = () => (
|
|
|
145
145
|
mimeType="audio/mpeg"
|
|
146
146
|
filename="Morning meditation.mp3"
|
|
147
147
|
onDismiss={handleDismiss}
|
|
148
|
-
/>
|
|
149
|
-
}
|
|
148
|
+
/>}
|
|
150
149
|
sent={<AudioAttachment.Sent items={STACK_AUDIO.slice(0, 2)} />}
|
|
151
150
|
received={<AudioAttachment.Received items={STACK_AUDIO.slice(0, 2)} />}
|
|
152
151
|
/>
|
|
@@ -158,8 +157,7 @@ export const Stacked: StoryFn = () => (
|
|
|
158
157
|
mimeType="audio/mpeg"
|
|
159
158
|
filename="Morning meditation.mp3"
|
|
160
159
|
onDismiss={handleDismiss}
|
|
161
|
-
/>
|
|
162
|
-
}
|
|
160
|
+
/>}
|
|
163
161
|
sent={<AudioAttachment.Sent items={STACK_AUDIO} />}
|
|
164
162
|
received={<AudioAttachment.Received items={STACK_AUDIO} />}
|
|
165
163
|
/>
|
|
@@ -275,9 +275,7 @@ export const Grouping: StoryFn = () => (
|
|
|
275
275
|
<StoryGrid>
|
|
276
276
|
<StoryRow
|
|
277
277
|
label="single"
|
|
278
|
-
sent={
|
|
279
|
-
<FileAttachment.Sent {...STACK_FILES[0]} groupPosition="single" />
|
|
280
|
-
}
|
|
278
|
+
sent={<FileAttachment.Sent {...STACK_FILES[0]} groupPosition="single" />}
|
|
281
279
|
received={
|
|
282
280
|
<FileAttachment.Received {...STACK_FILES[0]} groupPosition="single" />
|
|
283
281
|
}
|
|
@@ -245,10 +245,7 @@ export const Stacked: StoryFn = () => (
|
|
|
245
245
|
/>
|
|
246
246
|
}
|
|
247
247
|
sent={
|
|
248
|
-
<ImageAttachment.Sent
|
|
249
|
-
items={STACK_IMAGES.slice(0, 2)}
|
|
250
|
-
filename="album"
|
|
251
|
-
/>
|
|
248
|
+
<ImageAttachment.Sent items={STACK_IMAGES.slice(0, 2)} filename="album" />
|
|
252
249
|
}
|
|
253
250
|
received={
|
|
254
251
|
<ImageAttachment.Received
|
|
@@ -268,10 +265,7 @@ export const Stacked: StoryFn = () => (
|
|
|
268
265
|
/>
|
|
269
266
|
}
|
|
270
267
|
sent={
|
|
271
|
-
<ImageAttachment.Sent
|
|
272
|
-
items={STACK_IMAGES.slice(0, 3)}
|
|
273
|
-
filename="album"
|
|
274
|
-
/>
|
|
268
|
+
<ImageAttachment.Sent items={STACK_IMAGES.slice(0, 3)} filename="album" />
|
|
275
269
|
}
|
|
276
270
|
received={
|
|
277
271
|
<ImageAttachment.Received
|
|
@@ -291,10 +285,7 @@ export const Stacked: StoryFn = () => (
|
|
|
291
285
|
/>
|
|
292
286
|
}
|
|
293
287
|
sent={
|
|
294
|
-
<ImageAttachment.Sent
|
|
295
|
-
items={STACK_IMAGES.slice(0, 4)}
|
|
296
|
-
filename="album"
|
|
297
|
-
/>
|
|
288
|
+
<ImageAttachment.Sent items={STACK_IMAGES.slice(0, 4)} filename="album" />
|
|
298
289
|
}
|
|
299
290
|
received={
|
|
300
291
|
<ImageAttachment.Received
|
|
@@ -306,9 +297,7 @@ export const Stacked: StoryFn = () => (
|
|
|
306
297
|
<StoryRow
|
|
307
298
|
label="6 photos (+overflow)"
|
|
308
299
|
sent={<ImageAttachment.Sent items={STACK_IMAGES} filename="album" />}
|
|
309
|
-
received={
|
|
310
|
-
<ImageAttachment.Received items={STACK_IMAGES} filename="album" />
|
|
311
|
-
}
|
|
300
|
+
received={<ImageAttachment.Received items={STACK_IMAGES} filename="album" />}
|
|
312
301
|
/>
|
|
313
302
|
</StoryGrid>
|
|
314
303
|
</StoryPage>
|
|
@@ -3,8 +3,12 @@ import React from 'react'
|
|
|
3
3
|
import { optimizeMessagingAttachmentUrl } from '../../../utils/cdnImageUrl'
|
|
4
4
|
import Bubble from '../_shared/Bubble'
|
|
5
5
|
import DismissButton from '../_shared/DismissButton'
|
|
6
|
-
import ImageViewer, {
|
|
7
|
-
|
|
6
|
+
import ImageViewer, {
|
|
7
|
+
type ImageViewerItem,
|
|
8
|
+
} from '../_shared/ImageViewer'
|
|
9
|
+
import MediaStackGrid, {
|
|
10
|
+
type MediaStackTile,
|
|
11
|
+
} from '../_shared/MediaStackGrid'
|
|
8
12
|
import { useViewer } from '../_shared/useViewer'
|
|
9
13
|
import {
|
|
10
14
|
bubbleVariantForState,
|
|
@@ -146,8 +150,9 @@ const ImageComposerInner: React.FC<{
|
|
|
146
150
|
onClick?: (index: number) => boolean | void
|
|
147
151
|
onDismiss?: () => void
|
|
148
152
|
}> = ({ src, alt, filename, loading = 'lazy', onClick, onDismiss }) => {
|
|
149
|
-
const { viewerOpen, viewerIndex, handleActivate, closeViewer } =
|
|
150
|
-
|
|
153
|
+
const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(
|
|
154
|
+
onClick
|
|
155
|
+
)
|
|
151
156
|
|
|
152
157
|
return (
|
|
153
158
|
<div className="relative w-fit">
|
|
@@ -263,8 +268,9 @@ const ImageBubbleRow: React.FC<InternalImageRowProps> = ({
|
|
|
263
268
|
}) => {
|
|
264
269
|
const resolvedItems = resolveItems({ src, alt, items })
|
|
265
270
|
const variant = bubbleVariantForState(state)
|
|
266
|
-
const { viewerOpen, viewerIndex, handleActivate, closeViewer } =
|
|
267
|
-
|
|
271
|
+
const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(
|
|
272
|
+
onClick
|
|
273
|
+
)
|
|
268
274
|
const isSingle = resolvedItems.length === 1
|
|
269
275
|
const single = useSingleImageRatio(isSingle ? resolvedItems[0] : undefined)
|
|
270
276
|
|
|
@@ -307,10 +307,9 @@ describe('MessageAttachment.Image single-image aspect ratio', () => {
|
|
|
307
307
|
renderWithProviders(
|
|
308
308
|
<MessageAttachment.Image.Sent src="https://cdn.example.com/unsized.jpg" />
|
|
309
309
|
)
|
|
310
|
-
expect(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
)
|
|
310
|
+
expect(
|
|
311
|
+
Number(mediaBox('Open image 1 of 1').style.aspectRatio)
|
|
312
|
+
).toBeCloseTo(326 / 246, 4)
|
|
314
313
|
|
|
315
314
|
// Scoped to the tile: the always-mounted `ImageViewer` renders a
|
|
316
315
|
// second `<img>` for the same source.
|
|
@@ -327,10 +326,9 @@ describe('MessageAttachment.Image single-image aspect ratio', () => {
|
|
|
327
326
|
})
|
|
328
327
|
fireEvent.load(img)
|
|
329
328
|
|
|
330
|
-
expect(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
)
|
|
329
|
+
expect(
|
|
330
|
+
Number(mediaBox('Open image 1 of 1').style.aspectRatio)
|
|
331
|
+
).toBeCloseTo(0.5625, 4)
|
|
334
332
|
})
|
|
335
333
|
|
|
336
334
|
it('leaves a stacked grid on its fixed aspect', () => {
|
|
@@ -577,7 +575,9 @@ describe('MessageAttachment.Pdf', () => {
|
|
|
577
575
|
expect(() =>
|
|
578
576
|
rerender(
|
|
579
577
|
<MessageAttachment.Pdf.Received
|
|
580
|
-
items={[
|
|
578
|
+
items={[
|
|
579
|
+
{ src: 'https://cdn.example.com/a.pdf', filename: 'a.pdf' },
|
|
580
|
+
]}
|
|
581
581
|
/>
|
|
582
582
|
)
|
|
583
583
|
).not.toThrow()
|
|
@@ -731,7 +731,9 @@ describe('MessageAttachment.Audio', () => {
|
|
|
731
731
|
expect(screen.queryByText('song.mp3')).toBeNull()
|
|
732
732
|
// Native player handles its own download in the kebab menu, so
|
|
733
733
|
// no extra Download button should be rendered.
|
|
734
|
-
expect(
|
|
734
|
+
expect(
|
|
735
|
+
screen.queryByRole('button', { name: /download/i })
|
|
736
|
+
).toBeNull()
|
|
735
737
|
})
|
|
736
738
|
|
|
737
739
|
it('shows an inline dismiss button next to the player on Composer state', () => {
|
|
@@ -883,11 +885,7 @@ describe('MessageAttachment lazy-loading defaults', () => {
|
|
|
883
885
|
renderWithProviders(
|
|
884
886
|
<MessageAttachment.Image.Sent
|
|
885
887
|
items={[
|
|
886
|
-
{
|
|
887
|
-
src: 'https://cdn.example.com/a.jpg',
|
|
888
|
-
alt: 'A',
|
|
889
|
-
loading: 'eager',
|
|
890
|
-
},
|
|
888
|
+
{ src: 'https://cdn.example.com/a.jpg', alt: 'A', loading: 'eager' },
|
|
891
889
|
{ src: 'https://cdn.example.com/b.jpg', alt: 'B' },
|
|
892
890
|
]}
|
|
893
891
|
/>
|
|
@@ -929,7 +927,9 @@ describe('MessageAttachment lazy-loading defaults', () => {
|
|
|
929
927
|
mimeType="video/mp4"
|
|
930
928
|
/>
|
|
931
929
|
)
|
|
932
|
-
const poster = screen
|
|
930
|
+
const poster = screen
|
|
931
|
+
.getByTestId('video-attachment')
|
|
932
|
+
.querySelector('img')
|
|
933
933
|
expect(poster?.getAttribute('src')).toBe(
|
|
934
934
|
'https://cdn.example.com/poster.jpg'
|
|
935
935
|
)
|
|
@@ -947,7 +947,9 @@ describe('MessageAttachment lazy-loading defaults', () => {
|
|
|
947
947
|
/>
|
|
948
948
|
)
|
|
949
949
|
fireEvent.click(screen.getByLabelText('Play video 1 of 1'))
|
|
950
|
-
const video = screen
|
|
950
|
+
const video = screen
|
|
951
|
+
.getByTestId('video-viewer')
|
|
952
|
+
.querySelector('video')
|
|
951
953
|
expect(video?.getAttribute('preload')).toBe('metadata')
|
|
952
954
|
})
|
|
953
955
|
})
|
|
@@ -1009,45 +1011,16 @@ describe('MessageAttachment lazy-loading defaults', () => {
|
|
|
1009
1011
|
|
|
1010
1012
|
describe('bubbleGroupPositionFromStream', () => {
|
|
1011
1013
|
it.each([
|
|
1012
|
-
{
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
},
|
|
1018
|
-
{
|
|
1019
|
-
groupedByUser: true,
|
|
1020
|
-
firstOfGroup: true,
|
|
1021
|
-
endOfGroup: true,
|
|
1022
|
-
expected: 'single',
|
|
1023
|
-
},
|
|
1024
|
-
{
|
|
1025
|
-
groupedByUser: true,
|
|
1026
|
-
firstOfGroup: true,
|
|
1027
|
-
endOfGroup: false,
|
|
1028
|
-
expected: 'first',
|
|
1029
|
-
},
|
|
1030
|
-
{
|
|
1031
|
-
groupedByUser: true,
|
|
1032
|
-
firstOfGroup: false,
|
|
1033
|
-
endOfGroup: true,
|
|
1034
|
-
expected: 'end',
|
|
1035
|
-
},
|
|
1036
|
-
{
|
|
1037
|
-
groupedByUser: true,
|
|
1038
|
-
firstOfGroup: false,
|
|
1039
|
-
endOfGroup: false,
|
|
1040
|
-
expected: 'middle',
|
|
1041
|
-
},
|
|
1014
|
+
{ groupedByUser: false, firstOfGroup: true, endOfGroup: true, expected: 'single' },
|
|
1015
|
+
{ groupedByUser: true, firstOfGroup: true, endOfGroup: true, expected: 'single' },
|
|
1016
|
+
{ groupedByUser: true, firstOfGroup: true, endOfGroup: false, expected: 'first' },
|
|
1017
|
+
{ groupedByUser: true, firstOfGroup: false, endOfGroup: true, expected: 'end' },
|
|
1018
|
+
{ groupedByUser: true, firstOfGroup: false, endOfGroup: false, expected: 'middle' },
|
|
1042
1019
|
])(
|
|
1043
1020
|
'maps groupedByUser=$groupedByUser firstOfGroup=$firstOfGroup endOfGroup=$endOfGroup → $expected',
|
|
1044
1021
|
({ groupedByUser, firstOfGroup, endOfGroup, expected }) => {
|
|
1045
1022
|
expect(
|
|
1046
|
-
bubbleGroupPositionFromStream({
|
|
1047
|
-
groupedByUser,
|
|
1048
|
-
firstOfGroup,
|
|
1049
|
-
endOfGroup,
|
|
1050
|
-
})
|
|
1023
|
+
bubbleGroupPositionFromStream({ groupedByUser, firstOfGroup, endOfGroup })
|
|
1051
1024
|
).toBe(expected)
|
|
1052
1025
|
}
|
|
1053
1026
|
)
|
|
@@ -1077,15 +1050,10 @@ describe('MessageAttachment.Bubble grouping', () => {
|
|
|
1077
1050
|
'serializes groupPosition="%s" on the bubble',
|
|
1078
1051
|
(groupPosition) => {
|
|
1079
1052
|
renderWithProviders(
|
|
1080
|
-
<MessageAttachment.File.Sent
|
|
1081
|
-
{...FILE_PROPS}
|
|
1082
|
-
groupPosition={groupPosition}
|
|
1083
|
-
/>
|
|
1053
|
+
<MessageAttachment.File.Sent {...FILE_PROPS} groupPosition={groupPosition} />
|
|
1084
1054
|
)
|
|
1085
1055
|
expect(
|
|
1086
|
-
screen
|
|
1087
|
-
.getByTestId('file-attachment')
|
|
1088
|
-
.getAttribute('data-group-position')
|
|
1056
|
+
screen.getByTestId('file-attachment').getAttribute('data-group-position')
|
|
1089
1057
|
).toBe(groupPosition)
|
|
1090
1058
|
}
|
|
1091
1059
|
)
|
|
@@ -102,9 +102,7 @@ export const Single: StoryFn = () => (
|
|
|
102
102
|
/>
|
|
103
103
|
}
|
|
104
104
|
sent={<PdfAttachment.Sent src={PDF_SRC} filename="contract.pdf" />}
|
|
105
|
-
received={
|
|
106
|
-
<PdfAttachment.Received src={PDF_SRC} filename="contract.pdf" />
|
|
107
|
-
}
|
|
105
|
+
received={<PdfAttachment.Received src={PDF_SRC} filename="contract.pdf" />}
|
|
108
106
|
/>
|
|
109
107
|
</StoryGrid>
|
|
110
108
|
</StoryPage>
|
|
@@ -119,8 +119,9 @@ const PdfAttachmentRow: React.FC<InternalPdfRowProps> = ({
|
|
|
119
119
|
}) => {
|
|
120
120
|
const variant = bubbleVariantForState(state)
|
|
121
121
|
const resolvedItems = resolveItems({ src, filename, fileSize, title, items })
|
|
122
|
-
const { viewerOpen, viewerIndex, handleActivate, closeViewer } =
|
|
123
|
-
|
|
122
|
+
const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(
|
|
123
|
+
onClick
|
|
124
|
+
)
|
|
124
125
|
const showDismiss = state === 'composer' && !!onDismiss
|
|
125
126
|
|
|
126
127
|
if (resolvedItems.length === 0) {
|
|
@@ -183,10 +183,7 @@ export const Stacked: StoryFn = () => (
|
|
|
183
183
|
/>
|
|
184
184
|
}
|
|
185
185
|
sent={
|
|
186
|
-
<VideoAttachment.Sent
|
|
187
|
-
items={STACK_VIDEOS.slice(0, 2)}
|
|
188
|
-
filename="clips"
|
|
189
|
-
/>
|
|
186
|
+
<VideoAttachment.Sent items={STACK_VIDEOS.slice(0, 2)} filename="clips" />
|
|
190
187
|
}
|
|
191
188
|
received={
|
|
192
189
|
<VideoAttachment.Received
|
|
@@ -207,10 +204,7 @@ export const Stacked: StoryFn = () => (
|
|
|
207
204
|
/>
|
|
208
205
|
}
|
|
209
206
|
sent={
|
|
210
|
-
<VideoAttachment.Sent
|
|
211
|
-
items={STACK_VIDEOS.slice(0, 3)}
|
|
212
|
-
filename="clips"
|
|
213
|
-
/>
|
|
207
|
+
<VideoAttachment.Sent items={STACK_VIDEOS.slice(0, 3)} filename="clips" />
|
|
214
208
|
}
|
|
215
209
|
received={
|
|
216
210
|
<VideoAttachment.Received
|
|
@@ -231,9 +225,7 @@ export const Stacked: StoryFn = () => (
|
|
|
231
225
|
/>
|
|
232
226
|
}
|
|
233
227
|
sent={<VideoAttachment.Sent items={STACK_VIDEOS} filename="clips" />}
|
|
234
|
-
received={
|
|
235
|
-
<VideoAttachment.Received items={STACK_VIDEOS} filename="clips" />
|
|
236
|
-
}
|
|
228
|
+
received={<VideoAttachment.Received items={STACK_VIDEOS} filename="clips" />}
|
|
237
229
|
/>
|
|
238
230
|
</StoryGrid>
|
|
239
231
|
</StoryPage>
|
|
@@ -3,7 +3,9 @@ import React from 'react'
|
|
|
3
3
|
|
|
4
4
|
import Bubble from '../_shared/Bubble'
|
|
5
5
|
import DismissButton from '../_shared/DismissButton'
|
|
6
|
-
import MediaStackGrid, {
|
|
6
|
+
import MediaStackGrid, {
|
|
7
|
+
type MediaStackTile,
|
|
8
|
+
} from '../_shared/MediaStackGrid'
|
|
7
9
|
import { useViewer } from '../_shared/useViewer'
|
|
8
10
|
import VideoViewer, { type VideoViewerItem } from '../_shared/VideoViewer'
|
|
9
11
|
import {
|
|
@@ -115,9 +117,7 @@ const resolveItems = ({
|
|
|
115
117
|
items?: VideoItem[]
|
|
116
118
|
}): VideoItem[] => {
|
|
117
119
|
if (items && items.length > 0) {
|
|
118
|
-
return preload
|
|
119
|
-
? items.map((it) => ({ ...it, preload: it.preload ?? preload }))
|
|
120
|
-
: items
|
|
120
|
+
return preload ? items.map((it) => ({ ...it, preload: it.preload ?? preload })) : items
|
|
121
121
|
}
|
|
122
122
|
if (src) return [{ src, poster, mimeType, preload }]
|
|
123
123
|
return []
|
|
@@ -167,8 +167,9 @@ const VideoComposerInner: React.FC<{
|
|
|
167
167
|
onClick?: (index: number) => boolean | void
|
|
168
168
|
onDismiss?: () => void
|
|
169
169
|
}> = ({ src, poster, mimeType, filename, preload, onClick, onDismiss }) => {
|
|
170
|
-
const { viewerOpen, viewerIndex, handleActivate, closeViewer } =
|
|
171
|
-
|
|
170
|
+
const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(
|
|
171
|
+
onClick
|
|
172
|
+
)
|
|
172
173
|
|
|
173
174
|
return (
|
|
174
175
|
<div className="relative w-fit">
|
|
@@ -188,7 +189,10 @@ const VideoComposerInner: React.FC<{
|
|
|
188
189
|
|
|
189
190
|
<VideoViewer
|
|
190
191
|
open={viewerOpen}
|
|
191
|
-
items={buildViewerItems(
|
|
192
|
+
items={buildViewerItems(
|
|
193
|
+
[{ src, poster, mimeType, preload }],
|
|
194
|
+
filename
|
|
195
|
+
)}
|
|
192
196
|
initialIndex={viewerIndex}
|
|
193
197
|
onClose={closeViewer}
|
|
194
198
|
/>
|
|
@@ -215,8 +219,9 @@ const VideoBubbleRow: React.FC<InternalVideoRowProps> = ({
|
|
|
215
219
|
}) => {
|
|
216
220
|
const resolvedItems = resolveItems({ src, poster, mimeType, preload, items })
|
|
217
221
|
const variant = bubbleVariantForState(state)
|
|
218
|
-
const { viewerOpen, viewerIndex, handleActivate, closeViewer } =
|
|
219
|
-
|
|
222
|
+
const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(
|
|
223
|
+
onClick
|
|
224
|
+
)
|
|
220
225
|
|
|
221
226
|
if (resolvedItems.length === 0) {
|
|
222
227
|
return null
|
|
@@ -126,7 +126,9 @@ const CompactDocumentRow: React.FC<CompactDocumentRowProps> = ({
|
|
|
126
126
|
aria-label={activateLabel}
|
|
127
127
|
className={classNames(
|
|
128
128
|
'flex min-w-0 flex-1 items-center gap-3 rounded-sm text-left transition-colors',
|
|
129
|
-
variant === 'dark'
|
|
129
|
+
variant === 'dark'
|
|
130
|
+
? 'hover:bg-white/[0.04]'
|
|
131
|
+
: 'hover:bg-black/[0.04]'
|
|
130
132
|
)}
|
|
131
133
|
>
|
|
132
134
|
{iconTile}
|
|
@@ -67,7 +67,9 @@ const ImageViewer: React.FC<ImageViewerProps> = ({
|
|
|
67
67
|
open={open}
|
|
68
68
|
onClose={onClose}
|
|
69
69
|
ariaLabel={filename}
|
|
70
|
-
counter={
|
|
70
|
+
counter={
|
|
71
|
+
items.length > 1 ? `${index + 1} / ${items.length}` : undefined
|
|
72
|
+
}
|
|
71
73
|
actions={
|
|
72
74
|
<DownloadAction
|
|
73
75
|
url={item.src}
|
|
@@ -119,11 +119,7 @@ const MediaStackGrid: React.FC<MediaStackGridProps> = ({
|
|
|
119
119
|
const visible = tiles.slice(0, Math.min(total, maxVisible))
|
|
120
120
|
const overflow = total - visible.length
|
|
121
121
|
|
|
122
|
-
const renderTile = (
|
|
123
|
-
tile: MediaStackTile,
|
|
124
|
-
index: number,
|
|
125
|
-
extra?: React.ReactNode
|
|
126
|
-
) => {
|
|
122
|
+
const renderTile = (tile: MediaStackTile, index: number, extra?: React.ReactNode) => {
|
|
127
123
|
const sharedClass = classNames(
|
|
128
124
|
TILE_SHELL,
|
|
129
125
|
'h-full w-full',
|
|
@@ -65,7 +65,9 @@ const PdfViewer: React.FC<PdfViewerProps> = ({
|
|
|
65
65
|
open={open}
|
|
66
66
|
onClose={onClose}
|
|
67
67
|
ariaLabel={filename}
|
|
68
|
-
counter={
|
|
68
|
+
counter={
|
|
69
|
+
items.length > 1 ? `${index + 1} / ${items.length}` : undefined
|
|
70
|
+
}
|
|
69
71
|
data-testid="pdf-viewer"
|
|
70
72
|
>
|
|
71
73
|
<iframe
|