diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx index fc3096baab..aa84d9408c 100644 --- a/examples/vite/src/App.tsx +++ b/examples/vite/src/App.tsx @@ -18,8 +18,7 @@ import { type AttachmentProps, Chat, ChatView, - ReactionsList, - MessageInput, + MessageReactions, type NotificationListProps, NotificationList, Streami18n, @@ -132,12 +131,12 @@ const useUser = () => { return { tokenProvider, userId, userImage, userName }; }; -const CustomMessageReactions = (props: React.ComponentProps) => { +const CustomMessageReactions = (props: React.ComponentProps) => { const { visualStyle, verticalPosition, flipHorizontalPosition } = useAppSettingsSelector((state) => state.reactions); return ( - { emojiSearchIndex: SearchIndex, EmojiPicker: EmojiPickerWithCustomOptions, NotificationList: ConfigurableNotificationList, - ReactionsList: CustomMessageReactions, + MessageReactions: CustomMessageReactions, reactionOptions: newReactionOptions, Search: CustomChannelSearch, ...messageUiOverrides, diff --git a/examples/vite/src/ChatLayout/Panels.tsx b/examples/vite/src/ChatLayout/Panels.tsx index a82201fa67..36001a2b87 100644 --- a/examples/vite/src/ChatLayout/Panels.tsx +++ b/examples/vite/src/ChatLayout/Panels.tsx @@ -8,7 +8,7 @@ import { ChannelHeader, ChannelList, ChatView, - MessageInput, + MessageComposer, MessageList, Thread, ThreadList, @@ -79,7 +79,7 @@ export const ChannelsPanels = ({ - , -) => ; + props: React.ComponentProps, +) => ; // ─── Passthrough ReactionSelector (for custom handler demo) ────── diff --git a/examples/vite/src/CustomMessageUi/variants.tsx b/examples/vite/src/CustomMessageUi/variants.tsx index 8ed49ee849..c4ba06b741 100644 --- a/examples/vite/src/CustomMessageUi/variants.tsx +++ b/examples/vite/src/CustomMessageUi/variants.tsx @@ -3,7 +3,7 @@ import type { LocalMessage, UserResponse } from 'stream-chat'; import { Avatar, MessageText, - ReactionsList, + MessageReactions, useMessageContext, useChatContext, useComponentContext, @@ -360,7 +360,7 @@ export const CustomMessageUi_V8 = () => { - + )} diff --git a/src/components/Attachment/components/FileSizeIndicator.tsx b/src/components/Attachment/components/FileSizeIndicator.tsx index b1a5e4d0a5..c95291db35 100644 --- a/src/components/Attachment/components/FileSizeIndicator.tsx +++ b/src/components/Attachment/components/FileSizeIndicator.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { prettifyFileSize } from '../../MessageInput/hooks/utils'; +import { prettifyFileSize } from '../../MessageComposer/hooks/utils'; type FileSizeIndicatorProps = { /** file size in byte */ diff --git a/src/components/ChannelHeader/ChannelHeader.tsx b/src/components/ChannelHeader/ChannelHeader.tsx index 5d85003977..befb74e773 100644 --- a/src/components/ChannelHeader/ChannelHeader.tsx +++ b/src/components/ChannelHeader/ChannelHeader.tsx @@ -4,7 +4,7 @@ import { IconLayoutAlignLeft } from '../Icons/icons'; import { type ChannelAvatarProps, ChannelAvatar as DefaultAvatar } from '../Avatar'; import { TypingIndicatorHeader } from '../TypingIndicator/TypingIndicatorHeader'; import { useChannelHeaderOnlineStatus } from './hooks/useChannelHeaderOnlineStatus'; -import { useChannelPreviewInfo } from '../ChannelPreview/hooks/useChannelPreviewInfo'; +import { useChannelPreviewInfo } from '../ChannelListItem/hooks/useChannelPreviewInfo'; import { useChannelStateContext } from '../../context/ChannelStateContext'; import { useChatContext } from '../../context/ChatContext'; import { useTypingContext } from '../../context/TypingContext'; diff --git a/src/components/ChannelList/ChannelList.tsx b/src/components/ChannelList/ChannelList.tsx index 5d904571ad..f9e43757df 100644 --- a/src/components/ChannelList/ChannelList.tsx +++ b/src/components/ChannelList/ChannelList.tsx @@ -23,8 +23,8 @@ import type { ChannelListMessengerProps } from './ChannelListMessenger'; import { ChannelListMessenger } from './ChannelListMessenger'; import type { ChannelAvatarProps } from '../Avatar'; import { Avatar as DefaultAvatar } from '../Avatar'; -import type { ChannelPreviewUIComponentProps } from '../ChannelPreview/ChannelPreview'; -import { ChannelPreview } from '../ChannelPreview/ChannelPreview'; +import type { ChannelListItemUIProps } from '../ChannelListItem/ChannelListItem'; +import { ChannelListItem } from '../ChannelListItem/ChannelListItem'; import { Search as DefaultSearch } from '../Search'; import type { EmptyStateIndicatorProps } from '../EmptyStateIndicator'; import { EmptyStateIndicator as DefaultEmptyStateIndicator } from '../EmptyStateIndicator'; @@ -141,7 +141,7 @@ export type ChannelListProps = { /** Custom UI component to handle channel pagination logic, defaults to and accepts same props as: [LoadMorePaginator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/LoadMore/LoadMorePaginator.tsx) */ Paginator?: React.ComponentType; /** Custom UI component to display the channel preview in the list, defaults to and accepts same props as: [ChannelPreviewMessenger](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelPreview/ChannelPreviewMessenger.tsx) */ - Preview?: React.ComponentType; + Preview?: React.ComponentType; /** * Custom interval during which the recovery channel list queries will be prevented. * This is to avoid firing unnecessary queries during internet connection fluctuation. @@ -216,7 +216,7 @@ const UnMemoizedChannelList = (props: ChannelListProps) => { useImageFlagEmojisOnWindows, } = useChatContext('ChannelList'); const { NotificationList = DefaultNotificationList, Search = DefaultSearch } = - useComponentContext(); // FIXME: use component context to retrieve ChannelPreview UI components too + useComponentContext(); // FIXME: use component context to retrieve ChannelListItemUI components too const channelListRef = useRef(null); const [channelUpdateCount, setChannelUpdateCount] = useState(0); @@ -344,7 +344,7 @@ const UnMemoizedChannelList = (props: ChannelListProps) => { watchers, }; - return ; + return ; }; const baseClass = 'str-chat__channel-list'; diff --git a/src/components/ChannelList/__tests__/ChannelList.test.js b/src/components/ChannelList/__tests__/ChannelList.test.js index a24521cbfa..84ddbaf9d9 100644 --- a/src/components/ChannelList/__tests__/ChannelList.test.js +++ b/src/components/ChannelList/__tests__/ChannelList.test.js @@ -34,9 +34,9 @@ import { import { Chat } from '../../Chat'; import { ChannelList } from '../ChannelList'; import { + ChannelListItemUI, ChannelPreviewCompact, ChannelPreviewLastMessage, - ChannelPreviewMessenger, } from '../../ChannelPreview'; import { @@ -449,7 +449,7 @@ describe('ChannelList', () => {
Avatar
} List={ChannelListComponent} - Preview={ChannelPreviewMessenger} + Preview={ChannelListItemUI} /> , ); diff --git a/src/components/ChannelPreview/ChannelPreview.tsx b/src/components/ChannelListItem/ChannelListItem.tsx similarity index 85% rename from src/components/ChannelPreview/ChannelPreview.tsx rename to src/components/ChannelListItem/ChannelListItem.tsx index bcaca9edbb..7df5158fca 100644 --- a/src/components/ChannelPreview/ChannelPreview.tsx +++ b/src/components/ChannelListItem/ChannelListItem.tsx @@ -3,20 +3,23 @@ import React, { useContext, useEffect, useMemo, useState } from 'react'; import type { ReactNode } from 'react'; import type { Channel, Event, LocalMessage } from 'stream-chat'; -import { ChannelPreviewMessenger } from './ChannelPreviewMessenger'; +import { ChannelListItemUI as DefaultChannelListItemUI } from './ChannelListItemUI'; import { useIsChannelMuted } from './hooks/useIsChannelMuted'; import { useChannelPreviewInfo } from './hooks/useChannelPreviewInfo'; import { getLatestMessagePreview as defaultGetLatestMessagePreview } from './utils'; -import { useChatContext } from '../../context/ChatContext'; import { useTranslationContext } from '../../context/TranslationContext'; import { useMessageDeliveryStatus } from './hooks/useMessageDeliveryStatus'; import type { MessageDeliveryStatus } from './hooks/useMessageDeliveryStatus'; -import type { ChatContextValue } from '../../context/ChatContext'; import type { ChannelAvatarProps } from '../Avatar/ChannelAvatar'; import type { GroupChannelDisplayInfo } from './utils'; -import type { TranslationContextValue } from '../../context/TranslationContext'; - -export type ChannelPreviewUIComponentProps = ChannelPreviewProps & { +import { + type ChatContextValue, + type TranslationContextValue, + useChatContext, + useComponentContext, +} from '../../context'; + +export type ChannelListItemUIProps = ChannelListItemProps & { /** Image of Channel to display */ displayImage?: string; /** Title of Channel to display */ @@ -37,7 +40,7 @@ export type ChannelPreviewUIComponentProps = ChannelPreviewProps & { unread?: number; }; -export type ChannelPreviewProps = { +export type ChannelListItemProps = { /** Comes from either the `channelRenderFilterFn` or `usePaginatedChannels` call from [ChannelList](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelList/ChannelList.tsx) */ channel: Channel; /** If the component's channel is the active (selected) Channel */ @@ -50,7 +53,7 @@ export type ChannelPreviewProps = { channelUpdateCount?: number; /** Custom class for the channel preview root */ className?: string; - /** Custom function that generates the message preview in ChannelPreview component */ + /** Custom function that generates the message preview in ChannelListItem component */ getLatestMessagePreview?: ( channel: Channel, t: TranslationContextValue['t'], @@ -58,30 +61,28 @@ export type ChannelPreviewProps = { isMessageAIGenerated: ChatContextValue['isMessageAIGenerated'], ) => ReactNode; key?: string; - /** Custom ChannelPreview click handler function */ + /** Custom ChannelListItem click handler function */ onSelect?: (event: React.MouseEvent) => void; - /** Custom UI component to display the channel preview in the list, defaults to and accepts same props as: [ChannelPreviewMessenger](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelPreview/ChannelPreviewMessenger.tsx) */ - Preview?: React.ComponentType; /** Setter for selected Channel */ setActiveChannel?: ChatContextValue['setActiveChannel']; /** Object containing watcher parameters */ watchers?: { limit?: number; offset?: number }; }; -const ChannelPreviewContext = React.createContext<{ channel: Channel }>({ +const ChannelListItemContext = React.createContext<{ channel: Channel }>({ channel: null as unknown as Channel, }); -export const useChannelPreviewContext = () => useContext(ChannelPreviewContext); +export const useChannelListItemContext = () => useContext(ChannelListItemContext); -export const ChannelPreview = (props: ChannelPreviewProps) => { +export const ChannelListItem = (props: ChannelListItemProps) => { const { active, channel, channelUpdateCount, getLatestMessagePreview = defaultGetLatestMessagePreview, - Preview = ChannelPreviewMessenger, } = props; + const { ChannelListItemUI = DefaultChannelListItemUI } = useComponentContext(); const { channel: activeChannel, client, @@ -193,11 +194,11 @@ export const ChannelPreview = (props: ChannelPreviewProps) => { const channelPreviewContextValue = useMemo(() => ({ channel }), [channel]); - if (!Preview) return null; + if (!ChannelListItemUI) return null; return ( - - + { setActiveChannel={setActiveChannel} unread={unread} /> - + ); }; diff --git a/src/components/ChannelPreview/ChannelPreviewActionButtons.defaults.tsx b/src/components/ChannelListItem/ChannelListItemActionButtons.defaults.tsx similarity index 91% rename from src/components/ChannelPreview/ChannelPreviewActionButtons.defaults.tsx rename to src/components/ChannelListItem/ChannelListItemActionButtons.defaults.tsx index bb6b9dcec5..137efc7cd6 100644 --- a/src/components/ChannelPreview/ChannelPreviewActionButtons.defaults.tsx +++ b/src/components/ChannelListItem/ChannelListItemActionButtons.defaults.tsx @@ -3,7 +3,7 @@ import { match, P } from 'ts-pattern'; import { useChatContext, useTranslationContext } from '../../context'; import { useChannelMembershipState, useChannelMembersState } from '../ChannelList'; -import { useChannelPreviewContext } from './ChannelPreview'; +import { useChannelListItemContext } from './ChannelListItem'; import { Button } from '../Button'; import { IconArchive, @@ -15,11 +15,11 @@ import { import { useIsChannelMuted } from './hooks/useIsChannelMuted'; import { ContextMenuButton, useDialogOnNearestManager } from '../Dialog'; import { addNotificationTargetTag, useNotificationTarget } from '../Notifications'; -import { ChannelPreviewActionButtons } from './ChannelPreviewActionButtons'; +import { ChannelListItemActionButtons } from './ChannelListItemActionButtons'; const useMuteActionButtonBehavior = () => { const { client } = useChatContext(); - const { channel } = useChannelPreviewContext(); + const { channel } = useChannelListItemContext(); const { t } = useTranslationContext(); const { muted: isMuted } = useIsChannelMuted(channel); const [inProgress, setInProgress] = useState(false); @@ -44,10 +44,10 @@ const useMuteActionButtonBehavior = () => { originalError: error instanceof Error ? error : new Error('An unknown error occurred'), tags: addNotificationTargetTag(panel), - type: 'channelPreview:mute:failed', + type: 'channelListItem:mute:failed', }, origin: { - emitter: ChannelPreviewActionButtons.name, + emitter: ChannelListItemActionButtons.name, }, }); } finally { @@ -59,7 +59,7 @@ const useMuteActionButtonBehavior = () => { }; const useArchiveActionButtonBehavior = () => { - const { channel } = useChannelPreviewContext(); + const { channel } = useChannelListItemContext(); const { client } = useChatContext(); const membership = useChannelMembershipState(channel); const { t } = useTranslationContext(); @@ -85,10 +85,10 @@ const useArchiveActionButtonBehavior = () => { originalError: error instanceof Error ? error : new Error('An unknown error occurred'), tags: addNotificationTargetTag(panel), - type: 'channelPreview:archive:failed', + type: 'channelListItem:archive:failed', }, origin: { - emitter: ChannelPreviewActionButtons.name, + emitter: ChannelListItemActionButtons.name, }, }); } finally { @@ -183,7 +183,7 @@ export const defaultChannelActionSet: ChannelActionItem[] = [ Component() { const { client } = useChatContext(); const { t } = useTranslationContext(); - const { channel } = useChannelPreviewContext(); + const { channel } = useChannelListItemContext(); const [inProgress, setInProgress] = useState(false); const members = useChannelMembersState(channel); const panel = useNotificationTarget(); @@ -222,10 +222,10 @@ export const defaultChannelActionSet: ChannelActionItem[] = [ ? error : new Error('An unknown error occurred'), tags: addNotificationTargetTag(panel), - type: 'channelPreview:ban:failed', + type: 'channelListItem:ban:failed', }, origin: { - emitter: ChannelPreviewActionButtons.name, + emitter: ChannelListItemActionButtons.name, }, }); } finally { @@ -244,9 +244,9 @@ export const defaultChannelActionSet: ChannelActionItem[] = [ Component() { const { t } = useTranslationContext(); const { client } = useChatContext(); - const { channel } = useChannelPreviewContext(); + const { channel } = useChannelListItemContext(); const membership = useChannelMembershipState(channel); - const dialogId = ChannelPreviewActionButtons.getDialogId( + const dialogId = ChannelListItemActionButtons.getDialogId( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion channel.id!, ); @@ -278,10 +278,10 @@ export const defaultChannelActionSet: ChannelActionItem[] = [ options: { originalError: error, tags: addNotificationTargetTag(panel), - type: 'channelPreview:pin:failed', + type: 'channelListItem:pin:failed', }, origin: { - emitter: ChannelPreviewActionButtons.name, + emitter: ChannelListItemActionButtons.name, }, }); } finally { @@ -301,7 +301,7 @@ export const defaultChannelActionSet: ChannelActionItem[] = [ { Component() { const { t } = useTranslationContext(); - const { channel } = useChannelPreviewContext(); + const { channel } = useChannelListItemContext(); const { client } = useChatContext(); const [inProgress, setInProgress] = useState(false); const panel = useNotificationTarget(); @@ -328,10 +328,10 @@ export const defaultChannelActionSet: ChannelActionItem[] = [ ? error : new Error('An unknown error occurred'), tags: addNotificationTargetTag(panel), - type: 'channelPreview:leave:failed', + type: 'channelListItem:leave:failed', }, origin: { - emitter: ChannelPreviewActionButtons.name, + emitter: ChannelListItemActionButtons.name, }, }); } finally { @@ -351,7 +351,7 @@ export const defaultChannelActionSet: ChannelActionItem[] = [ ]; export const useBaseChannelActionSetFilter = (channelActionSet: ChannelActionItem[]) => { - const { channel } = useChannelPreviewContext(); + const { channel } = useChannelListItemContext(); const isDirectMessageChannel = channel.type === 'messaging' && // assuming one of the users is current user diff --git a/src/components/ChannelPreview/ChannelPreviewActionButtons.tsx b/src/components/ChannelListItem/ChannelListItemActionButtons.tsx similarity index 76% rename from src/components/ChannelPreview/ChannelPreviewActionButtons.tsx rename to src/components/ChannelListItem/ChannelListItemActionButtons.tsx index 673b914d02..f9d4a48f6f 100644 --- a/src/components/ChannelPreview/ChannelPreviewActionButtons.tsx +++ b/src/components/ChannelListItem/ChannelListItemActionButtons.tsx @@ -1,5 +1,4 @@ -import React, { type JSX } from 'react'; -import type { Channel } from 'stream-chat'; +import React, { type ComponentProps, type ComponentType, type JSX } from 'react'; import { Button } from '../Button'; import { IconDotGrid1x3Horizontal } from '../Icons'; @@ -9,25 +8,23 @@ import { ContextMenu, useDialogIsOpen, useDialogOnNearestManager } from '../Dial import { defaultChannelActionSet, useBaseChannelActionSetFilter, -} from './ChannelPreviewActionButtons.defaults'; +} from './ChannelListItemActionButtons.defaults'; import { useSplitActionSet } from '../Chat/hooks/useSplitActionSet'; +import { useChannelListItemContext } from './ChannelListItem'; -export type ChannelPreviewActionButtonsProps = { - channel: Channel; -}; +export type ChannelListItemActionButtonsProps = ComponentProps; // hack to allow empty props -interface ChannelPreviewActionButtonsInterface { - (props: ChannelPreviewActionButtonsProps): JSX.Element; +interface ChannelListItemActionButtonsInterface { + (props: ChannelListItemActionButtonsProps): JSX.Element; getDialogId: (channelId: string) => string; name: string; } -export const ChannelPreviewActionButtons: ChannelPreviewActionButtonsInterface = ({ - channel, -}) => { +export const ChannelListItemActionButtons: ChannelListItemActionButtonsInterface = () => { + const { channel } = useChannelListItemContext(); const [referenceElement, setReferenceElement] = React.useState(null); - const dialogId = ChannelPreviewActionButtons.getDialogId( + const dialogId = ChannelListItemActionButtons.getDialogId( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion channel.id!, ); @@ -82,5 +79,5 @@ export const ChannelPreviewActionButtons: ChannelPreviewActionButtonsInterface = ); }; -ChannelPreviewActionButtons.getDialogId = (channelId: string) => +ChannelListItemActionButtons.getDialogId = (channelId: string) => `channel-action-buttons-${channelId}`; diff --git a/src/components/ChannelPreview/ChannelPreviewTimestamp.tsx b/src/components/ChannelListItem/ChannelListItemTimestamp.tsx similarity index 80% rename from src/components/ChannelPreview/ChannelPreviewTimestamp.tsx rename to src/components/ChannelListItem/ChannelListItemTimestamp.tsx index e6515e85a2..0a25411a49 100644 --- a/src/components/ChannelPreview/ChannelPreviewTimestamp.tsx +++ b/src/components/ChannelListItem/ChannelListItemTimestamp.tsx @@ -4,13 +4,13 @@ import type { LocalMessage } from 'stream-chat'; import { useTranslationContext } from '../../context/TranslationContext'; import { getDateString, isDate } from '../../i18n/utils'; -export type ChannelPreviewTimestampProps = { +export type ChannelListItemTimestampProps = { /** The last message in the channel, used to extract the timestamp */ lastMessage?: LocalMessage; }; -export function ChannelPreviewTimestamp({ lastMessage }: ChannelPreviewTimestampProps) { - const { t, tDateTimeParser } = useTranslationContext('ChannelPreviewTimestamp'); +export function ChannelListItemTimestamp({ lastMessage }: ChannelListItemTimestampProps) { + const { t, tDateTimeParser } = useTranslationContext('ChannelListItemTimestamp'); const timestamp = lastMessage?.created_at; const normalizedTimestamp = diff --git a/src/components/ChannelPreview/ChannelPreviewMessenger.tsx b/src/components/ChannelListItem/ChannelListItemUI.tsx similarity index 81% rename from src/components/ChannelPreview/ChannelPreviewMessenger.tsx rename to src/components/ChannelListItem/ChannelListItemUI.tsx index fd6a54d4d5..664211a413 100644 --- a/src/components/ChannelPreview/ChannelPreviewMessenger.tsx +++ b/src/components/ChannelListItem/ChannelListItemUI.tsx @@ -1,17 +1,17 @@ import React, { useRef } from 'react'; import clsx from 'clsx'; -import { ChannelPreviewActionButtons as DefaultChannelPreviewActionButtons } from './ChannelPreviewActionButtons'; -import { ChannelPreviewTimestamp } from './ChannelPreviewTimestamp'; +import { ChannelListItemActionButtons as DefaultChannelListItemActionButtons } from './ChannelListItemActionButtons'; +import { ChannelListItemTimestamp } from './ChannelListItemTimestamp'; import { ChannelAvatar as DefaultChannelAvatar } from '../Avatar'; import { Badge } from '../Badge'; import { IconMute } from '../Icons'; import { useComponentContext } from '../../context'; -import type { ChannelPreviewUIComponentProps } from './ChannelPreview'; +import type { ChannelListItemUIProps } from './ChannelListItem'; import { SummarizedMessagePreview } from '../SummarizedMessagePreview'; -const UnMemoizedChannelPreviewMessenger = (props: ChannelPreviewUIComponentProps) => { +const UnMemoizedChannelListItemUI = (props: ChannelListItemUIProps) => { const { active, Avatar = DefaultChannelAvatar, @@ -29,7 +29,7 @@ const UnMemoizedChannelPreviewMessenger = (props: ChannelPreviewUIComponentProps watchers, } = props; - const { ChannelPreviewActionButtons = DefaultChannelPreviewActionButtons } = + const { ChannelListItemActionButtons = DefaultChannelListItemActionButtons } = useComponentContext(); const channelPreviewButton = useRef(null); @@ -50,7 +50,7 @@ const UnMemoizedChannelPreviewMessenger = (props: ChannelPreviewUIComponentProps return (
- +
- + {typeof unread === 'number' && unread > 0 && ( {unread} @@ -102,6 +102,6 @@ const UnMemoizedChannelPreviewMessenger = (props: ChannelPreviewUIComponentProps * Used as preview component for channel item in [ChannelList](#channellist) component. * Its best suited for messenger type chat. */ -export const ChannelPreviewMessenger = React.memo( - UnMemoizedChannelPreviewMessenger, -) as typeof UnMemoizedChannelPreviewMessenger; +export const ChannelListItemUI = React.memo( + UnMemoizedChannelListItemUI, +) as typeof UnMemoizedChannelListItemUI; diff --git a/src/components/ChannelPreview/__tests__/ChannelPreview.test.js b/src/components/ChannelListItem/__tests__/ChannelListItem.test.js similarity index 99% rename from src/components/ChannelPreview/__tests__/ChannelPreview.test.js rename to src/components/ChannelListItem/__tests__/ChannelListItem.test.js index 087360b04e..80483655df 100644 --- a/src/components/ChannelPreview/__tests__/ChannelPreview.test.js +++ b/src/components/ChannelListItem/__tests__/ChannelListItem.test.js @@ -3,7 +3,7 @@ import { act, render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import { ChannelAvatar } from '../../Avatar'; -import { ChannelPreview } from '../ChannelPreview'; +import { ChannelPreview } from '../ChannelListItem'; import { Chat } from '../../Chat'; import { ChatContext } from '../../../context/ChatContext'; @@ -77,7 +77,7 @@ describe('ChannelPreview', () => { setActiveChannel: () => jest.fn(), }} > - + , ); diff --git a/src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js b/src/components/ChannelListItem/__tests__/ChannelListItemUI.test.js similarity index 96% rename from src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js rename to src/components/ChannelListItem/__tests__/ChannelListItemUI.test.js index eb93a97cfc..f2661f4a83 100644 --- a/src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js +++ b/src/components/ChannelListItem/__tests__/ChannelListItemUI.test.js @@ -11,7 +11,7 @@ import { useMockedApis, } from 'mock-builders'; -import { ChannelPreviewMessenger } from '../ChannelPreviewMessenger'; +import { ChannelListItemUI } from '../ChannelListItemUI'; import { ChatProvider, ComponentProvider } from '../../../context'; expect.extend(toHaveNoViolations); @@ -27,7 +27,7 @@ describe('ChannelPreviewMessenger', () => {
- ([]); const [selectedDuration, setSelectedDuration] = useState(undefined); const [geolocationPosition, setGeolocationPosition] = diff --git a/src/components/Location/__tests__/ShareLocationDialog.test.js b/src/components/Location/__tests__/ShareLocationDialog.test.js index b089e93244..052ad8d766 100644 --- a/src/components/Location/__tests__/ShareLocationDialog.test.js +++ b/src/components/Location/__tests__/ShareLocationDialog.test.js @@ -5,7 +5,7 @@ import { Channel } from '../../Channel'; import { Chat } from '../../Chat'; import { initClientWithChannels } from '../../../mock-builders'; import ShareLocationDialog from '../ShareLocationDialog'; -import { useMessageComposer } from '../../MessageInput'; +import { useMessageComposerController } from '../../MessageInput'; jest.mock('../../MessageInput/hooks/useMessageComposer', () => ({ useMessageComposer: jest.fn().mockReturnValue({ @@ -158,7 +158,7 @@ describe('ShareLocationDialog', () => { it('closes the dialog', async () => { await renderComponent({ props: { close } }); - const messageComposer = useMessageComposer(); + const messageComposer = useMessageComposerController(); await act(async () => { await fireEvent.click(screen.getByText('Cancel')); }); @@ -176,7 +176,7 @@ describe('ShareLocationDialog', () => { ); const { justRerender } = await renderComponent({ props: { close } }); - const messageComposer = useMessageComposer(); + const messageComposer = useMessageComposerController(); const coords = { latitude: 1, longitude: 10 }; await act(() => { callbacks.onSuccess({ coords }); @@ -205,7 +205,7 @@ describe('ShareLocationDialog', () => { ); const { justRerender } = await renderComponent({ props: { close } }); - const messageComposer = useMessageComposer(); + const messageComposer = useMessageComposerController(); const coords = { latitude: 1, longitude: 10 }; await act(() => { callbacks.onSuccess({ coords }); diff --git a/src/components/MediaRecorder/AudioRecorder/AudioRecorder.tsx b/src/components/MediaRecorder/AudioRecorder/AudioRecorder.tsx index c6b8269d50..1de5232459 100644 --- a/src/components/MediaRecorder/AudioRecorder/AudioRecorder.tsx +++ b/src/components/MediaRecorder/AudioRecorder/AudioRecorder.tsx @@ -2,14 +2,14 @@ import React, { useMemo } from 'react'; import { AudioRecordingPlayback } from './AudioRecordingPlayback'; import { AudioRecordingPreview } from './AudioRecordingPreview'; import { MediaRecordingState } from '../classes'; -import { useMessageInputContext } from '../../../context/MessageInputContext'; +import { useMessageComposerContext } from '../../../context/MessageComposerContext'; import { AudioRecorderRecordingControls } from './AudioRecorderRecordingControls'; import { isStopped } from './recordingStateIdentity'; export const AudioRecorder = () => { const { recordingController: { recorder, recording, recordingState }, - } = useMessageInputContext(); + } = useMessageComposerContext(); const state = useMemo( () => ({ diff --git a/src/components/MediaRecorder/AudioRecorder/AudioRecorderRecordingControls.tsx b/src/components/MediaRecorder/AudioRecorder/AudioRecorderRecordingControls.tsx index 42f07c4efb..a947da035b 100644 --- a/src/components/MediaRecorder/AudioRecorder/AudioRecorderRecordingControls.tsx +++ b/src/components/MediaRecorder/AudioRecorder/AudioRecorderRecordingControls.tsx @@ -1,14 +1,14 @@ -import { CheckSignIcon, LoadingIndicatorIcon } from '../../MessageInput'; +import { CheckSignIcon, LoadingIndicatorIcon } from '../../MessageComposer'; import { IconMicrophone, IconPause, IconTrashBin } from '../../Icons'; import React from 'react'; -import { useMessageInputContext } from '../../../context'; +import { useMessageComposerContext } from '../../../context'; import { isRecording } from './recordingStateIdentity'; import { Button } from '../../Button'; const ToggleRecordingButton = () => { const { recordingController: { recorder, recordingState }, - } = useMessageInputContext(); + } = useMessageComposerContext(); return (
)} { <> {processedMessages.map((_, numItemsPrepended) => { const virtuosoContext = { - Message: MessageSimple, + Message: MessageUI, messageGroupStyles: {}, numItemsPrepended, ownMessagesDeliveredToOthers: {}, diff --git a/src/components/Poll/PollCreationDialog/MultipleAnswersField.tsx b/src/components/Poll/PollCreationDialog/MultipleAnswersField.tsx index 132320c908..3f715759a5 100644 --- a/src/components/Poll/PollCreationDialog/MultipleAnswersField.tsx +++ b/src/components/Poll/PollCreationDialog/MultipleAnswersField.tsx @@ -3,7 +3,7 @@ import React, { useMemo, useState } from 'react'; import { NumericInput } from '../../Form/NumericInput'; import { SwitchField, SwitchFieldLabel } from '../../Form/SwitchField'; import { useTranslationContext } from '../../../context'; -import { useMessageComposer } from '../../MessageInput'; +import { useMessageComposerController } from '../../MessageComposer'; import { useStateStore } from '../../../store'; import type { PollComposerState } from 'stream-chat'; @@ -15,7 +15,7 @@ const pollComposerStateSelector = (state: PollComposerState) => ({ export const MultipleAnswersField = () => { const { t } = useTranslationContext(); - const { pollComposer } = useMessageComposer(); + const { pollComposer } = useMessageComposerController(); const { enforce_unique_vote, error, max_votes_allowed } = useStateStore( pollComposer.state, pollComposerStateSelector, diff --git a/src/components/Poll/PollCreationDialog/NameField.tsx b/src/components/Poll/PollCreationDialog/NameField.tsx index 550e366c88..b734e8a39d 100644 --- a/src/components/Poll/PollCreationDialog/NameField.tsx +++ b/src/components/Poll/PollCreationDialog/NameField.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { TextInput } from '../../Form'; import { useTranslationContext } from '../../../context'; -import { useMessageComposer } from '../../MessageInput'; +import { useMessageComposerController } from '../../MessageComposer'; import { useStateStore } from '../../../store'; import type { PollComposerState } from 'stream-chat'; @@ -12,7 +12,7 @@ const pollComposerStateSelector = (state: PollComposerState) => ({ export const NameField = () => { const { t } = useTranslationContext(); - const { pollComposer } = useMessageComposer(); + const { pollComposer } = useMessageComposerController(); const { error, name } = useStateStore(pollComposer.state, pollComposerStateSelector); const knownValidationErrors = useMemo>( () => ({ diff --git a/src/components/Poll/PollCreationDialog/OptionFieldSet.tsx b/src/components/Poll/PollCreationDialog/OptionFieldSet.tsx index 712ffacb4f..5f099ec1fa 100644 --- a/src/components/Poll/PollCreationDialog/OptionFieldSet.tsx +++ b/src/components/Poll/PollCreationDialog/OptionFieldSet.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import React, { useCallback, useMemo } from 'react'; import { TextInput } from '../../Form/TextInput'; import { useTranslationContext } from '../../../context'; -import { useMessageComposer } from '../../MessageInput'; +import { useMessageComposerController } from '../../MessageComposer'; import { useStateStore } from '../../../store'; import type { PollComposerState } from 'stream-chat'; import { IconCircleMinus, IconDotGrid2x3 } from '../../Icons'; @@ -15,7 +15,7 @@ const pollComposerStateSelector = (state: PollComposerState) => ({ }); export const OptionFieldSet = () => { - const { pollComposer } = useMessageComposer(); + const { pollComposer } = useMessageComposerController(); const { errors, options } = useStateStore( pollComposer.state, pollComposerStateSelector, diff --git a/src/components/Poll/PollCreationDialog/PollCreationDialog.tsx b/src/components/Poll/PollCreationDialog/PollCreationDialog.tsx index d5636eb98b..57fddbb3f0 100644 --- a/src/components/Poll/PollCreationDialog/PollCreationDialog.tsx +++ b/src/components/Poll/PollCreationDialog/PollCreationDialog.tsx @@ -7,7 +7,7 @@ import { OptionFieldSet } from './OptionFieldSet'; import { PollCreationDialogControls } from './PollCreationDialogControls'; import { Prompt } from '../../Dialog'; import { SwitchField } from '../../Form/SwitchField'; -import { useMessageComposer } from '../../MessageInput'; +import { useMessageComposerController } from '../../MessageComposer'; import { useTranslationContext } from '../../../context'; import { useStateStore } from '../../../store'; @@ -23,7 +23,7 @@ const pollComposerStateSelector = (state: PollComposerState) => ({ export const PollCreationDialog = ({ close }: PollCreationDialogProps) => { const { t } = useTranslationContext(); - const { pollComposer } = useMessageComposer(); + const { pollComposer } = useMessageComposerController(); const { allow_answers, allow_user_suggested_options, voting_visibility } = useStateStore(pollComposer.state, pollComposerStateSelector); diff --git a/src/components/Poll/PollCreationDialog/PollCreationDialogControls.tsx b/src/components/Poll/PollCreationDialog/PollCreationDialogControls.tsx index 1daa055d68..f3aebf193b 100644 --- a/src/components/Poll/PollCreationDialog/PollCreationDialogControls.tsx +++ b/src/components/Poll/PollCreationDialog/PollCreationDialogControls.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { useCanCreatePoll, useMessageComposer } from '../../MessageInput'; -import { useMessageInputContext, useTranslationContext } from '../../../context'; +import { useCanCreatePoll, useMessageComposerController } from '../../MessageComposer'; +import { useMessageComposerContext, useTranslationContext } from '../../../context'; import clsx from 'clsx'; import { IconPaperPlane } from '../../Icons'; import { Prompt } from '../../Dialog'; @@ -13,8 +13,8 @@ export const PollCreationDialogControls = ({ close, }: PollCreationDialogControlsProps) => { const { t } = useTranslationContext('PollCreationDialogControls'); - const { handleSubmit: handleSubmitMessage } = useMessageInputContext(); - const messageComposer = useMessageComposer(); + const { handleSubmit: handleSubmitMessage } = useMessageComposerContext(); + const messageComposer = useMessageComposerController(); const canCreatePoll = useCanCreatePoll(); return ( diff --git a/src/components/Poll/__tests__/PollCreationDialog.test.js b/src/components/Poll/__tests__/PollCreationDialog.test.js index e3fc2793f8..6f2c9b0ef0 100644 --- a/src/components/Poll/__tests__/PollCreationDialog.test.js +++ b/src/components/Poll/__tests__/PollCreationDialog.test.js @@ -2,7 +2,7 @@ import React from 'react'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import { PollCreationDialog } from '../PollCreationDialog'; -import { MessageInputContextProvider } from '../../../context'; +import { MessageComposerContextProvider } from '../../../context'; import { generateUser, initClientWithChannels } from '../../../mock-builders'; import { Chat } from '../../Chat'; import { Channel } from '../../Channel'; @@ -43,9 +43,9 @@ const renderComponent = async ({ channel: customChannel, client: customClient } result = render( - + - + , ); diff --git a/src/components/ReactFileUtilities/UploadButton.tsx b/src/components/ReactFileUtilities/UploadButton.tsx index 081668871d..62fe4239e5 100644 --- a/src/components/ReactFileUtilities/UploadButton.tsx +++ b/src/components/ReactFileUtilities/UploadButton.tsx @@ -4,10 +4,10 @@ import type { ComponentProps } from 'react'; import React, { forwardRef, useCallback, useMemo } from 'react'; import { useHandleFileChangeWrapper } from './utils'; -import { useMessageInputContext, useTranslationContext } from '../../context'; -import { useMessageComposer } from '../MessageInput'; -import { useAttachmentManagerState } from '../MessageInput/hooks/useAttachmentManagerState'; -import { useIsCooldownActive } from '../MessageInput/hooks/useIsCooldownActive'; +import { useMessageComposerContext, useTranslationContext } from '../../context'; +import { useMessageComposerController } from '../MessageComposer'; +import { useAttachmentManagerState } from '../MessageComposer/hooks/useAttachmentManagerState'; +import { useIsCooldownActive } from '../MessageComposer/hooks/useIsCooldownActive'; import { useStateStore } from '../../store'; import type { MessageComposerConfig } from 'stream-chat'; import type { PartialSelected } from '../../types/types'; @@ -51,8 +51,8 @@ export const UploadFileInput = forwardRef(function UploadFileInput( ref: React.ForwardedRef, ) { const { t } = useTranslationContext('UploadFileInput'); - const { textareaRef } = useMessageInputContext(); - const messageComposer = useMessageComposer(); + const { textareaRef } = useMessageComposerContext(); + const messageComposer = useMessageComposerController(); const { attachmentManager } = messageComposer; const { isUploadEnabled } = useAttachmentManagerState(); const { acceptedFiles, maxNumberOfFilesPerMessage } = useStateStore( diff --git a/src/components/Reactions/ReactionsList.tsx b/src/components/Reactions/MessageReactions.tsx similarity index 95% rename from src/components/Reactions/ReactionsList.tsx rename to src/components/Reactions/MessageReactions.tsx index 66ff15478d..af3801f179 100644 --- a/src/components/Reactions/ReactionsList.tsx +++ b/src/components/Reactions/MessageReactions.tsx @@ -27,7 +27,7 @@ import type { } from './types'; import { DialogAnchor, useDialogOnNearestManager } from '../Dialog'; -export type ReactionsListProps = Partial< +export type MessageReactionsProps = Partial< Pick > & { /** An array of the own reaction objects to distinguish own reactions visually */ @@ -78,7 +78,7 @@ const FragmentOrButton = ({ return <>{children}; }; -const UnMemoizedReactionsList = (props: ReactionsListProps) => { +const UnMemoizedMessageReactions = (props: MessageReactionsProps) => { const { flipHorizontalPosition = false, handleFetchReactions, @@ -95,10 +95,10 @@ const UnMemoizedReactionsList = (props: ReactionsListProps) => { const [selectedReactionType, setSelectedReactionType] = useState( null, ); - const { t } = useTranslationContext('ReactionsList'); + const { t } = useTranslationContext('MessageReactions'); const { MessageReactionsDetail = DefaultMessageReactionsDetail } = useComponentContext(); - const { isMyMessage, message } = useMessageContext('ReactionsList'); + const { isMyMessage, message } = useMessageContext('MessageReactions'); const divRef = useRef>(null); const dialogId = `message-reactions-detail-${message.id}`; @@ -233,6 +233,6 @@ const UnMemoizedReactionsList = (props: ReactionsListProps) => { /** * Component that displays a list of reactions on a message. */ -export const ReactionsList = React.memo( - UnMemoizedReactionsList, -) as typeof UnMemoizedReactionsList; +export const MessageReactions = React.memo( + UnMemoizedMessageReactions, +) as typeof UnMemoizedMessageReactions; diff --git a/src/components/Reactions/SimpleReactionsList.tsx b/src/components/Reactions/SimpleReactionsList.tsx deleted file mode 100644 index 6b87b1de25..0000000000 --- a/src/components/Reactions/SimpleReactionsList.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import React, { useState } from 'react'; -import clsx from 'clsx'; - -import type { ReactionGroupResponse, ReactionResponse } from 'stream-chat'; -import type { MessageContextValue } from '../../context/MessageContext'; -import { useMessageContext } from '../../context/MessageContext'; -import { useProcessReactions } from './hooks/useProcessReactions'; -import { useEnterLeaveHandlers } from '../Tooltip/hooks'; -import { PopperTooltip } from '../Tooltip'; - -import type { ReactionOptions } from './reactionOptions'; - -type WithTooltipProps = { - title: React.ReactNode; - onMouseEnter?: React.MouseEventHandler; - onMouseLeave?: React.MouseEventHandler; -}; - -const WithTooltip = ({ - children, - onMouseEnter, - onMouseLeave, - title, -}: PropsWithChildren) => { - const [referenceElement, setReferenceElement] = useState(null); - const { handleEnter, handleLeave, tooltipVisible } = useEnterLeaveHandlers({ - onMouseEnter, - onMouseLeave, - }); - - return ( - <> - - {title} - - - {children} - - - ); -}; - -export type SimpleReactionsListProps = Partial< - Pick -> & { - /** An array of the own reaction objects to distinguish own reactions visually */ - own_reactions?: ReactionResponse[]; - /** - * An object that keeps track of the count of each type of reaction on a message - * @deprecated This override value is no longer taken into account. Use `reaction_groups` to override reaction counts instead. - * */ - reaction_counts?: Record; - /** An object containing summary for each reaction type on a message */ - reaction_groups?: Record; - /** A list of the currently supported reactions on a message */ - reactionOptions?: ReactionOptions; - /** An array of the reaction objects to display in the list */ - reactions?: ReactionResponse[]; -}; - -const UnMemoizedSimpleReactionsList = (props: SimpleReactionsListProps) => { - const { handleReaction: propHandleReaction, ...rest } = props; - - const { handleReaction: contextHandleReaction } = - useMessageContext('SimpleReactionsList'); - - const { existingReactions, hasReactions, totalReactionCount } = - useProcessReactions(rest); - - const handleReaction = propHandleReaction || contextHandleReaction; - - if (!hasReactions) return null; - - return ( -
-
    - {existingReactions.map( - ({ EmojiComponent, isOwnReaction, latestReactedUserNames, reactionType }) => { - const tooltipContent = latestReactedUserNames.join(', '); - - return ( - EmojiComponent && ( -
  • handleReaction(reactionType, event)} - onKeyUp={(event) => handleReaction(reactionType, event)} - > - - - -
  • - ) - ); - }, - )} - { -
  • - {totalReactionCount} -
  • - } -
-
- ); -}; - -export const SimpleReactionsList = React.memo( - UnMemoizedSimpleReactionsList, -) as typeof UnMemoizedSimpleReactionsList; diff --git a/src/components/Reactions/__tests__/ReactionsList.test.js b/src/components/Reactions/__tests__/MessageReactions.test.js similarity index 100% rename from src/components/Reactions/__tests__/ReactionsList.test.js rename to src/components/Reactions/__tests__/MessageReactions.test.js diff --git a/src/components/Reactions/__tests__/ReactionsListModal.test.js b/src/components/Reactions/__tests__/MessageReactionsDetail.test.js similarity index 100% rename from src/components/Reactions/__tests__/ReactionsListModal.test.js rename to src/components/Reactions/__tests__/MessageReactionsDetail.test.js diff --git a/src/components/Reactions/__tests__/SimpleReactionsList.test.js b/src/components/Reactions/__tests__/SimpleReactionsList.test.js deleted file mode 100644 index 87bff07065..0000000000 --- a/src/components/Reactions/__tests__/SimpleReactionsList.test.js +++ /dev/null @@ -1,143 +0,0 @@ -import React from 'react'; -import { fireEvent, render } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import { toHaveNoViolations } from 'jest-axe'; -import { axe } from '../../../../axe-helper'; -import * as utils from '../utils/utils'; -import { SimpleReactionsList } from '../SimpleReactionsList'; -import { defaultReactionOptions } from '../reactionOptions'; -import { MessageProvider } from '../../../context/MessageContext'; - -import { generateReaction } from '../../../mock-builders'; -import { ComponentProvider } from '../../../context'; - -expect.extend(toHaveNoViolations); - -const handleReactionMock = jest.fn(); -// const loveEmojiTestId = 'emoji-love'; - -const renderComponent = ({ reaction_groups = {}, ...props }) => { - const reactions = Object.entries(reaction_groups).flatMap(([type, { count }]) => - Array(count) - .fill() - .map((_, i) => generateReaction({ type, user: { id: `${USER_ID}-${i}` } })), - ); - - return { - ...render( - - - - - , - ), - reactions, - }; -}; - -const USER_ID = 'mark'; - -describe('SimpleReactionsList', () => { - afterEach(jest.clearAllMocks); - - it('should not render anything if there are no reactions', async () => { - const { container } = renderComponent({ - reaction_counts: {}, - }); - expect(container).toBeEmptyDOMElement(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it('should render the total reaction count', async () => { - const { container, getByText } = renderComponent({ - reaction_groups: { - haha: { count: 2 }, - love: { count: 5 }, - }, - }); - const count = getByText('7'); - expect(count).toBeInTheDocument(); - expect(count).toHaveClass('str-chat__simple-reactions-list-item--last-number'); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it('should render an emoji for each type of reaction', async () => { - const reaction_groups = { - haha: { count: 2 }, - love: { count: 5 }, - }; - // force to render default fallbacks - jest.spyOn(utils, 'getImageDimensions').mockRejectedValue('Error'); - jest.spyOn(console, 'error').mockImplementation(null); - - const { container } = renderComponent({ reaction_groups }); - - expect(container).toMatchSnapshot(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it('should handle custom reaction options', async () => { - const reaction_groups = { - banana: { count: 1 }, - cowboy: { count: 2 }, - }; - - const { container } = renderComponent({ - reaction_groups, - reactionOptions: [ - { emoji: '🍌', id: 'banana' }, - { emoji: '🤠', id: 'cowboy' }, - ], - }); - - expect(container).toMatchSnapshot(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it('should call handleReaction callback if a reaction emoji is clicked', async () => { - const reaction_groups = { - love: { count: 1 }, - }; - - const { container, getByText } = renderComponent({ reaction_groups }); - - fireEvent.click(getByText('❤️')); - - expect(handleReactionMock).toHaveBeenCalledWith('love', expect.any(Object)); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it('should render a tooltip with all users that reacted a certain way if the emoji is hovered', async () => { - const reaction_groups = { - love: { count: 3 }, - }; - - const { container, getByText, queryByText, reactions } = renderComponent({ - reaction_groups, - }); - - fireEvent.mouseEnter(getByText('❤️')); - - reactions.forEach(({ user }) => { - expect(queryByText(user.name || user.id, { exact: false })).toBeInTheDocument(); - }); - - fireEvent.mouseLeave(getByText('❤️')); - - reactions.forEach(({ user }) => { - expect(queryByText(user.id, { exact: false })).not.toBeInTheDocument(); - }); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); -}); diff --git a/src/components/Reactions/__tests__/__snapshots__/SimpleReactionsList.test.js.snap b/src/components/Reactions/__tests__/__snapshots__/SimpleReactionsList.test.js.snap deleted file mode 100644 index 6e244bbe51..0000000000 --- a/src/components/Reactions/__tests__/__snapshots__/SimpleReactionsList.test.js.snap +++ /dev/null @@ -1,36 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SimpleReactionsList should handle custom reaction options 1`] = `
`; - -exports[`SimpleReactionsList should render an emoji for each type of reaction 1`] = ` -
-
-
    -
  • - - 😂 - -
  • -
  • - - ❤️ - -
  • -
  • - 7 -
  • -
-
-
-`; diff --git a/src/components/Reactions/hooks/useProcessReactions.tsx b/src/components/Reactions/hooks/useProcessReactions.tsx index 030c4283ab..b7b99f2e66 100644 --- a/src/components/Reactions/hooks/useProcessReactions.tsx +++ b/src/components/Reactions/hooks/useProcessReactions.tsx @@ -3,18 +3,17 @@ import { useCallback, useMemo } from 'react'; import { useComponentContext, useMessageContext } from '../../../context'; import { defaultReactionOptions } from '../reactionOptions'; -import type { ReactionsListProps } from '../ReactionsList'; - +import type { MessageReactionsProps } from '../MessageReactions'; import type { ReactionsComparator, ReactionSummary } from '../types'; -type SharedReactionListProps = +type UseProcessReactionsParams = Pick< + MessageReactionsProps, | 'own_reactions' | 'reaction_counts' | 'reaction_groups' | 'reactionOptions' - | 'reactions'; - -type UseProcessReactionsParams = Pick & { + | 'reactions' +> & { sortReactions?: ReactionsComparator; }; diff --git a/src/components/Reactions/index.ts b/src/components/Reactions/index.ts index 7a3c5d29fc..116026e54f 100644 --- a/src/components/Reactions/index.ts +++ b/src/components/Reactions/index.ts @@ -1,7 +1,6 @@ export * from './ReactionSelector'; -export * from './ReactionsList'; +export * from './MessageReactions'; export * from './MessageReactionsDetail'; -export * from './SimpleReactionsList'; export * from './SpriteImage'; export * from './StreamEmoji'; export * from './reactionOptions'; diff --git a/src/components/Reactions/styling/ReactionList.scss b/src/components/Reactions/styling/MessageReactions.scss similarity index 100% rename from src/components/Reactions/styling/ReactionList.scss rename to src/components/Reactions/styling/MessageReactions.scss diff --git a/src/components/Reactions/styling/index.scss b/src/components/Reactions/styling/index.scss index 0b359446d4..364ea1d65a 100644 --- a/src/components/Reactions/styling/index.scss +++ b/src/components/Reactions/styling/index.scss @@ -1,3 +1,3 @@ -@use 'ReactionList'; @use 'ReactionSelector'; +@use 'MessageReactions'; @use 'MessageReactionsDetail'; diff --git a/src/components/Search/SearchResults/SearchResultItem.tsx b/src/components/Search/SearchResults/SearchResultItem.tsx index b47fa832bd..97dd9b1a05 100644 --- a/src/components/Search/SearchResults/SearchResultItem.tsx +++ b/src/components/Search/SearchResults/SearchResultItem.tsx @@ -5,7 +5,7 @@ import type { Channel, MessageResponse, User } from 'stream-chat'; import { useSearchContext } from '../SearchContext'; import { Avatar } from '../../../components/Avatar'; -import { ChannelPreview } from '../../../components/ChannelPreview'; +import { ChannelListItem } from '../../../components/ChannelListItem'; import { useChannelListContext, useChatContext } from '../../../context'; import { DEFAULT_JUMP_TO_PAGE_SIZE } from '../../../constants/limits'; import { Timestamp } from '../../../components/Message/Timestamp'; @@ -24,7 +24,7 @@ export const ChannelSearchResultItem = ({ item }: ChannelSearchResultItemProps) }, [item, setActiveChannel, setChannels]); return ( - { const { AutocompleteSuggestionItem = DefaultSuggestionListItem } = useComponentContext(); - const { textareaRef } = useMessageInputContext(); - const messageComposer = useMessageComposer(); + const { textareaRef } = useMessageComposerContext(); + const messageComposer = useMessageComposerController(); const { textComposer } = messageComposer; const { selection, suggestions } = useStateStore( textComposer.state, diff --git a/src/components/TextareaComposer/SuggestionList/SuggestionListItem.tsx b/src/components/TextareaComposer/SuggestionList/SuggestionListItem.tsx index 95bf292c68..378b33703a 100644 --- a/src/components/TextareaComposer/SuggestionList/SuggestionListItem.tsx +++ b/src/components/TextareaComposer/SuggestionList/SuggestionListItem.tsx @@ -1,12 +1,12 @@ import clsx from 'clsx'; import { type ComponentProps, useRef } from 'react'; import React, { useCallback, useLayoutEffect } from 'react'; -import { useMessageComposer } from '../../MessageInput'; +import { useMessageComposerController } from '../../MessageComposer'; import type { TextComposerSuggestion } from 'stream-chat'; import type { UserItemProps } from './UserItem'; import type { CommandItemProps } from './CommandItem'; import type { EmoticonItemProps } from './EmoticonItem'; -import { useMessageInputContext } from '../../../context'; +import { useMessageComposerContext } from '../../../context'; export type DefaultSuggestionListItemEntity = | UserItemProps['entity'] @@ -34,8 +34,8 @@ export const SuggestionListItem = ({ onMouseEnter, ...restProps }: SuggestionItemProps) => { - const { textComposer } = useMessageComposer(); - const { textareaRef } = useMessageInputContext(); + const { textComposer } = useMessageComposerController(); + const { textareaRef } = useMessageComposerContext(); const componentRef = useRef(null); const handleSelect = useCallback(() => { diff --git a/src/components/TextareaComposer/TextareaComposer.tsx b/src/components/TextareaComposer/TextareaComposer.tsx index d323ed6960..89f9567cd1 100644 --- a/src/components/TextareaComposer/TextareaComposer.tsx +++ b/src/components/TextareaComposer/TextareaComposer.tsx @@ -7,7 +7,7 @@ import type { } from 'react'; import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import Textarea from 'react-textarea-autosize'; -import { useCooldownRemaining, useMessageComposer } from '../MessageInput'; +import { useCooldownRemaining, useMessageComposerController } from '../MessageComposer'; import type { AttachmentManagerState, MessageComposerConfig, @@ -15,7 +15,7 @@ import type { SearchSourceState, TextComposerState, } from 'stream-chat'; -import { useComponentContext, useMessageInputContext } from '../../context'; +import { useComponentContext, useMessageComposerContext } from '../../context'; import { useStateStore } from '../../store'; import { SuggestionList as DefaultSuggestionList } from './SuggestionList'; import { useTextareaPlaceholder } from './hooks/useTextareaPlaceholder'; @@ -90,7 +90,7 @@ export const TextareaComposer = ({ onPaste, shouldSubmit: shouldSubmitContext, textareaRef, - } = useMessageInputContext(); + } = useMessageComposerContext(); const cooldownRemaining = useCooldownRemaining(); const placeholder = useTextareaPlaceholder({ placeholder: placeholderProp }); @@ -100,7 +100,7 @@ export const TextareaComposer = ({ const shouldSubmit = shouldSubmitProp ?? shouldSubmitContext ?? defaultShouldSubmit; - const messageComposer = useMessageComposer(); + const messageComposer = useMessageComposerController(); const { textComposer } = messageComposer; const { selection, suggestions, text } = useStateStore( textComposer.state, diff --git a/src/components/TextareaComposer/hooks/useTextareaPlaceholder.ts b/src/components/TextareaComposer/hooks/useTextareaPlaceholder.ts index 149161e212..ce66a71821 100644 --- a/src/components/TextareaComposer/hooks/useTextareaPlaceholder.ts +++ b/src/components/TextareaComposer/hooks/useTextareaPlaceholder.ts @@ -1,8 +1,11 @@ import { useMemo } from 'react'; import type { TextComposerState } from 'stream-chat'; -import { useMessageInputContext, useTranslationContext } from '../../../context'; +import { useMessageComposerContext, useTranslationContext } from '../../../context'; import { useStateStore } from '../../../store'; -import { useCooldownRemaining, useMessageComposer } from '../../MessageInput'; +import { + useCooldownRemaining, + useMessageComposerController, +} from '../../MessageComposer'; type UseTextareaPlaceholderProps = { placeholder?: string; @@ -14,9 +17,9 @@ export const useTextareaPlaceholder = ({ placeholder, }: UseTextareaPlaceholderProps = {}) => { const { t } = useTranslationContext(); - const { additionalTextareaProps } = useMessageInputContext(); + const { additionalTextareaProps } = useMessageComposerContext(); const cooldownRemaining = useCooldownRemaining(); - const messageComposer = useMessageComposer(); + const messageComposer = useMessageComposerController(); const { command } = useStateStore( messageComposer.textComposer.state, textComposerStateSelector, diff --git a/src/components/Thread/Thread.tsx b/src/components/Thread/Thread.tsx index 031327953b..0be5a22050 100644 --- a/src/components/Thread/Thread.tsx +++ b/src/components/Thread/Thread.tsx @@ -3,8 +3,8 @@ import clsx from 'clsx'; import { LegacyThreadContext } from './LegacyThreadContext'; import { MESSAGE_ACTIONS } from '../Message'; -import type { MessageInputProps } from '../MessageInput'; -import { MessageInput, MessageInputFlat } from '../MessageInput'; +import type { MessageComposerProps } from '../MessageComposer'; +import { MessageComposer } from '../MessageComposer'; import type { MessageListProps, VirtualizedMessageListProps } from '../MessageList'; import { MessageList, VirtualizedMessageList } from '../MessageList'; import { ThreadHeader as DefaultThreadHeader } from './ThreadHeader'; @@ -24,20 +24,18 @@ import type { MessageActionsArray } from '../Message/utils'; import type { ThreadState } from 'stream-chat'; export type ThreadProps = { - /** Additional props for `MessageInput` component: [available props](https://getstream.io/chat/docs/sdk/react/message-input-components/message_input/#props) */ - additionalMessageInputProps?: MessageInputProps; + /** Additional props for `MessageComposer` component: [available props](https://getstream.io/chat/docs/sdk/react/message-composer-components/message_composer/#props) */ + additionalMessageComposerProps?: MessageComposerProps; /** Additional props for `MessageList` component: [available props](https://getstream.io/chat/docs/sdk/react/core-components/message_list/#props) */ additionalMessageListProps?: MessageListProps; /** Additional props for `Message` component of the parent message: [available props](https://getstream.io/chat/docs/sdk/react/message-components/message/#props) */ additionalParentMessageProps?: Partial; /** Additional props for `VirtualizedMessageList` component: [available props](https://getstream.io/chat/docs/sdk/react/core-components/virtualized_list/#props) */ additionalVirtualizedMessageListProps?: VirtualizedMessageListProps; - /** If true, focuses the `MessageInput` component on opening a thread */ + /** If true, focuses the `MessageComposer` component on opening a thread */ autoFocus?: boolean; /** Injects date separator components into `Thread`, defaults to `false`. To be passed to the underlying `MessageList` or `VirtualizedMessageList` components */ enableDateSeparator?: boolean; - /** Custom thread input UI component used to override the default `Input` value stored in `ComponentContext` or the [MessageInputSmall](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/MessageInputSmall.tsx) default */ - Input?: React.ComponentType; /** Custom thread message UI component used to override the default `Message` value stored in `ComponentContext` */ Message?: React.ComponentType; /** Array of allowed message actions (ex: ['edit', 'delete', 'flag', 'mute', 'pin', 'quote', 'react', 'reply']). To disable all actions, provide an empty array. */ @@ -75,13 +73,12 @@ const selector = (nextValue: ThreadState) => ({ const ThreadInner = (props: ThreadProps & { key: string }) => { const { - additionalMessageInputProps, + additionalMessageComposerProps, additionalMessageListProps, additionalParentMessageProps, additionalVirtualizedMessageListProps, autoFocus = true, enableDateSeparator = false, - Input: PropInput, Message: PropMessage, messageActions = Object.keys(MESSAGE_ACTIONS), virtualized, @@ -101,16 +98,12 @@ const ThreadInner = (props: ThreadProps & { key: string }) => { Message: ContextMessage, ThreadHead = DefaultThreadHead, ThreadHeader = DefaultThreadHeader, - ThreadInput: ContextInput, VirtualMessage, } = useComponentContext('Thread'); const { isLoadingNext, isLoadingPrev, parentMessage, replies } = useStateStore(threadInstance?.state, selector) ?? {}; - const ThreadInput = - PropInput ?? additionalMessageInputProps?.Input ?? ContextInput ?? MessageInputFlat; - const ThreadMessage = PropMessage || additionalMessageListProps?.Message; const FallbackMessage = virtualized && VirtualMessage ? VirtualMessage : ContextMessage; const MessageUIComponent = ThreadMessage || FallbackMessage; @@ -190,12 +183,10 @@ const ThreadInner = (props: ThreadProps & { key: string }) => { ? additionalVirtualizedMessageListProps : additionalMessageListProps)} /> -
diff --git a/src/components/Thread/ThreadHeader.tsx b/src/components/Thread/ThreadHeader.tsx index cb953b38c4..41aa617128 100644 --- a/src/components/Thread/ThreadHeader.tsx +++ b/src/components/Thread/ThreadHeader.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { useChannelStateContext } from '../../context/ChannelStateContext'; import { useTranslationContext } from '../../context/TranslationContext'; import { useStateStore } from '../../store'; -import { useChannelPreviewInfo } from '../ChannelPreview/hooks/useChannelPreviewInfo'; +import { useChannelPreviewInfo } from '../ChannelListItem/hooks/useChannelPreviewInfo'; import { TypingIndicatorHeader } from '../TypingIndicator/TypingIndicatorHeader'; import { useThreadContext } from '../Threads'; import { useChatContext } from '../../context/ChatContext'; diff --git a/src/components/Thread/__tests__/Thread.test.js b/src/components/Thread/__tests__/Thread.test.js index 566a9f9e1c..309696d3aa 100644 --- a/src/components/Thread/__tests__/Thread.test.js +++ b/src/components/Thread/__tests__/Thread.test.js @@ -20,7 +20,7 @@ import { } from '../../../mock-builders'; import { Message as MessageMock } from '../../Message/Message'; -import { MessageInput as MessageInputMock } from '../../MessageInput/MessageInput'; +import { MessageComposer as MessageInputMock } from '../../MessageInput/MessageComposer'; import { MessageList as MessageListMock } from '../../MessageList'; import { Thread } from '../Thread'; diff --git a/src/components/Threads/ThreadList/ThreadListItemUI.tsx b/src/components/Threads/ThreadList/ThreadListItemUI.tsx index 1f73237d1d..2340aa625a 100644 --- a/src/components/Threads/ThreadList/ThreadListItemUI.tsx +++ b/src/components/Threads/ThreadList/ThreadListItemUI.tsx @@ -6,7 +6,7 @@ import type { ComponentPropsWithoutRef } from 'react'; import { Timestamp } from '../../Message/Timestamp'; import { Avatar, type AvatarProps, AvatarStack } from '../../Avatar'; -import { useChannelPreviewInfo } from '../../ChannelPreview'; +import { useChannelPreviewInfo } from '../../ChannelListItem'; import { useChatContext, useTranslationContext } from '../../../context'; import { useThreadsViewContext } from '../../ChatView'; import { useThreadListItemContext } from './ThreadListItem'; diff --git a/src/components/index.ts b/src/components/index.ts index dbd281f9a2..d9a04f6bb5 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,7 +8,7 @@ export * from './Button'; export * from './Channel'; export * from './ChannelHeader'; export * from './ChannelList'; -export * from './ChannelPreview'; +export * from './ChannelListItem'; export * from './Search'; export * from './Chat'; export * from './ChatView'; @@ -28,7 +28,7 @@ export * from './MediaRecorder'; export * from './Message'; export * from './MessageActions'; export * from './MessageBounce'; -export * from './MessageInput'; +export * from './MessageComposer'; export * from './MessageList'; export * from './Modal'; export * from './Notifications'; diff --git a/src/context/ComponentContext.tsx b/src/context/ComponentContext.tsx index 2da80242da..3d045c228b 100644 --- a/src/context/ComponentContext.tsx +++ b/src/context/ComponentContext.tsx @@ -8,7 +8,7 @@ import { type AvatarStackProps, type BaseImageProps, type CalloutDialogProps, - type ChannelPreviewActionButtonsProps, + type ChannelListItemUIProps, type DateSeparatorProps, type EmojiSearchIndex, type EmptyStateIndicatorProps, @@ -20,11 +20,12 @@ import { type LoadingErrorIndicatorProps, type LoadingIndicatorProps, type MessageBouncePromptProps, + type MessageComposerProps, type MessageDeletedProps, type MessageEditedIndicatorProps, - type MessageInputProps, type MessageProps, type MessageReactionsDetailProps, + type MessageReactionsProps, type MessageRepliesCountButtonProps, type MessageStatusProps, type MessageTimestampProps, @@ -39,7 +40,6 @@ import { type QuotedMessagePreviewProps, type ReactionOptions, type ReactionSelectorProps, - type ReactionsListProps, type RecordingPermissionDeniedNotificationProps, type ReminderNotificationProps, type SearchResultsPresearchProps, @@ -66,9 +66,9 @@ import type { } from '../components/TextareaComposer'; import type { PropsWithChildrenOnly } from '../types/types'; -import type { StopAIGenerationButtonProps } from '../components/MessageInput/StopAIGenerationButton'; +import type { StopAIGenerationButtonProps } from '../components/MessageComposer/StopAIGenerationButton'; import type { VideoPlayerProps } from '../components/VideoPlayer'; -import type { EditedMessagePreviewProps } from '../components/MessageInput/EditedMessagePreview'; +import type { EditedMessagePreviewProps } from '../components/MessageComposer/EditedMessagePreview'; import type { FileIconProps } from '../components/FileIcon/FileIcon'; export type ComponentContextValue = { @@ -78,15 +78,15 @@ export type ComponentContextValue = { Attachment?: React.ComponentType; /** Custom UI component for the file type icon shown on file attachments (e.g. PDF, doc). Accepts same props as [FileIcon](https://github.com/GetStream/stream-chat-react/blob/master/src/components/FileIcon/FileIcon.tsx) (e.g. mimeType, size, sizeConfig). Use this to override dimensions or provide a custom icon. */ AttachmentFileIcon?: React.ComponentType; - /** Custom UI component to display an attachment previews in MessageInput, defaults to and accepts same props as: [Attachment](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/AttachmentPreviewList.tsx) */ + /** Custom UI component to display an attachment previews in MessageComposer, defaults to and accepts same props as: [Attachment](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/AttachmentPreviewList.tsx) */ AttachmentPreviewList?: React.ComponentType; - /** Custom UI component to control adding attachments to MessageInput, defaults to and accepts same props as: [AttachmentSelector](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/AttachmentSelector.tsx) */ + /** Custom UI component to control adding attachments to MessageComposer, defaults to and accepts same props as: [AttachmentSelector](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/AttachmentSelector.tsx) */ AttachmentSelector?: React.ComponentType; - /** Custom UI component for the dedicated voice recording preview slot above composer attachments (REACT-794), defaults to and accepts same props as: [VoiceRecordingPreviewSlot](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/AttachmentPreviewList/VoiceRecordingPreviewSlot.tsx) */ + /** Custom UI component for the dedicated voice recording preview slot above composer attachments (REACT-794), defaults to and accepts same props as: [VoiceRecordingPreviewSlot](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/AttachmentPreviewList/VoiceRecordingPreviewSlot.tsx) */ VoiceRecordingPreviewSlot?: React.ComponentType; /** Custom UI component for contents of attachment selector initiation button */ AttachmentSelectorInitiationButtonContents?: React.ComponentType; - /** Custom UI component to display AudioRecorder in MessageInput, defaults to and accepts same props as: [AudioRecorder](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/AudioRecorder.tsx) */ + /** Custom UI component to display AudioRecorder in MessageComposer, defaults to and accepts same props as: [AudioRecorder](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/AudioRecorder.tsx) */ AudioRecorder?: React.ComponentType; /** Optional UI component to override the default suggestion Item component, defaults to and accepts same props as: [Item](https://github.com/GetStream/stream-chat-react/blob/master/src/components/AutoCompleteTextarea/Item.js) */ AutocompleteSuggestionItem?: React.ComponentType; @@ -102,21 +102,23 @@ export type ComponentContextValue = { CalloutDialog?: React.ComponentType; /** Custom UI component shown instead of the image when it fails to load, defaults to and accepts same props as: [ImagePlaceholder](https://github.com/GetStream/stream-chat-react/blob/master/src/components/BaseImage/ImagePlaceholder.tsx) */ ImagePlaceholder?: React.ComponentType; - /** Custom UI component to display set of action buttons within `ChannelPreviewMessenger` component, accepts same props as: [ChannelPreviewActionButtons](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelList/ChannelPreviewActionButtons.tsx) */ - ChannelPreviewActionButtons?: React.ComponentType; - /** Custom UI component to display command chip, defaults to and accepts same props as: [CommandChip](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/CommandChip.tsx) */ + /** Custom UI component to display set of action buttons within `ChannelPreviewMessenger` component, accepts same props as: [ChannelListItemActionButtons](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelList/ChannelListItemActionButtons.tsx) */ + ChannelListItemActionButtons?: React.ComponentType; + /** Custom UI component to display the channel preview in the list, defaults to and accepts same props as: [ChannelListItemUI](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelPreview/ChannelListItemUI.tsx) */ + ChannelListItemUI?: React.ComponentType; + /** Custom UI component to display command chip, defaults to and accepts same props as: [CommandChip](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/CommandChip.tsx) */ CommandChip?: React.ComponentType; - /** Custom UI component to display the slow mode cooldown timer, defaults to and accepts same props as: [CooldownTimer](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/CooldownTimer.tsx) */ + /** Custom UI component to display the slow mode cooldown timer, defaults to and accepts same props as: [CooldownTimer](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/CooldownTimer.tsx) */ CooldownTimer?: React.ComponentType; /** Custom UI component for date separators, defaults to and accepts same props as: [DateSeparator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/DateSeparator.tsx) */ DateSeparator?: React.ComponentType; - /** Custom UI component to display the contents on file drag-and-drop overlay, defaults to and accepts same props as: [FileDragAndDropContent](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/WithDragAndDropUpload.tsx) */ + /** Custom UI component to display the contents on file drag-and-drop overlay, defaults to and accepts same props as: [FileDragAndDropContent](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/WithDragAndDropUpload.tsx) */ FileDragAndDropContent?: React.ComponentType; - /** Custom UI component to override default preview of edited message, defaults to and accepts same props as: [EditedMessagePreview](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/EditedMessagePreview.tsx) */ + /** Custom UI component to override default preview of edited message, defaults to and accepts same props as: [EditedMessagePreview](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/EditedMessagePreview.tsx) */ EditedMessagePreview?: React.ComponentType; - /** Custom UI component for rendering button with emoji picker in MessageInput */ + /** Custom UI component for rendering button with emoji picker in MessageComposer */ EmojiPicker?: React.ComponentType; - /** Mechanism to be used with autocomplete and text replace features of the `MessageInput` component, see [emoji-mart `SearchIndex`](https://github.com/missive/emoji-mart#%EF%B8%8F%EF%B8%8F-headless-search) */ + /** Mechanism to be used with autocomplete and text replace features of the `MessageComposer` component, see [emoji-mart `SearchIndex`](https://github.com/missive/emoji-mart#%EF%B8%8F%EF%B8%8F-headless-search) */ emojiSearchIndex?: EmojiSearchIndex; /** Custom UI component to be displayed when the `MessageList` is empty, defaults to and accepts same props as: [EmptyStateIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/EmptyStateIndicator/EmptyStateIndicator.tsx) */ EmptyStateIndicator?: React.ComponentType; @@ -126,15 +128,15 @@ export type ComponentContextValue = { GiphyPreviewMessage?: React.ComponentType; /** Custom UI component to render at the top of the `MessageList` */ HeaderComponent?: React.ComponentType; - /** Custom UI component handling how the message input is rendered, defaults to and accepts the same props as [MessageInputFlat](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/MessageInputFlat.tsx) */ - Input?: React.ComponentType; - /** Custom component to render link previews in message input **/ + /** Custom UI component handling how the message composer is rendered, defaults to and accepts the same props as [MessageComposerUI](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/MessageComposerUI.tsx) */ + MessageComposerUI?: React.ComponentType; + /** Custom component to render link previews in message composer **/ LinkPreviewList?: React.ComponentType; /** Custom UI component to be shown if the channel query fails, defaults to and accepts same props as: [LoadingErrorIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Loading/LoadingErrorIndicator.tsx) */ LoadingErrorIndicator?: React.ComponentType; /** Custom UI component to render while the `MessageList` is loading new messages, defaults to and accepts same props as: [LoadingIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Loading/LoadingIndicator.tsx) */ LoadingIndicator?: React.ComponentType; - /** Custom UI component to display a message in the standard `MessageList`, defaults to and accepts the same props as: [MessageSimple](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageSimple.tsx) */ + /** Custom UI component to display a message in the standard `MessageList`, defaults to and accepts the same props as: [MessageUI](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageUI.tsx) */ Message?: React.ComponentType; /** Custom UI component for message actions popup, accepts no props, all the defaults are set within [MessageActions (unstable)](https://github.com/GetStream/stream-chat-react/blob/master/src/experimental/MessageActions/MessageActions.tsx) */ MessageActions?: React.ComponentType; @@ -179,14 +181,14 @@ export type ComponentContextValue = { PollOptionSelector?: React.ComponentType; /** Custom UI component to override quoted message UI on a sent message, defaults to and accepts same props as: [QuotedMessage](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/QuotedMessage.tsx) */ QuotedMessage?: React.ComponentType; - /** Custom UI component to override the message input's quoted message preview, defaults to and accepts same props as: [QuotedMessagePreview](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/QuotedMessagePreview.tsx) */ + /** Custom UI component to override the message input's quoted message preview, defaults to and accepts same props as: [QuotedMessagePreview](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/QuotedMessagePreview.tsx) */ QuotedMessagePreview?: React.ComponentType; /** Custom reaction options to be applied to ReactionSelector, ReactionList and SimpleReactionList components */ reactionOptions?: ReactionOptions; /** Custom UI component to display the reaction selector, defaults to and accepts same props as: [ReactionSelector](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Reactions/ReactionSelector.tsx) */ ReactionSelector?: React.ForwardRefExoticComponent; - /** Custom UI component to display the list of reactions on a message, defaults to and accepts same props as: [ReactionsList](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Reactions/ReactionsList.tsx) */ - ReactionsList?: React.ComponentType; + /** Custom UI component to display the list of reactions on a message, defaults to and accepts same props as: [MessageReactions](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Reactions/MessageReactions.tsx) */ + MessageReactions?: React.ComponentType; /** Custom UI component to display the reactions modal, defaults to and accepts same props as: [MessageReactionsDetail](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Reactions/MessageReactionsDetail.tsx) */ MessageReactionsDetail?: React.ComponentType; RecordingPermissionDeniedNotification?: React.ComponentType; @@ -218,9 +220,9 @@ export type ComponentContextValue = { SearchSourceResultsHeader?: React.ComponentType; /** Custom component to display the search source results UI during the search query execution, defaults to and accepts same props as: [SearchSourceResultsLoadingIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Search/SearchResults/SearchSourceResultsLoadingIndicator.tsx) */ SearchSourceResultsLoadingIndicator?: React.ComponentType; - /** Custom UI component for send button, defaults to and accepts same props as: [SendButton](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/icons.tsx) */ + /** Custom UI component for send button, defaults to and accepts same props as: [SendButton](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/icons.tsx) */ SendButton?: React.ComponentType; - /** Custom UI component checkbox that indicates message to be sent to main channel, defaults to and accepts same props as: [SendToChannelCheckbox](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/SendToChannelCheckbox.tsx) */ + /** Custom UI component checkbox that indicates message to be sent to main channel, defaults to and accepts same props as: [SendToChannelCheckbox](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageComposer/SendToChannelCheckbox.tsx) */ SendToChannelCheckbox?: React.ComponentType; /** Custom UI component to render the location sharing dialog, defaults to and accepts same props as: [ShareLocationDialog](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Location/ShareLocationDialog.tsx) */ ShareLocationDialog?: React.ComponentType; @@ -230,11 +232,10 @@ export type ComponentContextValue = { StreamedMessageText?: React.ComponentType; /** Custom UI component to handle message text input, defaults to and accepts same props as [TextareaComposer](https://github.com/GetStream/stream-chat-react/blob/master/src/components/TextareaComposer/TextareaComposer.tsx) */ TextareaComposer?: React.ComponentType; - /** Custom UI component that displays thread's parent or other message at the top of the `MessageList`, defaults to and accepts same props as [MessageSimple](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageSimple.tsx) */ + /** Custom UI component that displays thread's parent or other message at the top of the `MessageList`, defaults to and accepts same props as [MessageUI](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageUI.tsx) */ ThreadHead?: React.ComponentType; /** Custom UI component to display the header of a `Thread`, defaults to and accepts same props as: [DefaultThreadHeader](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Thread/Thread.tsx) */ ThreadHeader?: React.ComponentType; - ThreadInput?: React.ComponentType; ThreadListEmptyPlaceholder?: React.ComponentType; ThreadListItem?: React.ComponentType; ThreadListItemUI?: React.ComponentType; diff --git a/src/context/MessageBounceContext.tsx b/src/context/MessageBounceContext.tsx index d85ca9d06b..2cc859adf1 100644 --- a/src/context/MessageBounceContext.tsx +++ b/src/context/MessageBounceContext.tsx @@ -2,7 +2,7 @@ import type { ReactEventHandler } from 'react'; import React, { createContext, useCallback, useContext, useMemo } from 'react'; import { useMessageContext } from './MessageContext'; import { useChannelActionContext } from './ChannelActionContext'; -import { isMessageBounced, useMessageComposer } from '../components'; +import { isMessageBounced, useMessageComposerController } from '../components'; import type { LocalMessage } from 'stream-chat'; import type { PropsWithChildrenOnly } from '../types/types'; @@ -32,7 +32,7 @@ export function useMessageBounceContext(componentName?: string) { } export function MessageBounceProvider({ children }: PropsWithChildrenOnly) { - const messageComposer = useMessageComposer(); + const messageComposer = useMessageComposerController(); const { handleRetry: doHandleRetry, message } = useMessageContext( 'MessageBounceProvider', ); diff --git a/src/context/MessageComposerContext.tsx b/src/context/MessageComposerContext.tsx new file mode 100644 index 0000000000..c5de918e38 --- /dev/null +++ b/src/context/MessageComposerContext.tsx @@ -0,0 +1,38 @@ +import React, { createContext, useContext } from 'react'; +import type { PropsWithChildren } from 'react'; + +import type { MessageComposerProps } from '../components/MessageComposer'; +import type { UseMessageComposerBindingsParams } from '../components/MessageComposer/hooks/useMessageComposerBindings'; + +export type MessageComposerContextValue = UseMessageComposerBindingsParams & + Omit; + +export const MessageComposerContext = createContext< + UseMessageComposerBindingsParams | undefined +>(undefined); + +export const MessageComposerContextProvider = ({ + children, + value, +}: PropsWithChildren<{ + value: MessageComposerContextValue; +}>) => ( + + {children} + +); + +export const useMessageComposerContext = ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + componentName?: string, +) => { + const contextValue = useContext(MessageComposerContext); + + if (!contextValue) { + return {} as MessageComposerContextValue; + } + + return contextValue as unknown as MessageComposerContextValue; +}; diff --git a/src/context/MessageInputContext.tsx b/src/context/MessageInputContext.tsx deleted file mode 100644 index 5dfe0ebb4f..0000000000 --- a/src/context/MessageInputContext.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { createContext, useContext } from 'react'; -import type { PropsWithChildren } from 'react'; - -import type { MessageInputProps } from '../components/MessageInput'; -import type { MessageInputHookProps } from '../components/MessageInput/hooks/useMessageInputControls'; - -export type MessageInputContextValue = MessageInputHookProps & - Omit; - -export const MessageInputContext = createContext( - undefined, -); - -export const MessageInputContextProvider = ({ - children, - value, -}: PropsWithChildren<{ - value: MessageInputContextValue; -}>) => ( - - {children} - -); - -export const useMessageInputContext = ( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - componentName?: string, -) => { - const contextValue = useContext(MessageInputContext); - - if (!contextValue) { - return {} as MessageInputContextValue; - } - - return contextValue as unknown as MessageInputContextValue; -}; diff --git a/src/context/index.ts b/src/context/index.ts index f6487d69d3..ac868b14ff 100644 --- a/src/context/index.ts +++ b/src/context/index.ts @@ -6,7 +6,7 @@ export * from './ComponentContext'; export * from './DialogManagerContext'; export * from './MessageContext'; export * from './MessageBounceContext'; -export * from './MessageInputContext'; +export * from './MessageComposerContext'; export * from './MessageListContext'; export * from './MessageTranslationViewContext'; export * from './ModalContext'; diff --git a/src/plugins/Emojis/EmojiPicker.tsx b/src/plugins/Emojis/EmojiPicker.tsx index 974e74b9b6..acc91a28b7 100644 --- a/src/plugins/Emojis/EmojiPicker.tsx +++ b/src/plugins/Emojis/EmojiPicker.tsx @@ -1,15 +1,15 @@ import React, { useEffect, useState } from 'react'; import Picker from '@emoji-mart/react'; -import { useMessageInputContext, useTranslationContext } from '../../context'; +import { useMessageComposerContext, useTranslationContext } from '../../context'; import { Button, IconEmojiSmile, type PopperLikePlacement, - useMessageComposer, + useMessageComposerController, } from '../../components'; import { usePopoverPosition } from '../../components/Dialog/hooks/usePopoverPosition'; -import { useIsCooldownActive } from '../../components/MessageInput/hooks/useIsCooldownActive'; +import { useIsCooldownActive } from '../../components/MessageComposer/hooks/useIsCooldownActive'; const isShadowRoot = (node: Node): node is ShadowRoot => !!(node as ShadowRoot).host; @@ -46,8 +46,8 @@ const classNames: Pick< export const EmojiPicker = (props: EmojiPickerProps) => { const { t } = useTranslationContext('EmojiPicker'); - const { textareaRef } = useMessageInputContext('EmojiPicker'); - const { textComposer } = useMessageComposer(); + const { textareaRef } = useMessageComposerContext('EmojiPicker'); + const { textComposer } = useMessageComposerController(); const isCooldownActive = useIsCooldownActive(); const [displayPicker, setDisplayPicker] = useState(false); const [referenceElement, setReferenceElement] = useState( diff --git a/src/plugins/Emojis/middleware/textComposerEmojiMiddleware.ts b/src/plugins/Emojis/middleware/textComposerEmojiMiddleware.ts index baae9a4d33..2a8ce8c004 100644 --- a/src/plugins/Emojis/middleware/textComposerEmojiMiddleware.ts +++ b/src/plugins/Emojis/middleware/textComposerEmojiMiddleware.ts @@ -17,7 +17,7 @@ import { import type { EmojiSearchIndex, EmojiSearchIndexResult, -} from '../../../components/MessageInput'; +} from '../../../components/MessageComposer'; export type EmojiSuggestion = TextComposerSuggestion; diff --git a/src/styling/index.scss b/src/styling/index.scss index e49e99bc13..74eb58d6b0 100644 --- a/src/styling/index.scss +++ b/src/styling/index.scss @@ -30,7 +30,7 @@ @use '../components/Channel/styling' as Channel; @use '../components/ChannelHeader/styling' as ChannelHeader; @use '../components/ChannelList/styling' as ChannelList; -@use '../components/ChannelPreview/styling' as ChannelPreview; +@use '../components/ChannelListItem/styling' as ChannelListItem; @use '../components/ChatView/styling' as ChatView; @use '../components/DateSeparator/styling' as DateSeparator; @use '../components/DragAndDrop/styling' as DragAndDrop; @@ -43,7 +43,7 @@ @use '../components/Message/styling' as Message; @use '../components/MessageActions/styling' as MessageActions; @use '../components/MessageBounce/styling' as MessageBounce; -@use '../components/MessageInput/styling' as MessageComposer; +@use '../components/MessageComposer/styling' as MessageComposer; @use '../components/MessageList/styling' as MessageList; @use '../components/Notifications/styling' as Notifications; @use '../components/Poll/styling' as Poll;