stream-chat-react 12.0.0-rc.13 → 12.0.0-rc.15

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 (73) hide show
  1. package/dist/components/Channel/hooks/useCreateChannelStateContext.js +1 -0
  2. package/dist/components/ChannelHeader/ChannelHeader.js +4 -5
  3. package/dist/components/ChannelPreview/hooks/useChannelPreviewInfo.js +14 -16
  4. package/dist/components/ChannelPreview/utils.js +9 -20
  5. package/dist/components/ChannelSearch/hooks/useChannelSearch.js +2 -3
  6. package/dist/components/ChatView/ChatView.js +2 -1
  7. package/dist/components/Dialog/DialogAnchor.d.ts +25 -0
  8. package/dist/components/Dialog/DialogAnchor.js +68 -0
  9. package/dist/components/Dialog/DialogManager.d.ts +43 -0
  10. package/dist/components/Dialog/DialogManager.js +98 -0
  11. package/dist/components/Dialog/DialogPortal.d.ts +7 -0
  12. package/dist/components/Dialog/DialogPortal.js +25 -0
  13. package/dist/components/Dialog/hooks/index.d.ts +1 -0
  14. package/dist/components/Dialog/hooks/index.js +1 -0
  15. package/dist/components/Dialog/hooks/useDialog.d.ts +4 -0
  16. package/dist/components/Dialog/hooks/useDialog.js +26 -0
  17. package/dist/components/Dialog/index.d.ts +4 -0
  18. package/dist/components/Dialog/index.js +4 -0
  19. package/dist/components/Message/Message.js +3 -5
  20. package/dist/components/Message/MessageOptions.d.ts +1 -2
  21. package/dist/components/Message/MessageOptions.js +13 -9
  22. package/dist/components/Message/MessageSimple.js +5 -14
  23. package/dist/components/Message/hooks/useReactionHandler.d.ts +1 -7
  24. package/dist/components/Message/hooks/useReactionHandler.js +1 -63
  25. package/dist/components/Message/utils.js +3 -0
  26. package/dist/components/MessageActions/MessageActions.d.ts +1 -2
  27. package/dist/components/MessageActions/MessageActions.js +13 -47
  28. package/dist/components/MessageActions/MessageActionsBox.d.ts +1 -1
  29. package/dist/components/MessageActions/MessageActionsBox.js +6 -6
  30. package/dist/components/MessageInput/hooks/useUserTrigger.js +0 -1
  31. package/dist/components/MessageList/MessageList.js +7 -5
  32. package/dist/components/MessageList/VirtualizedMessageList.js +39 -37
  33. package/dist/components/Reactions/ReactionSelector.d.ts +1 -1
  34. package/dist/components/Reactions/ReactionSelector.js +33 -24
  35. package/dist/components/Reactions/ReactionSelectorWithButton.d.ts +13 -0
  36. package/dist/components/Reactions/ReactionSelectorWithButton.js +22 -0
  37. package/dist/components/Reactions/ReactionsList.d.ts +0 -3
  38. package/dist/components/Thread/Thread.js +2 -1
  39. package/dist/components/Threads/ThreadList/ThreadList.js +1 -1
  40. package/dist/components/Threads/ThreadList/ThreadListItemUI.js +1 -1
  41. package/dist/components/Threads/ThreadList/ThreadListLoadingIndicator.js +1 -1
  42. package/dist/components/Threads/ThreadList/ThreadListUnseenThreadsBanner.js +1 -1
  43. package/dist/components/Threads/hooks/useThreadManagerState.js +1 -1
  44. package/dist/components/Threads/hooks/useThreadState.js +1 -1
  45. package/dist/components/Threads/index.d.ts +0 -1
  46. package/dist/components/Threads/index.js +0 -1
  47. package/dist/components/index.d.ts +1 -0
  48. package/dist/components/index.js +1 -0
  49. package/dist/context/DialogManagerContext.d.ts +10 -0
  50. package/dist/context/DialogManagerContext.js +14 -0
  51. package/dist/context/MessageContext.d.ts +2 -10
  52. package/dist/context/index.d.ts +1 -0
  53. package/dist/context/index.js +1 -0
  54. package/dist/css/v2/index.css +1 -1
  55. package/dist/css/v2/index.layout.css +1 -1
  56. package/dist/index.browser.cjs +2164 -2004
  57. package/dist/index.browser.cjs.map +4 -4
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.js +1 -0
  60. package/dist/index.node.cjs +2087 -1918
  61. package/dist/index.node.cjs.map +4 -4
  62. package/dist/scss/v2/Dialog/Dialog-layout.scss +8 -0
  63. package/dist/scss/v2/Message/Message-layout.scss +8 -0
  64. package/dist/scss/v2/MessageReactions/MessageReactionsSelector-layout.scss +16 -0
  65. package/dist/scss/v2/ThreadList/ThreadList-layout.scss +4 -1
  66. package/dist/scss/v2/index.layout.scss +1 -0
  67. package/dist/store/hooks/index.d.ts +1 -0
  68. package/dist/store/hooks/index.js +1 -0
  69. package/dist/store/index.d.ts +1 -0
  70. package/dist/store/index.js +1 -0
  71. package/package.json +2 -2
  72. /package/dist/{components/Threads → store}/hooks/useStateStore.d.ts +0 -0
  73. /package/dist/{components/Threads → store}/hooks/useStateStore.js +0 -0
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useRef, useState } from 'react';
1
+ import { useCallback } from 'react';
2
2
  import throttle from 'lodash.throttle';
3
3
  import { useChannelActionContext } from '../../../context/ChannelActionContext';
4
4
  import { useChannelStateContext } from '../../../context/ChannelStateContext';
@@ -114,65 +114,3 @@ export const useReactionHandler = (message) => {
114
114
  }
115
115
  };
116
116
  };
