diff --git a/package.json b/package.json index e95b7b7c57..6e451634ea 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "emoji-mart": "^5.4.0", "react": "^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.14.0", "react-dom": "^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.14.0", - "stream-chat": "^9.0.0-rc.13" + "stream-chat": "^9.0.0-rc.15" }, "peerDependenciesMeta": { "@breezystack/lamejs": { @@ -239,7 +239,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "semantic-release": "^24.2.3", - "stream-chat": "^9.0.0-rc.13", + "stream-chat": "^9.0.0-rc.15", "ts-jest": "^29.2.5", "typescript": "^5.4.5", "typescript-eslint": "^8.17.0" diff --git a/src/components/MessageInput/__tests__/EditMessageForm.test.js b/src/components/MessageInput/__tests__/EditMessageForm.test.js index 31cc0128b2..30ac900b61 100644 --- a/src/components/MessageInput/__tests__/EditMessageForm.test.js +++ b/src/components/MessageInput/__tests__/EditMessageForm.test.js @@ -926,7 +926,7 @@ describe(`EditMessageForm`, () => { const CustomStateSetter = () => { const composer = useMessageComposerMock(); useEffect(() => { - composer.customDataManager.setData(customMessageData); + composer.customDataManager.setMessageData(customMessageData); }, [composer]); }; const { container, submit } = await renderComponent({ @@ -954,7 +954,7 @@ describe(`EditMessageForm`, () => { const CustomStateSetter = () => { const composer = useMessageComposerMock(); useEffect(() => { - composer.customDataManager.setData(customMessageData); + composer.customDataManager.setMessageData(customMessageData); }, [composer]); }; const { container, submit } = await renderComponent({ diff --git a/src/components/MessageInput/__tests__/MessageInput.test.js b/src/components/MessageInput/__tests__/MessageInput.test.js index 634aa903de..241139a273 100644 --- a/src/components/MessageInput/__tests__/MessageInput.test.js +++ b/src/components/MessageInput/__tests__/MessageInput.test.js @@ -794,7 +794,7 @@ describe(`MessageInputFlat`, () => { const customMessageData = { customX: 'customX' }; const CustomStateSetter = null; - customChannel.messageComposer.customDataManager.setData(customMessageData); + customChannel.messageComposer.customDataManager.setMessageData(customMessageData); const { container, submit } = await renderComponent({ customChannel, customClient, @@ -823,7 +823,7 @@ describe(`MessageInputFlat`, () => { const overrideMock = jest.fn().mockImplementation(() => Promise.resolve()); const { customChannel, customClient } = await setup(); const customMessageData = { customX: 'customX' }; - customChannel.messageComposer.customDataManager.setData(customMessageData); + customChannel.messageComposer.customDataManager.setMessageData(customMessageData); const { container, submit } = await renderComponent({ customChannel, customClient, diff --git a/src/components/TextareaComposer/SuggestionList/CommandItem.tsx b/src/components/TextareaComposer/SuggestionList/CommandItem.tsx index 4a66609758..92d73c4a3d 100644 --- a/src/components/TextareaComposer/SuggestionList/CommandItem.tsx +++ b/src/components/TextareaComposer/SuggestionList/CommandItem.tsx @@ -1,15 +1,9 @@ import type { PropsWithChildren } from 'react'; import React from 'react'; +import type { CommandResponse } from 'stream-chat'; export type CommandItemProps = { - entity: { - /** Arguments of command */ - args?: string; - /** Description of command */ - description?: string; - /** Name of the command */ - name?: string; - }; + entity: CommandResponse; }; export const CommandItem = (props: PropsWithChildren) => { diff --git a/src/components/TextareaComposer/SuggestionList/SuggestionList.tsx b/src/components/TextareaComposer/SuggestionList/SuggestionList.tsx index 6ce5dc2e1b..7b6bc45d0f 100644 --- a/src/components/TextareaComposer/SuggestionList/SuggestionList.tsx +++ b/src/components/TextareaComposer/SuggestionList/SuggestionList.tsx @@ -1,7 +1,10 @@ import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; +import type { CommandItemProps } from './CommandItem'; import { CommandItem } from './CommandItem'; +import type { EmoticonItemProps } from './EmoticonItem'; import { EmoticonItem } from './EmoticonItem'; +import type { SuggestionListItemComponentProps } from './SuggestionListItem'; import { SuggestionListItem as DefaultSuggestionListItem } from './SuggestionListItem'; import { UserItem } from './UserItem'; import { useComponentContext } from '../../../context/ComponentContext'; @@ -13,10 +16,15 @@ import type { TextComposerState, TextComposerSuggestion, } from 'stream-chat'; -import type { SuggestionItemProps } from './SuggestionListItem'; +import type { UserItemProps } from './UserItem'; + +type SuggestionTrigger = '/' | ':' | '@' | string; export type SuggestionListProps = Partial<{ - SuggestionItem: React.ComponentType; + suggestionItemComponents: Record< + SuggestionTrigger, + React.ComponentType + >; className?: string; closeOnClickOutside?: boolean; containerClassName?: string; @@ -34,11 +42,20 @@ const searchSourceStateSelector = ( items: nextValue.items ?? [], }); -export const defaultComponents = { - '/': CommandItem, - ':': EmoticonItem, - '@': UserItem, -}; +export const defaultComponents: Record< + SuggestionTrigger, + React.ComponentType +> = { + '/': (props: SuggestionListItemComponentProps) => ( + + ), + ':': (props: SuggestionListItemComponentProps) => ( + + ), + '@': (props: SuggestionListItemComponentProps) => ( + + ), +} as const; export const SuggestionList = ({ className, @@ -46,6 +63,7 @@ export const SuggestionList = ({ containerClassName, focusedItemIndex, setFocusedItemIndex, + suggestionItemComponents = defaultComponents, }: SuggestionListProps) => { const { AutocompleteSuggestionItem = DefaultSuggestionListItem } = useComponentContext(); @@ -56,8 +74,9 @@ export const SuggestionList = ({ useStateStore(suggestions?.searchSource.state, searchSourceStateSelector) ?? {}; const [container, setContainer] = useState(null); - // @ts-expect-error component type mismatch - const component = suggestions?.trigger && defaultComponents[suggestions?.trigger]; + const component = suggestions?.trigger + ? suggestionItemComponents[suggestions?.trigger] + : undefined; useEffect(() => { if (!closeOnClickOutside || !suggestions || !container) return; diff --git a/src/components/TextareaComposer/SuggestionList/SuggestionListItem.tsx b/src/components/TextareaComposer/SuggestionList/SuggestionListItem.tsx index eb90e1b191..7193749c29 100644 --- a/src/components/TextareaComposer/SuggestionList/SuggestionListItem.tsx +++ b/src/components/TextareaComposer/SuggestionList/SuggestionListItem.tsx @@ -1,21 +1,24 @@ import clsx from 'clsx'; import type { Ref } from 'react'; -import { useLayoutEffect } from 'react'; -import React, { useCallback, useRef } from 'react'; +import React, { useCallback, useLayoutEffect, useRef } from 'react'; import { useMessageComposer } from '../../MessageInput'; -import type { CommandResponse, TextComposerSuggestion, UserResponse } from 'stream-chat'; -import type { EmojiSearchIndexResult } from '../../MessageInput'; +import type { TextComposerSuggestion } from 'stream-chat'; +import type { UserItemProps } from './UserItem'; +import type { CommandItemProps } from './CommandItem'; +import type { EmoticonItemProps } from './EmoticonItem'; -export type SuggestionCommand = CommandResponse; -export type SuggestionUser = UserResponse; -export type SuggestionEmoji = EmojiSearchIndexResult; -export type SuggestionItem = SuggestionUser | SuggestionCommand | SuggestionEmoji; +export type DefaultSuggestionListItemEntity = + | UserItemProps['entity'] + | CommandItemProps['entity'] + | EmoticonItemProps['entity']; + +export type SuggestionListItemComponentProps = { + entity: DefaultSuggestionListItemEntity | unknown; + focused: boolean; +}; export type SuggestionItemProps = { - component: React.ComponentType<{ - entity: SuggestionItem; - focused: boolean; - }>; + component: React.ComponentType; item: TextComposerSuggestion; focused: boolean; className?: string; diff --git a/yarn.lock b/yarn.lock index dbdb518b47..bede706ca5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12155,10 +12155,10 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -stream-chat@^9.0.0-rc.13: - version "9.0.0-rc.13" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.0.0-rc.13.tgz#90396d724bd5f81e2d8eb051c9c56b075892305f" - integrity sha512-qJPcHsQUfu0qMb7p7Bv9OnL6qID/qxYLKOIsG6KbUNEQHwkKSw/aDwExpvgJR7dXsUOLyLZFhVtYnFkAPDdJ3A== +stream-chat@^9.0.0-rc.15: + version "9.0.0-rc.15" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.0.0-rc.15.tgz#6a35f27c38be2e021a3e62abcffa40aaef84fc0a" + integrity sha512-n8IftP103jp8IVOUZA4mnOvEIbyzGTVjoL3rDm42apk8DRHz2/vdvgyTYUJysDNYh+Oxu5HwWObToKv1c/Bxdw== dependencies: "@types/jsonwebtoken" "^9.0.8" "@types/ws" "^8.5.14"