stream-chat-react 13.5.1 → 13.6.1
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 +1 -1
- package/dist/components/Channel/Channel.js +2 -0
- package/dist/components/Chat/Chat.d.ts +1 -1
- package/dist/components/Chat/Chat.js +3 -1
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/Dialog/DialogManager.d.ts +3 -1
- package/dist/components/Dialog/DialogManager.js +3 -0
- package/dist/components/Dialog/DialogPortal.d.ts +1 -1
- package/dist/components/Dialog/DialogPortal.js +4 -2
- package/dist/components/Dialog/hooks/useDialog.d.ts +11 -3
- package/dist/components/Dialog/hooks/useDialog.js +10 -7
- package/dist/components/Gallery/Gallery.js +2 -2
- package/dist/components/Gallery/Image.js +11 -5
- package/dist/components/Message/utils.js +2 -2
- package/dist/components/MessageBounce/MessageBounceModal.js +3 -2
- package/dist/components/MessageInput/AttachmentSelector.js +2 -1
- package/dist/components/MessageInput/EditMessageForm.js +2 -2
- package/dist/components/Modal/GlobalModal.d.ts +4 -0
- package/dist/components/Modal/GlobalModal.js +57 -0
- package/dist/components/Modal/Modal.d.ts +3 -4
- package/dist/components/Modal/index.d.ts +1 -0
- package/dist/components/Modal/index.js +1 -0
- package/dist/components/Poll/PollActions/PollAction.js +8 -4
- package/dist/components/Poll/PollActions/PollActions.js +8 -6
- package/dist/components/Reactions/ReactionsListModal.d.ts +1 -1
- package/dist/components/Reactions/ReactionsListModal.js +3 -2
- package/dist/components/TextareaComposer/TextareaComposer.js +15 -24
- package/dist/context/ComponentContext.d.ts +3 -1
- package/dist/context/DialogManagerContext.d.ts +21 -4
- package/dist/context/DialogManagerContext.js +114 -5
- package/dist/css/v2/index.css +1 -1
- package/dist/css/v2/index.layout.css +1 -1
- package/dist/experimental/index.browser.cjs +94 -22
- package/dist/experimental/index.browser.cjs.map +4 -4
- package/dist/experimental/index.node.cjs +94 -22
- package/dist/experimental/index.node.cjs.map +4 -4
- package/dist/index.browser.cjs +1767 -1545
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +1774 -1545
- package/dist/index.node.cjs.map +4 -4
- package/dist/scss/v2/Modal/Modal-theme.scss +21 -6
- package/dist/scss/v2/Poll/Poll-layout.scss +20 -6
- package/dist/scss/v2/Poll/Poll-theme.scss +8 -8
- package/package.json +4 -4
|
@@ -5,7 +5,7 @@ import type { OnMentionAction } from './hooks/useMentionsHandlers';
|
|
|
5
5
|
import type { LoadingErrorIndicatorProps } from '../Loading';
|
|
6
6
|
import type { ComponentContextValue } from '../../context';
|
|
7
7
|
import type { ChannelUnreadUiState, GiphyVersions, ImageAttachmentSizeHandler, VideoAttachmentSizeHandler } from '../../types/types';
|
|
8
|
-
type ChannelPropsForwardedToComponentContext = Pick<ComponentContextValue, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'ShareLocationDialog' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' | 'MessageIsThreadReplyInChannelButtonIndicator' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'ReactionsListModal' | 'ReminderNotification' | 'SendButton' | 'SendToChannelCheckbox' | 'StartRecordingAudioButton' | 'TextareaComposer' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
|
|
8
|
+
type ChannelPropsForwardedToComponentContext = Pick<ComponentContextValue, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'ShareLocationDialog' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' | 'MessageIsThreadReplyInChannelButtonIndicator' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'Modal' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'ReactionsListModal' | 'ReminderNotification' | 'SendButton' | 'SendToChannelCheckbox' | 'StartRecordingAudioButton' | 'TextareaComposer' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
|
|
9
9
|
export type ChannelProps = ChannelPropsForwardedToComponentContext & {
|
|
10
10
|
/** Custom handler function that runs when the active channel has unread messages and the app is running on a separate browser tab */
|
|
11
11
|
activeUnreadHandler?: (unread: number, documentTitle: string) => void;
|
|
@@ -747,6 +747,7 @@ const ChannelInner = (props) => {
|
|
|
747
747
|
MessageStatus: props.MessageStatus,
|
|
748
748
|
MessageSystem: props.MessageSystem,
|
|
749
749
|
MessageTimestamp: props.MessageTimestamp,
|
|
750
|
+
Modal: props.Modal,
|
|
750
751
|
ModalGallery: props.ModalGallery,
|
|
751
752
|
PinIndicator: props.PinIndicator,
|
|
752
753
|
PollActions: props.PollActions,
|
|
@@ -813,6 +814,7 @@ const ChannelInner = (props) => {
|
|
|
813
814
|
props.MessageStatus,
|
|
814
815
|
props.MessageSystem,
|
|
815
816
|
props.MessageTimestamp,
|
|
817
|
+
props.Modal,
|
|
816
818
|
props.ModalGallery,
|
|
817
819
|
props.PinIndicator,
|
|
818
820
|
props.PollActions,
|
|
@@ -3,7 +3,7 @@ import { SearchController } from 'stream-chat';
|
|
|
3
3
|
import type { PropsWithChildren } from 'react';
|
|
4
4
|
import type { StreamChat } from 'stream-chat';
|
|
5
5
|
import type { CustomClasses } from '../../context/ChatContext';
|
|
6
|
-
import type
|
|
6
|
+
import { type MessageContextValue } from '../../context';
|
|
7
7
|
import type { SupportedTranslations } from '../../i18n/types';
|
|
8
8
|
import type { Streami18n } from '../../i18n/Streami18n';
|
|
9
9
|
export type ChatProps = {
|
|
@@ -5,6 +5,7 @@ import { useCreateChatContext } from './hooks/useCreateChatContext';
|
|
|
5
5
|
import { useChannelsQueryState } from './hooks/useChannelsQueryState';
|
|
6
6
|
import { ChatProvider } from '../../context/ChatContext';
|
|
7
7
|
import { TranslationProvider } from '../../context/TranslationContext';
|
|
8
|
+
import { ModalDialogManagerProvider } from '../../context';
|
|
8
9
|
/**
|
|
9
10
|
* Wrapper component for a StreamChat application. Chat needs to be placed around any other chat components
|
|
10
11
|
* as it provides the ChatContext.
|
|
@@ -41,5 +42,6 @@ export const Chat = (props) => {
|
|
|
41
42
|
if (!translators.t)
|
|
42
43
|
return null;
|
|
43
44
|
return (React.createElement(ChatProvider, { value: chatContextValue },
|
|
44
|
-
React.createElement(TranslationProvider, { value: translators },
|
|
45
|
+
React.createElement(TranslationProvider, { value: translators },
|
|
46
|
+
React.createElement(ModalDialogManagerProvider, null, children))));
|
|
45
47
|
};
|
|
@@ -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 = "13.
|
|
27
|
+
const version = "13.6.1";
|
|
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'
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { StateStore } from 'stream-chat';
|
|
3
|
-
export type
|
|
3
|
+
export type GetDialogParams = {
|
|
4
4
|
id: DialogId;
|
|
5
5
|
};
|
|
6
|
+
export type GetOrCreateDialogParams = GetDialogParams;
|
|
6
7
|
type DialogId = string;
|
|
7
8
|
export type Dialog = {
|
|
8
9
|
close: () => void;
|
|
@@ -35,6 +36,7 @@ export declare class DialogManager {
|
|
|
35
36
|
state: StateStore<DialogManagerState>;
|
|
36
37
|
constructor({ id }?: DialogManagerOptions);
|
|
37
38
|
get openDialogCount(): number;
|
|
39
|
+
get(id: DialogId): Dialog;
|
|
38
40
|
getOrCreate({ id }: GetOrCreateDialogParams): Dialog;
|
|
39
41
|
open(params: GetOrCreateDialogParams, closeRest?: boolean): void;
|
|
40
42
|
close(id: DialogId): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PropsWithChildren } from 'react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
export declare const DialogPortalDestination: () => React.JSX.Element;
|
|
3
|
+
export declare const DialogPortalDestination: () => React.JSX.Element | null;
|
|
4
4
|
type DialogPortalEntryProps = {
|
|
5
5
|
dialogId: string;
|
|
6
6
|
};
|
|
@@ -5,13 +5,15 @@ import { useDialogManager } from '../../context';
|
|
|
5
5
|
export const DialogPortalDestination = () => {
|
|
6
6
|
const { dialogManager } = useDialogManager();
|
|
7
7
|
const openedDialogCount = useOpenedDialogCount();
|
|
8
|
+
if (!openedDialogCount)
|
|
9
|
+
return null;
|
|
8
10
|
return (React.createElement("div", { className: 'str-chat__dialog-overlay', "data-str-chat__portal-id": dialogManager.id, "data-testid": 'str-chat__dialog-overlay', onClick: () => dialogManager.closeAll(), style: {
|
|
9
11
|
'--str-chat__dialog-overlay-height': openedDialogCount > 0 ? '100%' : '0',
|
|
10
12
|
} }));
|
|
11
13
|
};
|
|
12
14
|
export const DialogPortalEntry = ({ children, dialogId, }) => {
|
|
13
|
-
const { dialogManager } = useDialogManager();
|
|
14
|
-
const dialogIsOpen = useDialogIsOpen(dialogId);
|
|
15
|
+
const { dialogManager } = useDialogManager({ dialogId });
|
|
16
|
+
const dialogIsOpen = useDialogIsOpen(dialogId, dialogManager.id);
|
|
15
17
|
const getPortalDestination = useCallback(() => document.querySelector(`div[data-str-chat__portal-id="${dialogManager.id}"]`), [dialogManager.id]);
|
|
16
18
|
return (React.createElement(Portal, { getPortalDestination: getPortalDestination, isOpen: dialogIsOpen }, children));
|
|
17
19
|
};
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { GetOrCreateDialogParams } from '../DialogManager';
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export type UseDialogParams = GetOrCreateDialogParams & {
|
|
3
|
+
dialogManagerId?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const useDialog: ({ dialogManagerId, id }: UseDialogParams) => import("../DialogManager").Dialog;
|
|
6
|
+
export declare const modalDialogId: "modal-dialog";
|
|
7
|
+
export declare const useModalDialog: () => import("../DialogManager").Dialog;
|
|
8
|
+
export declare const useDialogIsOpen: (id: string, dialogManagerId?: string) => boolean;
|
|
9
|
+
export declare const useModalDialogIsOpen: () => boolean;
|
|
10
|
+
export declare const useOpenedDialogCount: ({ dialogManagerId, }?: {
|
|
11
|
+
dialogManagerId?: string;
|
|
12
|
+
}) => number;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useCallback, useEffect } from 'react';
|
|
2
|
-
import { useDialogManager } from '../../../context';
|
|
2
|
+
import { modalDialogManagerId, useDialogManager } from '../../../context';
|
|
3
3
|
import { useStateStore } from '../../../store';
|
|
4
|
-
export const useDialog = ({ id }) => {
|
|
5
|
-
const { dialogManager } = useDialogManager();
|
|
4
|
+
export const useDialog = ({ dialogManagerId, id }) => {
|
|
5
|
+
const { dialogManager } = useDialogManager({ dialogManagerId });
|
|
6
6
|
useEffect(() => () => {
|
|
7
7
|
// Since this cleanup can run even if the component is still mounted
|
|
8
8
|
// and dialog id is unchanged (e.g. in <StrictMode />), it's safer to
|
|
@@ -12,11 +12,14 @@ export const useDialog = ({ id }) => {
|
|
|
12
12
|
}, [dialogManager, id]);
|
|
13
13
|
return dialogManager.getOrCreate({ id });
|
|
14
14
|
};
|
|
15
|
-
export const
|
|
16
|
-
|
|
15
|
+
export const modalDialogId = 'modal-dialog';
|
|
16
|
+
export const useModalDialog = () => useDialog({ dialogManagerId: modalDialogManagerId, id: modalDialogId });
|
|
17
|
+
export const useDialogIsOpen = (id, dialogManagerId) => {
|
|
18
|
+
const { dialogManager } = useDialogManager({ dialogManagerId });
|
|
17
19
|
const dialogIsOpenSelector = useCallback(({ dialogsById }) => ({ isOpen: !!dialogsById[id]?.isOpen }), [id]);
|
|
18
20
|
return useStateStore(dialogManager.state, dialogIsOpenSelector).isOpen;
|
|
19
21
|
};
|
|
22
|
+
export const useModalDialogIsOpen = () => useDialogIsOpen(modalDialogId, modalDialogManagerId);
|
|
20
23
|
const openedDialogCountSelector = (nextValue) => ({
|
|
21
24
|
openedDialogCount: Object.values(nextValue.dialogsById).reduce((count, dialog) => {
|
|
22
25
|
if (dialog.isOpen)
|
|
@@ -24,7 +27,7 @@ const openedDialogCountSelector = (nextValue) => ({
|
|
|
24
27
|
return count;
|
|
25
28
|
}, 0),
|
|
26
29
|
});
|
|
27
|
-
export const useOpenedDialogCount = () => {
|
|
28
|
-
const { dialogManager } = useDialogManager();
|
|
30
|
+
export const useOpenedDialogCount = ({ dialogManagerId, } = {}) => {
|
|
31
|
+
const { dialogManager } = useDialogManager({ dialogManagerId });
|
|
29
32
|
return useStateStore(dialogManager.state, openedDialogCountSelector).openedDialogCount;
|
|
30
33
|
};
|
|
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|
|
2
2
|
import { sanitizeUrl } from '@braintree/sanitize-url';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
4
|
import { BaseImage as DefaultBaseImage } from './BaseImage';
|
|
5
|
-
import { Modal } from '../Modal';
|
|
5
|
+
import { Modal as DefaultModal } from '../Modal';
|
|
6
6
|
import { ModalGallery as DefaultModalGallery } from './ModalGallery';
|
|
7
7
|
import { useComponentContext } from '../../context/ComponentContext';
|
|
8
8
|
import { useTranslationContext } from '../../context/TranslationContext';
|
|
@@ -10,7 +10,7 @@ const UnMemoizedGallery = (props) => {
|
|
|
10
10
|
const { images, innerRefs } = props;
|
|
11
11
|
const [index, setIndex] = useState(0);
|
|
12
12
|
const [modalOpen, setModalOpen] = useState(false);
|
|
13
|
-
const { BaseImage = DefaultBaseImage, ModalGallery = DefaultModalGallery } = useComponentContext('Gallery');
|
|
13
|
+
const { BaseImage = DefaultBaseImage, Modal = DefaultModal, ModalGallery = DefaultModalGallery, } = useComponentContext('Gallery');
|
|
14
14
|
const { t } = useTranslationContext('Gallery');
|
|
15
15
|
const imageFallbackTitle = t('User uploaded content');
|
|
16
16
|
const countImagesDisplayedInPreview = 4;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
1
2
|
import React, { useState } from 'react';
|
|
2
3
|
import { sanitizeUrl } from '@braintree/sanitize-url';
|
|
3
4
|
import { BaseImage as DefaultBaseImage } from './BaseImage';
|
|
4
|
-
import { Modal } from '../Modal';
|
|
5
|
+
import { Modal as DefaultModal } from '../Modal';
|
|
5
6
|
import { ModalGallery as DefaultModalGallery } from './ModalGallery';
|
|
6
7
|
import { useComponentContext } from '../../context';
|
|
7
8
|
/**
|
|
@@ -10,11 +11,16 @@ import { useComponentContext } from '../../context';
|
|
|
10
11
|
export const ImageComponent = (props) => {
|
|
11
12
|
const { dimensions = {}, fallback, image_url, innerRef, previewUrl, style, thumb_url, } = props;
|
|
12
13
|
const [modalIsOpen, setModalIsOpen] = useState(false);
|
|
13
|
-
const { BaseImage = DefaultBaseImage, ModalGallery = DefaultModalGallery } = useComponentContext('ImageComponent');
|
|
14
|
+
const { BaseImage = DefaultBaseImage, Modal = DefaultModal, ModalGallery = DefaultModalGallery, } = useComponentContext('ImageComponent');
|
|
14
15
|
const imageSrc = sanitizeUrl(previewUrl || image_url || thumb_url);
|
|
15
|
-
const
|
|
16
|
+
const closeModal = useCallback(() => {
|
|
17
|
+
setModalIsOpen(false);
|
|
18
|
+
}, []);
|
|
19
|
+
const openModal = useCallback(() => {
|
|
20
|
+
setModalIsOpen(true);
|
|
21
|
+
}, []);
|
|
16
22
|
return (React.createElement(React.Fragment, null,
|
|
17
|
-
React.createElement(BaseImage, { alt: fallback, className: 'str-chat__message-attachment--img', "data-testid": 'image-test', onClick:
|
|
18
|
-
React.createElement(Modal, { className: 'str-chat__image-modal', onClose:
|
|
23
|
+
React.createElement(BaseImage, { alt: fallback, className: 'str-chat__message-attachment--img', "data-testid": 'image-test', onClick: openModal, src: imageSrc, style: style, tabIndex: 0, title: fallback, ...dimensions, ...(innerRef && { ref: innerRef }) }),
|
|
24
|
+
React.createElement(Modal, { className: 'str-chat__image-modal', onClose: closeModal, open: modalIsOpen },
|
|
19
25
|
React.createElement(ModalGallery, { images: [props], index: 0 }))));
|
|
20
26
|
};
|
|
@@ -135,14 +135,14 @@ export const getMessageActions = (actions, { canDelete, canEdit, canFlag, canMar
|
|
|
135
135
|
messageActionsAfterPermission.push(MESSAGE_ACTIONS.react);
|
|
136
136
|
}
|
|
137
137
|
if (channelConfig?.['user_message_reminders'] &&
|
|
138
|
-
messageActions.indexOf(MESSAGE_ACTIONS.remindMe)) {
|
|
138
|
+
messageActions.indexOf(MESSAGE_ACTIONS.remindMe) > -1) {
|
|
139
139
|
messageActionsAfterPermission.push(MESSAGE_ACTIONS.remindMe);
|
|
140
140
|
}
|
|
141
141
|
if (canReply && messageActions.indexOf(MESSAGE_ACTIONS.reply) > -1) {
|
|
142
142
|
messageActionsAfterPermission.push(MESSAGE_ACTIONS.reply);
|
|
143
143
|
}
|
|
144
144
|
if (channelConfig?.['user_message_reminders'] &&
|
|
145
|
-
messageActions.indexOf(MESSAGE_ACTIONS.saveForLater)) {
|
|
145
|
+
messageActions.indexOf(MESSAGE_ACTIONS.saveForLater) > -1) {
|
|
146
146
|
messageActionsAfterPermission.push(MESSAGE_ACTIONS.saveForLater);
|
|
147
147
|
}
|
|
148
148
|
return messageActionsAfterPermission;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Modal } from '../Modal';
|
|
3
|
-
import { MessageBounceProvider } from '../../context';
|
|
2
|
+
import { Modal as DefaultModal } from '../Modal';
|
|
3
|
+
import { MessageBounceProvider, useComponentContext } from '../../context';
|
|
4
4
|
export function MessageBounceModal({ MessageBouncePrompt, ...modalProps }) {
|
|
5
|
+
const { Modal = DefaultModal } = useComponentContext();
|
|
5
6
|
return (React.createElement(Modal, { className: 'str-chat__message-bounce-modal', ...modalProps },
|
|
6
7
|
React.createElement(MessageBounceProvider, null,
|
|
7
8
|
React.createElement(MessageBouncePrompt, { onClose: modalProps.onClose }))));
|
|
@@ -4,7 +4,7 @@ import { useAttachmentManagerState } from './hooks/useAttachmentManagerState';
|
|
|
4
4
|
import { CHANNEL_CONTAINER_ID } from '../Channel/constants';
|
|
5
5
|
import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
|
|
6
6
|
import { DialogMenuButton } from '../Dialog/DialogMenu';
|
|
7
|
-
import { Modal } from '../Modal';
|
|
7
|
+
import { Modal as DefaultModal } from '../Modal';
|
|
8
8
|
import { ShareLocationDialog as DefaultLocationDialog } from '../Location';
|
|
9
9
|
import { PollCreationDialog as DefaultPollCreationDialog } from '../Poll';
|
|
10
10
|
import { Portal } from '../Portal/Portal';
|
|
@@ -111,6 +111,7 @@ const useAttachmentSelectorActionsFiltered = (original) => {
|
|
|
111
111
|
};
|
|
112
112
|
export const AttachmentSelector = ({ attachmentSelectorActionSet = defaultAttachmentSelectorActionSet, getModalPortalDestination, }) => {
|
|
113
113
|
const { t } = useTranslationContext();
|
|
114
|
+
const { Modal = DefaultModal } = useComponentContext();
|
|
114
115
|
const { channelCapabilities } = useChannelStateContext();
|
|
115
116
|
const messageComposer = useMessageComposer();
|
|
116
117
|
const actions = useAttachmentSelectorActionsFiltered(attachmentSelectorActionSet);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useCallback, useEffect } from 'react';
|
|
2
2
|
import { MessageInput } from './MessageInput';
|
|
3
3
|
import { MessageInputFlat } from './MessageInputFlat';
|
|
4
|
-
import { Modal } from '../Modal';
|
|
4
|
+
import { Modal as DefaultModal } from '../Modal';
|
|
5
5
|
import { useComponentContext, useMessageContext, useMessageInputContext, useTranslationContext, } from '../../context';
|
|
6
6
|
import { useMessageComposer, useMessageComposerHasSendableData } from './hooks';
|
|
7
7
|
const EditMessageFormSendButton = () => {
|
|
@@ -32,7 +32,7 @@ export const EditMessageForm = () => {
|
|
|
32
32
|
React.createElement(EditMessageFormSendButton, null))));
|
|
33
33
|
};
|
|
34
34
|
export const EditMessageModal = ({ additionalMessageInputProps, }) => {
|
|
35
|
-
const { EditMessageInput = EditMessageForm } = useComponentContext();
|
|
35
|
+
const { EditMessageInput = EditMessageForm, Modal = DefaultModal } = useComponentContext();
|
|
36
36
|
const { clearEditingState } = useMessageContext();
|
|
37
37
|
const messageComposer = useMessageComposer();
|
|
38
38
|
const onEditModalClose = useCallback(() => {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PropsWithChildren } from 'react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import type { ModalProps } from './Modal';
|
|
4
|
+
export declare const GlobalModal: ({ children, className, onClose, onCloseAttempt, open, }: PropsWithChildren<ModalProps>) => React.JSX.Element | null;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import React, { useEffect, useRef } from 'react';
|
|
4
|
+
import { FocusScope } from '@react-aria/focus';
|
|
5
|
+
import { CloseIconRound } from './icons';
|
|
6
|
+
import { useTranslationContext } from '../../context';
|
|
7
|
+
import { DialogPortalEntry, modalDialogId, useModalDialog, useModalDialogIsOpen, } from '../Dialog';
|
|
8
|
+
export const GlobalModal = ({ children, className, onClose, onCloseAttempt, open, }) => {
|
|
9
|
+
const { t } = useTranslationContext('Modal');
|
|
10
|
+
const dialog = useModalDialog();
|
|
11
|
+
const isOpen = useModalDialogIsOpen();
|
|
12
|
+
const innerRef = useRef(null);
|
|
13
|
+
const closeButtonRef = useRef(null);
|
|
14
|
+
const maybeClose = useCallback((source, event) => {
|
|
15
|
+
const allow = onCloseAttempt?.(source, event);
|
|
16
|
+
if (allow !== false) {
|
|
17
|
+
onClose?.(event);
|
|
18
|
+
dialog.close();
|
|
19
|
+
}
|
|
20
|
+
}, [dialog, onClose, onCloseAttempt]);
|
|
21
|
+
const handleClick = (event) => {
|
|
22
|
+
const target = event.target;
|
|
23
|
+
if (!innerRef.current || !closeButtonRef.current)
|
|
24
|
+
return;
|
|
25
|
+
if (innerRef.current?.contains(target))
|
|
26
|
+
return;
|
|
27
|
+
if (closeButtonRef.current.contains(target)) {
|
|
28
|
+
maybeClose('button', event);
|
|
29
|
+
}
|
|
30
|
+
else if (!innerRef.current.contains(target)) {
|
|
31
|
+
maybeClose('overlay', event);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!isOpen)
|
|
36
|
+
return;
|
|
37
|
+
const handleKeyDown = (event) => {
|
|
38
|
+
if (event.key === 'Escape')
|
|
39
|
+
maybeClose('escape', event);
|
|
40
|
+
};
|
|
41
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
42
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
43
|
+
}, [isOpen, maybeClose]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (open && !dialog.isOpen) {
|
|
46
|
+
dialog.open();
|
|
47
|
+
}
|
|
48
|
+
}, [dialog, open]);
|
|
49
|
+
if (!open || !isOpen)
|
|
50
|
+
return null;
|
|
51
|
+
return (React.createElement(DialogPortalEntry, { dialogId: modalDialogId },
|
|
52
|
+
React.createElement("div", { className: clsx('str-chat str-chat__modal str-chat-react__modal str-chat__modal--open', className), onClick: handleClick },
|
|
53
|
+
React.createElement(FocusScope, { autoFocus: true, contain: true },
|
|
54
|
+
React.createElement("button", { className: 'str-chat__modal__close-button', ref: closeButtonRef, title: t('Close'), type: 'button' },
|
|
55
|
+
React.createElement(CloseIconRound, null)),
|
|
56
|
+
React.createElement("div", { className: 'str-chat__modal__inner str-chat-react__modal__inner', ref: innerRef }, children)))));
|
|
57
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type PropsWithChildren } from 'react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
type
|
|
3
|
+
export type ModalCloseEvent = KeyboardEvent | React.KeyboardEvent | React.MouseEvent<HTMLButtonElement | HTMLDivElement>;
|
|
4
4
|
export type ModalCloseSource = 'overlay' | 'button' | 'escape';
|
|
5
5
|
export type ModalProps = {
|
|
6
6
|
/** If true, modal is opened or visible. */
|
|
@@ -8,9 +8,8 @@ export type ModalProps = {
|
|
|
8
8
|
/** Custom class to be applied to the modal root div */
|
|
9
9
|
className?: string;
|
|
10
10
|
/** Callback handler for closing of modal. */
|
|
11
|
-
onClose?: (event:
|
|
11
|
+
onClose?: (event: ModalCloseEvent) => void;
|
|
12
12
|
/** Optional handler to intercept closing logic. Return false to prevent onClose. */
|
|
13
|
-
onCloseAttempt?: (source: ModalCloseSource, event:
|
|
13
|
+
onCloseAttempt?: (source: ModalCloseSource, event: ModalCloseEvent) => boolean;
|
|
14
14
|
};
|
|
15
15
|
export declare const Modal: ({ children, className, onClose, onCloseAttempt, open, }: PropsWithChildren<ModalProps>) => React.JSX.Element | null;
|
|
16
|
-
export {};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Modal } from '../../Modal';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { Modal as DefaultModal } from '../../Modal';
|
|
3
|
+
import { useComponentContext } from '../../../context';
|
|
4
|
+
export const PollAction = ({ buttonText, children, closeModal, modalClassName, modalIsOpen, openModal, }) => {
|
|
5
|
+
const { Modal = DefaultModal } = useComponentContext();
|
|
6
|
+
return (React.createElement(React.Fragment, null,
|
|
7
|
+
React.createElement("button", { className: 'str-chat__poll-action', onClick: openModal }, buttonText),
|
|
8
|
+
React.createElement(Modal, { className: modalClassName, onClose: closeModal, open: modalIsOpen }, children)));
|
|
9
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
1
2
|
import React, { useCallback, useState } from 'react';
|
|
2
3
|
import { PollAction } from './PollAction';
|
|
3
4
|
import { AddCommentForm as DefaultAddCommentForm } from './AddCommentForm';
|
|
@@ -9,6 +10,7 @@ import { PollResults as DefaultPollResults } from './PollResults';
|
|
|
9
10
|
import { MAX_OPTIONS_DISPLAYED, MAX_POLL_OPTIONS } from '../constants';
|
|
10
11
|
import { useChannelStateContext, useChatContext, useMessageContext, usePollContext, useTranslationContext, } from '../../../context';
|
|
11
12
|
import { useStateStore } from '../../../store';
|
|
13
|
+
const COMMON_MODAL_CLASS = 'str-chat__poll-action-modal';
|
|
12
14
|
const pollStateSelector = (nextValue) => ({
|
|
13
15
|
allow_answers: nextValue.allow_answers,
|
|
14
16
|
allow_user_suggested_options: nextValue.allow_user_suggested_options,
|
|
@@ -31,18 +33,18 @@ export const PollActions = ({ AddCommentForm = DefaultAddCommentForm, EndPollDia
|
|
|
31
33
|
return (React.createElement("div", { className: 'str-chat__poll-actions' },
|
|
32
34
|
options.length > MAX_OPTIONS_DISPLAYED && (React.createElement(PollAction, { buttonText: t('See all options ({{count}})', {
|
|
33
35
|
count: options.length,
|
|
34
|
-
}), closeModal: closeModal, modalIsOpen: modalOpen === 'view-all-options', openModal: () => setModalOpen('view-all-options') },
|
|
36
|
+
}), closeModal: closeModal, modalClassName: COMMON_MODAL_CLASS, modalIsOpen: modalOpen === 'view-all-options', openModal: () => setModalOpen('view-all-options') },
|
|
35
37
|
React.createElement(PollOptionsFullList, { close: closeModal }))),
|
|
36
38
|
!is_closed &&
|
|
37
39
|
allow_user_suggested_options &&
|
|
38
|
-
options.length < MAX_POLL_OPTIONS && (React.createElement(PollAction, { buttonText: t('Suggest an option'), closeModal: closeModal, modalClassName: 'str-chat__suggest-poll-option-modal', modalIsOpen: modalOpen === 'suggest-option', openModal: () => setModalOpen('suggest-option') },
|
|
40
|
+
options.length < MAX_POLL_OPTIONS && (React.createElement(PollAction, { buttonText: t('Suggest an option'), closeModal: closeModal, modalClassName: clsx(COMMON_MODAL_CLASS, 'str-chat__suggest-poll-option-modal'), modalIsOpen: modalOpen === 'suggest-option', openModal: () => setModalOpen('suggest-option') },
|
|
39
41
|
React.createElement(SuggestPollOptionForm, { close: closeModal, messageId: message.id }))),
|
|
40
|
-
!is_closed && allow_answers && (React.createElement(PollAction, { buttonText: ownAnswer ? t('Update your comment') : t('Add a comment'), closeModal: closeModal, modalClassName: 'str-chat__add-poll-answer-modal', modalIsOpen: modalOpen === 'add-comment', openModal: () => setModalOpen('add-comment') },
|
|
42
|
+
!is_closed && allow_answers && (React.createElement(PollAction, { buttonText: ownAnswer ? t('Update your comment') : t('Add a comment'), closeModal: closeModal, modalClassName: clsx(COMMON_MODAL_CLASS, 'str-chat__add-poll-answer-modal'), modalIsOpen: modalOpen === 'add-comment', openModal: () => setModalOpen('add-comment') },
|
|
41
43
|
React.createElement(AddCommentForm, { close: closeModal, messageId: message.id }))),
|
|
42
|
-
answers_count > 0 && channelCapabilities['query-poll-votes'] && (React.createElement(PollAction, { buttonText: t('View {{count}} comments', { count: answers_count }), closeModal: closeModal, modalClassName: 'str-chat__poll-answer-list-modal', modalIsOpen: modalOpen === 'view-comments', openModal: () => setModalOpen('view-comments') },
|
|
44
|
+
answers_count > 0 && channelCapabilities['query-poll-votes'] && (React.createElement(PollAction, { buttonText: t('View {{count}} comments', { count: answers_count }), closeModal: closeModal, modalClassName: clsx(COMMON_MODAL_CLASS, 'str-chat__poll-answer-list-modal'), modalIsOpen: modalOpen === 'view-comments', openModal: () => setModalOpen('view-comments') },
|
|
43
45
|
React.createElement(PollAnswerList, { close: closeModal, onUpdateOwnAnswerClick: onUpdateAnswerClick }))),
|
|
44
|
-
React.createElement(PollAction, { buttonText: t('View results'), closeModal: closeModal, modalClassName: 'str-chat__poll-results-modal', modalIsOpen: modalOpen === 'view-results', openModal: () => setModalOpen('view-results') },
|
|
46
|
+
React.createElement(PollAction, { buttonText: t('View results'), closeModal: closeModal, modalClassName: clsx(COMMON_MODAL_CLASS, 'str-chat__poll-results-modal'), modalIsOpen: modalOpen === 'view-results', openModal: () => setModalOpen('view-results') },
|
|
45
47
|
React.createElement(PollResults, { close: closeModal })),
|
|
46
|
-
!is_closed && created_by_id === client.user?.id && (React.createElement(PollAction, { buttonText: t('End vote'), closeModal: closeModal, modalClassName: 'str-chat__end-poll-modal', modalIsOpen: modalOpen === 'end-vote', openModal: () => setModalOpen('end-vote') },
|
|
48
|
+
!is_closed && created_by_id === client.user?.id && (React.createElement(PollAction, { buttonText: t('End vote'), closeModal: closeModal, modalClassName: clsx(COMMON_MODAL_CLASS, 'str-chat__end-poll-modal'), modalIsOpen: modalOpen === 'end-vote', openModal: () => setModalOpen('end-vote') },
|
|
47
49
|
React.createElement(EndPollDialog, { close: closeModal })))));
|
|
48
50
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ReactionDetailsComparator, ReactionSummary, ReactionType } from './types';
|
|
3
|
+
import type { ReactionSort } from 'stream-chat';
|
|
3
4
|
import type { ModalProps } from '../Modal';
|
|
4
5
|
import type { MessageContextValue } from '../../context';
|
|
5
|
-
import type { ReactionSort } from 'stream-chat';
|
|
6
6
|
export type ReactionsListModalProps = ModalProps & Partial<Pick<MessageContextValue, 'handleFetchReactions' | 'reactionDetailsSort'>> & {
|
|
7
7
|
reactions: ReactionSummary[];
|
|
8
8
|
selectedReactionType: ReactionType;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
|
-
import { Modal } from '../Modal';
|
|
3
|
+
import { Modal as DefaultModal } from '../Modal';
|
|
4
4
|
import { useFetchReactions } from './hooks/useFetchReactions';
|
|
5
5
|
import { LoadingIndicator } from '../Loading';
|
|
6
6
|
import { Avatar } from '../Avatar';
|
|
7
|
-
import { useMessageContext } from '../../context';
|
|
7
|
+
import { useComponentContext, useMessageContext } from '../../context';
|
|
8
8
|
const defaultReactionDetailsSort = { created_at: -1 };
|
|
9
9
|
export function ReactionsListModal({ handleFetchReactions, onSelectedReactionTypeChange, reactionDetailsSort: propReactionDetailsSort, reactions, selectedReactionType, sortReactionDetails: propSortReactionDetails, ...modalProps }) {
|
|
10
|
+
const { Modal = DefaultModal } = useComponentContext();
|
|
10
11
|
const selectedReaction = reactions.find(({ reactionType }) => reactionType === selectedReactionType);
|
|
11
12
|
const SelectedEmojiComponent = selectedReaction?.EmojiComponent ?? null;
|
|
12
13
|
const { reactionDetailsSort: contextReactionDetailsSort, sortReactionDetails: contextSortReactionDetails, } = useMessageContext('ReactionsListModal');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
3
|
import Textarea from 'react-textarea-autosize';
|
|
4
4
|
import { useMessageComposer } from '../MessageInput';
|
|
5
5
|
import { useComponentContext, useMessageInputContext, useTranslationContext, } from '../../context';
|
|
@@ -116,7 +116,6 @@ export const TextareaComposer = ({ className, closeSuggestionsOnClickOutside, co
|
|
|
116
116
|
event.preventDefault();
|
|
117
117
|
}
|
|
118
118
|
handleSubmit();
|
|
119
|
-
textareaRef.current.selectionEnd = 0;
|
|
120
119
|
}
|
|
121
120
|
}, [
|
|
122
121
|
focusedItemIndex,
|
|
@@ -135,23 +134,13 @@ export const TextareaComposer = ({ className, closeSuggestionsOnClickOutside, co
|
|
|
135
134
|
textComposer.closeSuggestions();
|
|
136
135
|
}
|
|
137
136
|
}, [onScroll, textComposer]);
|
|
138
|
-
const
|
|
137
|
+
const setSelection = useCallback((e) => {
|
|
139
138
|
onSelect?.(e);
|
|
140
139
|
textComposer.setSelection({
|
|
141
140
|
end: e.target.selectionEnd,
|
|
142
141
|
start: e.target.selectionStart,
|
|
143
142
|
});
|
|
144
143
|
}, [onSelect, textComposer]);
|
|
145
|
-
useEffect(() => {
|
|
146
|
-
// FIXME: find the real reason for cursor being set to the end on each change
|
|
147
|
-
// This is a workaround to prevent the cursor from jumping
|
|
148
|
-
// to the end of the textarea when the user is typing
|
|
149
|
-
// at the position that is not at the end of the textarea value.
|
|
150
|
-
if (textareaRef.current && !isComposing) {
|
|
151
|
-
textareaRef.current.selectionStart = selection.start;
|
|
152
|
-
textareaRef.current.selectionEnd = selection.end;
|
|
153
|
-
}
|
|
154
|
-
}, [text, textareaRef, selection.start, selection.end, isComposing]);
|
|
155
144
|
useEffect(() => {
|
|
156
145
|
if (textComposer.suggestions) {
|
|
157
146
|
setFocusedItemIndex(0);
|
|
@@ -163,24 +152,26 @@ export const TextareaComposer = ({ className, closeSuggestionsOnClickOutside, co
|
|
|
163
152
|
return;
|
|
164
153
|
textareaRef.current.focus();
|
|
165
154
|
}, [attachments, focus, quotedMessage, textareaRef]);
|
|
166
|
-
|
|
155
|
+
useLayoutEffect(() => {
|
|
167
156
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* it would not be possible to type composed characters (ô).
|
|
171
|
-
* On the other hand, just removing the value override via prop (value={text}) would not allow us to change the text based on
|
|
172
|
-
* middleware results (e.g. replace characters with emojis)
|
|
157
|
+
* It is important to perform set text and after that the range
|
|
158
|
+
* to prevent cursor reset to the end of the textarea if doing it in separate effects.
|
|
173
159
|
*/
|
|
174
160
|
const textarea = textareaRef.current;
|
|
175
|
-
if (!textarea)
|
|
161
|
+
if (!textarea || isComposing)
|
|
162
|
+
return;
|
|
163
|
+
const length = textarea.value.length;
|
|
164
|
+
const start = Math.max(0, Math.min(selection.start, length));
|
|
165
|
+
const end = Math.max(start, Math.min(selection.end, length));
|
|
166
|
+
if (textarea.selectionStart === start && textarea.selectionEnd === end)
|
|
176
167
|
return;
|
|
177
|
-
textarea.
|
|
178
|
-
}, [
|
|
168
|
+
textarea.setSelectionRange(start, end, 'forward');
|
|
169
|
+
}, [text, selection.start, selection.end, isComposing, textareaRef]);
|
|
179
170
|
return (React.createElement("div", { className: clsx('rta', 'str-chat__textarea str-chat__message-textarea-react-host', containerClassName, {
|
|
180
171
|
['rta--loading']: isLoadingItems,
|
|
181
172
|
}), ref: containerRef },
|
|
182
|
-
React.createElement(Textarea, { ...additionalTextareaProps, ...restTextareaProps, "aria-label": cooldownRemaining ? t('Slow Mode ON') : placeholder, className: clsx('rta__textarea', 'str-chat__textarea__textarea str-chat__message-textarea', className), "data-testid": 'message-input', disabled: !enabled || !!cooldownRemaining, maxRows: maxRows, minRows: minRows, onBlur: onBlur, onChange: changeHandler, onCompositionEnd: onCompositionEnd, onCompositionStart: onCompositionStart, onKeyDown: keyDownHandler, onPaste: onPaste, onScroll: scrollHandler, onSelect:
|
|
173
|
+
React.createElement(Textarea, { ...additionalTextareaProps, ...restTextareaProps, "aria-label": cooldownRemaining ? t('Slow Mode ON') : placeholder, className: clsx('rta__textarea', 'str-chat__textarea__textarea str-chat__message-textarea', className), "data-testid": 'message-input', disabled: !enabled || !!cooldownRemaining, maxRows: maxRows, minRows: minRows, onBlur: onBlur, onChange: changeHandler, onCompositionEnd: onCompositionEnd, onCompositionStart: onCompositionStart, onKeyDown: keyDownHandler, onPaste: onPaste, onScroll: scrollHandler, onSelect: setSelection, placeholder: placeholder || t('Type your message'), ref: (ref) => {
|
|
183
174
|
textareaRef.current = ref;
|
|
184
|
-
} }),
|
|
175
|
+
}, value: text }),
|
|
185
176
|
!isComposing && (React.createElement(AutocompleteSuggestionList, { className: listClassName, closeOnClickOutside: closeSuggestionsOnClickOutside, focusedItemIndex: focusedItemIndex, setFocusedItemIndex: setFocusedItemIndex }))));
|
|
186
177
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PropsWithChildren } from 'react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import type { AttachmentPreviewListProps, AttachmentProps, AvatarProps, BaseImageProps, ChannelPreviewActionButtonsProps, CooldownTimerProps, CustomMessageActionsListProps, DateSeparatorProps, EmojiSearchIndex, EmptyStateIndicatorProps, EventComponentProps, FixedHeightMessageProps, GiphyPreviewMessageProps, LoadingIndicatorProps, MessageBouncePromptProps, MessageDeletedProps, MessageInputProps, MessageListNotificationsProps, MessageNotificationProps, MessageOptionsProps, MessageProps, MessageRepliesCountButtonProps, MessageStatusProps, MessageTimestampProps, MessageUIComponentProps, ModalGalleryProps, PinIndicatorProps, PollCreationDialogProps, PollOptionSelectorProps, QuotedMessagePreviewProps, ReactionOptions, ReactionSelectorProps, ReactionsListModalProps, ReactionsListProps, RecordingPermissionDeniedNotificationProps, ReminderNotificationProps, SendButtonProps, StartRecordingAudioButtonProps, StreamedMessageTextProps, TextareaComposerProps, ThreadHeaderProps, ThreadListItemProps, ThreadListItemUIProps, TimestampProps, TypingIndicatorProps, UnreadMessagesNotificationProps, UnreadMessagesSeparatorProps } from '../components';
|
|
3
|
+
import type { AttachmentPreviewListProps, AttachmentProps, AvatarProps, BaseImageProps, ChannelPreviewActionButtonsProps, CooldownTimerProps, CustomMessageActionsListProps, DateSeparatorProps, EmojiSearchIndex, EmptyStateIndicatorProps, EventComponentProps, FixedHeightMessageProps, GiphyPreviewMessageProps, LoadingIndicatorProps, MessageBouncePromptProps, MessageDeletedProps, MessageInputProps, MessageListNotificationsProps, MessageNotificationProps, MessageOptionsProps, MessageProps, MessageRepliesCountButtonProps, MessageStatusProps, MessageTimestampProps, MessageUIComponentProps, ModalGalleryProps, ModalProps, PinIndicatorProps, PollCreationDialogProps, PollOptionSelectorProps, QuotedMessagePreviewProps, ReactionOptions, ReactionSelectorProps, ReactionsListModalProps, ReactionsListProps, RecordingPermissionDeniedNotificationProps, ReminderNotificationProps, SendButtonProps, StartRecordingAudioButtonProps, StreamedMessageTextProps, TextareaComposerProps, ThreadHeaderProps, ThreadListItemProps, ThreadListItemUIProps, TimestampProps, TypingIndicatorProps, UnreadMessagesNotificationProps, UnreadMessagesSeparatorProps } from '../components';
|
|
4
4
|
import type { SuggestionItemProps, SuggestionListProps } from '../components/TextareaComposer';
|
|
5
5
|
import type { SearchProps, SearchResultsPresearchProps, SearchSourceResultListProps } from '../experimental';
|
|
6
6
|
import type { PropsWithChildrenOnly, UnknownType } from '../types/types';
|
|
@@ -87,6 +87,8 @@ export type ComponentContextValue = {
|
|
|
87
87
|
MessageSystem?: React.ComponentType<EventComponentProps>;
|
|
88
88
|
/** Custom UI component to display a timestamp on a message, defaults to and accepts same props as: [MessageTimestamp](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageTimestamp.tsx) */
|
|
89
89
|
MessageTimestamp?: React.ComponentType<MessageTimestampProps>;
|
|
90
|
+
/** Custom UI component for viewing content in a modal, defaults to and accepts the same props as [Modal](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Modal/Modal.tsx) */
|
|
91
|
+
Modal?: React.ComponentType<ModalProps>;
|
|
90
92
|
/** Custom UI component for viewing message's image attachments, defaults to and accepts the same props as [ModalGallery](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Gallery/ModalGallery.tsx) */
|
|
91
93
|
ModalGallery?: React.ComponentType<ModalGalleryProps>;
|
|
92
94
|
/** Custom UI component to override default pinned message indicator, defaults to and accepts same props as: [PinIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/icons.tsx) */
|