stream-chat-react 13.12.0 → 13.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/components/Attachment/AttachmentActions.js +7 -2
  2. package/dist/components/Chat/hooks/useChat.js +1 -1
  3. package/dist/components/Message/ReminderNotification.js +2 -2
  4. package/dist/components/Message/hooks/useMuteHandler.js +2 -2
  5. package/dist/components/MessageInput/AttachmentPreviewList/AttachmentPreviewList.js +1 -1
  6. package/dist/components/MessageInput/AttachmentPreviewList/FileAttachmentPreview.d.ts +2 -1
  7. package/dist/components/MessageInput/AttachmentPreviewList/FileAttachmentPreview.js +3 -2
  8. package/dist/components/MessageInput/AttachmentPreviewList/ImageAttachmentPreview.js +1 -1
  9. package/dist/components/MessageInput/AttachmentPreviewList/VoiceRecordingPreview.js +1 -1
  10. package/dist/components/MessageInput/SendButton.js +3 -1
  11. package/dist/components/MessageList/MessageList.js +8 -5
  12. package/dist/components/MessageList/renderMessages.js +6 -6
  13. package/dist/components/Poll/PollCreationDialog/MultipleAnswersField.js +6 -2
  14. package/dist/components/Poll/PollCreationDialog/NameField.js +5 -2
  15. package/dist/components/Poll/PollCreationDialog/OptionFieldSet.js +6 -2
  16. package/dist/components/TextareaComposer/SuggestionList/CommandItem.js +21 -3
  17. package/dist/context/ComponentContext.d.ts +4 -0
  18. package/dist/context/MessageListContext.d.ts +3 -0
  19. package/dist/css/v2/index.css +1 -1
  20. package/dist/css/v2/index.layout.css +1 -1
  21. package/dist/experimental/Search/SearchResults/SearchResultsHeader.js +7 -2
  22. package/dist/experimental/Search/SearchResults/SearchSourceResultsLoadingIndicator.js +3 -1
  23. package/dist/experimental/index.browser.cjs +18 -2
  24. package/dist/experimental/index.browser.cjs.map +2 -2
  25. package/dist/experimental/index.node.cjs +18 -2
  26. package/dist/experimental/index.node.cjs.map +2 -2
  27. package/dist/i18n/Streami18n.d.ts +90 -78
  28. package/dist/i18n/de.json +91 -89
  29. package/dist/i18n/en.json +91 -79
  30. package/dist/i18n/es.json +99 -97
  31. package/dist/i18n/fr.json +99 -97
  32. package/dist/i18n/hi.json +91 -89
  33. package/dist/i18n/it.json +99 -97
  34. package/dist/i18n/ja.json +88 -86
  35. package/dist/i18n/ko.json +88 -86
  36. package/dist/i18n/nl.json +91 -89
  37. package/dist/i18n/pt.json +99 -97
  38. package/dist/i18n/ru.json +104 -102
  39. package/dist/i18n/tr.json +91 -89
  40. package/dist/index.browser.cjs +1720 -1617
  41. package/dist/index.browser.cjs.map +2 -2
  42. package/dist/index.node.cjs +1720 -1617
  43. package/dist/index.node.cjs.map +2 -2
  44. package/dist/scss/v2/Message/Message-layout.scss +1 -0
  45. package/dist/scss/v2/Poll/Poll-layout.scss +4 -0
  46. package/package.json +7 -7