117
- export const useReactionClick = (message, reactionSelectorRef, messageWrapperRef, closeReactionSelectorOnClick) => {
118
- const { channelCapabilities = {} } = useChannelStateContext('useReactionClick');
119
- const [showDetailedReactions, setShowDetailedReactions] = useState(false);
120
- const hasListener = useRef(false);
121
- const isReactionEnabled = channelCapabilities['send-reaction'];
122
- const messageDeleted = !!message?.deleted_at;
123
- const closeDetailedReactions = useCallback((event) => {
124
- if (event.target instanceof HTMLElement &&
125
- reactionSelectorRef?.current?.contains(event.target) &&
126
- !closeReactionSelectorOnClick) {
127
- return;
128
- }
129
- setShowDetailedReactions(false);
130
- },
131
- // eslint-disable-next-line react-hooks/exhaustive-deps
132
- [setShowDetailedReactions, reactionSelectorRef]);
133
- useEffect(() => {
134
- const messageWrapper = messageWrapperRef?.current;
135
- if (showDetailedReactions && !hasListener.current) {
136
- hasListener.current = true;
137
- document.addEventListener('click', closeDetailedReactions);
138
- if (messageWrapper) {
139
- messageWrapper.addEventListener('mouseleave', closeDetailedReactions);
140
- }
141
- }
142
- if (!showDetailedReactions && hasListener.current) {
143
- document.removeEventListener('click', closeDetailedReactions);
144
- if (messageWrapper) {
145
- messageWrapper.removeEventListener('mouseleave', closeDetailedReactions);
146
- }
147
- hasListener.current = false;
148
- }
149
- return () => {
150
- if (hasListener.current) {
151
- document.removeEventListener('click', closeDetailedReactions);
152
- if (messageWrapper) {
153
- messageWrapper.removeEventListener('mouseleave', closeDetailedReactions);
154
- }
155
- hasListener.current = false;
156
- }
157
- };
158
- }, [showDetailedReactions, closeDetailedReactions, messageWrapperRef]);
159
- useEffect(() => {
160
- const messageWrapper = messageWrapperRef?.current;
161
- if (messageDeleted && hasListener.current) {
162
- document.removeEventListener('click', closeDetailedReactions);
163
- if (messageWrapper) {
164
- messageWrapper.removeEventListener('mouseleave', closeDetailedReactions);
165
- }
166
- hasListener.current = false;
167
- }
168
- }, [messageDeleted, closeDetailedReactions, messageWrapperRef]);
169
- const onReactionListClick = (event) => {
170
- event?.stopPropagation?.();
171
- setShowDetailedReactions((prev) => !prev);
172
- };
173
- return {
174
- isReactionEnabled,
175
- onReactionListClick,
176
- showDetailedReactions,
177
- };
178
- };
@@ -194,6 +194,9 @@ export const areMessagePropsEqual = (prevProps, nextProps) => {
194
194
  if (nextProps.showDetailedReactions !== prevProps.showDetailedReactions) {
195
195
  return false;
196
196
  }
197
+ if (nextProps.closeReactionSelectorOnClick !== prevProps.closeReactionSelectorOnClick) {
198
+ return false;
199
+ }
197
200
  const messagesAreEqual = areMessagesEqual(prevMessage, nextMessage);
198
201
  if (!messagesAreEqual)
199
202
  return false;
@@ -6,13 +6,12 @@ export type MessageActionsProps<StreamChatGenerics extends DefaultStreamChatGene
6
6
  ActionsIcon?: React.ComponentType<IconProps>;
7
7
  customWrapperClass?: string;
8
8
  inline?: boolean;
9
- messageWrapperRef?: React.RefObject<HTMLDivElement>;
10
9
  mine?: () => boolean;
11
10
  };
12
11
  export declare const MessageActions: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(props: MessageActionsProps<StreamChatGenerics>) => React.JSX.Element | null;
13
12
  export type MessageActionsWrapperProps = {
14
- setActionsBoxOpen: React.Dispatch<React.SetStateAction<boolean>>;
15
13
  customWrapperClass?: string;
16
14
  inline?: boolean;
15
+ toggleOpen?: () => void;
17
16
  };
18
17
  export {};
@@ -1,14 +1,14 @@
1
- import React, { useCallback, useEffect, useRef, useState, } from 'react';
2
1
  import clsx from 'clsx';
2
+ import React, { useCallback, useRef } from 'react';
3
3
  import { MessageActionsBox } from './MessageActionsBox';
4
+ import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
4
5
  import { ActionsIcon as DefaultActionsIcon } from '../Message/icons';
5
6
  import { isUserMuted, shouldRenderMessageActions } from '../Message/utils';
6
7
  import { useChatContext } from '../../context/ChatContext';
7
8
  import { useMessageContext } from '../../context/MessageContext';
8
- import { useMessageActionsBoxPopper } from './hooks';
9
9
  import { useComponentContext, useTranslationContext } from '../../context';
