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.
Files changed (42) hide show
  1. package/dist/components/Channel/Channel.d.ts +1 -1
  2. package/dist/components/Channel/Channel.js +2 -0
  3. package/dist/components/Chat/Chat.d.ts +1 -1
  4. package/dist/components/Chat/Chat.js +3 -1
  5. package/dist/components/Chat/hooks/useChat.js +1 -1
  6. package/dist/components/Dialog/DialogManager.d.ts +3 -1
  7. package/dist/components/Dialog/DialogManager.js +3 -0
  8. package/dist/components/Dialog/DialogPortal.d.ts +1 -1
  9. package/dist/components/Dialog/DialogPortal.js +4 -2
  10. package/dist/components/Dialog/hooks/useDialog.d.ts +11 -3
  11. package/dist/components/Dialog/hooks/useDialog.js +10 -7
  12. package/dist/components/Gallery/Gallery.js +2 -2
  13. package/dist/components/Gallery/Image.js +11 -5
  14. package/dist/components/MessageBounce/MessageBounceModal.js +3 -2
  15. package/dist/components/MessageInput/AttachmentSelector.js +2 -1
  16. package/dist/components/MessageInput/EditMessageForm.js +2 -2
  17. package/dist/components/Modal/GlobalModal.d.ts +4 -0
  18. package/dist/components/Modal/GlobalModal.js +57 -0
  19. package/dist/components/Modal/Modal.d.ts +3 -4
  20. package/dist/components/Modal/index.d.ts +1 -0
  21. package/dist/components/Modal/index.js +1 -0
  22. package/dist/components/Poll/PollActions/PollAction.js +8 -4
  23. package/dist/components/Poll/PollActions/PollActions.js +8 -6
  24. package/dist/components/Reactions/ReactionsListModal.d.ts +1 -1
  25. package/dist/components/Reactions/ReactionsListModal.js +3 -2
  26. package/dist/context/ComponentContext.d.ts +3 -1
  27. package/dist/context/DialogManagerContext.d.ts +21 -4
  28. package/dist/context/DialogManagerContext.js +114 -5
  29. package/dist/css/v2/index.css +1 -1
  30. package/dist/css/v2/index.layout.css +1 -1
  31. package/dist/experimental/index.browser.cjs +94 -22
  32. package/dist/experimental/index.browser.cjs.map +4 -4
  33. package/dist/experimental/index.node.cjs +94 -22
  34. package/dist/experimental/index.node.cjs.map +4 -4
  35. package/dist/index.browser.cjs +1756 -1532
  36. package/dist/index.browser.cjs.map +4 -4
  37. package/dist/index.node.cjs +1763 -1532
  38. package/dist/index.node.cjs.map +4 -4
  39. package/dist/scss/v2/Modal/Modal-theme.scss +21 -6
  40. package/dist/scss/v2/Poll/Poll-layout.scss +20 -6
  41. package/dist/scss/v2/Poll/Poll-theme.scss +8 -8
  42. 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 { MessageContextValue } from '../../context';
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 }, children)));
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.5.1";
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 GetOrCreateDialogParams = {
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;
@@ -24,6 +24,9 @@ export class DialogManager {
24
24
  return count;
25
25
  }, 0);
26
26
  }
27
+ get(id) {
28
+ return this.state.getLatestValue().dialogsById[id];
29
+ }
27
30
  getOrCreate({ id }) {
28
31
  let dialog = this.state.getLatestValue().dialogsById[id];
29
32
  if (!dialog) {
@@ -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 declare const useDialog: ({ id }: GetOrCreateDialogParams) => import("../DialogManager").Dialog;
3
- export declare const useDialogIsOpen: (id: string) => boolean;
4
- export declare const useOpenedDialogCount: () => number;
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 useDialogIsOpen = (id) => {
16
- const { dialogManager } = useDialogManager();
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 toggleModal = () => setModalIsOpen((modalIsOpen) => !modalIsOpen);
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: toggleModal, src: imageSrc, style: style, tabIndex: 0, title: fallback, ...dimensions, ...(innerRef && { ref: innerRef }) }),
18
- React.createElement(Modal, { className: 'str-chat__image-modal', onClose: toggleModal, open: modalIsOpen },
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 CloseEvent = KeyboardEvent | React.KeyboardEvent | React.MouseEvent<HTMLButtonElement | HTMLDivElement>;
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: CloseEvent) => void;
11
+ onClose?: (event: ModalCloseEvent) => void;
12
12
  /** Optional handler to intercept closing logic. Return false to prevent onClose. */
13
- onCloseAttempt?: (source: ModalCloseSource, event: CloseEvent) => boolean;
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 +1,2 @@
1
+ export * from './GlobalModal';
1
2
  export * from './Modal';
@@ -1 +1,2 @@
1
+ export * from './GlobalModal';
1
2
  export * from './Modal';
@@ -1,5 +1,9 @@
1
1
  import React from 'react';
2
- import { Modal } from '../../Modal';
3
- export const PollAction = ({ buttonText, children, closeModal, modalClassName, modalIsOpen, openModal, }) => (React.createElement(React.Fragment, null,
4
- React.createElement("button", { className: 'str-chat__poll-action', onClick: openModal }, buttonText),
5
- React.createElement(Modal, { className: modalClassName, onClose: closeModal, open: modalIsOpen }, children)));
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 declare const useDialogManager: () => DialogManagerProviderContextValue;
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 {};