stream-chat-react
Version:
React components to create chat conversations or livestream style chat
97 lines (96 loc) • 4.47 kB
JavaScript
import React, { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
import { useDropzone } from 'react-dropzone';
import clsx from 'clsx';
import { useMessageInputContext, useTranslationContext } from '../../context';
import { useAttachmentManagerState, useMessageComposer } from './hooks';
import { useStateStore } from '../../store';
const DragAndDropUploadContext = React.createContext({
subscribeToDrop: null,
});
export const useDragAndDropUploadContext = () => useContext(DragAndDropUploadContext);
/**
* @private This hook should be used only once directly in the `MessageInputProvider` to
* register `uploadNewFiles` functions of the rendered `MessageInputs`. Each `MessageInput`
* will then be notified when the drop event occurs from within the `WithDragAndDropUpload`
* component.
*/
export const useRegisterDropHandlers = () => {
const { subscribeToDrop } = useDragAndDropUploadContext();
const messageComposer = useMessageComposer();
useEffect(() => {
const unsubscribe = subscribeToDrop?.(messageComposer.attachmentManager.uploadFiles);
return unsubscribe;
}, [subscribeToDrop, messageComposer]);
};
const attachmentManagerConfigStateSelector = (state) => ({
acceptedFiles: state.attachments.acceptedFiles,
multipleUploads: state.attachments.maxNumberOfFilesPerMessage > 1,
});
/**
* Wrapper to replace now deprecated `Channel.dragAndDropWindow` option.
*
* @example
* ```tsx
* <Channel>
* <WithDragAndDropUpload component="section" className="message-list-dnd-wrapper">
* <Window>
* <MessageList />
* <MessageInput />
* </Window>
* </WithDragAndDropUpload>
* <Thread />
* <Channel>
* ```
*/
export const WithDragAndDropUpload = ({ children, className, component: Component = 'div', style, }) => {
const dropHandlersRef = useRef(new Set());
const { t } = useTranslationContext();
const messageInputContext = useMessageInputContext();
const dragAndDropUploadContext = useDragAndDropUploadContext();
const messageComposer = useMessageComposer();
const { isUploadEnabled } = useAttachmentManagerState();
const { acceptedFiles, multipleUploads } = useStateStore(messageComposer.configState, attachmentManagerConfigStateSelector);
// if message input context is available, there's no need to use the queue
const isWithinMessageInputContext = Object.keys(messageInputContext).length > 0;
const accept = useMemo(() => acceptedFiles.reduce((mediaTypeMap, mediaType) => {
mediaTypeMap[mediaType] ?? (mediaTypeMap[mediaType] = []);
return mediaTypeMap;
}, {}), [acceptedFiles]);
const subscribeToDrop = useCallback((fn) => {
dropHandlersRef.current.add(fn);
return () => {
dropHandlersRef.current.delete(fn);
};
}, []);
const handleDrop = useCallback((files) => {
dropHandlersRef.current.forEach((fn) => fn(files));
}, []);
const { getRootProps, isDragActive, isDragReject } = useDropzone({
accept,
// apply `disabled` rules if available, otherwise allow anything and
// let the `uploadNewFiles` handle the limitations internally
disabled: isWithinMessageInputContext
? !isUploadEnabled || (messageInputContext.cooldownRemaining ?? 0) > 0
: false,
multiple: multipleUploads,
noClick: true,
onDrop: isWithinMessageInputContext
? messageComposer.attachmentManager.uploadFiles
: handleDrop,
});
// nested WithDragAndDropUpload components render wrappers without functionality
// (MessageInputFlat has a default WithDragAndDropUpload)
if (dragAndDropUploadContext.subscribeToDrop !== null) {
return React.createElement(Component, { className: className }, children);
}
return (React.createElement(DragAndDropUploadContext.Provider, { value: {
subscribeToDrop,
} },
React.createElement(Component, { ...getRootProps({ className, style }) },
isDragActive && (React.createElement("div", { className: clsx('str-chat__dropzone-container', {
'str-chat__dropzone-container--not-accepted': isDragReject,
}) },
!isDragReject && React.createElement("p", null, t('Drag your files here')),
isDragReject && React.createElement("p", null, t('Some of the files will not be accepted')))),
children)));
};