10
10
  export const MessageActions = (props) => {
11
- const { ActionsIcon = DefaultActionsIcon, customWrapperClass = '', getMessageActions: propGetMessageActions, handleDelete: propHandleDelete, handleFlag: propHandleFlag, handleMarkUnread: propHandleMarkUnread, handleMute: propHandleMute, handlePin: propHandlePin, inline, message: propMessage, messageWrapperRef, mine, } = props;
11
+ const { ActionsIcon = DefaultActionsIcon, customWrapperClass = '', getMessageActions: propGetMessageActions, handleDelete: propHandleDelete, handleFlag: propHandleFlag, handleMarkUnread: propHandleMarkUnread, handleMute: propHandleMute, handlePin: propHandlePin, inline, message: propMessage, mine, } = props;
12
12
  const { mutes } = useChatContext('MessageActions');
13
13
  const { customMessageActions, getMessageActions: contextGetMessageActions, handleDelete: contextHandleDelete, handleFlag: contextHandleFlag, handleMarkUnread: contextHandleMarkUnread, handleMute: contextHandleMute, handlePin: contextHandlePin, isMyMessage, message: contextMessage, setEditingState, threadList, } = useMessageContext('MessageActions');
14
14
  const { CustomMessageActionsList } = useComponentContext('MessageActions');
@@ -21,8 +21,10 @@ export const MessageActions = (props) => {
21
21
  const handlePin = propHandlePin || contextHandlePin;
22
22
  const message = propMessage || contextMessage;
23
23
  const isMine = mine ? mine() : isMyMessage();
24
- const [actionsBoxOpen, setActionsBoxOpen] = useState(false);
25
24
  const isMuted = useCallback(() => isUserMuted(message, mutes), [message, mutes]);
25
+ const dialogId = `message-actions--${message.id}`;
26
+ const dialog = useDialog({ id: dialogId });
27
+ const dialogIsOpen = useDialogIsOpen(dialogId);
26
28
  const messageActions = getMessageActions();
27
29
  const renderMessageActions = shouldRenderMessageActions({
28
30
  customMessageActions,
@@ -30,58 +32,22 @@ export const MessageActions = (props) => {
30
32
  inThread: threadList,
31
33
  messageActions,
32
34
  });
33
- const hideOptions = useCallback((event) => {
34
- if (event instanceof KeyboardEvent && event.key !== 'Escape') {
35
- return;
36
- }
37
- setActionsBoxOpen(false);
38
- }, []);
39
- const messageDeletedAt = !!message?.deleted_at;
40
- useEffect(() => {
41
- if (messageWrapperRef?.current) {
42
- messageWrapperRef.current.addEventListener('mouseleave', hideOptions);
43
- }
44
- }, [hideOptions, messageWrapperRef]);
45
- useEffect(() => {
46
- if (messageDeletedAt) {
47
- document.removeEventListener('click', hideOptions);
48
- }
49
- }, [hideOptions, messageDeletedAt]);
50
- useEffect(() => {
51
- if (!actionsBoxOpen)
52
- return;
53
- document.addEventListener('click', hideOptions);
54
- document.addEventListener('keyup', hideOptions);
55
- return () => {
56
- document.removeEventListener('click', hideOptions);
57
- document.removeEventListener('keyup', hideOptions);
58
- };
59
- }, [actionsBoxOpen, hideOptions]);
60
35
  const actionsBoxButtonRef = useRef(null);
61
- const { attributes, popperElementRef, styles } = useMessageActionsBoxPopper({
62
- open: actionsBoxOpen,
63
- placement: isMine ? 'top-end' : 'top-start',
64
- referenceElement: actionsBoxButtonRef.current,
65
- });
66
36
  if (!renderMessageActions)
67
37
  return null;
68
- return (React.createElement(MessageActionsWrapper, { customWrapperClass: customWrapperClass, inline: inline, setActionsBoxOpen: setActionsBoxOpen },
69
- React.createElement(MessageActionsBox, { ...attributes.popper, getMessageActions: getMessageActions, handleDelete: handleDelete, handleEdit: setEditingState, handleFlag: handleFlag, handleMarkUnread: handleMarkUnread, handleMute: handleMute, handlePin: handlePin, isUserMuted: isMuted, mine: isMine, open: actionsBoxOpen, ref: popperElementRef, style: styles.popper }),
70
- React.createElement("button", { "aria-expanded": actionsBoxOpen, "aria-haspopup": 'true', "aria-label": t('aria/Open Message Actions Menu'), className: 'str-chat__message-actions-box-button', ref: actionsBoxButtonRef },
38
+ return (React.createElement(MessageActionsWrapper, { customWrapperClass: customWrapperClass, inline: inline, toggleOpen: dialog?.toggle },
39
+ React.createElement(DialogAnchor, { id: dialogId, placement: isMine ? 'top-end' : 'top-start', referenceElement: actionsBoxButtonRef.current, trapFocus: true },
40
+ React.createElement(MessageActionsBox, { getMessageActions: getMessageActions, handleDelete: handleDelete, handleEdit: setEditingState, handleFlag: handleFlag, handleMarkUnread: handleMarkUnread, handleMute: handleMute, handlePin: handlePin, isUserMuted: isMuted, mine: isMine, open: dialogIsOpen })),
41
+ React.createElement("button", { "aria-expanded": dialogIsOpen, "aria-haspopup": 'true', "aria-label": t('aria/Open Message Actions Menu'), className: 'str-chat__message-actions-box-button', "data-testid": 'message-actions-toggle-button', ref: actionsBoxButtonRef },
71
42
  React.createElement(ActionsIcon, { className: 'str-chat__message-action-icon' }))));
72
43
  };
73
44
  const MessageActionsWrapper = (props) => {
74
- const { children, customWrapperClass, inline, setActionsBoxOpen } = props;
45
+ const { children, customWrapperClass, inline, toggleOpen } = props;
75
46
  const defaultWrapperClass = clsx('str-chat__message-simple__actions__action', 'str-chat__message-simple__actions__action--options', 'str-chat__message-actions-container');
76
- const wrapperClass = customWrapperClass || defaultWrapperClass;
77
- const onClickOptionsAction = (event) => {
78
- event.stopPropagation();
79
- setActionsBoxOpen((prev) => !prev);
80
- };
81
47
  const wrapperProps = {
82
- className: wrapperClass,
48
+ className: customWrapperClass || defaultWrapperClass,
83
49
  'data-testid': 'message-actions',
84
- onClick: onClickOptionsAction,
50
+ onClick: toggleOpen,
85
51
  };
86
52
  if (inline)
87
53
  return React.createElement("span", { ...wrapperProps }, children);
@@ -10,5 +10,5 @@ export type MessageActionsBoxProps<StreamChatGenerics extends DefaultStreamChatG
10
10
  /**
11
11
  * A popup box that displays the available actions on a message, such as edit, delete, pin, etc.
12
12
  */
13
- export declare const MessageActionsBox: React.ForwardRefExoticComponent<Omit<MessageActionsBoxProps<DefaultStreamChatGenerics>, "ref"> & React.RefAttributes<HTMLDivElement | null>>;
13
+ export declare const MessageActionsBox: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(props: MessageActionsBoxProps<StreamChatGenerics>) => React.JSX.Element;
14
14
  export {};
@@ -1,10 +1,10 @@
1
- import React from 'react';
2
1
  import clsx from 'clsx';
2
+ import React from 'react';
3
3
  import { MESSAGE_ACTIONS } from '../Message/utils';
4
4
  import { useChannelActionContext, useComponentContext, useMessageContext, useTranslationContext, } from '../../context';
5
5
  import { CustomMessageActionsList as DefaultCustomMessageActionsList } from './CustomMessageActionsList';
6
- const UnMemoizedMessageActionsBox = React.forwardRef((props, ref) => {
7
- const { getMessageActions, handleDelete, handleEdit, handleFlag, handleMarkUnread, handleMute, handlePin, isUserMuted, mine, open = false, ...restDivProps } = props;
6
+ const UnMemoizedMessageActionsBox = (props) => {
7
+ const { className, getMessageActions, handleDelete, handleEdit, handleFlag, handleMarkUnread, handleMute, handlePin, isUserMuted, mine, open, ...restDivProps } = props;
8
8
  const { CustomMessageActionsList = DefaultCustomMessageActionsList, } = useComponentContext('MessageActionsBox');
9
9
  const { setQuotedMessage } = useChannelActionContext('MessageActionsBox');
10
10
  const { customMessageActions, message, threadList } = useMessageContext('MessageActionsBox');
@@ -20,11 +20,11 @@ const UnMemoizedMessageActionsBox = React.forwardRef((props, ref) => {
20
20
  textarea.focus();
21
21
  }
22
22
  };
23
- const rootClassName = clsx('str-chat__message-actions-box', {
23
+ const rootClassName = clsx('str-chat__message-actions-box', className, {
24
24
  'str-chat__message-actions-box--open': open,
25
25
  });
26
26
  const buttonClassName = 'str-chat__message-actions-list-item str-chat__message-actions-list-item-button';
27
- return (React.createElement("div", { ...restDivProps, className: rootClassName, "data-testid": 'message-actions-box', ref: ref },
27
+ return (React.createElement("div", { ...restDivProps, className: rootClassName, "data-testid": 'message-actions-box' },
28
28
  React.createElement("div", { "aria-label": t('aria/Message Options'), className: 'str-chat__message-actions-list', role: 'listbox' },
29
29
  React.createElement(CustomMessageActionsList, { customMessageActions: customMessageActions, message: message }),
30
30
  messageActions.indexOf(MESSAGE_ACTIONS.quote) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleQuote, role: 'option' }, t('Reply'))),
@@ -34,7 +34,7 @@ const UnMemoizedMessageActionsBox = React.forwardRef((props, ref) => {
34
34
  messageActions.indexOf(MESSAGE_ACTIONS.mute) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleMute, role: 'option' }, isUserMuted() ? t('Unmute') : t('Mute'))),
35
35
  messageActions.indexOf(MESSAGE_ACTIONS.edit) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleEdit, role: 'option' }, t('Edit Message'))),
36
36
  messageActions.indexOf(MESSAGE_ACTIONS.delete) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleDelete, role: 'option' }, t('Delete'))))));
37
- });
37
+ };
38
38
  /**
39
39
  * A popup box that displays the available actions on a message, such as edit, delete, pin, etc.
40
40
  */