@@ -1,13 +1,18 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { useTranslationContext } from '../../context';
3
3
  const UnMemoizedAttachmentActions = (props) => {
4
4
  const { actionHandler, actions, id, text } = props;
5
5
  const { t } = useTranslationContext('UnMemoizedAttachmentActions');
6
6
  const handleActionClick = (event, name, value) => actionHandler?.(name, value, event);
7
+ const knownActionText = useMemo(() => ({
8
+ Cancel: t('Cancel'),
9
+ Send: t('Send'),
10
+ Shuffle: t('Shuffle'),
11
+ }), [t]);
7
12
  return (React.createElement("div", { className: 'str-chat__message-attachment-actions' },
8
13
  React.createElement("div", { className: 'str-chat__message-attachment-actions-form' },
9
14
  React.createElement("span", null, text),
10
- actions.map((action) => (React.createElement("button", { className: `str-chat__message-attachment-actions-button str-chat__message-attachment-actions-button--${action.style}`, "data-testid": `${action.name}`, "data-value": action.value, key: `${id}-${action.value}`, onClick: (event) => handleActionClick(event, action.name, action.value) }, action.text ? t(action.text) : null))))));
15
+ actions.map((action) => (React.createElement("button", { className: `str-chat__message-attachment-actions-button str-chat__message-attachment-actions-button--${action.style}`, "data-testid": `${action.name}`, "data-value": action.value, key: `${id}-${action.value}`, onClick: (event) => handleActionClick(event, action.name, action.value) }, action.text ? (knownActionText[action.text] ?? t(action.text)) : null))))));
11
16
  };
