stream-chat-react 13.5.1 → 13.6.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 +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/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/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 +1756 -1532
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +1763 -1532
- 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 +2 -2
|
@@ -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.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'
|
|
@@ -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
|
};
|
|
@@ -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,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) */
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { PropsWithChildren } from 'react';
|
|
1
|
+
import React, { type PropsWithChildren } from 'react';
|
|
3
2
|
import { DialogManager } from '../components/Dialog/DialogManager';
|
|
3
|
+
import type { PropsWithChildrenOnly } from '../types/types';
|
|
4
4
|
type DialogManagerProviderContextValue = {
|
|
5
5
|
dialogManager: DialogManager;
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Marks the portal location
|
|
9
|
+
* @param children
|
|
10
|
+
* @param id
|
|
11
|
+
* @constructor
|
|
12
|
+
*/
|
|
7
13
|
export declare const DialogManagerProvider: ({ children, id, }: PropsWithChildren<{
|
|
8
14
|
id?: string;
|
|
9
|
-
}>) => React.JSX.Element;
|
|
10
|
-
export
|
|
15
|
+
}>) => React.JSX.Element | null;
|
|
16
|
+
export type UseDialogManagerParams = {
|
|
17
|
+
dialogId?: string;
|
|
18
|
+
dialogManagerId?: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves the nearest dialog manager or searches for the dialog manager by dialog manager id or dialog id.
|
|
22
|
+
* Dialog id will take precedence over dialog manager id if both are provided and dialog manager is found by dialog id.
|
|
23
|
+
*/
|
|
24
|
+
export declare const useDialogManager: ({ dialogId, dialogManagerId, }?: UseDialogManagerParams) => DialogManagerProviderContextValue;
|
|
25
|
+
export declare const modalDialogManagerId: "modal-dialog-manager";
|
|
26
|
+
export declare const ModalDialogManagerProvider: ({ children }: PropsWithChildrenOnly) => React.JSX.Element;
|
|
27
|
+
export declare const useModalDialogManager: () => DialogManager | undefined;
|
|
11
28
|
export {};
|