@@ -52,7 +52,6 @@ export const useUserTrigger = (params) => {
52
52
  // @ts-expect-error
53
53
  {
54
54
  $or: [{ id: { $autocomplete: query } }, { name: { $autocomplete: query } }],
55
- id: { $ne: client.userID },
56
55
  ...(typeof mentionQueryParams.filters === 'function'
57
56
  ? mentionQueryParams.filters(query)
58
57
  : mentionQueryParams.filters),
@@ -7,6 +7,7 @@ import { MessageListNotifications as DefaultMessageListNotifications } from './M
7
7
  import { UnreadMessagesNotification as DefaultUnreadMessagesNotification } from './UnreadMessagesNotification';
8
8
  import { useChannelActionContext, } from '../../context/ChannelActionContext';
9
9
  import { useChannelStateContext, } from '../../context/ChannelStateContext';
10
+ import { DialogManagerProvider } from '../../context';
10
11
  import { useChatContext } from '../../context/ChatContext';
11
12
  import { useComponentContext } from '../../context/ComponentContext';
12
13
  import { MessageListContextProvider } from '../../context/MessageListContext';
@@ -126,11 +127,12 @@ const MessageListWithContext = (props) => {
126
127
  const showEmptyStateIndicator = elements.length === 0 && !threadList;
127
128
  return (React.createElement(MessageListContextProvider, { value: { listElement, scrollToBottom } },
128
129
  React.createElement(MessageListMainPanel, null,
129
- !threadList && showUnreadMessagesNotification && (React.createElement(UnreadMessagesNotification, { unreadCount: channelUnreadUiState?.unread_messages })),
130
- React.createElement("div", { className: clsx(messageListClass, customClasses?.threadList), onScroll: onScroll, ref: setListElement, tabIndex: 0 }, showEmptyStateIndicator ? (React.createElement(EmptyStateIndicator, { listType: threadList ? 'thread' : 'message' })) : (React.createElement(InfiniteScroll, { className: 'str-chat__message-list-scroll', "data-testid": 'reverse-infinite-scroll', hasNextPage: props.hasMoreNewer, hasPreviousPage: props.hasMore, head: props.head, isLoading: props.loadingMore, loader: React.createElement("div", { className: 'str-chat__list__loading', key: 'loading-indicator' }, props.loadingMore && React.createElement(LoadingIndicator, { size: 20 })), loadNextPage: loadMoreNewer, loadPreviousPage: loadMore, threshold: loadMoreScrollThreshold, ...restInternalInfiniteScrollProps },
131
- React.createElement("ul", { className: 'str-chat__ul', ref: setUlElement }, elements),
132
- React.createElement(TypingIndicator, { threadList: threadList }),
133
- React.createElement("div", { key: 'bottom' }))))),
130
+ React.createElement(DialogManagerProvider, { id: 'message-list-dialog-manager' },
131
+ !threadList && showUnreadMessagesNotification && (React.createElement(UnreadMessagesNotification, { unreadCount: channelUnreadUiState?.unread_messages })),
132
+ React.createElement("div", { className: clsx(messageListClass, customClasses?.threadList), onScroll: onScroll, ref: setListElement, tabIndex: 0 }, showEmptyStateIndicator ? (React.createElement(EmptyStateIndicator, { listType: threadList ? 'thread' : 'message' })) : (React.createElement(InfiniteScroll, { className: 'str-chat__message-list-scroll', "data-testid": 'reverse-infinite-scroll', hasNextPage: props.hasMoreNewer, hasPreviousPage: props.hasMore, head: props.head, isLoading: props.loadingMore, loader: React.createElement("div", { className: 'str-chat__list__loading', key: 'loading-indicator' }, props.loadingMore && React.createElement(LoadingIndicator, { size: 20 })), loadNextPage: loadMoreNewer, loadPreviousPage: loadMore, threshold: loadMoreScrollThreshold, ...restInternalInfiniteScrollProps },
133
+ React.createElement("ul", { className: 'str-chat__ul', ref: setUlElement }, elements),
134
+ React.createElement(TypingIndicator, { threadList: threadList }),
135
+ React.createElement("div", { key: 'bottom' })))))),
134
136
  React.createElement(MessageListNotifications, { hasNewMessages: hasNewMessages, isMessageListScrolledToBottom: isMessageListScrolledToBottom, isNotAtLatestMessageSet: hasMoreNewer, MessageNotification: MessageNotification, notifications: notifications, scrollToBottom: scrollToBottomFromNotification, threadList: threadList, unreadCount: threadList ? undefined : channelUnreadUiState?.unread_messages })));
135
137
  };
136
138
  /**
@@ -14,6 +14,7 @@ import { calculateFirstItemIndex, calculateItemIndex, EmptyPlaceholder, Header,
14
14
  import { UnreadMessagesSeparator as DefaultUnreadMessagesSeparator } from '../MessageList';
15
15
  import { DateSeparator as DefaultDateSeparator } from '../DateSeparator';
16
16
  import { EventComponent as DefaultMessageSystem } from '../EventComponent';
17
+ import { DialogManagerProvider } from '../../context';
17
18
  import { useChannelActionContext, } from '../../context/ChannelActionContext';
18
19
  import { useChannelStateContext, } from '../../context/ChannelStateContext';
19
20
  import { useChatContext } from '../../context/ChatContext';
@@ -190,43 +191,44 @@ const VirtualizedMessageListWithContext = (props) => {
190
191
  return null;
191
192
  return (React.createElement(React.Fragment, null,
192
193
  React.createElement(MessageListMainPanel, null,
193
- !threadList && showUnreadMessagesNotification && (React.createElement(UnreadMessagesNotification, { unreadCount: channelUnreadUiState?.unread_messages })),
194
- React.createElement("div", { className: customClasses?.virtualizedMessageList || 'str-chat__virtual-list' },
195
- React.createElement(Virtuoso, { atBottomStateChange: atBottomStateChange, atBottomThreshold: 100, atTopStateChange: atTopStateChange, atTopThreshold: 100, className: 'str-chat__message-list-scroll', components: {
196
- EmptyPlaceholder,
197
- Header,
198
- Item,
199
- ...virtuosoComponentsFromProps,
200
- }, computeItemKey: computeItemKey, context: {
201
- additionalMessageInputProps,
202
- closeReactionSelectorOnClick,
203
- customClasses,
204
- customMessageActions,
205
- customMessageRenderer,
206
- DateSeparator,
207
- firstUnreadMessageId: channelUnreadUiState?.first_unread_message_id,
208
- formatDate,
209
- head,
210
- lastReadDate: channelUnreadUiState?.last_read,
211
- lastReadMessageId: channelUnreadUiState?.last_read_message_id,
212
- lastReceivedMessageId,
213
- loadingMore,
214
- Message: MessageUIComponent,
215
- messageActions,
216
- messageGroupStyles,
217
- MessageSystem,
218
- numItemsPrepended,
219
- ownMessagesReadByOthers,
220
- processedMessages,
221
- reactionDetailsSort,
222
- shouldGroupByUser,
223
- sortReactionDetails,
224
- sortReactions,
225
- threadList,
226
- unreadMessageCount: channelUnreadUiState?.unread_messages,
227
- UnreadMessagesSeparator,
228
- virtuosoRef: virtuoso,
229
- }, firstItemIndex: calculateFirstItemIndex(numItemsPrepended), followOutput: followOutput, increaseViewportBy: { bottom: 200, top: 0 }, initialTopMostItemIndex: calculateInitialTopMostItemIndex(processedMessages, highlightedMessageId), itemContent: messageRenderer, itemSize: fractionalItemSize, itemsRendered: handleItemsRendered, key: messageSetKey, overscan: overscan, ref: virtuoso, style: { overflowX: 'hidden' }, totalCount: processedMessages.length, ...overridingVirtuosoProps, ...(scrollSeekPlaceHolder ? { scrollSeek: scrollSeekPlaceHolder } : {}), ...(defaultItemHeight ? { defaultItemHeight } : {}) })),
194
+ React.createElement(DialogManagerProvider, { id: 'virtualized-message-list-dialog-manager' },
195
+ !threadList && showUnreadMessagesNotification && (React.createElement(UnreadMessagesNotification, { unreadCount: channelUnreadUiState?.unread_messages })),
196
+ React.createElement("div", { className: customClasses?.virtualizedMessageList || 'str-chat__virtual-list' },
197
+ React.createElement(Virtuoso, { atBottomStateChange: atBottomStateChange, atBottomThreshold: 100, atTopStateChange: atTopStateChange, atTopThreshold: 100, className: 'str-chat__message-list-scroll', components: {
198
+ EmptyPlaceholder,
199
+ Header,
200
+ Item,
201
+ ...virtuosoComponentsFromProps,
202
+ }, computeItemKey: computeItemKey, context: {
203
+ additionalMessageInputProps,
204
+ closeReactionSelectorOnClick,
205
+ customClasses,
206
+ customMessageActions,
207
+ customMessageRenderer,
208
+ DateSeparator,
209
+ firstUnreadMessageId: channelUnreadUiState?.first_unread_message_id,
210
+ formatDate,
211
+ head,
212
+ lastReadDate: channelUnreadUiState?.last_read,
213
+ lastReadMessageId: channelUnreadUiState?.last_read_message_id,
214
+ lastReceivedMessageId,
215
+ loadingMore,
216
+ Message: MessageUIComponent,
217
+ messageActions,
218
+ messageGroupStyles,
219
+ MessageSystem,
220
+ numItemsPrepended,
221
+ ownMessagesReadByOthers,
222
+ processedMessages,
223
+ reactionDetailsSort,
224
+ shouldGroupByUser,
225
+ sortReactionDetails,
226
+ sortReactions,
227
+ threadList,
228
+ unreadMessageCount: channelUnreadUiState?.unread_messages,
229
+ UnreadMessagesSeparator,
230
+ virtuosoRef: virtuoso,
231
+ }, firstItemIndex: calculateFirstItemIndex(numItemsPrepended), followOutput: followOutput, increaseViewportBy: { bottom: 200, top: 0 }, initialTopMostItemIndex: calculateInitialTopMostItemIndex(processedMessages, highlightedMessageId), itemContent: messageRenderer, itemSize: fractionalItemSize, itemsRendered: handleItemsRendered, key: messageSetKey, overscan: overscan, ref: virtuoso, style: { overflowX: 'hidden' }, totalCount: processedMessages.length, ...overridingVirtuosoProps, ...(scrollSeekPlaceHolder ? { scrollSeek: scrollSeekPlaceHolder } : {}), ...(defaultItemHeight ? { defaultItemHeight } : {}) }))),
230
232
  TypingIndicator && React.createElement(TypingIndicator, null)),
231
233
  React.createElement(MessageListNotifications, { hasNewMessages: newMessagesNotification, isMessageListScrolledToBottom: isMessageListScrolledToBottom, isNotAtLatestMessageSet: hasMoreNewer, MessageNotification: MessageNotification, notifications: notifications, scrollToBottom: scrollToBottom, threadList: threadList, unreadCount: threadList ? undefined : channelUnreadUiState?.unread_messages }),
232
234
  giphyPreviewMessage && React.createElement(GiphyPreviewMessage, { message: giphyPreviewMessage })));
@@ -32,4 +32,4 @@ export type ReactionSelectorProps<StreamChatGenerics extends DefaultStreamChatGe
32
32
  /**
33
33
  * Component that allows a user to select a reaction.
34
34
  */
35
- export declare const ReactionSelector: React.ForwardRefExoticComponent<ReactionSelectorProps<DefaultStreamChatGenerics> & React.RefAttributes<HTMLDivElement | null>>;
35
+ export declare const ReactionSelector: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(props: ReactionSelectorProps<StreamChatGenerics>) => React.JSX.Element;
@@ -1,14 +1,17 @@
1
1
  import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import clsx from 'clsx';
3
- import { isMutableRef } from './utils/utils';
4
3
  import { Avatar as DefaultAvatar } from '../Avatar';
4
+ import { useDialog } from '../Dialog';
5
+ import { defaultReactionOptions } from './reactionOptions';
6
+ import { isMutableRef } from './utils/utils';
5
7
  import { useComponentContext } from '../../context/ComponentContext';
6
8
  import { useMessageContext } from '../../context/MessageContext';
7
- import { defaultReactionOptions } from './reactionOptions';
8
- const UnMemoizedReactionSelector = React.forwardRef((props, ref) => {
9
+ const UnMemoizedReactionSelector = (props) => {
9
10
  const { Avatar: propAvatar, detailedView = true, handleReaction: propHandleReaction, latest_reactions: propLatestReactions, own_reactions: propOwnReactions, reaction_groups: propReactionGroups, reactionOptions: propReactionOptions, reverse = false, } = props;
10
11
  const { Avatar: contextAvatar, reactionOptions: contextReactionOptions = defaultReactionOptions, } = useComponentContext('ReactionSelector');
11
- const { handleReaction: contextHandleReaction, message, } = useMessageContext('ReactionSelector');
12
+ const { closeReactionSelectorOnClick, handleReaction: contextHandleReaction, message, } = useMessageContext('ReactionSelector');
13
+ const dialogId = `reaction-selector--${message.id}`;
14
+ const dialog = useDialog({ id: dialogId });
12
15
  const reactionOptions = propReactionOptions ?? contextReactionOptions;
13
16
  const Avatar = propAvatar || contextAvatar || DefaultAvatar;
14
17
  const handleReaction = propHandleReaction || contextHandleReaction;
@@ -17,6 +20,7 @@ const UnMemoizedReactionSelector = React.forwardRef((props, ref) => {
17
20
  const reactionGroups = propReactionGroups || message?.reaction_groups || {};
18
21
  const [tooltipReactionType, setTooltipReactionType] = useState(null);
19
22
  const [tooltipPositions, setTooltipPositions] = useState(null);
23
+ const rootRef = useRef(null);
20
24
  const targetRef = useRef(null);
21
25
  const tooltipRef = useRef(null);
22
26
  const showTooltip = useCallback((event, reactionType) => {
@@ -28,22 +32,22 @@ const UnMemoizedReactionSelector = React.forwardRef((props, ref) => {
28
32
  setTooltipPositions(null);
29
33
  }, []);
30
34
  useEffect(() => {
31
- if (tooltipReactionType) {
32
- const tooltip = tooltipRef.current?.getBoundingClientRect();
33
- const target = targetRef.current?.getBoundingClientRect();
34
- const container = isMutableRef(ref) ? ref.current?.getBoundingClientRect() : null;
35
- if (!tooltip || !target || !container)
36
- return;
37
- const tooltipPosition = tooltip.width === container.width || tooltip.x < container.x
38
- ? 0
39
- : target.left + target.width / 2 - container.left - tooltip.width / 2;
40
- const arrowPosition = target.x - tooltip.x + target.width / 2 - tooltipPosition;
41
- setTooltipPositions({
42
- arrow: arrowPosition,
43
- tooltip: tooltipPosition,
44
- });
45
- }
46
- }, [tooltipReactionType, ref]);
35
+ if (!tooltipReactionType || !rootRef.current)
36
+ return;
37
+ const tooltip = tooltipRef.current?.getBoundingClientRect();
38
+ const target = targetRef.current?.getBoundingClientRect();
39
+ const container = isMutableRef(rootRef) ? rootRef.current?.getBoundingClientRect() : null;
40
+ if (!tooltip || !target || !container)
41
+ return;
42
+ const tooltipPosition = tooltip.width === container.width || tooltip.x < container.x
43
+ ? 0
44
+ : target.left + target.width / 2 - container.left - tooltip.width / 2;
45
+ const arrowPosition = target.x - tooltip.x + target.width / 2 - tooltipPosition;
46
+ setTooltipPositions({
47
+ arrow: arrowPosition,
48
+ tooltip: tooltipPosition,
49
+ });
50
+ }, [tooltipReactionType, rootRef]);
47
51
  const getUsersPerReactionType = (type) => latestReactions
48
52
  .map((reaction) => {
49
53
  if (reaction.type === type) {
@@ -55,9 +59,9 @@ const UnMemoizedReactionSelector = React.forwardRef((props, ref) => {
55
59
  const iHaveReactedWithReaction = (reactionType) => ownReactions.find((reaction) => reaction.type === reactionType);
56
60
  const getLatestUserForReactionType = (type) => latestReactions.find((reaction) => reaction.type === type && !!reaction.user)?.user ||
57
61
  undefined;
58
- return (React.createElement("div", { className: clsx('str-chat__reaction-selector str-chat__message-reaction-selector', {
62
+ return (React.createElement("div", { className: clsx('str-chat__reaction-selector str-chat__message-reaction-selector str-chat-react__message-reaction-selector', {
59
63
  'str-chat__reaction-selector--reverse': reverse,
60
- }), "data-testid": 'reaction-selector', ref: ref },
64
+ }), "data-testid": 'reaction-selector', ref: rootRef },
61
65
  !!tooltipReactionType && detailedView && (React.createElement("div", { className: 'str-chat__reaction-selector-tooltip', ref: tooltipRef, style: {
62
66
  left: tooltipPositions?.tooltip,
63
67
  visibility: tooltipPositions ? 'visible' : 'hidden',
@@ -70,13 +74,18 @@ const UnMemoizedReactionSelector = React.forwardRef((props, ref) => {
70
74
  return (React.createElement("li", { key: reactionType },
71
75
  React.createElement("button", { "aria-label": `Select Reaction: ${reactionName || reactionType}`, className: clsx('str-chat__message-reactions-list-item str-chat__message-reactions-option', {
72
76
  'str-chat__message-reactions-option-selected': iHaveReactedWithReaction(reactionType),
73
- }), "data-text": reactionType, onClick: (event) => handleReaction(reactionType, event) },
77
+ }), "data-testid": 'select-reaction-button', "data-text": reactionType, onClick: (event) => {
78
+ handleReaction(reactionType, event);
79
+ if (closeReactionSelectorOnClick) {
80
+ dialog.close();
81
+ }
82
+ } },
74
83
  !!count && detailedView && (React.createElement("div", { className: 'latest-user str-chat__message-reactions-last-user', onClick: hideTooltip, onMouseEnter: (e) => showTooltip(e, reactionType), onMouseLeave: hideTooltip }, latestUser ? (React.createElement(Avatar, { image: latestUser.image, name: latestUser.name, size: 20, user: latestUser })) : (React.createElement("div", { className: 'latest-user-not-found' })))),
75
84
  React.createElement("span", { className: 'str-chat__message-reaction-emoji' },
76
85
  React.createElement(Component, null)),
77
86
  Boolean(count) && detailedView && (React.createElement("span", { className: 'str-chat__message-reactions-list-item__count' }, count || '')))));
78
87
  }))));
79
- });
88
+ };
80
89
  /**
81
90
  * Component that allows a user to select a reaction.
82
91
  */
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { DefaultStreamChatGenerics } from '../../types';
3
+ import type { IconProps } from '../../types/types';
4
+ type ReactionSelectorWithButtonProps = {
5
+ ReactionIcon: React.ComponentType<IconProps>;
6
+ theme: string;
7
+ };
8
+ /**
9
+ * Internal convenience component - not to be exported. It just groups the button and the dialog anchor and thus prevents
10
+ * cluttering the parent component.
11
+ */
12
+ export declare const ReactionSelectorWithButton: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ ReactionIcon, theme, }: ReactionSelectorWithButtonProps) => React.JSX.Element;
13
+ export {};
@@ -0,0 +1,22 @@
1
+ import React, { useRef } from 'react';
2
+ import { ReactionSelector as DefaultReactionSelector } from './ReactionSelector';
3
+ import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
4
+ import { useComponentContext, useMessageContext, useTranslationContext } from '../../context';
5
+ /**
6
+ * Internal convenience component - not to be exported. It just groups the button and the dialog anchor and thus prevents
7
+ * cluttering the parent component.
8
+ */
9
+ export const ReactionSelectorWithButton = ({ ReactionIcon, theme, }) => {
10
+ const { t } = useTranslationContext('ReactionSelectorWithButton');
11
+ const { isMyMessage, message } = useMessageContext('MessageOptions');
12
+ const { ReactionSelector = DefaultReactionSelector } = useComponentContext('MessageOptions');
13
+ const buttonRef = useRef(null);
14
+ const dialogId = `reaction-selector--${message.id}`;
15
+ const dialog = useDialog({ id: dialogId });
16
+ const dialogIsOpen = useDialogIsOpen(dialogId);
17
+ return (React.createElement(React.Fragment, null,
18
+ React.createElement(DialogAnchor, { id: dialogId, placement: isMyMessage() ? 'top-end' : 'top-start', referenceElement: buttonRef.current, trapFocus: true },
19
+ React.createElement(ReactionSelector, null)),
20
+ React.createElement("button", { "aria-expanded": dialogIsOpen, "aria-label": t('aria/Open Reaction Selector'), className: `str-chat__message-${theme}__actions__action str-chat__message-${theme}__actions__action--reactions str-chat__message-reactions-button`, "data-testid": 'message-reaction-action', onClick: () => dialog?.toggle(), ref: buttonRef },
21
+ React.createElement(ReactionIcon, { className: 'str-chat__message-action-icon' }))));
22
+ };
@@ -1,13 +1,10 @@
1
1
  import React from 'react';
2
2
  import type { ReactionGroupResponse, ReactionResponse } from 'stream-chat';
3
- import type { ReactEventHandler } from '../Message/types';
4
3
  import type { DefaultStreamChatGenerics } from '../../types/types';
5
4
  import type { ReactionOptions } from './reactionOptions';
6
5
  import type { ReactionDetailsComparator, ReactionsComparator } from './types';
7
6
  import { MessageContextValue } from '../../context';
8
7
  export type ReactionsListProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = Partial<Pick<MessageContextValue<StreamChatGenerics>, 'handleFetchReactions' | 'reactionDetailsSort'>> & {
9
- /** Custom on click handler for an individual reaction, defaults to `onReactionListClick` from the `MessageContext` */
10
- onClick?: ReactEventHandler;
11
8
  /** An array of the own reaction objects to distinguish own reactions visually */
12
9
  own_reactions?: ReactionResponse<StreamChatGenerics>[];
13
10
  /**
@@ -6,7 +6,8 @@ import { MessageList, VirtualizedMessageList, } from '../MessageList';
6
6
  import { ThreadHeader as DefaultThreadHeader } from './ThreadHeader';
7
7
  import { ThreadHead as DefaultThreadHead } from '../Thread/ThreadHead';
8
8
  import { useChannelActionContext, useChannelStateContext, useChatContext, useComponentContext, } from '../../context';
9
- import { useStateStore, useThreadContext } from '../../components/Threads';
9
+ import { useThreadContext } from '../Threads';
10
+ import { useStateStore } from '../../store';
10
11
  /**
11
12
  * The Thread component renders a parent Message with a list of replies
12
13
  */
@@ -5,7 +5,7 @@ import { ThreadListEmptyPlaceholder as DefaultThreadListEmptyPlaceholder } from
5
5
  import { ThreadListUnseenThreadsBanner as DefaultThreadListUnseenThreadsBanner } from './ThreadListUnseenThreadsBanner';
6
6
  import { ThreadListLoadingIndicator as DefaultThreadListLoadingIndicator } from './ThreadListLoadingIndicator';
7
7
  import { useChatContext, useComponentContext } from '../../../context';
8
- import { useStateStore } from '../hooks/useStateStore';
8
+ import { useStateStore } from '../../../store';
9
9
  const selector = (nextValue) => [nextValue.threads];
10
10
  const computeItemKey = (_, item) => item.id;
11
11
  export const useThreadList = () => {
@@ -7,7 +7,7 @@ import { UnreadCountBadge } from '../UnreadCountBadge';
7
7
  import { useChatContext } from '../../../context';
8
8
  import { useThreadsViewContext } from '../../ChatView';
9
9
  import { useThreadListItemContext } from './ThreadListItem';
10
- import { useStateStore } from '../hooks/useStateStore';
10
+ import { useStateStore } from '../../../store';
11
11
  /**
12
12
  * TODO:
13
13
  * - maybe hover state? ask design
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { LoadingIndicator as DefaultLoadingIndicator } from '../../Loading';
3
3
  import { useChatContext, useComponentContext } from '../../../context';
4
- import { useStateStore } from '../hooks/useStateStore';
4
+ import { useStateStore } from '../../../store';
5
5
  const selector = (nextValue) => [nextValue.pagination.isLoadingNext];
6
6
  export const ThreadListLoadingIndicator = () => {
7
7
  const { LoadingIndicator = DefaultLoadingIndicator } = useComponentContext();
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Icon } from '../icons';
3
3
  import { useChatContext } from '../../../context';
4
- import { useStateStore } from '../hooks/useStateStore';
4
+ import { useStateStore } from '../../../store';
5
5
  const selector = (nextValue) => [nextValue.unseenThreadIds];
6
6
  export const ThreadListUnseenThreadsBanner = () => {
7
7
  const { client } = useChatContext();
@@ -1,5 +1,5 @@
1
1
  import { useChatContext } from 'context';
2
- import { useStateStore } from './useStateStore';
2
+ import { useStateStore } from '../../../store';
3
3
  export const useThreadManagerState = (selector) => {
4
4
  const { client } = useChatContext();
5
5
  return useStateStore(client.threads.state, selector);
@@ -1,6 +1,6 @@
1
- import { useStateStore } from './useStateStore';
2
1
  import { useThreadListItemContext } from '../ThreadList';
3
2
  import { useThreadContext } from '../ThreadContext';
3
+ import { useStateStore } from '../../../store/';
4
4
  /**
5
5
  * @description returns thread state, prioritizes `ThreadListItemContext` falls back to `ThreadContext` if not former is not present
6
6
  */
@@ -1,3 +1,2 @@
1
1
  export * from './ThreadContext';
2
2
  export * from './ThreadList';
3
- export * from './hooks/useStateStore';