Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComponentType } from 'react';
import { type ComponentType, useMemo } from 'react';
import React from 'react';
import {
isLocalAttachment,
Expand All @@ -8,12 +8,12 @@ import {
isLocalVideoAttachment,
isLocalVoiceRecordingAttachment,
isScrapedContent,
isVoiceRecordingAttachment,
} from 'stream-chat';
import {
UnsupportedAttachmentPreview as DefaultUnknownAttachmentPreview,
type UnsupportedAttachmentPreviewProps,
} from './UnsupportedAttachmentPreview';
import { type VoiceRecordingPreviewProps } from './VoiceRecordingPreview';
import {
FileAttachmentPreview as DefaultFileAttachmentPreview,
type FileAttachmentPreviewProps,
Expand All @@ -40,7 +40,6 @@ export type AttachmentPreviewListProps = {
ImageAttachmentPreview?: ComponentType<ImageAttachmentPreviewProps>;
UnsupportedAttachmentPreview?: ComponentType<UnsupportedAttachmentPreviewProps>;
VideoAttachmentPreview?: ComponentType<MediaAttachmentPreviewProps>;
VoiceRecordingPreview?: ComponentType<VoiceRecordingPreviewProps>;
};

export const AttachmentPreviewList = ({
Expand All @@ -50,21 +49,20 @@ export const AttachmentPreviewList = ({
ImageAttachmentPreview = MediaAttachmentPreview,
UnsupportedAttachmentPreview = DefaultUnknownAttachmentPreview,
VideoAttachmentPreview = MediaAttachmentPreview,
VoiceRecordingPreview = DefaultAudioAttachmentPreview,
}: AttachmentPreviewListProps) => {
const messageComposer = useMessageComposer();

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

if (!attachments.length && !location) return null;
if (!filteredAttachments.length && !location) return null;

return (
<div className='str-chat__attachment-preview-list'>
{/*<div*/}
{/* className='str-chat__attachment-list-scroll-container'*/}
{/* data-testid='attachment-list-scroll-container'*/}
{/*>*/}
{location && (
<GeolocationPreview
location={location}
Expand All @@ -79,16 +77,9 @@ export const AttachmentPreviewList = ({
)}
{attachments.map((attachment) => {
if (isScrapedContent(attachment)) return null;
if (isLocalVoiceRecordingAttachment(attachment)) {
return (
<VoiceRecordingPreview
attachment={attachment}
handleRetry={messageComposer.attachmentManager.uploadAttachment}
key={attachment.localMetadata.id || attachment.asset_url}
removeAttachments={messageComposer.attachmentManager.removeAttachments}
/>
);
} else if (isLocalAudioAttachment(attachment)) {
// Voice recordings are rendered in the dedicated slot above (VoiceRecordingPreviewSlot)
if (isLocalVoiceRecordingAttachment(attachment)) return null;
if (isLocalAudioAttachment(attachment)) {
return (
<AudioAttachmentPreview
attachment={attachment}
Expand Down Expand Up @@ -136,7 +127,6 @@ export const AttachmentPreviewList = ({
}
return null;
})}
{/*</div>*/}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { ComponentType } from 'react';
import React from 'react';
import { isLocalVoiceRecordingAttachment } from 'stream-chat';
import { useAttachmentsForPreview, useMessageComposer } from '../hooks';
import { AudioAttachmentPreview } from './AudioAttachmentPreview';
import type { VoiceRecordingPreviewProps } from './VoiceRecordingPreview';

export type VoiceRecordingPreviewSlotProps = {
/** Custom UI component for each voice recording preview in the slot; defaults to AudioAttachmentPreview */
VoiceRecordingPreview?: ComponentType<VoiceRecordingPreviewProps>;
};

/**
* Dedicated slot for voice recording preview(s), rendered apart from the main attachment preview list
*/
export const VoiceRecordingPreviewSlot = ({
VoiceRecordingPreview = AudioAttachmentPreview,
}: VoiceRecordingPreviewSlotProps) => {
const messageComposer = useMessageComposer();
const { attachments } = useAttachmentsForPreview();

const voiceAttachments = attachments.filter(isLocalVoiceRecordingAttachment);
const firstVoice = voiceAttachments[0];
if (!firstVoice) return null;

return (
<div
className='str-chat__message-composer-voice-preview-slot'
data-testid='voice-preview-slot'
>
<VoiceRecordingPreview
attachment={firstVoice}
handleRetry={messageComposer.attachmentManager.uploadAttachment}
removeAttachments={messageComposer.attachmentManager.removeAttachments}
/>
</div>
);
};
4 changes: 4 additions & 0 deletions src/components/MessageInput/AttachmentPreviewList/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ export type { UploadAttachmentPreviewProps as AttachmentPreviewProps } from './t
export type { UnsupportedAttachmentPreviewProps } from './UnsupportedAttachmentPreview';
export type { MediaAttachmentPreviewProps } from './MediaAttachmentPreview';
export type { VoiceRecordingPreviewProps } from './VoiceRecordingPreview';
export {
VoiceRecordingPreviewSlot,
type VoiceRecordingPreviewSlotProps,
} from './VoiceRecordingPreviewSlot';
7 changes: 6 additions & 1 deletion src/components/MessageInput/MessageInputFlat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {
AttachmentSelector as DefaultAttachmentSelector,
SimpleAttachmentSelector,
} from './AttachmentSelector/AttachmentSelector';
import { AttachmentPreviewList as DefaultAttachmentPreviewList } from './AttachmentPreviewList';
import {
AttachmentPreviewList as DefaultAttachmentPreviewList,
VoiceRecordingPreviewSlot as DefaultVoiceRecordingPreviewSlot,
} from './AttachmentPreviewList';
import { AudioRecorder as DefaultAudioRecorder } from '../MediaRecorder';
import { EditedMessagePreview as DefaultEditedMessagePreview } from './EditedMessagePreview';
import { QuotedMessagePreview as DefaultQuotedMessagePreview } from './QuotedMessagePreview';
Expand Down Expand Up @@ -55,6 +58,7 @@ const MessageComposerPreviews = () => {
EditedMessagePreview = DefaultEditedMessagePreview,
LinkPreviewList = DefaultLinkPreviewList,
QuotedMessagePreview = DefaultQuotedMessagePreview,
VoiceRecordingPreviewSlot = DefaultVoiceRecordingPreviewSlot,
} = useComponentContext();

const messageComposer = useMessageComposer();
Expand Down Expand Up @@ -97,6 +101,7 @@ const MessageComposerPreviews = () => {
) : (
<QuotedMessagePreview />
)}
<VoiceRecordingPreviewSlot />
<AttachmentPreviewList />
<LinkPreviewList />
</div>
Expand Down
14 changes: 8 additions & 6 deletions src/components/MessageInput/QuotedMessagePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,14 @@ export const QuotedMessagePreview = ({
);

return quotedMessage ? (
<QuotedMessagePreviewUI
getQuotedMessageAuthor={getQuotedMessageAuthor}
onRemove={() => messageComposer.setQuotedMessage(null)}
quotedMessage={quotedMessage}
renderText={renderText}
/>
<div className='str-chat__message-composer__quoted-message-preview-slot'>
<QuotedMessagePreviewUI
getQuotedMessageAuthor={getQuotedMessageAuthor}
onRemove={() => messageComposer.setQuotedMessage(null)}
quotedMessage={quotedMessage}
renderText={renderText}
/>
</div>
) : null;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,13 @@ describe('AttachmentPreviewList', () => {
expect(screen.getByTitle(`file-upload-${state}`)).toBeInTheDocument();
expect(screen.getByTitle(`image-upload-${state}`)).toBeInTheDocument();
expect(screen.getByTitle(`audio-attachment-${state}`)).toBeInTheDocument();
expect(
screen.getByTitle(`voice-recording-attachment-${state}`),
).toBeInTheDocument();
// Voice recordings are rendered in VoiceRecordingPreviewSlot above the list (REACT-794)
expect(screen.getByTitle(`video-attachment-${state}`)).toBeInTheDocument();
},
);

describe.each(['audio', 'file', 'image', 'unsupported', 'voiceRecording', 'video'])(
// voiceRecording is rendered in VoiceRecordingPreviewSlot (REACT-794), not in AttachmentPreviewList
describe.each(['audio', 'file', 'image', 'unsupported', 'video'])(
'%s attachments rendering',
(type) => {
const customAttachment = {
Expand All @@ -143,7 +142,6 @@ describe('AttachmentPreviewList', () => {
image: generateImageAttachment,
unsupported: () => customAttachment,
video: generateVideoAttachment,
voiceRecording: generateVoiceRecordingAttachment,
};

it('retries upload on upload button click', async () => {
Expand Down Expand Up @@ -265,7 +263,6 @@ describe('AttachmentPreviewList', () => {
image: 'ImageAttachmentPreview',
unsupported: 'UnsupportedAttachmentPreview',
video: 'MediaAttachmentPreview',
voiceRecording: 'VoiceRecordingPreview',
};
const title = `${type}-attachment`;
const id = `${type}-id`;
Expand Down
1 change: 1 addition & 0 deletions src/components/MessageInput/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type {
AttachmentPreviewProps,
UnsupportedAttachmentPreviewProps,
VoiceRecordingPreviewProps,
VoiceRecordingPreviewSlotProps,
} from './AttachmentPreviewList';
export * from './CommandChip';
export * from './CooldownTimer';
Expand Down
19 changes: 18 additions & 1 deletion src/components/MessageInput/styling/AttachmentPreview.scss
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,24 @@

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

.str-chat__message-composer-voice-preview-slot {
display: flex;
align-items: center;
width: 100%;
padding: var(--spacing-xxs);
min-width: 0;

.str-chat__attachment-preview-audio {
width: 100%;
min-width: 0;
max-width: none;

.str-chat__attachment-preview-file__data {
width: 100%;
}
}
}

.str-chat__attachment-preview-list {
@include utils.component-layer-overrides('attachment-preview-list');
padding: var(--spacing-xxs);
Expand Down Expand Up @@ -309,6 +327,5 @@
height: var(--button-visual-height-md);
width: var(--button-visual-height-md);
border: 1px solid var(--control-play-control-border);
background-color: var(--control-play-control-bg);
}
}
5 changes: 2 additions & 3 deletions src/components/MessageInput/styling/MessageComposer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,14 @@
flex-direction: column;
width: 100%;
min-width: 0;
padding-inline: var(--spacing-xs);
padding-block: var(--spacing-sm);
@include utils.component-layer-overrides('message-input');
}

.str-chat__message-composer-previews {
display: flex;
flex-direction: column;
width: 100%;
padding-bottom: var(--spacing-xs);
padding: var(--spacing-xs) var(--spacing-xs) 0;
gap: var(--spacing-xxs);

min-width: 0;
Expand All @@ -83,6 +81,7 @@
align-items: end;
width: 100%;
gap: var(--spacing-xs);
padding: var(--spacing-sm);

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
@use '../../../styling/utils';

.str-chat {
.str-chat__message-composer__quoted-message-preview-slot {
padding: var(--spacing-xxs);
}

.str-chat__quoted-message-preview {
display: flex;
align-items: center;
Expand Down
3 changes: 3 additions & 0 deletions src/context/ComponentContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import type {
TypingIndicatorProps,
UnreadMessagesNotificationProps,
UnreadMessagesSeparatorProps,
VoiceRecordingPreviewSlotProps,
} from '../components';

import type {
Expand Down Expand Up @@ -79,6 +80,8 @@ export type ComponentContextValue = {
AttachmentPreviewList?: React.ComponentType<AttachmentPreviewListProps>;
/** 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) */
AttachmentSelector?: React.ComponentType;
/** 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) */
VoiceRecordingPreviewSlot?: React.ComponentType<VoiceRecordingPreviewSlotProps>;
/** Custom UI component for contents of attachment selector initiation button */
AttachmentSelectorInitiationButtonContents?: React.ComponentType;
/** 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) */
Expand Down
Loading