Skip to content

Commit f62741f

Browse files
Add WithDragAndDropUpload component
1 parent 5fa6b0f commit f62741f

File tree

6 files changed

+227
-109
lines changed

6 files changed

+227
-109
lines changed

src/components/Channel/Channel.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,10 @@ export type ChannelProps<
212212
updatedMessage: UpdatedMessage<StreamChatGenerics>,
213213
options?: UpdateMessageOptions,
214214
) => ReturnType<StreamChat<StreamChatGenerics>['updateMessage']>;
215-
/** If true, chat users will be able to drag and drop file uploads to the entire channel window */
215+
/**
216+
* @deprecated Use `WithDragAndDropUpload` instead (wrap draggable-to elements with this component).
217+
* @description If true, chat users will be able to drag and drop file uploads to the entire channel window
218+
*/
216219
dragAndDropWindow?: boolean;
217220
/** Custom UI component to be shown if no active channel is set, defaults to null and skips rendering the Channel component */
218221
EmptyPlaceholder?: React.ReactElement;
@@ -246,7 +249,10 @@ export type ChannelProps<
246249
onMentionsClick?: OnMentionAction<StreamChatGenerics>;
247250
/** Custom action handler function to run on hover of an @mention in a message */
248251
onMentionsHover?: OnMentionAction<StreamChatGenerics>;
249-
/** If `dragAndDropWindow` prop is true, the props to pass to the MessageInput component (overrides props placed directly on MessageInput) */
252+
/**
253+
* @deprecated Use `WithDragAndDropUpload` instead (wrap draggable-to elements with this component).
254+
* @description If `dragAndDropWindow` prop is `true`, the props to pass to the `MessageInput` component (overrides props placed directly on `MessageInput`)
255+
*/
250256
optionalMessageInputProps?: MessageInputProps<StreamChatGenerics, V>;
251257
/** You can turn on/off thumbnail generation for video attachments */
252258
shouldGenerateVideoThumbnail?: boolean;

src/components/MessageInput/MessageInput.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
} from '../../types/types';
2727
import type { URLEnrichmentConfig } from './hooks/useLinkPreviews';
2828
import type { CustomAudioRecordingConfig } from '../MediaRecorder';
29+
import { useHandleDragAndDropQueuedFiles } from './WithDragAndDropUpload';
2930

3031
export type EmojiSearchIndexResult = {
3132
id: string;
@@ -151,6 +152,9 @@ const MessageInputProvider = <
151152
emojiSearchIndex: props.emojiSearchIndex ?? emojiSearchIndex,
152153
});
153154

