stream-chat-react 12.14.0 → 12.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Channel/Channel.d.ts +8 -2
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/MessageInput/MessageInput.js +3 -0
- package/dist/components/MessageInput/MessageInputFlat.js +28 -48
- package/dist/components/MessageInput/WithDragAndDropUpload.d.ts +37 -0
- package/dist/components/MessageInput/WithDragAndDropUpload.js +85 -0
- package/dist/components/MessageInput/index.d.ts +1 -0
- package/dist/components/MessageInput/index.js +1 -0
- package/dist/context/MessageInputContext.js +3 -2
- package/dist/css/v2/index.css +1 -1
- package/dist/css/v2/index.layout.css +1 -1
- package/dist/index.browser.cjs +442 -400
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +467 -424
- package/dist/index.node.cjs.map +4 -4
- package/dist/plugins/Emojis/index.browser.cjs +0 -3
- package/dist/plugins/Emojis/index.browser.cjs.map +2 -2
- package/dist/plugins/Emojis/index.node.cjs +0 -3
- package/dist/plugins/Emojis/index.node.cjs.map +2 -2
- package/dist/scss/v2/DropzoneContainer/DropzoneContainer-layout.scss +14 -0
- package/dist/scss/v2/DropzoneContainer/DropzoneContainer-theme.scss +17 -0
- package/dist/scss/v2/MessageInput/MessageInput-layout.scss +0 -13
- package/dist/scss/v2/MessageInput/MessageInput-theme.scss +8 -19
- package/dist/scss/v2/index.layout.scss +2 -1
- package/dist/scss/v2/index.scss +1 -0
- package/package.json +2 -2
- /package/dist/scss/v2/DragAndDropContainer/{DragAmdDropContainer-layout.scss → DragAndDropContainer-layout.scss} +0 -0
|
@@ -29,7 +29,10 @@ export type ChannelProps<StreamChatGenerics extends DefaultStreamChatGenerics =
|
|
|
29
29
|
doSendMessageRequest?: (channel: StreamChannel<StreamChatGenerics>, message: Message<StreamChatGenerics>, options?: SendMessageOptions) => ReturnType<StreamChannel<StreamChatGenerics>['sendMessage']> | void;
|
|
30
30
|
/** Custom action handler to override the default `client.updateMessage` request function (advanced usage only) */
|
|
31
31
|
doUpdateMessageRequest?: (cid: string, updatedMessage: UpdatedMessage<StreamChatGenerics>, options?: UpdateMessageOptions) => ReturnType<StreamChat<StreamChatGenerics>['updateMessage']>;
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* @deprecated Use `WithDragAndDropUpload` instead (wrap draggable-to elements with this component).
|
|
34
|
+
* @description If true, chat users will be able to drag and drop file uploads to the entire channel window
|
|
35
|
+
*/
|
|
33
36
|
dragAndDropWindow?: boolean;
|
|
34
37
|
/** Custom UI component to be shown if no active channel is set, defaults to null and skips rendering the Channel component */
|
|
35
38
|
EmptyPlaceholder?: React.ReactElement;
|
|
@@ -63,7 +66,10 @@ export type ChannelProps<StreamChatGenerics extends DefaultStreamChatGenerics =
|
|
|
63
66
|
onMentionsClick?: OnMentionAction<StreamChatGenerics>;
|
|
64
67
|
/** Custom action handler function to run on hover of an @mention in a message */
|
|
65
68
|
onMentionsHover?: OnMentionAction<StreamChatGenerics>;
|
|
66
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* @deprecated Use `WithDragAndDropUpload` instead (wrap draggable-to elements with this component).
|
|
71
|
+
* @description If `dragAndDropWindow` prop is `true`, the props to pass to the `MessageInput` component (overrides props placed directly on `MessageInput`)
|
|
72
|
+
*/
|
|
67
73
|
optionalMessageInputProps?: MessageInputProps<StreamChatGenerics, V>;
|
|
68
74
|
/** You can turn on/off thumbnail generation for video attachments */
|
|
69
75
|
shouldGenerateVideoThumbnail?: boolean;
|
|
@@ -24,7 +24,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
|
|
|
24
24
|
useEffect(() => {
|
|
25
25
|
if (!client)
|
|
26
26
|
return;
|
|
27
|
-
const version = "12.
|
|
27
|
+
const version = "12.15.0";
|
|
28
28
|
const userAgent = client.getUserAgent();
|
|
29
29
|
if (!userAgent.includes('stream-chat-react')) {
|
|
30
30
|
// result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
|
|
@@ -8,6 +8,7 @@ import { useChannelStateContext } from '../../context/ChannelStateContext';
|
|
|
8
8
|
import { useComponentContext, } from '../../context/ComponentContext';
|
|
9
9
|
import { MessageInputContextProvider } from '../../context/MessageInputContext';
|
|
10
10
|
import { DialogManagerProvider } from '../../context';
|
|
11
|
+
import { useRegisterDropHandlers } from './WithDragAndDropUpload';
|
|
11
12
|
const MessageInputProvider = (props) => {
|
|
12
13
|
const cooldownTimerState = useCooldownTimer();
|
|
13
14
|
const messageInputState = useMessageInputState(props);
|
|
@@ -18,6 +19,8 @@ const MessageInputProvider = (props) => {
|
|
|
18
19
|
...props,
|
|
19
20
|
emojiSearchIndex: props.emojiSearchIndex ?? emojiSearchIndex,
|
|
20
21
|
});
|
|
22
|
+
// @ts-expect-error generics to be removed
|
|
23
|
+
useRegisterDropHandlers(messageInputContextValue);
|
|
21
24
|
return (React.createElement(MessageInputContextProvider, { value: messageInputContextValue }, props.children));
|
|
22
25
|
};
|
|
23
26
|
const UnMemoizedMessageInput = (props) => {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import clsx from 'clsx';
|
|
3
|
-
import { useDropzone } from 'react-dropzone';
|
|
4
2
|
import { AttachmentSelector as DefaultAttachmentSelector, SimpleAttachmentSelector, } from './AttachmentSelector';
|
|
5
3
|
import { AttachmentPreviewList as DefaultAttachmentPreviewList } from './AttachmentPreviewList';
|
|
6
4
|
import { CooldownTimer as DefaultCooldownTimer } from './CooldownTimer';
|
|
@@ -14,15 +12,14 @@ import { RecordingAttachmentType } from '../MediaRecorder/classes';
|
|
|
14
12
|
import { useChatContext } from '../../context/ChatContext';
|
|
15
13
|
import { useChannelActionContext } from '../../context/ChannelActionContext';
|
|
16
14
|
import { useChannelStateContext } from '../../context/ChannelStateContext';
|
|
17
|
-
import { useTranslationContext } from '../../context/TranslationContext';
|
|
18
15
|
import { useMessageInputContext } from '../../context/MessageInputContext';
|
|
19
16
|
import { useComponentContext } from '../../context/ComponentContext';
|
|
20
17
|
import { AIStates, useAIState } from '../AIStateIndicator';
|
|
18
|
+
import { WithDragAndDropUpload } from './WithDragAndDropUpload';
|
|
21
19
|
export const MessageInputFlat = () => {
|
|
22
|
-
const {
|
|
23
|
-
const { asyncMessagesMultiSendEnabled, attachments, cooldownRemaining, findAndEnqueueURLsToEnrich, handleSubmit, hideSendButton, isUploadEnabled, linkPreviews, maxFilesLeft, message, numberOfUploads, parent, recordingController, setCooldownRemaining, text, uploadNewFiles, } = useMessageInputContext('MessageInputFlat');
|
|
20
|
+
const { asyncMessagesMultiSendEnabled, attachments, cooldownRemaining, findAndEnqueueURLsToEnrich, handleSubmit, hideSendButton, isUploadEnabled, linkPreviews, message, numberOfUploads, parent, recordingController, setCooldownRemaining, text, } = useMessageInputContext('MessageInputFlat');
|
|
24
21
|
const { AttachmentPreviewList = DefaultAttachmentPreviewList, AttachmentSelector = message ? SimpleAttachmentSelector : DefaultAttachmentSelector, AudioRecorder = DefaultAudioRecorder, CooldownTimer = DefaultCooldownTimer, EmojiPicker, LinkPreviewList = DefaultLinkPreviewList, QuotedMessagePreview = DefaultQuotedMessagePreview, RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification, SendButton = DefaultSendButton, StartRecordingAudioButton = DefaultStartRecordingAudioButton, StopAIGenerationButton: StopAIGenerationButtonOverride, } = useComponentContext('MessageInputFlat');
|
|
25
|
-
const {
|
|
22
|
+
const { quotedMessage } = useChannelStateContext('MessageInputFlat');
|
|
26
23
|
const { setQuotedMessage } = useChannelActionContext('MessageInputFlat');
|
|
27
24
|
const { channel } = useChatContext('MessageInputFlat');
|
|
28
25
|
const { aiState } = useAIState(channel);
|
|
@@ -32,17 +29,6 @@ export const MessageInputFlat = () => {
|
|
|
32
29
|
setShowRecordingPermissionDeniedNotification(false);
|
|
33
30
|
}, []);
|
|
34
31
|
const failedUploadsCount = useMemo(() => attachments.filter((a) => a.localMetadata?.uploadState === 'failed').length, [attachments]);
|
|
35
|
-
const accept = useMemo(() => acceptedFiles.reduce((mediaTypeMap, mediaType) => {
|
|
36
|
-
mediaTypeMap[mediaType] ?? (mediaTypeMap[mediaType] = []);
|
|
37
|
-
return mediaTypeMap;
|
|
38
|
-
}, {}), [acceptedFiles]);
|
|
39
|
-
const { getRootProps, isDragActive, isDragReject } = useDropzone({
|
|
40
|
-
accept,
|
|
41
|
-
disabled: !isUploadEnabled || maxFilesLeft === 0,
|
|
42
|
-
multiple: multipleUploads,
|
|
43
|
-
noClick: true,
|
|
44
|
-
onDrop: uploadNewFiles,
|
|
45
|
-
});
|
|
46
32
|
useEffect(() => {
|
|
47
33
|
const handleQuotedMessageUpdate = (e) => {
|
|
48
34
|
if (e.message?.id !== quotedMessage?.id)
|
|
@@ -79,35 +65,29 @@ export const MessageInputFlat = () => {
|
|
|
79
65
|
: StopAIGenerationButtonOverride;
|
|
80
66
|
const shouldDisplayStopAIGeneration = [AIStates.Thinking, AIStates.Generating].includes(aiState) &&
|
|
81
67
|
!!StopAIGenerationButton;
|
|
82
|
-
return (React.createElement(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
recordingEnabled && (React.createElement(StartRecordingAudioButton, { disabled: isRecording ||
|
|
108
|
-
(!asyncMessagesMultiSendEnabled &&
|
|
109
|
-
attachments.some((a) => a.type === RecordingAttachmentType.VOICE_RECORDING)), onClick: () => {
|
|
110
|
-
recordingController.recorder?.start();
|
|
111
|
-
setShowRecordingPermissionDeniedNotification(true);
|
|
112
|
-
} })))))))))));
|
|
68
|
+
return (React.createElement(WithDragAndDropUpload, { className: 'str-chat__message-input', component: 'div' },
|
|
69
|
+
recordingEnabled &&
|
|
70
|
+
recordingController.permissionState === 'denied' &&
|
|
71
|
+
showRecordingPermissionDeniedNotification && (React.createElement(RecordingPermissionDeniedNotification, { onClose: closePermissionDeniedNotification, permissionName: RecordingPermission.MIC })),
|
|
72
|
+
findAndEnqueueURLsToEnrich && (React.createElement(LinkPreviewList, { linkPreviews: Array.from(linkPreviews.values()) })),
|
|
73
|
+
displayQuotedMessage && React.createElement(QuotedMessagePreviewHeader, null),
|
|
74
|
+
React.createElement("div", { className: 'str-chat__message-input-inner' },
|
|
75
|
+
React.createElement(AttachmentSelector, null),
|
|
76
|
+
React.createElement("div", { className: 'str-chat__message-textarea-container' },
|
|
77
|
+
displayQuotedMessage && React.createElement(QuotedMessagePreview, { quotedMessage: quotedMessage }),
|
|
78
|
+
isUploadEnabled &&
|
|
79
|
+
!!(numberOfUploads + failedUploadsCount || attachments.length > 0) && (React.createElement(AttachmentPreviewList, null)),
|
|
80
|
+
React.createElement("div", { className: 'str-chat__message-textarea-with-emoji-picker' },
|
|
81
|
+
React.createElement(ChatAutoComplete, null),
|
|
82
|
+
EmojiPicker && React.createElement(EmojiPicker, null))),
|
|
83
|
+
shouldDisplayStopAIGeneration ? (React.createElement(StopAIGenerationButton, { onClick: stopGenerating })) : (!hideSendButton && (React.createElement(React.Fragment, null, cooldownRemaining ? (React.createElement(CooldownTimer, { cooldownInterval: cooldownRemaining, setCooldownRemaining: setCooldownRemaining })) : (React.createElement(React.Fragment, null,
|
|
84
|
+
React.createElement(SendButton, { disabled: !numberOfUploads &&
|
|
85
|
+
!text.length &&
|
|
86
|
+
attachments.length - failedUploadsCount === 0, sendMessage: handleSubmit }),
|
|
87
|
+
recordingEnabled && (React.createElement(StartRecordingAudioButton, { disabled: isRecording ||
|
|
88
|
+
(!asyncMessagesMultiSendEnabled &&
|
|
89
|
+
attachments.some((a) => a.type === RecordingAttachmentType.VOICE_RECORDING)), onClick: () => {
|
|
90
|
+
recordingController.recorder?.start();
|
|
91
|
+
setShowRecordingPermissionDeniedNotification(true);
|
|
92
|
+
} }))))))))));
|
|
113
93
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { CSSProperties, ElementType, PropsWithChildren } from 'react';
|
|
2
|
+
import { MessageInputContextValue } from '../../context';
|
|
3
|
+
export declare const useDragAndDropUploadContext: () => {
|
|
4
|
+
subscribeToDrop: ((fn: (files: File[]) => void) => () => void) | null;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* @private This hook should be used only once directly in the `MessageInputProvider` to
|
|
8
|
+
* register `uploadNewFiles` functions of the rendered `MessageInputs`. Each `MessageInput`
|
|
9
|
+
* will then be notified when the drop event occurs from within the `WithDragAndDropUpload`
|
|
10
|
+
* component.
|
|
11
|
+
*/
|
|
12
|
+
export declare const useRegisterDropHandlers: ({ uploadNewFiles }: MessageInputContextValue) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Wrapper to replace now deprecated `Channel.dragAndDropWindow` option.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <Channel>
|
|
19
|
+
* <WithDragAndDropUpload component="section" className="message-list-dnd-wrapper">
|
|
20
|
+
* <Window>
|
|
21
|
+
* <MessageList />
|
|
22
|
+
* <MessageInput />
|
|
23
|
+
* </Window>
|
|
24
|
+
* </WithDragAndDropUpload>
|
|
25
|
+
* <Thread />
|
|
26
|
+
* <Channel>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare const WithDragAndDropUpload: ({ children, className, component: Component, style, }: PropsWithChildren<{
|
|
30
|
+
/**
|
|
31
|
+
* @description An element to render as a wrapper onto which drag & drop functionality will be applied.
|
|
32
|
+
* @default 'div'
|
|
33
|
+
*/
|
|
34
|
+
component?: ElementType;
|
|
35
|
+
className?: string;
|
|
36
|
+
style?: CSSProperties;
|
|
37
|
+
}>) => React.JSX.Element;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React, { useCallback, useContext, useEffect, useMemo, useRef, } from 'react';
|
|
2
|
+
import { useChannelStateContext, useMessageInputContext, useTranslationContext, } from '../../context';
|
|
3
|
+
import { useDropzone } from 'react-dropzone';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
const DragAndDropUploadContext = React.createContext({
|
|
6
|
+
subscribeToDrop: null,
|
|
7
|
+
});
|
|
8
|
+
export const useDragAndDropUploadContext = () => useContext(DragAndDropUploadContext);
|
|
9
|
+
/**
|
|
10
|
+
* @private This hook should be used only once directly in the `MessageInputProvider` to
|
|
11
|
+
* register `uploadNewFiles` functions of the rendered `MessageInputs`. Each `MessageInput`
|
|
12
|
+
* will then be notified when the drop event occurs from within the `WithDragAndDropUpload`
|
|
13
|
+
* component.
|
|
14
|
+
*/
|
|
15
|
+
export const useRegisterDropHandlers = ({ uploadNewFiles }) => {
|
|
16
|
+
const { subscribeToDrop } = useDragAndDropUploadContext();
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const unsubscribe = subscribeToDrop?.(uploadNewFiles);
|
|
19
|
+
return unsubscribe;
|
|
20
|
+
}, [subscribeToDrop, uploadNewFiles]);
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Wrapper to replace now deprecated `Channel.dragAndDropWindow` option.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* <Channel>
|
|
28
|
+
* <WithDragAndDropUpload component="section" className="message-list-dnd-wrapper">
|
|
29
|
+
* <Window>
|
|
30
|
+
* <MessageList />
|
|
31
|
+
* <MessageInput />
|
|
32
|
+
* </Window>
|
|
33
|
+
* </WithDragAndDropUpload>
|
|
34
|
+
* <Thread />
|
|
35
|
+
* <Channel>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export const WithDragAndDropUpload = ({ children, className, component: Component = 'div', style, }) => {
|
|
39
|
+
const dropHandlersRef = useRef(new Set());
|
|
40
|
+
const { acceptedFiles = [], multipleUploads } = useChannelStateContext();
|
|
41
|
+
const { t } = useTranslationContext();
|
|
42
|
+
const messageInputContext = useMessageInputContext();
|
|
43
|
+
const dragAndDropUploadContext = useDragAndDropUploadContext();
|
|
44
|
+
// if message input context is available, there's no need to use the queue
|
|
45
|
+
const isWithinMessageInputContext = typeof messageInputContext.uploadNewFiles === 'function';
|
|
46
|
+
const accept = useMemo(() => acceptedFiles.reduce((mediaTypeMap, mediaType) => {
|
|
47
|
+
mediaTypeMap[mediaType] ?? (mediaTypeMap[mediaType] = []);
|
|
48
|
+
return mediaTypeMap;
|
|
49
|
+
}, {}), [acceptedFiles]);
|
|
50
|
+
const subscribeToDrop = useCallback((fn) => {
|
|
51
|
+
dropHandlersRef.current.add(fn);
|
|
52
|
+
return () => {
|
|
53
|
+
dropHandlersRef.current.delete(fn);
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
const handleDrop = useCallback((files) => {
|
|
57
|
+
dropHandlersRef.current.forEach((fn) => fn(files));
|
|
58
|
+
}, []);
|
|
59
|
+
const { getRootProps, isDragActive, isDragReject } = useDropzone({
|
|
60
|
+
accept,
|
|
61
|
+
// apply `disabled` rules if available, otherwise allow anything and
|
|
62
|
+
// let the `uploadNewFiles` handle the limitations internally
|
|
63
|
+
disabled: isWithinMessageInputContext
|
|
64
|
+
? !messageInputContext.isUploadEnabled || messageInputContext.maxFilesLeft === 0
|
|
65
|
+
: false,
|
|
66
|
+
multiple: multipleUploads,
|
|
67
|
+
noClick: true,
|
|
68
|
+
onDrop: isWithinMessageInputContext ? messageInputContext.uploadNewFiles : handleDrop,
|
|
69
|
+
});
|
|
70
|
+
// nested WithDragAndDropUpload components render wrappers without functionality
|
|
71
|
+
// (MessageInputFlat has a default WithDragAndDropUpload)
|
|
72
|
+
if (dragAndDropUploadContext.subscribeToDrop !== null) {
|
|
73
|
+
return React.createElement(Component, { className: className }, children);
|
|
74
|
+
}
|
|
75
|
+
return (React.createElement(DragAndDropUploadContext.Provider, { value: {
|
|
76
|
+
subscribeToDrop,
|
|
77
|
+
} },
|
|
78
|
+
React.createElement(Component, { ...getRootProps({ className, style }) },
|
|
79
|
+
isDragActive && (React.createElement("div", { className: clsx('str-chat__dropzone-container', {
|
|
80
|
+
'str-chat__dropzone-container--not-accepted': isDragReject,
|
|
81
|
+
}) },
|
|
82
|
+
!isDragReject && React.createElement("p", null, t('Drag your files here')),
|
|
83
|
+
isDragReject && React.createElement("p", null, t('Some of the files will not be accepted')))),
|
|
84
|
+
children)));
|
|
85
|
+
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React, { createContext, useContext } from 'react';
|
|
2
2
|
export const MessageInputContext = createContext(undefined);
|
|
3
3
|
export const MessageInputContextProvider = ({ children, value, }) => (React.createElement(MessageInputContext.Provider, { value: value }, children));
|
|
4
|
-
export const useMessageInputContext = (
|
|
4
|
+
export const useMessageInputContext = (
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6
|
+
componentName) => {
|
|
5
7
|
const contextValue = useContext(MessageInputContext);
|
|
6
8
|
if (!contextValue) {
|
|
7
|
-
console.warn(`The useMessageInputContext hook was called outside of the MessageInputContext provider. Make sure this hook is called within the MessageInput's UI component. The errored call is located in the ${componentName} component.`);
|
|
8
9
|
return {};
|
|
9
10
|
}
|
|
10
11
|
return contextValue;
|