Skip to content

Commit 2859c76

Browse files
authored
feat: render voice recording preview in a dedicated slot (#2957)
1 parent 5702090 commit 2859c76

11 files changed

Lines changed: 97 additions & 37 deletions

src/components/MessageInput/AttachmentPreviewList/AttachmentPreviewList.tsx

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ComponentType } from 'react';
1+
import { type ComponentType, useMemo } from 'react';
22
import React from 'react';
33
import {
44
isLocalAttachment,
@@ -8,12 +8,12 @@ import {
88
isLocalVideoAttachment,
99
isLocalVoiceRecordingAttachment,
1010
isScrapedContent,
11+
isVoiceRecordingAttachment,
1112
} from 'stream-chat';
1213
import {
1314
UnsupportedAttachmentPreview as DefaultUnknownAttachmentPreview,
1415
type UnsupportedAttachmentPreviewProps,
1516
} from './UnsupportedAttachmentPreview';
16-
import { type VoiceRecordingPreviewProps } from './VoiceRecordingPreview';
1717
import {
1818
FileAttachmentPreview as DefaultFileAttachmentPreview,
1919
type FileAttachmentPreviewProps,
@@ -40,7 +40,6 @@ export type AttachmentPreviewListProps = {
4040
ImageAttachmentPreview?: ComponentType<ImageAttachmentPreviewProps>;
4141
UnsupportedAttachmentPreview?: ComponentType<UnsupportedAttachmentPreviewProps>;
4242
VideoAttachmentPreview?: ComponentType<MediaAttachmentPreviewProps>;
43-
VoiceRecordingPreview?: ComponentType<VoiceRecordingPreviewProps>;
4443
};
4544

4645
export const AttachmentPreviewList = ({
@@ -50,21 +49,20 @@ export const AttachmentPreviewList = ({
5049
ImageAttachmentPreview = MediaAttachmentPreview,
5150
UnsupportedAttachmentPreview = DefaultUnknownAttachmentPreview,
5251
VideoAttachmentPreview = MediaAttachmentPreview,
53-
VoiceRecordingPreview = DefaultAudioAttachmentPreview,
5452
}: AttachmentPreviewListProps) => {
5553
const messageComposer = useMessageComposer();
5654

5755
// todo: we could also allow to attach poll to a message composition
5856
const { attachments, location } = useAttachmentsForPreview();
57+
const filteredAttachments = useMemo(
58+
() => attachments.filter((a) => !isVoiceRecordingAttachment(a)),
59+
[attachments],
60+
);
5961

60-
if (!attachments.length && !location) return null;
62+
if (!filteredAttachments.length && !location) return null;
6163

6264
return (
6365
<div className='str-chat__attachment-preview-list'>
64-
{/*<div*/}
65-
{/* className='str-chat__attachment-list-scroll-container'*/}
66-
{/* data-testid='attachment-list-scroll-container'*/}
67-
{/*>*/}
6866
{location && (
6967
<GeolocationPreview
7068
location={location}
@@ -79,16 +77,9 @@ export const AttachmentPreviewList = ({
7977
)}
8078
{attachments.map((attachment) => {
8179
if (isScrapedContent(attachment)) return null;
82-
if (isLocalVoiceRecordingAttachment(attachment)) {
83-
return (
84-
<VoiceRecordingPreview
85-
attachment={attachment}
86-
handleRetry={messageComposer.attachmentManager.uploadAttachment}
87-
key={attachment.localMetadata.id || attachment.asset_url}
88-
removeAttachments={messageComposer.attachmentManager.removeAttachments}
89-
/>
90-
);
91-
} else if (isLocalAudioAttachment(attachment)) {
80+
// Voice recordings are rendered in the dedicated slot above (VoiceRecordingPreviewSlot)
81+
if (isLocalVoiceRecordingAttachment(attachment)) return null;
82+
if (isLocalAudioAttachment(attachment)) {
9283
return (
9384
<AudioAttachmentPreview
9485
attachment={attachment}
@@ -136,7 +127,6 @@ export const AttachmentPreviewList = ({
136127
}
137128
return null;
138129
})}
139-
{/*</div>*/}
140130
</div>
141131
);
142132
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { ComponentType } from 'react';
2+
import React from 'react';
3+
import { isLocalVoiceRecordingAttachment } from 'stream-chat';
4+
import { useAttachmentsForPreview, useMessageComposer } from '../hooks';
5+
import { AudioAttachmentPreview } from './AudioAttachmentPreview';
6+
import type { VoiceRecordingPreviewProps } from './VoiceRecordingPreview';
7+
8+
export type VoiceRecordingPreviewSlotProps = {
9+
/** Custom UI component for each voice recording preview in the slot; defaults to AudioAttachmentPreview */
10+
VoiceRecordingPreview?: ComponentType<VoiceRecordingPreviewProps>;
11+
};
12+
13+
/**
14+
* Dedicated slot for voice recording preview(s), rendered apart from the main attachment preview list
15+
*/
16+
export const VoiceRecordingPreviewSlot = ({
17+
VoiceRecordingPreview = AudioAttachmentPreview,
18+
}: VoiceRecordingPreviewSlotProps) => {
19+
const messageComposer = useMessageComposer();
20+
const { attachments } = useAttachmentsForPreview();
21+
22+
const voiceAttachments = attachments.filter(isLocalVoiceRecordingAttachment);
23+
const firstVoice = voiceAttachments[0];
24+
if (!firstVoice) return null;
25+
26+
return (
27+
<div
28+
className='str-chat__message-composer-voice-preview-slot'
29+
data-testid='voice-preview-slot'
30+
>
31+
<VoiceRecordingPreview
32+
attachment={firstVoice}
33+
handleRetry={messageComposer.attachmentManager.uploadAttachment}
34+
removeAttachments={messageComposer.attachmentManager.removeAttachments}
35+
/>
36+
</div>
37+
);
38+
};

src/components/MessageInput/AttachmentPreviewList/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ export type { UploadAttachmentPreviewProps as AttachmentPreviewProps } from './t
77
export type { UnsupportedAttachmentPreviewProps } from './UnsupportedAttachmentPreview';
88
export type { MediaAttachmentPreviewProps } from './MediaAttachmentPreview';
99
export type { VoiceRecordingPreviewProps } from './VoiceRecordingPreview';
10+
export {
11+
VoiceRecordingPreviewSlot,
12+
type VoiceRecordingPreviewSlotProps,
13+
} from './VoiceRecordingPreviewSlot';

src/components/MessageInput/MessageInputFlat.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import {
44
AttachmentSelector as DefaultAttachmentSelector,
55
SimpleAttachmentSelector,
66
} from './AttachmentSelector/AttachmentSelector';
7-
import { AttachmentPreviewList as DefaultAttachmentPreviewList } from './AttachmentPreviewList';
7+
import {
8+
AttachmentPreviewList as DefaultAttachmentPreviewList,
9+
VoiceRecordingPreviewSlot as DefaultVoiceRecordingPreviewSlot,
10+
} from './AttachmentPreviewList';
811
import { AudioRecorder as DefaultAudioRecorder } from '../MediaRecorder';
912
import { EditedMessagePreview as DefaultEditedMessagePreview } from './EditedMessagePreview';
1013
import { QuotedMessagePreview as DefaultQuotedMessagePreview } from './QuotedMessagePreview';
@@ -55,6 +58,7 @@ const MessageComposerPreviews = () => {
5558
EditedMessagePreview = DefaultEditedMessagePreview,
5659
LinkPreviewList = DefaultLinkPreviewList,
5760
QuotedMessagePreview = DefaultQuotedMessagePreview,
61+
VoiceRecordingPreviewSlot = DefaultVoiceRecordingPreviewSlot,
5862
} = useComponentContext();
5963

6064
const messageComposer = useMessageComposer();
@@ -97,6 +101,7 @@ const MessageComposerPreviews = () => {
97101
) : (
98102
<QuotedMessagePreview />
99103
)}
104+
<VoiceRecordingPreviewSlot />
100105
<AttachmentPreviewList />
101106
<LinkPreviewList />
102107
</div>

src/components/MessageInput/QuotedMessagePreview.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,14 @@ export const QuotedMessagePreview = ({
253253
);
254254

255255
return quotedMessage ? (
256-
<QuotedMessagePreviewUI
257-
getQuotedMessageAuthor={getQuotedMessageAuthor}
258-
onRemove={() => messageComposer.setQuotedMessage(null)}
259-
quotedMessage={quotedMessage}
260-
renderText={renderText}
261-
/>
256+
<div className='str-chat__message-composer__quoted-message-preview-slot'>
257+
<QuotedMessagePreviewUI
258+
getQuotedMessageAuthor={getQuotedMessageAuthor}
259+
onRemove={() => messageComposer.setQuotedMessage(null)}
260+
quotedMessage={quotedMessage}
261+
renderText={renderText}
262+
/>
263+
</div>
262264
) : null;
263265
};
264266

src/components/MessageInput/__tests__/AttachmentPreviewList.test.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,13 @@ describe('AttachmentPreviewList', () => {
118118
expect(screen.getByTitle(`file-upload-${state}`)).toBeInTheDocument();
119119
expect(screen.getByTitle(`image-upload-${state}`)).toBeInTheDocument();
120120
expect(screen.getByTitle(`audio-attachment-${state}`)).toBeInTheDocument();
121-
expect(
122-
screen.getByTitle(`voice-recording-attachment-${state}`),
123-
).toBeInTheDocument();
121+
// Voice recordings are rendered in VoiceRecordingPreviewSlot above the list (REACT-794)
124122
expect(screen.getByTitle(`video-attachment-${state}`)).toBeInTheDocument();
125123
},
126124
);
127125

128-
describe.each(['audio', 'file', 'image', 'unsupported', 'voiceRecording', 'video'])(
126+
// voiceRecording is rendered in VoiceRecordingPreviewSlot (REACT-794), not in AttachmentPreviewList
127+
describe.each(['audio', 'file', 'image', 'unsupported', 'video'])(
129128
'%s attachments rendering',
130129
(type) => {
131130
const customAttachment = {
@@ -143,7 +142,6 @@ describe('AttachmentPreviewList', () => {
143142
image: generateImageAttachment,
144143
unsupported: () => customAttachment,
145144
video: generateVideoAttachment,
146-
voiceRecording: generateVoiceRecordingAttachment,
147145
};
148146

149147
it('retries upload on upload button click', async () => {
@@ -265,7 +263,6 @@ describe('AttachmentPreviewList', () => {
265263
image: 'ImageAttachmentPreview',
266264
unsupported: 'UnsupportedAttachmentPreview',
267265
video: 'MediaAttachmentPreview',
268-
voiceRecording: 'VoiceRecordingPreview',
269266
};
270267
const title = `${type}-attachment`;
271268
const id = `${type}-id`;

src/components/MessageInput/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type {
88
AttachmentPreviewProps,
99
UnsupportedAttachmentPreviewProps,
1010
VoiceRecordingPreviewProps,
11+
VoiceRecordingPreviewSlotProps,
1112
} from './AttachmentPreviewList';
1213
export * from './CommandChip';
1314
export * from './CooldownTimer';

src/components/MessageInput/styling/AttachmentPreview.scss

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,24 @@
7373

7474
--str-chat__attachment-preview-file-fatal-error-color: var(--color-accent-error);
7575

76+
.str-chat__message-composer-voice-preview-slot {
77+
display: flex;
78+
align-items: center;
79+
width: 100%;
80+
padding: var(--spacing-xxs);
81+
min-width: 0;
82+
83+
.str-chat__attachment-preview-audio {
84+
width: 100%;
85+
min-width: 0;
86+
max-width: none;
87+
88+
.str-chat__attachment-preview-file__data {
89+
width: 100%;
90+
}
91+
}
92+
}
93+
7694
.str-chat__attachment-preview-list {
7795
@include utils.component-layer-overrides('attachment-preview-list');
7896
padding: var(--spacing-xxs);
@@ -309,6 +327,5 @@
309327
height: var(--button-visual-height-md);
310328
width: var(--button-visual-height-md);
311329
border: 1px solid var(--control-play-control-border);
312-
background-color: var(--control-play-control-bg);
313330
}
314331
}

src/components/MessageInput/styling/MessageComposer.scss

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,14 @@
6363
flex-direction: column;
6464
width: 100%;
6565
min-width: 0;
66-
padding-inline: var(--spacing-xs);
67-
padding-block: var(--spacing-sm);
6866
@include utils.component-layer-overrides('message-input');
6967
}
7068

7169
.str-chat__message-composer-previews {
7270
display: flex;
7371
flex-direction: column;
7472
width: 100%;
75-
padding-bottom: var(--spacing-xs);
73+
padding: var(--spacing-xs) var(--spacing-xs) 0;
7674
gap: var(--spacing-xxs);
7775

7876
min-width: 0;
@@ -83,6 +81,7 @@
8381
align-items: end;
8482
width: 100%;
8583
gap: var(--spacing-xs);
84+
padding: var(--spacing-sm);
8685

8786
$controls-containers-min-height: 26px;
8887

src/components/MessageInput/styling/QuotedMessagePreview.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
@use '../../../styling/utils';
22

33
.str-chat {
4+
.str-chat__message-composer__quoted-message-preview-slot {
5+
padding: var(--spacing-xxs);
6+
}
7+
48
.str-chat__quoted-message-preview {
59
display: flex;
610
align-items: center;

0 commit comments

Comments
 (0)