Skip to content

Commit 914950c

Browse files
committed
feat: render voice recording preview in a dedicated slot
1 parent 44e2eaf commit 914950c

8 files changed

Lines changed: 76 additions & 21 deletions

File tree

src/components/MessageInput/AttachmentPreviewList/AttachmentPreviewList.tsx

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
UnsupportedAttachmentPreview as DefaultUnknownAttachmentPreview,
1414
type UnsupportedAttachmentPreviewProps,
1515
} from './UnsupportedAttachmentPreview';
16-
import { type VoiceRecordingPreviewProps } from './VoiceRecordingPreview';
1716
import {
1817
FileAttachmentPreview as DefaultFileAttachmentPreview,
1918
type FileAttachmentPreviewProps,
@@ -40,7 +39,6 @@ export type AttachmentPreviewListProps = {
4039
ImageAttachmentPreview?: ComponentType<ImageAttachmentPreviewProps>;
4140
UnsupportedAttachmentPreview?: ComponentType<UnsupportedAttachmentPreviewProps>;
4241
VideoAttachmentPreview?: ComponentType<MediaAttachmentPreviewProps>;
43-
VoiceRecordingPreview?: ComponentType<VoiceRecordingPreviewProps>;
4442
};
4543

4644
export const AttachmentPreviewList = ({
@@ -50,7 +48,6 @@ export const AttachmentPreviewList = ({
5048
ImageAttachmentPreview = MediaAttachmentPreview,
5149
UnsupportedAttachmentPreview = DefaultUnknownAttachmentPreview,
5250
VideoAttachmentPreview = MediaAttachmentPreview,
53-
VoiceRecordingPreview = DefaultAudioAttachmentPreview,
5451
}: AttachmentPreviewListProps) => {
5552
const messageComposer = useMessageComposer();
5653

@@ -79,16 +76,9 @@ export const AttachmentPreviewList = ({
7976
)}
8077
{attachments.map((attachment) => {
8178
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)) {
79+
// Voice recordings are rendered in the dedicated slot above (VoiceRecordingPreviewSlot)
80+
if (isLocalVoiceRecordingAttachment(attachment)) return null;
81+
if (isLocalAudioAttachment(attachment)) {
9282
return (
9383
<AudioAttachmentPreview
9484
attachment={attachment}
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/__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/context/ComponentContext.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import type {
5151
TypingIndicatorProps,
5252
UnreadMessagesNotificationProps,
5353
UnreadMessagesSeparatorProps,
54+
VoiceRecordingPreviewSlotProps,
5455
} from '../components';
5556

5657
import type {
@@ -79,6 +80,8 @@ export type ComponentContextValue = {
7980
AttachmentPreviewList?: React.ComponentType<AttachmentPreviewListProps>;
8081
/** Custom UI component to control adding attachments to MessageInput, defaults to and accepts same props as: [AttachmentSelector](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/AttachmentSelector.tsx) */
8182
AttachmentSelector?: React.ComponentType;
83+
/** Custom UI component for the dedicated voice recording preview slot above composer attachments (REACT-794), defaults to and accepts same props as: [VoiceRecordingPreviewSlot](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/AttachmentPreviewList/VoiceRecordingPreviewSlot.tsx) */
84+
VoiceRecordingPreviewSlot?: React.ComponentType<VoiceRecordingPreviewSlotProps>;
8285
/** Custom UI component for contents of attachment selector initiation button */
8386
AttachmentSelectorInitiationButtonContents?: React.ComponentType;
8487
/** Custom UI component to display AudioRecorder in MessageInput, defaults to and accepts same props as: [AudioRecorder](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/AudioRecorder.tsx) */

0 commit comments

Comments
 (0)