diff --git a/AGENTS.md b/AGENTS.md index 9609b7790..7e750c2a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -221,7 +221,7 @@ Date separators come from `withDateSeparator`: on by default in `MessageList`, o ### 3. Memoization - `useStateStore` selectors scope re-renders: an over-broad or unstable slice re-renders everything that reads it. -- `areMessageUIPropsEqual` (`src/components/Message/utils.tsx`) is the per-message `React.memo` comparator. It checks cheap props first (`highlighted`, `endOfGroup`, `readBy.length`, `deliveredTo.length`, `groupStyles`, `showDetailedReactions`, `lastReceivedId`) before comparing messages. A prop it does not check will not re-render a message. +- `areMessageUIPropsEqual` (`src/components/Message/utils.tsx`) is the per-message `React.memo` comparator. It checks cheap props first (`highlighted`, `readBy.length`, `deliveredTo.length`, `groupStyles`, `showDetailedReactions`, `lastReceivedId`) before comparing messages. A prop it does not check will not re-render a message. ## Critical gotchas & invariants diff --git a/ai-docs/ai-migration-v14-v15.md b/ai-docs/ai-migration-v14-v15.md index e7102d75f..3e65f7dd3 100644 --- a/ai-docs/ai-migration-v14-v15.md +++ b/ai-docs/ai-migration-v14-v15.md @@ -2089,7 +2089,8 @@ Code that drove these props uses the paginator: - `MessageList.threadList`, `VirtualizedMessageList.threadList`, `VirtuosoContext.threadList`, `TypingIndicatorProps.threadList` and `ScrollToLatestMessageButtonProps.threadList` are removed. The SDK no longer passes `threadList` to custom `TypingIndicator` / `ScrollToLatestMessageButton` overrides; in untyped code it is `undefined`, so a thread-aware override behaves as if it were in the channel list. Detect the thread with `const isThreadList = !!useThreadContext();`. - `MessageList.head` and `VirtualizedMessageList.head` are removed. Both lists render the parent message from the thread context. Override it with a `ThreadHead` slot (`ComponentType`). On `VirtualizedMessageList`, `additionalVirtuosoProps.components.Header` still replaces the whole header slot. -- `Message` / `MessageContext` still have a `threadList` field; only the list-level prop is gone. +- `MessageProps.threadList` and `MessageContextValue.threadList` are removed too. A custom `MessageUI` or message child that read `useMessageContext().threadList` uses `!!useThreadContext()` instead. +- `ReactionSelector.getDialogId` takes only `{ messageId }`, and the reaction selector's dialog id no longer has a `-thread` part. The channel list and the thread list each have their own dialog manager, so the same message open in both still gets two separate dialogs. ### `disableDateSeparator` → `withDateSeparator` @@ -2121,6 +2122,41 @@ export const Lists = () => ( ); ``` +### Message grouping: `groupByUser` and `getGroupStyles` + +Both lists take `groupByUser`; the defaults are unchanged. + +| Component | v14 | v15 | +| ------------------------ | ------------------------------------- | ------------------------------- | +| `MessageList` | `noGroupByUser` (default `false`) | `groupByUser` (default `true`) | +| `VirtualizedMessageList` | `shouldGroupByUser` (default `false`) | `groupByUser` (default `false`) | + +The `groupStyles` prop of both lists is renamed `getGroupStyles`, and the function takes one object instead of five positional arguments: `{ groupByUser, maxTimeBetweenGroupedMessages, message, nextMessage, previousMessage }` (`GetGroupStylesParams`; `nextMessage` and `previousMessage` are `undefined` at the ends of the list). `groupByUser` replaces the inverted `noGroupByUser` argument. The exported default `getGroupStyles` takes the same object, so a custom function can still delegate to it. `useEnrichedMessages` takes `getGroupStyles` and `groupByUser` instead of `groupStyles` and `noGroupByUser`. `VirtuosoContext.shouldGroupByUser` is `groupByUser`. + +In untyped code the old props are ignored and the defaults apply: `MessageList noGroupByUser` groups again, `VirtualizedMessageList shouldGroupByUser` stops grouping, and a `groupStyles` function is never called. A positional function passed as `getGroupStyles` receives the object as its first argument and `undefined` for the others, so it computes wrong styles or throws, for example on `previousMessage.created_at`; TypeScript rejects it. + +```tsx +// v14 + + + + 'pinned' in message && message.pinned + ? 'single' + : getGroupStyles(message, previousMessage, nextMessage, noGroupByUser, maxTime) + } +/> + +// v15 + + + + 'pinned' in params.message && params.message.pinned ? 'single' : getGroupStyles(params) + } +/> +``` + ### Custom `renderMessages` gets different options - `RenderMessagesOptions.channel: Channel` is new and required. @@ -2132,6 +2168,7 @@ export const Lists = () => ( - Removed: `Message`, `messageActions`, `openThread`, `threadList`. - New and required: `channel: Channel`. New: `focusedMessageId?`. +- Renamed: `shouldGroupByUser` → `groupByUser`. - `head`, `firstUnreadMessageId` and `lastReadMessageId` are `... | null` (checks written as `=== undefined` miss them); `lastReadDate` is `TimestampNS | null` (unix nanoseconds); `loadingMore: boolean`; `unreadMessageCount: number`. ### `headerPosition` and `insertIntro` take unix nanoseconds @@ -2234,6 +2271,10 @@ export const MessagePreview = ({ - `mutes` → `useChatContext().mutes` (now `UserMuteResponse[]`), for example `isUserMuted(message, useChatContext().mutes)`. In untyped code the removed field is `undefined`, so `isUserMuted(message, mutes)` returns `false`. - `messageIsUnread` has no successor. Derive it from the channel's read state, for example `!isMyMessage() && (channel.lastRead() == null || message.created_at > channel.lastRead())`, with `channel` from `useChannel()`. `channel.lastRead()` returns `TimestampNS | null | undefined` and is not reactive; to re-render on read changes, subscribe to `read` on `channel.state`. +### `MessageContext` grouping flags are removed; read `groupStyles` + +`endOfGroup`, `firstOfGroup` and `groupedByUser` are removed from `MessageContextValue`. The message lists never set them, so in v14 they were always `undefined`. A message's place in its group of consecutive messages from one user is `groupStyles[0]` from `useMessageContext()`: `top`, `middle`, `bottom` or `single`. The default `MessageUI` renders the metadata on every message, and the default CSS hides it on `top` and `middle` list items (`.str-chat__li--top`, `.str-chat__li--middle`), so an app can show it again with CSS alone. The `str-chat__virtual-message__wrapper--end`, `--first` and `--group` classes these flags added are removed too ([section 19](#19-css-and-markup)). + ### `retrySendMessage` and `useRetryHandler` - The `retrySendMessage` prop on `Message`, `MessageList` and `SharedMessageProps` is removed. To change the retry request, register `retrySendMessageRequest` on the client ([section 14](#14-request-handlers)). @@ -3073,6 +3114,7 @@ Four formatter keys carry English day words inside their `calendarFormats` (`tim | `.str-chat__channel-list-inner`, `.str-chat__channel-list-inner__main` (from `ChannelListUI`, which held `role="listbox"`) | Gone. Target `.str-chat__channel-list .str-chat__infinite-scroll-paginator` (the `role="listbox"` scroll container, inside `ChannelNavigation`) and `.str-chat__infinite-scroll-paginator__content`. A bare `` has no `.str-chat__channel-list` ancestor. | | The channel list root carried `str-chat str-chat__channel-list` and `customClasses.channelList` | Inside `ChatView` the `str-chat ` scope comes from the `ChatView` root, and the channel list adds none of its own: a nested `.str-chat` would reset the tokens you override on the `ChatView` root. Outside `ChatView`, `ChannelNavigation`'s root renders `str-chat str-chat__channel-list`, and a bare `` wraps itself in `div.str-chat..str-chat__theme-scope` (`display: contents`). `customClasses.chat` replaces `str-chat` where the scope is applied; `customClasses.channelList` replaces `str-chat__channel-list` on `ChannelNavigation`'s root. App CSS that targeted the list root as a `.str-chat` element no longer matches it inside `ChatView`. | | `.str-chat__chat-view__channels` / `.str-chat__chat-view__threads` | Rendered only when `ChatView` is given `views`. With the built-in workspace layout, content sits in `.str-chat__chat-view__workspace-layout`, `.str-chat__chat-view__workspace-layout-slots`, `.str-chat__chat-view__slot` (`--hidden`) and `.str-chat__chat-view__slot-layer`. | +| `str-chat__virtual-message__wrapper--end`, `--first`, `--group` on `.str-chat__message` | Not applied; the SDK stylesheet never styled them. Target the list item's group class instead: `.str-chat__li--top`, `--middle`, `--bottom` or `--single`. | | `.str-chat__thread--virtualized` on the thread container | Not applied. Scope by the list you render (`.str-chat__thread .str-chat__virtual-list`, or your own class). | | `.str-chat__quoted-message-preview` text inherited the surrounding colour | It sets `color: var(--str-chat__chat-text-incoming)`, and `--own` `var(--str-chat__chat-text-outgoing)`, matching the bubble backgrounds. In the default light theme your own quotes change to the outgoing bubble's text colour. Remove contrast workarounds written for v14; theme with those two tokens. | | `.str-chat__channel-list-item[aria-pressed=true]` and `.str-chat__thread-list-item[aria-pressed=true]` highlighted the active row | Only `[aria-selected=true]` is styled. A custom row UI sets `role="option"` and `aria-selected`, not `aria-pressed`: `aria-selected={active}` on a channel row, `aria-selected={isThreadActive(thread.id)}` (from `useWorkspaceNavigation()`) on a thread row | @@ -3082,7 +3124,7 @@ Four formatter keys carry English day words inside their `calendarFormats` (`tim Find every app rule that uses a removed class. They also hide inside combinators and pseudo-classes, where a rule does more than style the element: `.create-channel-panel + .str-chat__main-panel { display: none }` hid the channel while another panel was open, and without it both render side by side. Nothing reports such a rule; it simply stops matching. ```bash -grep -rnE "str-chat__(main-panel(--[a-z-]+)?|container|channel-list-inner(__main)?|thread--virtualized)([^a-z0-9_-]|$)" \ +grep -rnE "str-chat__(main-panel(--[a-z-]+)?|container|channel-list-inner(__main)?|thread--virtualized|virtual-message__wrapper--(end|first|group))([^a-z0-9_-]|$)" \ --include='*.css' --include='*.scss' --include='*.sass' --include='*.less' \ --exclude-dir=node_modules --exclude-dir=build --exclude-dir=dist . ``` @@ -3257,3 +3299,5 @@ Run after every section is applied. | BC-082 | Custom preview slots mount only while their item is visible | [13](#new-composer-previews-are-a-configurable-set) | | BC-083 | Message action handlers reject | [12](#message-action-handlers-reject-when-they-fail) | | BC-084 | First message loses its top padding | [19](#19-css-and-markup) | +| BC-085 | `MessageContext` grouping flags and `virtual-message__wrapper--*` classes | [12](#messagecontext-grouping-flags-are-removed-read-groupstyles), [19](#19-css-and-markup) | +| BC-086 | `groupByUser`, `getGroupStyles` | [11](#message-grouping-groupbyuser-and-getgroupstyles) | diff --git a/ai-docs/v15-breaking-changes.md b/ai-docs/v15-breaking-changes.md index 07565f7d3..dbfde78dd 100644 --- a/ai-docs/v15-breaking-changes.md +++ b/ai-docs/v15-breaking-changes.md @@ -64,7 +64,7 @@ yarn api-surface check --doc ai-docs/v15-breaking-changes.md --diff diff.json ## Index -84 entries. +86 entries. | ID | Title | Group | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | @@ -152,6 +152,8 @@ yarn api-surface check --doc ai-docs/v15-breaking-changes.md --diff diff.json | [BC-082](#bc-082) | custom `AttachmentPreviewList`, `VoiceRecordingPreviewSlot` and `LinkPreviewList` mount only while their preview item is visible | Message composer | | [BC-083](#bc-083) | message action handlers reject when the request fails or when called without the message | Messages and message lists | | [BC-084](#bc-084) | CSS, the first message of a channel list loses its `4.5rem` top padding | CSS and markup | +| [BC-085](#bc-085) | `MessageContext` grouping flags `endOfGroup` / `firstOfGroup` / `groupedByUser` and the `str-chat__virtual-message__wrapper--*` classes are removed | Messages and message lists | +| [BC-086](#bc-086) | message grouping: `groupByUser` replaces `noGroupByUser` / `shouldGroupByUser`; `getGroupStyles` replaces `groupStyles` and takes one object | Messages and message lists | ## Package and runtime @@ -2436,13 +2438,16 @@ export const ChannelWithThread = ({ channel }: { channel: StreamChannel }) => { - Area: MessageList / VirtualizedMessageList / TypingIndicator / ScrollToLatestMessageButton - User impact: - compile errors on `threadList` / `head` props, and in custom `TypingIndicator` / `ScrollToLatestMessageButton` overrides that destructure `threadList`, which the SDK no longer passes + - compile errors in custom `MessageUI` or message children that read `threadList` from `useMessageContext()`, and on `ReactionSelector.getDialogId({ messageId, threadList })` - in untyped overrides, `threadList` is `undefined`, so a thread-aware custom component behaves as if it were in the channel list - Old API: - `MessageList.threadList`, `VirtualizedMessageList.threadList`, `VirtuosoContext.threadList` - `TypingIndicatorProps.threadList`, `ScrollToLatestMessageButtonProps.threadList` + - `MessageProps.threadList`, `MessageContextValue.threadList`; `ReactionSelector.getDialogId`'s `threadList` argument and the `-thread` part of the reaction selector dialog id - `MessageList.head`, `VirtualizedMessageList.head` (the thread's parent message element) - Replacement: - - thread detection: `const thread = useThreadContext(); const isThreadList = !!thread;` (the SDK's own components do this) + - thread detection: `const thread = useThreadContext(); const isThreadList = !!thread;` (the SDK's own components do this), also in place of `useMessageContext().threadList` + - reaction selector dialog id: `ReactionSelector.getDialogId({ messageId })`. Each message list has its own dialog manager, which keeps the channel's and the thread's dialogs for one message apart. - parent message: both lists render it from `ThreadContext` via `useThreadHead()`. Override it with the `ThreadHead` slot: ``, typed `React.ComponentType`. - `VirtuosoContext.head` is now `ReactElement | null` and always present - Evidence: @@ -2457,7 +2462,7 @@ export const ChannelWithThread = ({ channel }: { channel: StreamChannel }) => { - Docs impact: - `02-ui-components/07-message-list/01-message_list.md`, `02-ui-components/07-message-list/02-virtualized_list.md` (`threadList`, `head`) - `02-ui-components/12-indicators.md`, `03-ui-cookbook/05-message-composer/07-typing_indicator.md` (`threadList`) - - `02-ui-components/08-message/01-message.md`, `02-ui-components/08-message/02-message_context.md`, `03-ui-cookbook/04-message/01-message_ui.md` mention `threadList`, which is still a `Message` / `MessageContext` field; only the list-level prop is gone, so check each mention + - `02-ui-components/08-message/01-message.md`, `02-ui-components/08-message/02-message_context.md`, `03-ui-cookbook/04-message/01-message_ui.md` mention `threadList` as a `Message` / `MessageContext` field, which is removed too; replace each mention with `useThreadContext()` - Example needed: no (see BC-004 for a custom `TypingIndicator`) @@ -3570,6 +3575,53 @@ Found by the staleness pass of 2026-11-03: commits that landed on `master` after - none - Example needed: no + + +### BC-085: `MessageContext` grouping flags and the `str-chat__virtual-message__wrapper--*` classes are removed + +- Status: confirmed +- Area: Message / MessageUI / CSS +- User impact: + - compile errors in custom `MessageUI` or message children that read `endOfGroup`, `firstOfGroup` or `groupedByUser` from `useMessageContext()`. The lists never set them, so at runtime they were already `undefined`. + - app CSS keyed on `str-chat__virtual-message__wrapper--end`, `--first` or `--group` stops matching. The SDK stylesheet never styled them. +- Old API: + - `MessageContextValue.endOfGroup`, `.firstOfGroup`, `.groupedByUser` + - `str-chat__virtual-message__wrapper--end`, `--first`, `--group` on `.str-chat__message` +- Replacement: + - `useMessageContext().groupStyles[0]`: `top`, `middle`, `bottom` or `single` + - CSS: the list item's `.str-chat__li--top`, `--middle`, `--bottom`, `--single`. The default `MessageUI` renders the metadata on every message, and the default CSS hides it on `top` and `middle` items. +- Evidence: + - `release-v14:src/components/Message/MessageUI.tsx:144` `showMetadata = !groupedByUser || endOfGroup`, with no list setting either flag + - `master:src/components/Message/styling/Message.scss` `.str-chat__li--middle, .str-chat__li--top` hide `.str-chat__message-metadata` +- Docs impact: + - `02-ui-components/08-message/02-message_context.md` and `03-ui-cookbook/04-message/01-message_ui.md`, if they mention the three flags +- Example needed: no + + + +### BC-086: message grouping: `noGroupByUser` / `shouldGroupByUser` → `groupByUser`, `groupStyles` → `getGroupStyles` with one object argument + +- Status: confirmed +- Area: MessageList / VirtualizedMessageList / getGroupStyles / useEnrichedMessages +- User impact: + - compile errors on `noGroupByUser`, `shouldGroupByUser` and `groupStyles`, on positional calls of `getGroupStyles`, and on the old `useEnrichedMessages` options + - in untyped code the old props are ignored and the defaults apply: `MessageList noGroupByUser` groups again, `VirtualizedMessageList shouldGroupByUser` stops grouping, and a `groupStyles` function is never called. A positional function passed as `getGroupStyles` receives the object as `message` and `undefined` for the other arguments, so it computes wrong styles or throws (for example on `previousMessage.created_at`). +- Old API: + - `MessageList.noGroupByUser` (default `false`), `VirtualizedMessageList.shouldGroupByUser` (default `false`), `VirtuosoContext.shouldGroupByUser` + - `MessageList.groupStyles`, `VirtualizedMessageList.groupStyles`: `(message, previousMessage, nextMessage, noGroupByUser, maxTimeBetweenGroupedMessages?) => GroupStyle` + - `getGroupStyles(message, previousMessage, nextMessage, noGroupByUser, maxTimeBetweenGroupedMessages?)` + - `useEnrichedMessages({ groupStyles, noGroupByUser })` +- Replacement: + - `groupByUser` on both lists. `MessageList` defaults to `true`, `VirtualizedMessageList` to `false`. `VirtuosoContext.groupByUser`. + - `getGroupStyles?: GetGroupStyles` on both lists, called with `GetGroupStylesParams`: `{ groupByUser, maxTimeBetweenGroupedMessages?, message, nextMessage?, previousMessage? }`. The exported `getGroupStyles` takes the same object. + - `useEnrichedMessages({ getGroupStyles, groupByUser })` +- Evidence: + - `release-v14:src/components/MessageList/MessageList.tsx:105` `noGroupByUser = false`, `:517` positional `groupStyles`, `:555` `noGroupByUser?` + - `release-v14:src/components/MessageList/VirtualizedMessageList.tsx:236` `shouldGroupByUser = false`, `:102` picked into the context, `:693` positional `groupStyles` +- Docs impact: + - `02-ui-components/07-message-list/01-message_list.md`, `02-ui-components/07-message-list/02-virtualized_list.md` +- Example needed: no (the guide section has one) + ## Pending (open PRs) - `GetStream/stream-chat-react#3306` `feat: adopt TS generator changes` (open, targets `master`). It changes the request shapes for sending, editing and deleting messages, reactions, reminders and poll-option suggestions to follow `GetStream/stream-chat-js#1896`. If it merges before the stable release, re-run the surface diff and update the request-handler entries (search this file for `sendMessageRequest`). @@ -3898,6 +3950,7 @@ Non-gated items checked and ruled out: `Chat.isMessageAIGenerated`, `SummarizedM - `getIsFirstUnreadMessage` -> BC-054 - `insertIntro` -> BC-056 - `MessageList.disableDateSeparator`, `MessageListProps.disableDateSeparator`, `ProcessMessagesParams.enableDateSeparator` -> BC-049 + - `MessageList.noGroupByUser`, `MessageList.groupStyles` (+ `MessageListProps`), `getGroupStyles` signature, `useEnrichedMessages` options -> BC-086 - `MessageList.head`, `MessageListProps.head`, `MessageList.threadList`, `MessageListProps.threadList` -> BC-048 - `MessageList.loadingMoreForJumpToChannelMessage`, `.loadMore`, `.loadMoreNewer` (+ `MessageListProps`) -> BC-046 - `MessageList.Message`, `MessageListProps.Message`, `SharedMessageProps.Message` -> BC-042 @@ -3911,6 +3964,7 @@ Non-gated items checked and ruled out: `Chat.isMessageAIGenerated`, `SummarizedM - `useEnrichedMessages`, `useNewMessageNotification`, `UseScrollLocationLogicParams.disableAutoScrollToBottom` / `.disableScrollManagement` -> BC-055 - `VirtualizedMessageList.channelUnreadUiState` / `hasMore` / `hasMoreNewer` / `highlightedMessageId` / `loadingMore` / `loadingMoreNewer` / `loadMore` / `loadMoreNewer` / `messageLimit` (+ `VirtualizedMessageListProps`) -> BC-046 - `VirtualizedMessageList.disableDateSeparator` (+ Props) -> BC-049 + - `VirtualizedMessageList.shouldGroupByUser`, `VirtualizedMessageList.groupStyles` (+ Props), `VirtuosoContext.shouldGroupByUser` -> BC-086 - `VirtualizedMessageList.head`, `.threadList` (+ Props) -> BC-048 - `VirtualizedMessageList.Message` (+ Props) -> BC-042 - `VirtualizedMessageList.messageActions` (+ Props) -> BC-041 @@ -4047,7 +4101,7 @@ Non-gated items checked and ruled out: `Chat.isMessageAIGenerated`, `SummarizedM #### diff.md "CSS" -- Classes removed from markup: `str-chat__main-panel`, `str-chat__main-panel---with-thread-opn`, `str-chat__main-panel--thread-open` -> BC-073; `str-chat__channel-list-inner`, `str-chat__channel-list-inner__main` -> BC-074; `str-chat__chat-view__channels`, `str-chat__chat-view__threads` -> BC-075 (partially a false positive); `str-chat__thread--virtualized` -> BC-076 +- Classes removed from markup: `str-chat__main-panel`, `str-chat__main-panel---with-thread-opn`, `str-chat__main-panel--thread-open` -> BC-073; `str-chat__channel-list-inner`, `str-chat__channel-list-inner__main` -> BC-074; `str-chat__chat-view__channels`, `str-chat__chat-view__threads` -> BC-075 (partially a false positive); `str-chat__thread--virtualized` -> BC-076; `str-chat__virtual-message__wrapper--end`, `--first`, `--group` -> BC-085 - Classes removed from stylesheets (diff reports 0): actually `str-chat__container`, `str-chat__main-panel` -> BC-073 - Custom properties removed: 0 (confirmed by own diff of `--str-chat*` declarations) diff --git a/examples/cookbook/src/recipes/composer/inline-message-editing/InlineEditAction.tsx b/examples/cookbook/src/recipes/composer/inline-message-editing/InlineEditAction.tsx index 8731f0947..ac07bccf3 100644 --- a/examples/cookbook/src/recipes/composer/inline-message-editing/InlineEditAction.tsx +++ b/examples/cookbook/src/recipes/composer/inline-message-editing/InlineEditAction.tsx @@ -6,20 +6,22 @@ import { useComponentContextIcons, useContextMenuContext, useMessageContext, + useThreadContext, } from 'stream-chat-react'; import { useInlineEdit } from './InlineEditContext'; const InlineEditAction = () => { const { IconEdit } = useComponentContextIcons(); const { closeMenu } = useContextMenuContext(); - const { message, threadList = false } = useMessageContext(); + const { message } = useMessageContext(); + const isInThread = !!useThreadContext(); const { startEditing } = useInlineEdit(); return ( { - startEditing(message, threadList); + startEditing(message, isInThread); closeMenu(); }} > diff --git a/examples/cookbook/src/recipes/composer/inline-message-editing/InlineEditContext.tsx b/examples/cookbook/src/recipes/composer/inline-message-editing/InlineEditContext.tsx index 4ac01b636..bdb67adf0 100644 --- a/examples/cookbook/src/recipes/composer/inline-message-editing/InlineEditContext.tsx +++ b/examples/cookbook/src/recipes/composer/inline-message-editing/InlineEditContext.tsx @@ -15,13 +15,13 @@ import { useChatContext } from 'stream-chat-react'; /** The message being edited, the list it is edited in, and the composer that holds the edit. */ export type InlineEdit = { composer: MessageComposerController; + isInThread: boolean; messageId: string; - threadList: boolean; }; type InlineEditContextValue = { edit: InlineEdit | null; - startEditing: (message: LocalMessage, threadList: boolean) => void; + startEditing: (message: LocalMessage, isInThread: boolean) => void; }; const InlineEditContext = createContext({ @@ -46,7 +46,7 @@ export const InlineEditProvider = ({ children }: PropsWithChildren) => { }, [edit]); const startEditing = useCallback( - (message: LocalMessage, threadList: boolean) => { + (message: LocalMessage, isInThread: boolean) => { // The composition context is the message, so this composer edits only that message. const composer = new MessageComposerController({ client, @@ -54,7 +54,7 @@ export const InlineEditProvider = ({ children }: PropsWithChildren) => { compositionContext: message, }); editRef.current?.composer.clear(); - setEdit({ composer, messageId: message.id, threadList }); + setEdit({ composer, isInThread, messageId: message.id }); }, [client], ); diff --git a/examples/cookbook/src/recipes/composer/inline-message-editing/WithInlineMessageEditing.tsx b/examples/cookbook/src/recipes/composer/inline-message-editing/WithInlineMessageEditing.tsx index c62a6fca5..026fd203c 100644 --- a/examples/cookbook/src/recipes/composer/inline-message-editing/WithInlineMessageEditing.tsx +++ b/examples/cookbook/src/recipes/composer/inline-message-editing/WithInlineMessageEditing.tsx @@ -1,16 +1,22 @@ import type { PropsWithChildren } from 'react'; -import { MessageUI, useMessageContext, WithComponents } from 'stream-chat-react'; +import { + MessageUI, + useMessageContext, + useThreadContext, + WithComponents, +} from 'stream-chat-react'; import { InlineEditMessageActions } from './InlineEditAction'; import { InlineEditor } from './InlineEditor'; import { InlineEditProvider, useInlineEdit } from './InlineEditContext'; const InlineEditingMessageUI = () => { - const { message, threadList = false } = useMessageContext(); + const { message } = useMessageContext(); + const isInThread = !!useThreadContext(); const { edit } = useInlineEdit(); // The same message can show in the channel and in its thread; only the list it was opened in // shows the editor. - const isEditedHere = edit?.messageId === message.id && edit.threadList === threadList; + const isEditedHere = edit?.messageId === message.id && edit.isInThread === isInThread; return isEditedHere ? : ; }; diff --git a/examples/cookbook/src/recipes/message-actions/action-bar/CustomMessageUi.tsx b/examples/cookbook/src/recipes/message-actions/action-bar/CustomMessageUi.tsx index f232f6d57..3a5aea59c 100644 --- a/examples/cookbook/src/recipes/message-actions/action-bar/CustomMessageUi.tsx +++ b/examples/cookbook/src/recipes/message-actions/action-bar/CustomMessageUi.tsx @@ -24,7 +24,7 @@ type MessageStatusParams = { message: LocalMessage; ownUserId: string; readBy: UserResponse[]; - threadList: boolean; + isInThread: boolean; }; const getMessageStatus = ({ @@ -32,19 +32,19 @@ const getMessageStatus = ({ message, ownUserId, readBy, - threadList, + isInThread, }: MessageStatusParams): keyof typeof statusIconMap => { const [firstReader] = readBy; const [firstDeliveredUser] = deliveredTo; const justReadByMe = readBy.length === 1 && firstReader?.id === ownUserId; - const read = readBy.length > 0 && !justReadByMe && !threadList; + const read = readBy.length > 0 && !justReadByMe && !isInThread; const deliveredOnlyToMe = deliveredTo.length === 1 && firstDeliveredUser?.id === ownUserId; - const delivered = deliveredTo.length > 0 && !deliveredOnlyToMe && !read && !threadList; + const delivered = deliveredTo.length > 0 && !deliveredOnlyToMe && !read && !isInThread; - const sent = message.status === 'received' && !delivered && !read && !threadList; + const sent = message.status === 'received' && !delivered && !read && !isInThread; const sending = message.status === 'sending'; if (read) return 'read'; @@ -63,7 +63,7 @@ const CustomMessageUiMetadata = () => { readBy = [], } = useMessageContext(); const { client } = useChatContext(); - const threadList = !!useThreadContext(); + const isInThread = !!useThreadContext(); const createdAt = convertTimestampToDate(message.created_at); const editedAt = convertTimestampToDate(message.message_text_updated_at); @@ -74,7 +74,7 @@ const CustomMessageUiMetadata = () => { message, ownUserId: client.user?.id ?? '', readBy, - threadList, + isInThread, }) ]; @@ -89,7 +89,7 @@ const CustomMessageUiMetadata = () => { )} {editedAt && Edited} - {!threadList && message.reply_count > 0 && ( + {!isInThread && message.reply_count > 0 && ( )} - {canReply && !threadList && ( + {canReply && !isInThread && ( )} - {canReply && !threadList && ( + {canReply && !isInThread && (