12
17
  /**
13
18
  * A component for rendering the actions you can take on an attachment.
@@ -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.12.0";
27
+ const version = "13.13.1";
28
28
  const userAgent = client.getUserAgent();
29
29
  if (!userAgent.includes('stream-chat-react')) {
30
30
  // result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
@@ -18,11 +18,11 @@ export const ReminderNotification = ({ reminder }) => {
18
18
  React.createElement("span", null, " | "),
19
19
  React.createElement("span", null, isBehindRefreshBoundary
20
20
  ? t('Due since {{ dueSince }}', {
21
- dueSince: t(`timestamp/ReminderNotification`, {
21
+ dueSince: t('timestamp/ReminderNotification', {
22
22
  timestamp: reminder.remindAt,
23
23
  }),
24
24
  })
25
- : t(`Due {{ timeLeft }}`, {
25
+ : t('Due {{ timeLeft }}', {
26
26
  timeLeft: t('duration/Message reminder', {
27
27
  milliseconds: timeLeftMs,
28
28
  }),
@@ -20,7 +20,7 @@ export const useMuteHandler = (message, notifications = {}) => {
20
20
  const successMessage = getSuccessNotification &&
21
21
  validateAndGetMessage(getSuccessNotification, [message.user]);
22
22
  notify(successMessage ||
23
- t(`{{ user }} has been muted`, {
23
+ t('{{ user }} has been muted', {
24
24
  user: message.user.name || message.user.id,
25
25
  }), 'success');
26
26
  }
@@ -33,7 +33,7 @@ export const useMuteHandler = (message, notifications = {}) => {
33
33
  else {
34
34
  try {
35
35
  await client.unmuteUser(message.user.id);
36
- const fallbackMessage = t(`{{ user }} has been unmuted`, {
36
+ const fallbackMessage = t('{{ user }} has been unmuted', {
37
37
  user: message.user.name || message.user.id,
38
38
  });
39
39
  const successMessage = (getSuccessNotification &&
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { isLocalAttachment, isLocalAudioAttachment, isLocalFileAttachment, isLocalImageAttachment, isLocalVideoAttachment, isLocalVoiceRecordingAttachment, isScrapedContent, } from 'stream-chat';
3
3
  import { UnsupportedAttachmentPreview as DefaultUnknownAttachmentPreview } from './UnsupportedAttachmentPreview';
4
4
  import { VoiceRecordingPreview as DefaultVoiceRecordingPreview } from './VoiceRecordingPreview';
5
- import { FileAttachmentPreview as DefaultFilePreview } from './FileAttachmentPreview';
5
+ import DefaultFilePreview from './FileAttachmentPreview';
6
6
  import { ImageAttachmentPreview as DefaultImagePreview } from './ImageAttachmentPreview';
7
7
  import { useAttachmentsForPreview, useMessageComposer } from '../hooks';
8
8
  import { GeolocationPreview as DefaultGeolocationPreview, } from './GeolocationPreview';
@@ -2,4 +2,5 @@ import React from 'react';
2
2
  import type { LocalAudioAttachment, LocalFileAttachment, LocalVideoAttachment } from 'stream-chat';
3
3
  import type { UploadAttachmentPreviewProps } from './types';
4
4
  export type FileAttachmentPreviewProps<CustomLocalMetadata = unknown> = UploadAttachmentPreviewProps<LocalFileAttachment<CustomLocalMetadata> | LocalAudioAttachment<CustomLocalMetadata> | LocalVideoAttachment<CustomLocalMetadata>>;
5
- export declare const FileAttachmentPreview: ({ attachment, handleRetry, removeAttachments, }: FileAttachmentPreviewProps) => React.JSX.Element;
5
+ declare const FileAttachmentPreview: ({ attachment, handleRetry, removeAttachments, }: FileAttachmentPreviewProps) => React.JSX.Element;
6
+ export default FileAttachmentPreview;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { useTranslationContext } from '../../../context';
3
3
  import { FileIcon } from '../../ReactFileUtilities';
4
4
  import { CloseIcon, DownloadIcon, LoadingIndicatorIcon, RetryIcon } from '../icons';
5
- export const FileAttachmentPreview = ({ attachment, handleRetry, removeAttachments, }) => {
5
+ const FileAttachmentPreview = ({ attachment, handleRetry, removeAttachments, }) => {
6
6
  const { t } = useTranslationContext('FilePreview');
7
7
  const uploadState = attachment.localMetadata?.uploadState;
8
8
  return (React.createElement("div", { className: 'str-chat__attachment-preview-file', "data-testid": 'attachment-preview-file' },
@@ -11,7 +11,7 @@ export const FileAttachmentPreview = ({ attachment, handleRetry, removeAttachmen
11
11
  React.createElement("button", { "aria-label": t('aria/Remove attachment'), className: 'str-chat__attachment-preview-delete', "data-testid": 'file-preview-item-delete-button', disabled: uploadState === 'uploading', onClick: () => attachment.localMetadata?.id &&
12
12
  removeAttachments([attachment.localMetadata?.id]), type: 'button' },
13
13
  React.createElement(CloseIcon, null)),
14
- ['blocked', 'failed'].includes(uploadState) && !!handleRetry && (React.createElement("button", { className: 'str-chat__attachment-preview-error str-chat__attachment-preview-error-file', "data-testid": 'file-preview-item-retry-button', onClick: () => {
14
+ ['blocked', 'failed'].includes(uploadState) && !!handleRetry && (React.createElement("button", { "aria-label": t('aria/Retry upload'), className: 'str-chat__attachment-preview-error str-chat__attachment-preview-error-file', "data-testid": 'file-preview-item-retry-button', onClick: () => {
15
15
  handleRetry(attachment);
16
16
  } },
17
17
  React.createElement(RetryIcon, null))),
@@ -22,3 +22,4 @@ export const FileAttachmentPreview = ({ attachment, handleRetry, removeAttachmen
22
22
  React.createElement(DownloadIcon, null))),
23
23
  uploadState === 'uploading' && React.createElement(LoadingIndicatorIcon, { size: 17 }))));
24
24
  };
25
+ export default FileAttachmentPreview;
@@ -15,7 +15,7 @@ export const ImageAttachmentPreview = ({ attachment, handleRetry, removeAttachme
15
15
  }), "data-testid": 'attachment-preview-image' },
16
16
  React.createElement("button", { "aria-label": t('aria/Remove attachment'), className: 'str-chat__attachment-preview-delete', "data-testid": 'image-preview-item-delete-button', disabled: uploadState === 'uploading', onClick: () => id && removeAttachments([id]), type: 'button' },
17
17
  React.createElement(CloseIcon, null)),
18
- ['blocked', 'failed'].includes(uploadState) && (React.createElement("button", { className: 'str-chat__attachment-preview-error str-chat__attachment-preview-error-image', "data-testid": 'image-preview-item-retry-button', onClick: () => handleRetry(attachment) },
18
+ ['blocked', 'failed'].includes(uploadState) && (React.createElement("button", { "aria-label": t('aria/Retry upload'), className: 'str-chat__attachment-preview-error str-chat__attachment-preview-error-image', "data-testid": 'image-preview-item-retry-button', onClick: () => handleRetry(attachment) },
19
19
  React.createElement(RetryIcon, null))),
20
20
  uploadState === 'uploading' && (React.createElement("div", { className: 'str-chat__attachment-preview-image-loading' },
21
21
  React.createElement(LoadingIndicatorIcon, { size: 17 }))),
@@ -30,7 +30,7 @@ export const VoiceRecordingPreview = ({ attachment, handleRetry, removeAttachmen
30
30
  React.createElement("button", { "aria-label": t('aria/Remove attachment'), className: 'str-chat__attachment-preview-delete', "data-testid": 'file-preview-item-delete-button', disabled: attachment.localMetadata?.uploadState === 'uploading', onClick: () => attachment.localMetadata?.id && removeAttachments([attachment.localMetadata.id]), type: 'button' },
31
31
  React.createElement(CloseIcon, null)),
32
32
  ['blocked', 'failed'].includes(attachment.localMetadata?.uploadState) &&
33
- !!handleRetry && (React.createElement("button", { className: 'str-chat__attachment-preview-error str-chat__attachment-preview-error-file', "data-testid": 'file-preview-item-retry-button', onClick: () => handleRetry(attachment) },
33
+ !!handleRetry && (React.createElement("button", { "aria-label": t('aria/Retry upload'), className: 'str-chat__attachment-preview-error str-chat__attachment-preview-error-file', "data-testid": 'file-preview-item-retry-button', onClick: () => handleRetry(attachment) },
34
34
  React.createElement(RetryIcon, null))),
35
35
  React.createElement("div", { className: 'str-chat__attachment-preview-metadata' },
36
36
  React.createElement("div", { className: 'str-chat__attachment-preview-file-name', title: attachment.title }, attachment.title),
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
2
  import { SendIcon } from './icons';
3
3
  import { useMessageComposerHasSendableData } from './hooks';
4
+ import { useTranslationContext } from '../../context';
4
5
  export const SendButton = ({ sendMessage, ...rest }) => {
6
+ const { t } = useTranslationContext();
5
7
  const hasSendableData = useMessageComposerHasSendableData();
6
- return (React.createElement("button", { "aria-label": 'Send', className: 'str-chat__send-button', "data-testid": 'send-button', disabled: !hasSendableData, onClick: sendMessage, type: 'button', ...rest },
8
+ return (React.createElement("button", { "aria-label": t('aria/Send'), className: 'str-chat__send-button', "data-testid": 'send-button', disabled: !hasSendableData, onClick: sendMessage, type: 'button', ...rest },
7
9
  React.createElement(SendIcon, null)));
8
10
  };
@@ -25,9 +25,8 @@ const MessageListWithContext = (props) => {
25
25
  const { channel, channelUnreadUiState, disableDateSeparator = false, groupStyles, hasMoreNewer = false, headerPosition, hideDeletedMessages = false, hideNewMessageSeparator = false, highlightedMessageId, internalInfiniteScrollProps: { threshold: loadMoreScrollThreshold = DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, ...restInternalInfiniteScrollProps } = {}, jumpToLatestMessage = () => Promise.resolve(), loadMore: loadMoreCallback, loadMoreNewer: loadMoreNewerCallback, // @deprecated in favor of `channelCapabilities` - TODO: remove in next major release
26
26
  maxTimeBetweenGroupedMessages, messageActions = Object.keys(MESSAGE_ACTIONS), messageLimit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE, messages = [], noGroupByUser = false, notifications, pinPermissions = defaultPinPermissions, reactionDetailsSort, renderMessages = defaultRenderMessages, returnAllReadData = false, reviewProcessedMessage, showUnreadNotificationAlways, sortReactionDetails, sortReactions, suppressAutoscroll, threadList = false, unsafeHTML = false, } = props;
27
27
  const [listElement, setListElement] = React.useState(null);
28
- const [ulElement, setUlElement] = React.useState(null);
29
28
  const { customClasses } = useChatContext('MessageList');
30
- const { EmptyStateIndicator = DefaultEmptyStateIndicator, LoadingIndicator = DefaultLoadingIndicator, MessageListMainPanel = DefaultMessageListMainPanel, MessageListNotifications = DefaultMessageListNotifications, MessageNotification = DefaultMessageNotification, TypingIndicator = DefaultTypingIndicator, UnreadMessagesNotification = DefaultUnreadMessagesNotification, } = useComponentContext('MessageList');
29
+ const { EmptyStateIndicator = DefaultEmptyStateIndicator, LoadingIndicator = DefaultLoadingIndicator, MessageListMainPanel = DefaultMessageListMainPanel, MessageListNotifications = DefaultMessageListNotifications, MessageListWrapper = 'ul', MessageNotification = DefaultMessageNotification, TypingIndicator = DefaultTypingIndicator, UnreadMessagesNotification = DefaultUnreadMessagesNotification, } = useComponentContext('MessageList');
31
30
  const { hasNewMessages, isMessageListScrolledToBottom, onScroll, scrollToBottom, wrapperRect, } = useScrollLocationLogic({
32
31
  hasMoreNewer,
33
32
  listElement,
@@ -125,7 +124,7 @@ const MessageListWithContext = (props) => {
125
124
  }, [scrollToBottom, hasMoreNewer]);
126
125
  React.useLayoutEffect(() => {
127
126
  if (highlightedMessageId) {
128
- const element = ulElement?.querySelector(`[data-message-id='${highlightedMessageId}']`);
127
+ const element = listElement?.querySelector(`[data-message-id='${highlightedMessageId}']`);
129
128
  element?.scrollIntoView({ block: 'center' });
130
129
  }
131
130
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -135,12 +134,16 @@ const MessageListWithContext = (props) => {
135
134
  const dialogManagerId = threadList
136
135
  ? `message-list-dialog-manager-thread-${id}`
137
136
  : `message-list-dialog-manager-${id}`;
138
- return (React.createElement(MessageListContextProvider, { value: { listElement, scrollToBottom } },
137
+ return (React.createElement(MessageListContextProvider, { value: {
138
+ listElement,
139
+ processedMessages: enrichedMessages,
140
+ scrollToBottom,
141
+ } },
139
142
  React.createElement(MessageListMainPanel, null,
140
143
  React.createElement(DialogManagerProvider, { id: dialogManagerId },
141
144
  !threadList && showUnreadMessagesNotification && (React.createElement(UnreadMessagesNotification, { unreadCount: channelUnreadUiState?.unread_messages })),
142
145
  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 },
143
- React.createElement("ul", { className: 'str-chat__ul', ref: setUlElement }, elements),
146
+ React.createElement(MessageListWrapper, { className: 'str-chat__ul' }, elements),
144
147
  React.createElement(TypingIndicator, { threadList: threadList }),
145
148
  React.createElement("div", { key: 'bottom' })))))),
146
149
  React.createElement(MessageListNotifications, { hasNewMessages: hasNewMessages, isMessageListScrolledToBottom: isMessageListScrolledToBottom, isNotAtLatestMessageSet: hasMoreNewer, MessageNotification: MessageNotification, notifications: notifications, scrollToBottom: scrollToBottomFromNotification, threadList: threadList, unreadCount: threadList ? undefined : channelUnreadUiState?.unread_messages })));
@@ -5,24 +5,24 @@ import { DateSeparator as DefaultDateSeparator } from '../DateSeparator';
5
5
  import { EventComponent as DefaultMessageSystem } from '../EventComponent';
6
6
  import { UnreadMessagesSeparator as DefaultUnreadMessagesSeparator } from './UnreadMessagesSeparator';
7
7
  export function defaultRenderMessages({ channelUnreadUiState, components, customClasses, lastOwnMessage, lastReceivedMessageId: lastReceivedId, messageGroupStyles, messages, ownMessagesDeliveredToOthers, readData, sharedMessageProps: messageProps, }) {
8
- const { DateSeparator = DefaultDateSeparator, HeaderComponent, MessageSystem = DefaultMessageSystem, UnreadMessagesSeparator = DefaultUnreadMessagesSeparator, } = components;
8
+ const { DateSeparator = DefaultDateSeparator, HeaderComponent, MessageListItem = 'li', MessageSystem = DefaultMessageSystem, UnreadMessagesSeparator = DefaultUnreadMessagesSeparator, } = components;
9
9
  const renderedMessages = [];
10
10
  let firstMessage;
11
11
  let previousMessage = undefined;
12
12
  for (let index = 0; index < messages.length; index++) {
13
13
  const message = messages[index];
14
14
  if (isDateSeparatorMessage(message)) {
15
- renderedMessages.push(React.createElement("li", { key: `${message.date.toISOString()}-i` },
15
+ renderedMessages.push(React.createElement(MessageListItem, { "data-index": index, key: `${message.date.toISOString()}-i` },
16
16
  React.createElement(DateSeparator, { date: message.date, formatDate: messageProps.formatDate, unread: message.unread })));
17
17
  }
18
18
  else if (isIntroMessage(message)) {
19
19
  if (HeaderComponent) {
20
- renderedMessages.push(React.createElement("li", { key: 'intro' },
20
+ renderedMessages.push(React.createElement(MessageListItem, { "data-index": index, key: 'intro' },
21
21
  React.createElement(HeaderComponent, null)));
22
22
  }
23
23
  }
24
24
  else if (message.type === 'system') {
25
- renderedMessages.push(React.createElement("li", { "data-message-id": message.id, key: message.id || message.created_at.toISOString() },
25
+ renderedMessages.push(React.createElement(MessageListItem, { "data-index": index, "data-message-id": message.id, key: message.id || message.created_at.toISOString() },
26
26
  React.createElement(MessageSystem, { message: message })));
27
27
  }
28
28
  else {
@@ -41,9 +41,9 @@ export function defaultRenderMessages({ channelUnreadUiState, components, custom
41
41
  unreadMessageCount: channelUnreadUiState?.unread_messages,
42
42
  });
43
43
  renderedMessages.push(React.createElement(Fragment, { key: message.id || message.created_at.toISOString() },
44
- isFirstUnreadMessage && UnreadMessagesSeparator && (React.createElement("li", { className: 'str-chat__li str-chat__unread-messages-separator-wrapper' },
44
+ isFirstUnreadMessage && UnreadMessagesSeparator && (React.createElement(MessageListItem, { className: 'str-chat__li str-chat__unread-messages-separator-wrapper' },
45
45
  React.createElement(UnreadMessagesSeparator, { unreadCount: channelUnreadUiState?.unread_messages }))),
46
- React.createElement("li", { className: messageClass, "data-message-id": message.id, "data-testid": messageClass },
46
+ React.createElement(MessageListItem, { className: messageClass, "data-index": index, "data-message-id": message.id, "data-testid": messageClass },
47
47
  React.createElement(Message, { deliveredTo: ownMessagesDeliveredToOthers[message.id] || [], groupStyles: [groupStyles], lastOwnMessage: lastOwnMessage, lastReceivedId: lastReceivedId, message: message, readBy: readData[message.id] || [], ...messageProps }))));
48
48
  previousMessage = message;
49
49
  }
@@ -1,5 +1,5 @@
1
1
  import clsx from 'clsx';
2
- import React from 'react';
2
+ import React, { useMemo } from 'react';
3
3
  import { SimpleSwitchField } from '../../Form/SwitchField';
4
4
  import { FieldError } from '../../Form/FieldError';
5
5
  import { useTranslationContext } from '../../../context';
@@ -14,6 +14,10 @@ export const MultipleAnswersField = () => {
14
14
  const { t } = useTranslationContext();
15
15
  const { pollComposer } = useMessageComposer();
16
16
  const { enforce_unique_vote, error, max_votes_allowed } = useStateStore(pollComposer.state, pollComposerStateSelector);
17
+ const knownValidationErrors = useMemo(() => ({
18
+ 'Enforce unique vote is enabled': t('Enforce unique vote is enabled'),
19
+ 'Type a number from 2 to 10': t('Type a number from 2 to 10'),
20
+ }), [t]);
17
21
  return (React.createElement("div", { className: clsx('str-chat__form__expandable-field', {
18
22
  'str-chat__form__expandable-field--expanded': !enforce_unique_vote,
19
23
  }) },
@@ -24,7 +28,7 @@ export const MultipleAnswersField = () => {
24
28
  'str-chat__form__input-field--has-error': error,
25
29
  }) },
26
30
  React.createElement("div", { className: clsx('str-chat__form__input-field__value') },
27
- React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-max-votes-allowed-input-field-error', text: error && t(error) }),
31
+ React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-max-votes-allowed-input-field-error', text: error && (knownValidationErrors[error] ?? t('Error')) }),
28
32
  React.createElement("input", { id: 'max_votes_allowed', onBlur: () => {
29
33
  pollComposer.handleFieldBlur('max_votes_allowed');
30
34
  }, onChange: (e) => {
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import { FieldError } from '../../Form/FieldError';
4
4
  import { useTranslationContext } from '../../../context';
@@ -12,12 +12,15 @@ export const NameField = () => {
12
12
  const { t } = useTranslationContext();
13
13
  const { pollComposer } = useMessageComposer();
14
14
  const { error, name } = useStateStore(pollComposer.state, pollComposerStateSelector);
15
+ const knownValidationErrors = useMemo(() => ({
16
+ 'Question is required': t('Question is required'),
17
+ }), [t]);
15
18
  return (React.createElement("div", { className: clsx('str-chat__form__field str-chat__form__input-field str-chat__form__input-field--with-label', {
16
19
  'str-chat__form__input-field--has-error': error,
17
20
  }) },
18
21
  React.createElement("label", { className: 'str-chat__form__field-label', htmlFor: 'name' }, t('Question')),
19
22
  React.createElement("div", { className: clsx('str-chat__form__input-field__value') },
20
- React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-name-input-field-error', text: error && t(error) }),
23
+ React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-name-input-field-error', text: error && (knownValidationErrors[error] ?? t('Error')) }),
21
24
  React.createElement("input", { id: 'name', onBlur: () => {
22
25
  pollComposer.handleFieldBlur('name');
23
26
  }, onChange: (e) => {
@@ -1,5 +1,5 @@
1
1
  import clsx from 'clsx';
2
- import React, { useCallback } from 'react';
2
+ import React, { useCallback, useMemo } from 'react';
3
3
  import { FieldError } from '../../Form/FieldError';
4
4
  import { DragAndDropContainer } from '../../DragAndDrop/DragAndDropContainer';
5
5
  import { useTranslationContext } from '../../../context';
@@ -13,6 +13,10 @@ export const OptionFieldSet = () => {
13
13
  const { pollComposer } = useMessageComposer();
14
14
  const { errors, options } = useStateStore(pollComposer.state, pollComposerStateSelector);
15
15
  const { t } = useTranslationContext('OptionFieldSet');
16
+ const knownValidationErrors = useMemo(() => ({
17
+ 'Option already exists': t('Option already exists'),
18
+ 'Option is empty': t('Option is empty'),
19
+ }), [t]);
16
20
  const onSetNewOrder = useCallback((newOrder) => {
17
21
  const prevOptions = pollComposer.options;
18
22
  pollComposer.updateFields({ options: newOrder.map((index) => prevOptions[index]) });
@@ -27,7 +31,7 @@ export const OptionFieldSet = () => {
27
31
  'str-chat__form__input-field--has-error': error,
28
32
  }), key: `new-poll-option-${i}` },
29
33
  React.createElement("div", { className: 'str-chat__form__input-field__value' },
30
- React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-option-input-field-error', text: error && t(error) }),
34
+ React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-option-input-field-error', text: error && (knownValidationErrors[error] ?? t('Error')) }),
31
35
  React.createElement("input", { id: option.id, onBlur: () => {
32
36
  pollComposer.handleFieldBlur('options');
33
37
  }, onChange: (e) => {
@@ -1,11 +1,29 @@
1
+ import { useMemo } from 'react';
1
2
  import React from 'react';
3
+ import { useTranslationContext } from '../../../context';
2
4
  export const CommandItem = (props) => {
5
+ const { t } = useTranslationContext();
3
6
  const { entity } = props;
7
+ const knownArgsTranslations = useMemo(() => ({
8
+ ban: t('ban-command-args'),
9
+ giphy: t('giphy-command-args'),
10
+ mute: t('mute-command-args'),
11
+ unban: t('unban-command-args'),
12
+ unmute: t('unmute-command-args'),
13
+ }), [t]);
14
+ const knownDescriptionTranslations = useMemo(() => ({
15
+ ban: t('ban-command-description'),
16
+ giphy: t('giphy-command-description'),
17
+ mute: t('mute-command-description'),
18
+ unban: t('unban-command-description'),
19
+ unmute: t('unmute-command-description'),
20
+ }), [t]);
4
21
  return (React.createElement("div", { className: 'str-chat__slash-command' },
5
22
  React.createElement("span", { className: 'str-chat__slash-command-header' },
6
23
  React.createElement("strong", null, entity.name),
7
- " ",
8
- entity.args),
24
+ ' ',
25
+ entity.args && (knownArgsTranslations[entity.name ?? ''] ?? t(entity.args))),
9
26
  React.createElement("br", null),
10
- React.createElement("span", { className: 'str-chat__slash-command-description' }, entity.description)));
27
+ React.createElement("span", { className: 'str-chat__slash-command-description' }, entity.description &&
28
+ (knownDescriptionTranslations[entity.name ?? ''] ?? t(entity.description)))));
11
29
  };
@@ -178,6 +178,10 @@ export type ComponentContextValue = {
178
178
  UnreadMessagesSeparator?: React.ComponentType<UnreadMessagesSeparatorProps>;
179
179
  /** Custom UI component to display a message in the `VirtualizedMessageList`, does not have a default implementation */
180
180
  VirtualMessage?: React.ComponentType<FixedHeightMessageProps>;
181
+ /** Custom UI component to wrap MessageList children. Default is the `ul` tag */
182
+ MessageListWrapper?: React.ComponentType<PropsWithChildren>;
183
+ /** Custom UI component to wrap each element of MessageList. Default is the `li` tag */
184
+ MessageListItem?: React.ComponentType<PropsWithChildren>;
181
185
  };
182
186
  export declare const ComponentContext: React.Context<ComponentContextValue>;
183
187
  export declare const ComponentProvider: ({ children, value, }: PropsWithChildren<{
@@ -1,6 +1,9 @@
1
1
  import React from 'react';
2
2
  import type { PropsWithChildren } from 'react';
3
+ import type { RenderedMessage } from '../components';
3
4
  export type MessageListContextValue = {
5
+ /** Enriched message list, including date separators and intro message (if enabled) */
6
+ processedMessages: RenderedMessage[];
4
7
  /** The scroll container within which the messages and typing indicator are rendered */
5
8
  listElement: HTMLDivElement | null;
6
9
  /** Function that scrolls the `listElement` to the bottom. */