155+
// @ts-expect-error generics to be removed
156+
useHandleDragAndDropQueuedFiles(messageInputContextValue);
157+
154158
return (
155159
<MessageInputContextProvider<StreamChatGenerics, V> value={messageInputContextValue}>
156160
{props.children}
Lines changed: 64 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import React, { useCallback, useEffect, useMemo, useState } from 'react';
22
import type { Event } from 'stream-chat';
3-
import clsx from 'clsx';
4-
import { useDropzone } from 'react-dropzone';
53
import {
64
AttachmentSelector as DefaultAttachmentSelector,
75
SimpleAttachmentSelector,
@@ -28,17 +26,16 @@ import { RecordingAttachmentType } from '../MediaRecorder/classes';
2826
import { useChatContext } from '../../context/ChatContext';
2927
import { useChannelActionContext } from '../../context/ChannelActionContext';
3028
import { useChannelStateContext } from '../../context/ChannelStateContext';
31-
import { useTranslationContext } from '../../context/TranslationContext';
3229
import { useMessageInputContext } from '../../context/MessageInputContext';
3330
import { useComponentContext } from '../../context/ComponentContext';
3431

3532
import type { DefaultStreamChatGenerics } from '../../types/types';
3633
import { AIStates, useAIState } from '../AIStateIndicator';
34+
import { WithDragAndDropUpload } from './WithDragAndDropUpload';
3735

3836
export const MessageInputFlat = <
3937
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
4038
>() => {
41-
const { t } = useTranslationContext('MessageInputFlat');
4239
const {
4340
asyncMessagesMultiSendEnabled,
4441
attachments,
@@ -48,14 +45,12 @@ export const MessageInputFlat = <
4845
hideSendButton,
4946
isUploadEnabled,
5047
linkPreviews,
51-
maxFilesLeft,
5248
message,
5349
numberOfUploads,
5450
parent,
5551
recordingController,
5652
setCooldownRemaining,
5753
text,
58-
uploadNewFiles,
5954
} = useMessageInputContext<StreamChatGenerics>('MessageInputFlat');
6055

6156
const {
@@ -71,11 +66,8 @@ export const MessageInputFlat = <
7166
StartRecordingAudioButton = DefaultStartRecordingAudioButton,
7267
StopAIGenerationButton: StopAIGenerationButtonOverride,
7368
} = useComponentContext<StreamChatGenerics>('MessageInputFlat');
74-
const {
75-
acceptedFiles = [],
76-
multipleUploads,
77-
quotedMessage,
78-
} = useChannelStateContext<StreamChatGenerics>('MessageInputFlat');
69+
const { quotedMessage } =
70+
useChannelStateContext<StreamChatGenerics>('MessageInputFlat');
7971
const { setQuotedMessage } = useChannelActionContext('MessageInputFlat');
8072
const { channel } = useChatContext<StreamChatGenerics>('MessageInputFlat');
8173

@@ -96,23 +88,6 @@ export const MessageInputFlat = <
9688
[attachments],
9789
);
9890

99-
const accept = useMemo(
100-
() =>
101-
acceptedFiles.reduce<Record<string, Array<string>>>((mediaTypeMap, mediaType) => {
102-
mediaTypeMap[mediaType] ??= [];
103-
return mediaTypeMap;
104-
}, {}),
105-
[acceptedFiles],
106-
);
107-
108-
const { getRootProps, isDragActive, isDragReject } = useDropzone({
109-
accept,
110-
disabled: !isUploadEnabled || maxFilesLeft === 0,
111-
multiple: multipleUploads,
112-
noClick: true,
113-
onDrop: uploadNewFiles,
114-
});
115-
11691
useEffect(() => {
11792
const handleQuotedMessageUpdate = (e: Event<StreamChatGenerics>) => {
11893
if (e.message?.id !== quotedMessage?.id) return;
@@ -156,90 +131,76 @@ export const MessageInputFlat = <
156131
!!StopAIGenerationButton;
157132

158133
return (
159-
<>
160-
<div {...getRootProps({ className: 'str-chat__message-input' })}>
161-
{recordingEnabled &&
162-
recordingController.permissionState === 'denied' &&
163-
showRecordingPermissionDeniedNotification && (
164-
<RecordingPermissionDeniedNotification
165-
onClose={closePermissionDeniedNotification}
166-
permissionName={RecordingPermission.MIC}
167-
/>
168-
)}
169-
{findAndEnqueueURLsToEnrich && (
170-
<LinkPreviewList linkPreviews={Array.from(linkPreviews.values())} />
171-
)}
172-
{isDragActive && (
173-
<div
174-
className={clsx('str-chat__dropzone-container', {
175-
'str-chat__dropzone-container--not-accepted': isDragReject,
176-
})}
177-
>
178-
{!isDragReject && <p>{t<string>('Drag your files here')}</p>}
179-
{isDragReject && <p>{t<string>('Some of the files will not be accepted')}</p>}
180-
</div>
134+
<WithDragAndDropUpload as='div' className='str-chat__message-input'>
135+
{recordingEnabled &&
136+
recordingController.permissionState === 'denied' &&
137+
showRecordingPermissionDeniedNotification && (
138+
<RecordingPermissionDeniedNotification
139+
onClose={closePermissionDeniedNotification}
140+
permissionName={RecordingPermission.MIC}
141+
/>
181142
)}
182-
{displayQuotedMessage && <QuotedMessagePreviewHeader />}
183-
184-
<div className='str-chat__message-input-inner'>
185-
<AttachmentSelector />
186-
<div className='str-chat__message-textarea-container'>
187-
{displayQuotedMessage && (
188-
<QuotedMessagePreview quotedMessage={quotedMessage} />
143+
{findAndEnqueueURLsToEnrich && (
144+
<LinkPreviewList linkPreviews={Array.from(linkPreviews.values())} />
145+
)}
146+
{displayQuotedMessage && <QuotedMessagePreviewHeader />}
147+
148+
<div className='str-chat__message-input-inner'>
149+
<AttachmentSelector />
150+
<div className='str-chat__message-textarea-container'>
151+
{displayQuotedMessage && <QuotedMessagePreview quotedMessage={quotedMessage} />}
152+
{isUploadEnabled &&
153+
!!(numberOfUploads + failedUploadsCount || attachments.length > 0) && (
154+
<AttachmentPreviewList />
189155
)}
190-
{isUploadEnabled &&
191-
!!(numberOfUploads + failedUploadsCount || attachments.length > 0) && (
192-
<AttachmentPreviewList />
193-
)}
194156

195-
<div className='str-chat__message-textarea-with-emoji-picker'>
196-
<ChatAutoComplete />
157+
<div className='str-chat__message-textarea-with-emoji-picker'>
158+
<ChatAutoComplete />
197159

198-
{EmojiPicker && <EmojiPicker />}
199-
</div>
160+
{EmojiPicker && <EmojiPicker />}
200161
</div>
201-
{shouldDisplayStopAIGeneration ? (
202-
<StopAIGenerationButton onClick={stopGenerating} />
203-
) : (
204-
!hideSendButton && (
205-
<>
206-
{cooldownRemaining ? (
207-
<CooldownTimer
208-
cooldownInterval={cooldownRemaining}
209-
setCooldownRemaining={setCooldownRemaining}
162+
</div>
163+
{shouldDisplayStopAIGeneration ? (
164+
<StopAIGenerationButton onClick={stopGenerating} />
165+
) : (
166+
!hideSendButton && (
167+
<>
168+
{cooldownRemaining ? (
169+
<CooldownTimer
170+
cooldownInterval={cooldownRemaining}
171+
setCooldownRemaining={setCooldownRemaining}
172+
/>
173+
) : (
174+
<>
175+
<SendButton
176+
disabled={
177+
!numberOfUploads &&
178+
!text.length &&
179+
attachments.length - failedUploadsCount === 0
180+
}
181+
sendMessage={handleSubmit}
210182
/>
211-
) : (
212-
<>
213-
<SendButton
183+
{recordingEnabled && (
184+
<StartRecordingAudioButton
214185
disabled={
215-
!numberOfUploads &&
216-
!text.length &&
217-
attachments.length - failedUploadsCount === 0
186+
isRecording ||
187+
(!asyncMessagesMultiSendEnabled &&
188+
attachments.some(
189+
(a) => a.type === RecordingAttachmentType.VOICE_RECORDING,
190+
))
218191
}
219-
sendMessage={handleSubmit}
192+
onClick={() => {
193+
recordingController.recorder?.start();
194+
setShowRecordingPermissionDeniedNotification(true);
195+
}}
220196
/>
221-
{recordingEnabled && (
222-
<StartRecordingAudioButton
223-
disabled={
224-
isRecording ||
225-
(!asyncMessagesMultiSendEnabled &&
226-
attachments.some(
227-
(a) => a.type === RecordingAttachmentType.VOICE_RECORDING,
228-
))
229-
}
230-
onClick={() => {
231-
recordingController.recorder?.start();
232-
setShowRecordingPermissionDeniedNotification(true);
233-
}}
234-
/>
235-
)}
236-
</>
237-
)}
238-
</>
239-
)
240-
)}
241-
</div>
197+
)}
198+
</>
199+
)}
200+
</>
201+
)
202+
)}
242203
</div>
243-
</>
204+
</WithDragAndDropUpload>
244205
);
245206
};

0 commit comments

Comments
 (0)