diff --git a/examples/ExpoMessaging/app/channel/[cid]/index.tsx b/examples/ExpoMessaging/app/channel/[cid]/index.tsx index e3e5a8de94..c52c8ef231 100644 --- a/examples/ExpoMessaging/app/channel/[cid]/index.tsx +++ b/examples/ExpoMessaging/app/channel/[cid]/index.tsx @@ -6,6 +6,7 @@ import { useChatContext, ThreadContextValue, MessageList, + WithComponents, } from 'stream-chat-expo'; import { Stack, useLocalSearchParams, useRouter } from 'expo-router'; import { AuthProgressLoader } from '../../../components/AuthProgressLoader'; @@ -70,22 +71,23 @@ export default function ChannelScreen() { - - { - setThread(thread); - router.push(`/channel/${channel.cid}/thread/${thread?.cid ?? ''}`); - }} - /> - - + + + { + setThread(thread); + router.push(`/channel/${channel.cid}/thread/${thread?.cid ?? ''}`); + }} + /> + + + ); } diff --git a/examples/ExpoMessaging/components/InputButtons.tsx b/examples/ExpoMessaging/components/InputButtons.tsx index 929b10acef..dcb0de4227 100644 --- a/examples/ExpoMessaging/components/InputButtons.tsx +++ b/examples/ExpoMessaging/components/InputButtons.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; import { Pressable, StyleSheet } from 'react-native'; -import { Channel, InputButtons as DefaultInputButtons } from 'stream-chat-expo'; +import type { ComponentOverrides } from 'stream-chat-expo'; +import { InputButtons as DefaultInputButtons } from 'stream-chat-expo'; import { ShareLocationIcon } from '../icons/ShareLocationIcon'; import { LiveLocationCreateModal } from './LocationSharing/CreateLocationModal'; -const InputButtons: NonNullable['InputButtons']> = (props) => { +const InputButtons: NonNullable = (props) => { const [modalVisible, setModalVisible] = useState(false); const onRequestClose = () => { diff --git a/examples/SampleApp/src/screens/ChannelListScreen.tsx b/examples/SampleApp/src/screens/ChannelListScreen.tsx index e92095e8c2..5408e6438a 100644 --- a/examples/SampleApp/src/screens/ChannelListScreen.tsx +++ b/examples/SampleApp/src/screens/ChannelListScreen.tsx @@ -10,12 +10,7 @@ import { View, } from 'react-native'; import { useNavigation, useScrollToTop } from '@react-navigation/native'; -import { - ChannelList, - useTheme, - useStableCallback, - ChannelActionItem, -} from 'stream-chat-react-native'; +import { ChannelList, useTheme, useStableCallback, ChannelActionItem, WithComponents } from 'stream-chat-react-native'; import { Channel } from 'stream-chat'; import { ChannelPreview } from '../components/ChannelPreview'; import { ChatScreenHeader } from '../components/ChatScreenHeader'; @@ -254,18 +249,23 @@ export const ChannelListScreen: React.FC = () => { )} - + + + diff --git a/examples/SampleApp/src/screens/ChannelScreen.tsx b/examples/SampleApp/src/screens/ChannelScreen.tsx index b9329d4c34..025f31038e 100644 --- a/examples/SampleApp/src/screens/ChannelScreen.tsx +++ b/examples/SampleApp/src/screens/ChannelScreen.tsx @@ -17,6 +17,7 @@ import { MessageActionsParams, ChannelAvatar, PortalWhileClosingView, + WithComponents, } from 'stream-chat-react-native'; import { Pressable, StyleSheet, View } from 'react-native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; @@ -265,19 +266,23 @@ export const ChannelScreen: React.FC = ({ navigation, route return ( + null, + }} + > null} onAlsoSentToChannelHeaderPress={onAlsoSentToChannelHeaderPress} thread={selectedThread} maximumMessageLimit={messageListPruning} @@ -306,6 +311,7 @@ export const ChannelScreen: React.FC = ({ navigation, route /> )} + ); }; diff --git a/examples/SampleApp/src/screens/NewDirectMessagingScreen.tsx b/examples/SampleApp/src/screens/NewDirectMessagingScreen.tsx index 5bad31912f..cb83e70aa0 100644 --- a/examples/SampleApp/src/screens/NewDirectMessagingScreen.tsx +++ b/examples/SampleApp/src/screens/NewDirectMessagingScreen.tsx @@ -8,6 +8,7 @@ import { MessageList, UserAdd, useTheme, + WithComponents, } from 'stream-chat-react-native'; import { User } from '../icons/User'; @@ -338,32 +339,37 @@ export const NewDirectMessagingScreen: React.FC = }, ]} > - { - setFocusOnMessageInput(true); - setFocusOnSearchInput(false); - if (messageInputRef.current) { - messageInputRef.current.focus(); - } - }, + (messageInputRef.current = ref)} > - {renderUserSearch({ inSafeArea: true })} - {results && results.length >= 0 && !focusOnSearchInput && focusOnMessageInput && ( - - )} - - + { + setFocusOnMessageInput(true); + setFocusOnSearchInput(false); + if (messageInputRef.current) { + messageInputRef.current.focus(); + } + }, + }} + audioRecordingEnabled={true} + channel={currentChannel.current} + enforceUniqueReaction + keyboardVerticalOffset={0} + onChangeText={setMessageInputText} + overrideOwnCapabilities={{ sendMessage: true }} + setInputRef={(ref) => (messageInputRef.current = ref)} + > + {renderUserSearch({ inSafeArea: true })} + {results && results.length >= 0 && !focusOnSearchInput && focusOnMessageInput && ( + + )} + + + ); }; diff --git a/examples/SampleApp/src/screens/SharedGroupsScreen.tsx b/examples/SampleApp/src/screens/SharedGroupsScreen.tsx index 6bbf45f432..c092f1e97d 100644 --- a/examples/SampleApp/src/screens/SharedGroupsScreen.tsx +++ b/examples/SampleApp/src/screens/SharedGroupsScreen.tsx @@ -3,8 +3,6 @@ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { NavigationProp, RouteProp, useNavigation } from '@react-navigation/native'; import { ChannelList, - ChannelListView, - ChannelListViewProps, ChannelPreviewViewProps, getChannelPreviewDisplayAvatar, GroupAvatar, @@ -13,6 +11,7 @@ import { useTheme, Avatar, getInitialsFromName, + WithComponents, } from 'stream-chat-react-native'; import { ScreenHeader } from '../components/ScreenHeader'; @@ -145,18 +144,19 @@ const EmptyListComponent = () => { ); }; -type ListComponentProps = ChannelListViewProps; - -// If the length of channels is 1, which means we only got 1:1-distinct channel, -// And we don't want to show 1:1-distinct channel in this list. -const ListComponent: React.FC = (props) => { +// Custom empty state that also shows when there's only the 1:1 direct channel +const SharedGroupsEmptyState = () => { const { channels, loadingChannels, refreshing } = useChannelsContext(); - if (channels && channels.length <= 1 && !loadingChannels && !refreshing) { + if (loadingChannels || refreshing) { + return null; + } + + if (!channels || channels.length <= 1) { return ; } - return ; + return null; }; type SharedGroupsScreenRouteProp = RouteProp; @@ -179,19 +179,24 @@ export const SharedGroupsScreen: React.FC = ({ return ( - + > + + ); }; diff --git a/examples/SampleApp/src/screens/ThreadScreen.tsx b/examples/SampleApp/src/screens/ThreadScreen.tsx index 96e2283356..8884bf9bc0 100644 --- a/examples/SampleApp/src/screens/ThreadScreen.tsx +++ b/examples/SampleApp/src/screens/ThreadScreen.tsx @@ -12,6 +12,7 @@ import { useTranslationContext, useTypingString, PortalWhileClosingView, + WithComponents, } from 'stream-chat-react-native'; import { useStateStore } from 'stream-chat-react-native'; @@ -148,15 +149,19 @@ export const ThreadScreen: React.FC = ({ return ( + = ({ shouldUseFlashList={messageListImplementation === 'flashlist'} /> + ); }; diff --git a/package/foobar.db-journal b/package/foobar.db-journal deleted file mode 100644 index 334d055043..0000000000 Binary files a/package/foobar.db-journal and /dev/null differ diff --git a/package/src/__tests__/offline-support/offline-feature.js b/package/src/__tests__/offline-support/offline-feature.js index 5cbbcae21f..bf6b72e38e 100644 --- a/package/src/__tests__/offline-support/offline-feature.js +++ b/package/src/__tests__/offline-support/offline-feature.js @@ -9,7 +9,7 @@ import { v4 as uuidv4 } from 'uuid'; import { ChannelList } from '../../components/ChannelList/ChannelList'; import { Chat } from '../../components/Chat/Chat'; -import { useChannelsContext } from '../../contexts/channelsContext/ChannelsContext'; +import { WithComponents } from '../../contexts/componentsContext/ComponentsContext'; import { getOrCreateChannelApi } from '../../mock-builders/api/getOrCreateChannel'; import { queryChannelsApi } from '../../mock-builders/api/queryChannels'; import { useMockedApis } from '../../mock-builders/api/useMockedApis'; @@ -48,33 +48,17 @@ import { BetterSqlite } from '../../test-utils/BetterSqlite'; * to those components might end up breaking tests for ChannelList, which will be quite painful * to debug. */ -const ChannelPreviewComponent = ({ channel, setActiveChannel }) => ( - - {channel.data.name} - {channel.state.messages[0]?.text} +/** + * Custom Preview component used via WithComponents. + * Receives { channel, muted, unread, lastMessage } from ChannelPreview. + */ +const ChannelPreviewComponent = ({ channel }) => ( + + {channel.data?.name} + {channel.state?.messages?.[0]?.text} ); -const ChannelListComponent = (props) => { - const { channels, onSelect } = useChannelsContext(); - if (!channels) { - return null; - } - - return ( - - {channels?.map((channel) => ( - - ))} - - ); -}; - test('Workaround to allow exporting tests', () => expect(true).toBe(true)); export const Generic = () => { @@ -223,12 +207,9 @@ export const Generic = () => { const renderComponent = () => render( - + + + , ); @@ -325,7 +306,7 @@ export const Generic = () => { await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); await waitFor(async () => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); }); }); @@ -340,7 +321,7 @@ export const Generic = () => { await waitFor( async () => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); await expectAllChannelsWithStateToBeInDB(screen.queryAllByLabelText); }, { timeout: 5000 }, @@ -359,7 +340,7 @@ export const Generic = () => { await act( async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true), ); - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); expect(screen.getByTestId(emptyChannel.cid)).toBeTruthy(); expect(chatClient.hydrateActiveChannels).toHaveBeenCalled(); expect(chatClient.hydrateActiveChannels.mock.calls[0][0]).toStrictEqual([emptyChannel]); @@ -372,7 +353,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[0].channel; const newMessage = generateMessage({ cid: targetChannel.cid, @@ -401,7 +382,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[0].channel; // check if the reads state is correct first @@ -463,7 +444,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[0].channel; // check if the reads state is correct first @@ -526,7 +507,7 @@ export const Generic = () => { act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); const newChannel = createChannel(); @@ -534,13 +515,26 @@ export const Generic = () => { useMockedApis(chatClient, [getOrCreateChannelApi(newChannel)]); await act(() => dispatchNotificationMessageNewEvent(chatClient, newChannel.channel)); + + // Verify the new channel appears on the UI await waitFor(() => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') .map((node) => node._fiber.pendingProps.testID); expect(channelIdsOnUI.includes(newChannel.channel.cid)).toBeTruthy(); }); - await expectAllChannelsWithStateToBeInDB(screen.queryAllByLabelText); + + // Verify the new channel and its state are persisted in the DB + await waitFor(async () => { + const channelsRows = await BetterSqlite.selectFromTable('channels'); + const messagesRows = await BetterSqlite.selectFromTable('messages'); + + expect(channelsRows.length).toBe(channels.length); + expect(messagesRows.length).toBe(allMessages.length); + + const matchingChannelRow = channelsRows.filter((c) => c.id === newChannel.channel.id); + expect(matchingChannelRow.length).toBe(1); + }); }); it('should update a message in database', async () => { @@ -549,7 +543,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const updatedMessage = { ...channels[0].messages[0] }; updatedMessage.text = uuidv4(); @@ -571,7 +565,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const removedChannel = channels[getRandomInt(0, channels.length - 1)].channel; act(() => dispatchNotificationRemovedFromChannel(chatClient, removedChannel)); await waitFor(async () => { @@ -598,7 +592,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const removedChannel = channels[getRandomInt(0, channels.length - 1)].channel; act(() => dispatchChannelDeletedEvent(chatClient, removedChannel)); await waitFor(async () => { @@ -625,7 +619,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const hiddenChannel = channels[getRandomInt(0, channels.length - 1)].channel; act(() => dispatchChannelHiddenEvent(chatClient, hiddenChannel)); await waitFor(async () => { @@ -655,7 +649,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const hiddenChannel = channels[getRandomInt(0, channels.length - 1)].channel; // first, we mark it as hidden act(() => dispatchChannelHiddenEvent(chatClient, hiddenChannel)); @@ -708,20 +702,23 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const newChannel = createChannel(); useMockedApis(chatClient, [getOrCreateChannelApi(newChannel)]); - act(() => dispatchNotificationAddedToChannel(chatClient, newChannel.channel)); + await act(() => dispatchNotificationAddedToChannel(chatClient, newChannel.channel)); - await waitFor(async () => { + // Verify the new channel appears on the UI + await waitFor(() => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') .map((node) => node._fiber.pendingProps.testID); expect(channelIdsOnUI.includes(newChannel.channel.cid)).toBeTruthy(); + }); - await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); + // Verify the new channel is persisted in the DB + await waitFor(async () => { const channelsRows = await BetterSqlite.selectFromTable('channels'); const matchingChannelsRows = channelsRows.filter((c) => c.id === newChannel.channel.id); @@ -739,7 +736,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const channelToTruncate = channels[getRandomInt(0, channels.length - 1)].channel; act(() => dispatchChannelTruncatedEvent(chatClient, channelToTruncate)); @@ -771,7 +768,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const channelResponse = channels[getRandomInt(0, channels.length - 1)]; const channelToTruncate = channelResponse.channel; @@ -815,7 +812,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const channelResponse = channels[getRandomInt(0, channels.length - 1)]; const channelToTruncate = channelResponse.channel; @@ -847,7 +844,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const channelResponse = channels[getRandomInt(0, channels.length - 1)]; const channelToTruncate = channelResponse.channel; @@ -881,7 +878,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = @@ -926,7 +923,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = @@ -1010,7 +1007,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = @@ -1076,7 +1073,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = @@ -1130,7 +1127,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = @@ -1167,7 +1164,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = @@ -1264,7 +1261,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = @@ -1323,7 +1320,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = @@ -1380,7 +1377,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = @@ -1438,7 +1435,7 @@ export const Generic = () => { act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const oldMemberCount = targetChannel.channel.member_count; @@ -1466,7 +1463,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)]; @@ -1494,7 +1491,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)]; @@ -1521,7 +1518,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; targetChannel.channel.name = uuidv4(); @@ -1547,7 +1544,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)]; @@ -1582,7 +1579,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)]; diff --git a/package/src/components/Attachment/Attachment.tsx b/package/src/components/Attachment/Attachment.tsx index b4039db3cd..02dd621db0 100644 --- a/package/src/components/Attachment/Attachment.tsx +++ b/package/src/components/Attachment/Attachment.tsx @@ -11,17 +11,8 @@ import { type Attachment as AttachmentType, } from 'stream-chat'; -import { AudioAttachment as AudioAttachmentDefault } from './Audio'; - -import { UnsupportedAttachment as UnsupportedAttachmentDefault } from './UnsupportedAttachment'; -import { URLPreview as URLPreviewDefault } from './UrlPreview'; -import { URLPreviewCompact as URLPreviewCompactDefault } from './UrlPreview/URLPreviewCompact'; - -import { FileAttachment as FileAttachmentDefault } from '../../components/Attachment/FileAttachment'; -import { Gallery as GalleryDefault } from '../../components/Attachment/Gallery'; -import { Giphy as GiphyDefault } from '../../components/Attachment/Giphy'; - import { useTheme } from '../../contexts'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, @@ -39,16 +30,7 @@ export type ActionHandler = (name: string, value: string) => void; export type AttachmentPropsWithContext = Pick< MessagesContextValue, - | 'AudioAttachment' - | 'FileAttachment' - | 'Gallery' - | 'Giphy' - | 'isAttachmentEqual' - | 'UrlPreview' - | 'URLPreviewCompact' - | 'myMessageTheme' - | 'urlPreviewType' - | 'UnsupportedAttachment' + 'isAttachmentEqual' | 'myMessageTheme' | 'urlPreviewType' > & Pick & { /** @@ -62,19 +44,16 @@ export type AttachmentPropsWithContext = Pick< }; const AttachmentWithContext = (props: AttachmentPropsWithContext) => { + const { attachment, index, message, urlPreviewType } = props; const { - attachment, AudioAttachment, FileAttachment, Gallery, Giphy, UrlPreview, URLPreviewCompact, - index, - message, - urlPreviewType, UnsupportedAttachment, - } = props; + } = useComponentsContext(); const audioAttachmentStyles = useAudioAttachmentStyles(); if (attachment.type === FileTypes.Giphy || attachment.type === FileTypes.Imgur) { @@ -164,31 +143,9 @@ export type AttachmentProps = Partial; * Attachment - The message attachment */ export const Attachment = (props: AttachmentProps) => { - const { - attachment, - AudioAttachment: PropAudioAttachment, - FileAttachment: PropFileAttachment, - Gallery: PropGallery, - Giphy: PropGiphy, - myMessageTheme: PropMyMessageTheme, - UrlPreview: PropUrlPreview, - URLPreviewCompact: PropURLPreviewCompact, - urlPreviewType: PropUrlPreviewType, - UnsupportedAttachment: PropUnsupportedAttachment, - } = props; + const { attachment } = props; - const { - AudioAttachment: ContextAudioAttachment, - FileAttachment: ContextFileAttachment, - Gallery: ContextGallery, - Giphy: ContextGiphy, - isAttachmentEqual, - myMessageTheme: ContextMyMessageTheme, - UrlPreview: ContextUrlPreview, - URLPreviewCompact: ContextURLPreviewCompact, - urlPreviewType: ContextUrlPreviewType, - UnsupportedAttachment: ContextUnsupportedAttachment, - } = useMessagesContext(); + const { isAttachmentEqual, myMessageTheme, urlPreviewType } = useMessagesContext(); const { message } = useMessageContext(); @@ -196,33 +153,14 @@ export const Attachment = (props: AttachmentProps) => { return null; } - const AudioAttachment = PropAudioAttachment || ContextAudioAttachment || AudioAttachmentDefault; - const FileAttachment = PropFileAttachment || ContextFileAttachment || FileAttachmentDefault; - const Gallery = PropGallery || ContextGallery || GalleryDefault; - const Giphy = PropGiphy || ContextGiphy || GiphyDefault; - const UrlPreview = PropUrlPreview || ContextUrlPreview || URLPreviewDefault; - const myMessageTheme = PropMyMessageTheme || ContextMyMessageTheme; - const URLPreviewCompact = - PropURLPreviewCompact || ContextURLPreviewCompact || URLPreviewCompactDefault; - const urlPreviewType = PropUrlPreviewType || ContextUrlPreviewType; - const UnsupportedAttachment = - PropUnsupportedAttachment || ContextUnsupportedAttachment || UnsupportedAttachmentDefault; - return ( ); diff --git a/package/src/components/Attachment/FileAttachment.tsx b/package/src/components/Attachment/FileAttachment.tsx index 4cc79069a4..e7b3def311 100644 --- a/package/src/components/Attachment/FileAttachment.tsx +++ b/package/src/components/Attachment/FileAttachment.tsx @@ -7,6 +7,7 @@ import { openUrlSafely } from './utils/openUrlSafely'; import { FileIconProps } from '../../components/Attachment/FileIcon'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, @@ -21,7 +22,7 @@ export type FileAttachmentPropsWithContext = Pick< MessageContextValue, 'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress' > & - Pick & { + Pick & { /** The attachment to render */ attachment: Attachment; attachmentIconSize?: FileIconProps['size']; @@ -41,13 +42,13 @@ const FileAttachmentWithContext = (props: FileAttachmentPropsWithContext) => { additionalPressableProps, attachment, attachmentIconSize, - FilePreview, onLongPress, onPress, onPressIn, preventPress, styles: stylesProp = styles, } = props; + const { FilePreview } = useComponentsContext(); const defaultOnPress = () => openUrlSafely(attachment.asset_url); @@ -99,17 +100,13 @@ export type FileAttachmentProps = Partial; export const FileAttachment = (props: FileAttachmentProps) => { - const { FilePreview: PropFilePreview } = props; const { onLongPress, onPress, onPressIn, preventPress } = useMessageContext(); - const { additionalPressableProps, FilePreview: ContextFilePreview } = useMessagesContext(); - - const FilePreview = PropFilePreview || ContextFilePreview; + const { additionalPressableProps } = useMessagesContext(); return ( & - Pick & { - styles?: Partial<{ - attachmentContainer: StyleProp; - container: StyleProp; - }>; - }; +export type FileAttachmentGroupPropsWithContext = Pick & { + styles?: Partial<{ + attachmentContainer: StyleProp; + container: StyleProp; + }>; +}; const FileAttachmentGroupWithContext = (props: FileAttachmentGroupPropsWithContext) => { - const { Attachment, files, message, styles: stylesProp = {} } = props; + const { files, message, styles: stylesProp = {} } = props; + const { Attachment } = useComponentsContext(); const { theme: { @@ -75,8 +69,6 @@ export const FileAttachmentGroup = (props: FileAttachmentGroupProps) => { const { files: contextFiles, message } = useMessageContext(); - const { Attachment = AttachmentDefault, AudioAttachment } = useMessagesContext(); - const files = propFiles || contextFiles; if (!files.length) { @@ -86,8 +78,6 @@ export const FileAttachmentGroup = (props: FileAttachmentGroupProps) => { return ( > & { +export type FilePreviewProps = { /** The attachment to render */ attachment: Attachment; attachmentIconSize?: FileIconProps['size']; @@ -30,14 +27,12 @@ export type FilePreviewProps = Partial { const { attachment, - FileAttachmentIcon: PropFileAttachmentIcon, attachmentIconSize, styles: stylesProp = {}, titleNumberOfLines = 2, indicator, } = props; - const { FileAttachmentIcon: ContextFileAttachmentIcon } = useMessagesContext(); - const FileAttachmentIcon = PropFileAttachmentIcon || ContextFileAttachmentIcon || FileIconDefault; + const { FileAttachmentIcon } = useComponentsContext(); const styles = useStyles(); diff --git a/package/src/components/Attachment/Gallery.tsx b/package/src/components/Attachment/Gallery.tsx index 28261d325e..2157f09dfe 100644 --- a/package/src/components/Attachment/Gallery.tsx +++ b/package/src/components/Attachment/Gallery.tsx @@ -16,6 +16,7 @@ import { openUrlSafely } from './utils/openUrlSafely'; import { useTranslationContext } from '../../contexts'; import { useChatConfigContext } from '../../contexts/chatConfigContext/ChatConfigContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { ImageGalleryContextValue, useImageGalleryContext, @@ -54,14 +55,7 @@ export type GalleryPropsWithContext = Pick & - Pick< - MessagesContextValue, - | 'additionalPressableProps' - | 'VideoThumbnail' - | 'ImageLoadingIndicator' - | 'ImageLoadingFailedIndicator' - | 'myMessageTheme' - > & + Pick & Pick & { channelId: string | undefined; messageHasOnlyOneMedia: boolean; @@ -72,8 +66,6 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => { additionalPressableProps, alignment, imageGalleryStateStore, - ImageLoadingFailedIndicator, - ImageLoadingIndicator, images, message, onLongPress, @@ -82,10 +74,8 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => { preventPress, setOverlay, videos, - VideoThumbnail, messageHasOnlyOneMedia = false, } = props; - const { resizableCDNHosts } = useChatConfigContext(); const { theme: { @@ -103,9 +93,7 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => { }, }, } = useTheme(); - const styles = useStyles(); - const sizeConfig = { gridHeight, gridWidth, @@ -114,12 +102,10 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => { minHeight, minWidth, }; - const imagesAndVideos = [...(images || []), ...(videos || [])]; const imagesAndVideosValue = `${images?.length}${videos?.length}${images ?.map((i) => `${i.image_url}${i.thumb_url}`) .join('')}${videos?.map((i) => `${i.image_url}${i.thumb_url}`).join('')}`; - const { height, invertedDirections, thumbnailGrid, width } = useMemo( () => buildGallery({ @@ -130,12 +116,10 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => { // eslint-disable-next-line react-hooks/exhaustive-deps [imagesAndVideosValue], ); - if (!imagesAndVideos?.length) { return null; } const numOfColumns = thumbnailGrid.length; - return ( { width, messageHasOnlyOneMedia, }); - if (!message) { return null; } - return ( { rowIndex={rowIndex} setOverlay={setOverlay} thumbnail={thumbnail} - VideoThumbnail={VideoThumbnail} /> ); })} @@ -216,7 +195,6 @@ const GalleryWithContext = (props: GalleryPropsWithContext) => { ); }; - type GalleryThumbnailProps = { borderRadius: GalleryImageBorderRadius; colIndex: number; @@ -227,24 +205,15 @@ type GalleryThumbnailProps = { numOfRows: number; rowIndex: number; thumbnail: Thumbnail; -} & Pick< - MessagesContextValue, - | 'additionalPressableProps' - | 'VideoThumbnail' - | 'ImageLoadingIndicator' - | 'ImageLoadingFailedIndicator' -> & +} & Pick & Pick & Pick & Pick; - const GalleryThumbnail = ({ additionalPressableProps, borderRadius, colIndex, imageGalleryStateStore, - ImageLoadingFailedIndicator, - ImageLoadingIndicator, imagesAndVideos, invertedDirections, message, @@ -257,8 +226,8 @@ const GalleryThumbnail = ({ rowIndex, setOverlay, thumbnail, - VideoThumbnail, }: GalleryThumbnailProps) => { + const { VideoThumbnail } = useComponentsContext(); const { theme: { messageItemView: { @@ -269,7 +238,6 @@ const GalleryThumbnail = ({ } = useTheme(); const { t } = useTranslationContext(); const styles = useStyles(); - const openImageViewer = () => { if (!message) { return; @@ -280,7 +248,6 @@ const GalleryThumbnail = ({ }); setOverlay('gallery'); }; - const defaultOnPress = () => { // If the url is defined then only try to open the file. if (thumbnail.url) { @@ -293,7 +260,6 @@ const GalleryThumbnail = ({ } } }; - return ( )} @@ -362,16 +326,11 @@ const GalleryThumbnail = ({ ); }; - const GalleryImageThumbnail = ({ borderRadius, - ImageLoadingFailedIndicator, - ImageLoadingIndicator, thumbnail, -}: Pick< - GalleryThumbnailProps, - 'ImageLoadingFailedIndicator' | 'ImageLoadingIndicator' | 'thumbnail' | 'borderRadius' ->) => { +}: Pick) => { + const { ImageLoadingFailedIndicator, ImageLoadingIndicator } = useComponentsContext(); const { isLoadingImage, isLoadingImageError, @@ -379,33 +338,27 @@ const GalleryImageThumbnail = ({ setLoadingImage, setLoadingImageError, } = useLoadingImage(); - const { theme: { messageItemView: { gallery }, }, } = useTheme(); - const styles = useStyles(); - const onLoadStart = useStableCallback(() => { setLoadingImageError(false); setLoadingImage(true); }); - const onLoad = useStableCallback(() => { setTimeout(() => { setLoadingImage(false); setLoadingImageError(false); }, 0); }); - const onError = useStableCallback(({ nativeEvent: { error } }: ImageErrorEvent) => { console.warn(error); setLoadingImage(false); setLoadingImageError(true); }); - return ( {isLoadingImageError ? ( @@ -426,7 +379,6 @@ const GalleryImageThumbnail = ({ ); }; - const areEqual = (prevProps: GalleryPropsWithContext, nextProps: GalleryPropsWithContext) => { const { alignment: prevAlignment, @@ -442,19 +394,16 @@ const areEqual = (prevProps: GalleryPropsWithContext, nextProps: GalleryPropsWit myMessageTheme: nextMyMessageTheme, videos: nextVideos, } = nextProps; - const alignmentEqual = prevAlignment === nextAlignment; if (!alignmentEqual) { return false; } - const messageEqual = prevMessage?.id === nextMessage?.id && `${prevMessage?.updated_at}` === `${nextMessage?.updated_at}`; if (!messageEqual) { return false; } - const imagesEqual = prevImages.length === nextImages.length && prevImages.every( @@ -465,7 +414,6 @@ const areEqual = (prevProps: GalleryPropsWithContext, nextProps: GalleryPropsWit if (!imagesEqual) { return false; } - const videosEqual = prevVideos.length === nextVideos.length && prevVideos.every( @@ -476,20 +424,15 @@ const areEqual = (prevProps: GalleryPropsWithContext, nextProps: GalleryPropsWit if (!videosEqual) { return false; } - const messageThemeEqual = JSON.stringify(prevMyMessageTheme) === JSON.stringify(nextMyMessageTheme); if (!messageThemeEqual) { return false; } - return true; }; - const MemoizedGallery = React.memo(GalleryWithContext, areEqual) as typeof GalleryWithContext; - export type GalleryProps = Partial; - /** * UI component for card in attachments. */ @@ -497,8 +440,6 @@ export const Gallery = (props: GalleryProps) => { const { alignment: propAlignment, additionalPressableProps: propAdditionalPressableProps, - ImageLoadingFailedIndicator: PropImageLoadingFailedIndicator, - ImageLoadingIndicator: PropImageLoadingIndicator, images: propImages, message: propMessage, myMessageTheme: propMyMessageTheme, @@ -508,10 +449,8 @@ export const Gallery = (props: GalleryProps) => { preventPress: propPreventPress, setOverlay: propSetOverlay, videos: propVideos, - VideoThumbnail: PropVideoThumbnail, messageContentOrder: propMessageContentOrder, } = props; - const { imageGalleryStateStore } = useImageGalleryContext(); const { alignment: contextAlignment, @@ -526,13 +465,9 @@ export const Gallery = (props: GalleryProps) => { } = useMessageContext(); const { additionalPressableProps: contextAdditionalPressableProps, - ImageLoadingFailedIndicator: ContextImageLoadingFailedIndicator, - ImageLoadingIndicator: ContextImageLoadingIndicator, myMessageTheme: contextMyMessageTheme, - VideoThumbnail: ContextVideoThumnbnail, } = useMessagesContext(); const { setOverlay: contextSetOverlay } = useOverlayContext(); - const images = propImages ?? contextImages ?? []; const videos = propVideos ?? contextVideos ?? []; const imagesAndVideos = [...images, ...videos]; @@ -541,7 +476,6 @@ export const Gallery = (props: GalleryProps) => { if (!images.length && !videos.length) { return null; } - const additionalPressableProps = propAdditionalPressableProps || contextAdditionalPressableProps; const onLongPress = propOnLongPress || contextOnLongPress; const onPressIn = propOnPressIn || contextOnPressIn; @@ -549,18 +483,12 @@ export const Gallery = (props: GalleryProps) => { const preventPress = typeof propPreventPress === 'boolean' ? propPreventPress : contextPreventPress; const setOverlay = propSetOverlay || contextSetOverlay; - const VideoThumbnail = PropVideoThumbnail || ContextVideoThumnbnail; - const ImageLoadingFailedIndicator = - PropImageLoadingFailedIndicator || ContextImageLoadingFailedIndicator; - const ImageLoadingIndicator = PropImageLoadingIndicator || ContextImageLoadingIndicator; const myMessageTheme = propMyMessageTheme || contextMyMessageTheme; const messageContentOrder = propMessageContentOrder || contextMessageContentOrder; - const messageHasOnlyOneMedia = messageContentOrder?.length === 1 && messageContentOrder?.includes('gallery') && imagesAndVideos.length === 1; - return ( { alignment, channelId: message?.cid, imageGalleryStateStore, - ImageLoadingFailedIndicator, - ImageLoadingIndicator, images, message, myMessageTheme, @@ -579,14 +505,12 @@ export const Gallery = (props: GalleryProps) => { preventPress, setOverlay, videos, - VideoThumbnail, messageHasOnlyOneMedia, messageContentOrder, }} /> ); }; - const useStyles = () => { const { theme: { semantics }, @@ -675,5 +599,4 @@ const useStyles = () => { }); }, [semantics, isMyMessage]); }; - Gallery.displayName = 'Gallery{messageItemView{gallery}}'; diff --git a/package/src/components/Attachment/GalleryImage.tsx b/package/src/components/Attachment/GalleryImage.tsx index fa174b0831..70a754d540 100644 --- a/package/src/components/Attachment/GalleryImage.tsx +++ b/package/src/components/Attachment/GalleryImage.tsx @@ -1,12 +1,13 @@ import React from 'react'; import { Image, ImageProps, StyleSheet } from 'react-native'; -import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { getUrlWithoutParams, isLocalUrl, makeImageCompatibleUrl } from '../../utils/utils'; -export type GalleryImageWithContextProps = GalleryImageProps & - Pick; +export type GalleryImageWithContextProps = GalleryImageProps & { + ImageComponent?: React.ComponentType; +}; export const GalleryImageWithContext = (props: GalleryImageWithContextProps) => { const { ImageComponent = Image, uri, style, ...rest } = props; @@ -48,7 +49,7 @@ export type GalleryImageProps = Omit & { }; export const GalleryImage = (props: GalleryImageProps) => { - const { ImageComponent } = useChatContext(); + const { ImageComponent } = useComponentsContext(); return ; }; diff --git a/package/src/components/Attachment/Giphy/GiphyImage.tsx b/package/src/components/Attachment/Giphy/GiphyImage.tsx index 05287c56d1..f5126b9ed5 100644 --- a/package/src/components/Attachment/Giphy/GiphyImage.tsx +++ b/package/src/components/Attachment/Giphy/GiphyImage.tsx @@ -1,9 +1,9 @@ import React, { useMemo } from 'react'; -import { Image, StyleSheet, View } from 'react-native'; +import { Image, ImageProps, StyleSheet, View } from 'react-native'; import type { Attachment } from 'stream-chat'; -import { ChatContextValue, useChatContext } from '../../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { MessagesContextValue, @@ -15,27 +15,19 @@ import { useLoadingImage } from '../../../hooks/useLoadingImage'; import { makeImageCompatibleUrl } from '../../../utils/utils'; import { GiphyBadge } from '../../ui/Badge/GiphyBadge'; -export type GiphyImagePropsWithContext = Pick & - Pick< - MessagesContextValue, - 'giphyVersion' | 'ImageLoadingIndicator' | 'ImageLoadingFailedIndicator' - > & { - attachment: Attachment; - /** - * Whether to render the preview image or the full image - */ - preview?: boolean; - }; +export type GiphyImagePropsWithContext = Pick & { + ImageComponent?: React.ComponentType; +} & { + attachment: Attachment; + /** + * Whether to render the preview image or the full image + */ + preview?: boolean; +}; const GiphyImageWithContext = (props: GiphyImagePropsWithContext) => { - const { - attachment, - giphyVersion, - ImageComponent = Image, - ImageLoadingFailedIndicator, - ImageLoadingIndicator, - preview = false, - } = props; + const { attachment, giphyVersion, ImageComponent = Image, preview = false } = props; + const { ImageLoadingFailedIndicator, ImageLoadingIndicator } = useComponentsContext(); const { giphy: giphyData, image_url, thumb_url, type } = attachment; @@ -168,25 +160,11 @@ export type GiphyImageProps = Partial & { * UI component for card in attachments. */ export const GiphyImage = (props: GiphyImageProps) => { - const { ImageComponent } = useChatContext(); + const { ImageComponent } = useComponentsContext(); const { giphyVersion } = useMessagesContext(); - const { - ImageLoadingFailedIndicator: ContextImageLoadingFailedIndicator, - ImageLoadingIndicator: ContextImageLoadingIndicator, - } = useMessagesContext(); - const ImageLoadingFailedIndicator = - ContextImageLoadingFailedIndicator || props.ImageLoadingFailedIndicator; - const ImageLoadingIndicator = ContextImageLoadingIndicator || props.ImageLoadingIndicator; - return ( - + ); }; diff --git a/package/src/components/Attachment/UnsupportedAttachment.tsx b/package/src/components/Attachment/UnsupportedAttachment.tsx index a4ed9361bc..b3fb8706f1 100644 --- a/package/src/components/Attachment/UnsupportedAttachment.tsx +++ b/package/src/components/Attachment/UnsupportedAttachment.tsx @@ -3,32 +3,27 @@ import { StyleSheet, Text, View } from 'react-native'; import type { Attachment } from 'stream-chat'; -import { FileIconProps } from './FileIcon'; +import type { FileIconProps } from './FileIcon'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, } from '../../contexts/messageContext/MessageContext'; -import { - MessagesContextValue, - useMessagesContext, -} from '../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; import { primitives } from '../../theme'; -export type UnsupportedAttachmentProps = Partial< - Pick & Pick -> & { +export type UnsupportedAttachmentProps = Partial> & { /** The attachment to render */ attachment: Attachment; attachmentIconSize?: FileIconProps['size']; }; export const UnsupportedAttachment = (props: UnsupportedAttachmentProps) => { - const { FileAttachmentIcon: FileAttachmentIconDefault } = useMessagesContext(); + const { FileAttachmentIcon } = useComponentsContext(); const { isMyMessage } = useMessageContext(); - const { attachment, attachmentIconSize, FileAttachmentIcon = FileAttachmentIconDefault } = props; + const { attachment, attachmentIconSize } = props; const styles = useStyles({ isMyMessage }); diff --git a/package/src/components/Attachment/UrlPreview/URLPreview.tsx b/package/src/components/Attachment/UrlPreview/URLPreview.tsx index fc7fd729be..424c2d1d1b 100644 --- a/package/src/components/Attachment/UrlPreview/URLPreview.tsx +++ b/package/src/components/Attachment/UrlPreview/URLPreview.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; import { Image, + ImageProps, ImageStyle, Pressable, StyleProp, @@ -13,7 +14,7 @@ import { import type { Attachment } from 'stream-chat'; -import { ChatContextValue, useChatContext } from '../../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, @@ -32,8 +33,9 @@ import { VideoPlayIndicator } from '../../ui'; import { ImageBackground } from '../../UIComponents/ImageBackground'; import { openUrlSafely } from '../utils/openUrlSafely'; -export type URLPreviewPropsWithContext = Pick & - Pick & +export type URLPreviewPropsWithContext = { + ImageComponent?: React.ComponentType; +} & Pick & Pick< MessagesContextValue, 'additionalPressableProps' | 'myMessageTheme' | 'isAttachmentEqual' @@ -210,7 +212,7 @@ export type URLPreviewProps = Partial & { * UI component for card in attachments. */ export const URLPreview = (props: URLPreviewProps) => { - const { ImageComponent } = useChatContext(); + const { ImageComponent } = useComponentsContext(); const { message, onLongPress, onPress, onPressIn, preventPress } = useMessageContext(); const { additionalPressableProps, isAttachmentEqual, myMessageTheme } = useMessagesContext(); diff --git a/package/src/components/Attachment/UrlPreview/URLPreviewCompact.tsx b/package/src/components/Attachment/UrlPreview/URLPreviewCompact.tsx index ed84992704..07a0fc9415 100644 --- a/package/src/components/Attachment/UrlPreview/URLPreviewCompact.tsx +++ b/package/src/components/Attachment/UrlPreview/URLPreviewCompact.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; import { Image, + ImageProps, ImageStyle, Pressable, StyleProp, @@ -13,7 +14,7 @@ import { import type { Attachment } from 'stream-chat'; -import { ChatContextValue, useChatContext } from '../../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, @@ -32,8 +33,9 @@ import { VideoPlayIndicator } from '../../ui'; import { ImageBackground } from '../../UIComponents/ImageBackground'; import { openUrlSafely } from '../utils/openUrlSafely'; -export type URLPreviewCompactPropsWithContext = Pick & - Pick & +export type URLPreviewCompactPropsWithContext = { + ImageComponent?: React.ComponentType; +} & Pick & Pick & { attachment: Attachment; channelId: string | undefined; @@ -208,7 +210,7 @@ export type URLPreviewCompactProps = Partial * UI component for card in attachments. */ export const URLPreviewCompact = (props: URLPreviewCompactProps) => { - const { ImageComponent } = useChatContext(); + const { ImageComponent } = useComponentsContext(); const { message, onLongPress, onPress, onPressIn, preventPress } = useMessageContext(); const { additionalPressableProps, myMessageTheme } = useMessagesContext(); diff --git a/package/src/components/AttachmentPicker/AttachmentPicker.tsx b/package/src/components/AttachmentPicker/AttachmentPicker.tsx index 5ddf942875..19022bbc90 100644 --- a/package/src/components/AttachmentPicker/AttachmentPicker.tsx +++ b/package/src/components/AttachmentPicker/AttachmentPicker.tsx @@ -15,6 +15,7 @@ import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; import { useAttachmentPickerContext } from '../../contexts/attachmentPickerContext/AttachmentPickerContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useStableCallback } from '../../hooks'; import { BottomSheet } from '../BottomSheetCompatibility/BottomSheet'; @@ -35,12 +36,11 @@ export const AttachmentPicker = () => { const { closePicker, attachmentPickerStore, - AttachmentPickerSelectionBar, - AttachmentPickerContent, attachmentPickerBottomSheetHeight, bottomSheetRef: ref, disableAttachmentPicker, } = useAttachmentPickerContext(); + const { AttachmentPickerContent, AttachmentPickerSelectionBar } = useComponentsContext(); const { theme: { semantics }, } = useTheme(); diff --git a/package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx b/package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx index ecbbc6218c..feeb508af4 100644 --- a/package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx +++ b/package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx @@ -7,6 +7,7 @@ import { FileReference, isLocalImageAttachment, isLocalVideoAttachment } from 's import { isIosLimited, type PhotoContentItemType } from './shared'; import { useAttachmentPickerContext } from '../../../../contexts'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { useAttachmentManagerState } from '../../../../contexts/messageInputContext/hooks/useAttachmentManagerState'; import { useMessageComposer } from '../../../../contexts/messageInputContext/hooks/useMessageComposer'; import { useMessageInputContext } from '../../../../contexts/messageInputContext/MessageInputContext'; @@ -26,8 +27,8 @@ type AttachmentPickerItemType = { const AttachmentVideo = (props: AttachmentPickerItemType) => { const { asset } = props; - const { numberOfAttachmentPickerImageColumns, ImageOverlaySelectedComponent } = - useAttachmentPickerContext(); + const { numberOfAttachmentPickerImageColumns } = useAttachmentPickerContext(); + const { ImageOverlaySelectedComponent } = useComponentsContext(); const { vw } = useViewport(); const { t } = useTranslationContext(); const messageComposer = useMessageComposer(); @@ -90,8 +91,8 @@ const AttachmentVideo = (props: AttachmentPickerItemType) => { const AttachmentImage = (props: AttachmentPickerItemType) => { const { asset } = props; - const { numberOfAttachmentPickerImageColumns, ImageOverlaySelectedComponent } = - useAttachmentPickerContext(); + const { numberOfAttachmentPickerImageColumns } = useAttachmentPickerContext(); + const { ImageOverlaySelectedComponent } = useComponentsContext(); const { theme: { attachmentPicker: { image, imageOverlay }, diff --git a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx index 897bf2c271..0f490ae913 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx @@ -7,20 +7,15 @@ import Animated, { LinearTransition, ZoomIn, ZoomOut } from 'react-native-reanim import { SearchSourceState, TextComposerState, TextComposerSuggestion } from 'stream-chat'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useMessageComposer } from '../../contexts/messageInputContext/hooks/useMessageComposer'; -import { - MessageInputContextValue, - useMessageInputContext, -} from '../../contexts/messageInputContext/MessageInputContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useStableCallback } from '../../hooks'; import { useStateStore } from '../../hooks/useStateStore'; export const DEFAULT_LIST_HEIGHT = 208; -export type AutoCompleteSuggestionListProps = Partial< - Pick ->; +export type AutoCompleteSuggestionListProps = Record; const textComposerStateSelector = (state: TextComposerState) => ({ suggestions: state.suggestions, @@ -31,19 +26,8 @@ const searchSourceStateSelector = (nextValue: SearchSourceState) => ({ items: nextValue.items, }); -export const AutoCompleteSuggestionList = ({ - AutoCompleteSuggestionHeader: propsAutoCompleteSuggestionHeader, - AutoCompleteSuggestionItem: propsAutoCompleteSuggestionItem, -}: AutoCompleteSuggestionListProps) => { - const { - AutoCompleteSuggestionHeader: contextAutoCompleteSuggestionHeader, - AutoCompleteSuggestionItem: contextAutoCompleteSuggestionItem, - } = useMessageInputContext(); - - const AutoCompleteSuggestionHeader = - propsAutoCompleteSuggestionHeader ?? contextAutoCompleteSuggestionHeader; - const AutoCompleteSuggestionItem = - propsAutoCompleteSuggestionItem ?? contextAutoCompleteSuggestionItem; +export const AutoCompleteSuggestionList = () => { + const { AutoCompleteSuggestionHeader, AutoCompleteSuggestionItem } = useComponentsContext(); const messageComposer = useMessageComposer(); const { textComposer } = messageComposer; diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 3a8542f53d..0cde399ac9 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -54,6 +54,7 @@ import { ChannelContextValue, ChannelProvider } from '../../contexts/channelCont import type { UseChannelStateValue } from '../../contexts/channelsStateContext/useChannelState'; import { useChannelState } from '../../contexts/channelsStateContext/useChannelState'; import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { MessageComposerProvider } from '../../contexts/messageComposerContext/MessageComposerContext'; import { MessageContextValue } from '../../contexts/messageContext/MessageContext'; import { @@ -111,116 +112,11 @@ import { MessageStatusTypes, ReactionData, } from '../../utils/utils'; -import { Attachment as AttachmentDefault } from '../Attachment/Attachment'; -import { AudioAttachment as AudioAttachmentDefault } from '../Attachment/Audio'; -import { FileAttachment as FileAttachmentDefault } from '../Attachment/FileAttachment'; -import { FileAttachmentGroup as FileAttachmentGroupDefault } from '../Attachment/FileAttachmentGroup'; -import { FileIcon as FileIconDefault } from '../Attachment/FileIcon'; -import { FilePreview as FilePreviewDefault } from '../Attachment/FilePreview'; -import { Gallery as GalleryDefault } from '../Attachment/Gallery'; -import { Giphy as GiphyDefault } from '../Attachment/Giphy'; -import { ImageLoadingFailedIndicator as ImageLoadingFailedIndicatorDefault } from '../Attachment/ImageLoadingFailedIndicator'; -import { ImageLoadingIndicator as ImageLoadingIndicatorDefault } from '../Attachment/ImageLoadingIndicator'; -import { UnsupportedAttachment as UnsupportedAttachmentDefault } from '../Attachment/UnsupportedAttachment'; -import { URLPreview as URLPreviewDefault } from '../Attachment/UrlPreview'; -import { URLPreviewCompact as URLPreviewCompactDefault } from '../Attachment/UrlPreview/URLPreviewCompact'; -import { VideoThumbnail as VideoThumbnailDefault } from '../Attachment/VideoThumbnail'; import { AttachmentPicker } from '../AttachmentPicker/AttachmentPicker'; -import { AttachmentPickerContent as DefaultAttachmentPickerContent } from '../AttachmentPicker/components/AttachmentPickerContent'; -import { AttachmentPickerSelectionBar as DefaultAttachmentPickerSelectionBar } from '../AttachmentPicker/components/AttachmentPickerSelectionBar'; -import { ImageOverlaySelectedComponent as DefaultImageOverlaySelectedComponent } from '../AttachmentPicker/components/ImageOverlaySelectedComponent'; -import { AutoCompleteSuggestionHeader as AutoCompleteSuggestionHeaderDefault } from '../AutoCompleteInput/AutoCompleteSuggestionHeader'; -import { AutoCompleteSuggestionItem as AutoCompleteSuggestionItemDefault } from '../AutoCompleteInput/AutoCompleteSuggestionItem'; -import { AutoCompleteSuggestionList as AutoCompleteSuggestionListDefault } from '../AutoCompleteInput/AutoCompleteSuggestionList'; -import { InputView as InputViewDefault } from '../AutoCompleteInput/InputView'; -import { EmptyStateIndicator as EmptyStateIndicatorDefault } from '../Indicators/EmptyStateIndicator'; -import { - LoadingErrorIndicator as LoadingErrorIndicatorDefault, - LoadingErrorProps, -} from '../Indicators/LoadingErrorIndicator'; -import { LoadingIndicator as LoadingIndicatorDefault } from '../Indicators/LoadingIndicator'; -import { - KeyboardCompatibleView as KeyboardCompatibleViewDefault, - KeyboardCompatibleViewProps, -} from '../KeyboardCompatibleView/KeyboardControllerAvoidingView'; -import { Message as MessageDefault } from '../Message/Message'; -import { MessagePinnedHeader as MessagePinnedHeaderDefault } from '../Message/MessageItemView/Headers/MessagePinnedHeader'; -import { MessageReminderHeader as MessageReminderHeaderDefault } from '../Message/MessageItemView/Headers/MessageReminderHeader'; -import { MessageSavedForLaterHeader as MessageSavedForLaterHeaderDefault } from '../Message/MessageItemView/Headers/MessageSavedForLaterHeader'; -import { SentToChannelHeader as SentToChannelHeaderDefault } from '../Message/MessageItemView/Headers/SentToChannelHeader'; -import { MessageAuthor as MessageAuthorDefault } from '../Message/MessageItemView/MessageAuthor'; -import { MessageBlocked as MessageBlockedDefault } from '../Message/MessageItemView/MessageBlocked'; -import { MessageBounce as MessageBounceDefault } from '../Message/MessageItemView/MessageBounce'; -import { MessageContent as MessageContentDefault } from '../Message/MessageItemView/MessageContent'; -import { MessageDeleted as MessageDeletedDefault } from '../Message/MessageItemView/MessageDeleted'; -import { MessageError as MessageErrorDefault } from '../Message/MessageItemView/MessageError'; -import { MessageFooter as MessageFooterDefault } from '../Message/MessageItemView/MessageFooter'; -import { MessageHeader as MessageHeaderDefault } from '../Message/MessageItemView/MessageHeader'; -import { MessageItemView as MessageItemViewDefault } from '../Message/MessageItemView/MessageItemView'; -import { MessageReplies as MessageRepliesDefault } from '../Message/MessageItemView/MessageReplies'; -import { MessageRepliesAvatars as MessageRepliesAvatarsDefault } from '../Message/MessageItemView/MessageRepliesAvatars'; -import { MessageStatus as MessageStatusDefault } from '../Message/MessageItemView/MessageStatus'; -import { MessageSwipeContent as MessageSwipeContentDefault } from '../Message/MessageItemView/MessageSwipeContent'; -import { MessageTimestamp as MessageTimestampDefault } from '../Message/MessageItemView/MessageTimestamp'; -import { ReactionListBottom as ReactionListBottomDefault } from '../Message/MessageItemView/ReactionList/ReactionListBottom'; -import { ReactionListClustered as ReactionListClusteredDefault } from '../Message/MessageItemView/ReactionList/ReactionListClustered'; -import { ReactionListCountItem as ReactionListCountItemDefault } from '../Message/MessageItemView/ReactionList/ReactionListItem'; -import { ReactionListItem as ReactionListItemDefault } from '../Message/MessageItemView/ReactionList/ReactionListItem'; -import { ReactionListItemWrapper as ReactionListItemWrapperDefault } from '../Message/MessageItemView/ReactionList/ReactionListItemWrapper'; -import { ReactionListTop as ReactionListTopDefault } from '../Message/MessageItemView/ReactionList/ReactionListTop'; -import { StreamingMessageView as DefaultStreamingMessageView } from '../Message/MessageItemView/StreamingMessageView'; -import { AttachmentUploadPreviewList as AttachmentUploadPreviewDefault } from '../MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList'; -import { FileUploadInProgressIndicator as FileUploadInProgressIndicatorDefault } from '../MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator'; -import { FileUploadRetryIndicator as FileUploadRetryIndicatorDefault } from '../MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator'; -import { FileUploadNotSupportedIndicator as FileUploadNotSupportedIndicatorDefault } from '../MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator'; -import { ImageUploadInProgressIndicator as ImageUploadInProgressIndicatorDefault } from '../MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator'; -import { ImageUploadRetryIndicator as ImageUploadRetryIndicatorDefault } from '../MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator'; -import { ImageUploadNotSupportedIndicator as ImageUploadNotSupportedIndicatorDefault } from '../MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator'; -import { AudioAttachmentUploadPreview as AudioAttachmentUploadPreviewDefault } from '../MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview'; -import { FileAttachmentUploadPreview as FileAttachmentUploadPreviewDefault } from '../MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview'; -import { ImageAttachmentUploadPreview as ImageAttachmentUploadPreviewDefault } from '../MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview'; -import { VideoAttachmentUploadPreview as VideoAttachmentUploadPreviewDefault } from '../MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview'; -import { AudioRecorder as AudioRecorderDefault } from '../MessageInput/components/AudioRecorder/AudioRecorder'; -import { AudioRecordingButton as AudioRecordingButtonDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingButton'; -import { AudioRecordingInProgress as AudioRecordingInProgressDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingInProgress'; -import { AudioRecordingLockIndicator as AudioRecordingLockIndicatorDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingLockIndicator'; -import { AudioRecordingPreview as AudioRecordingPreviewDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingPreview'; -import { AudioRecordingWaveform as AudioRecordingWaveformDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingWaveform'; -import { InputButtons as InputButtonsDefault } from '../MessageInput/components/InputButtons'; -import { AttachButton as AttachButtonDefault } from '../MessageInput/components/InputButtons/AttachButton'; -import { CooldownTimer as CooldownTimerDefault } from '../MessageInput/components/OutputButtons/CooldownTimer'; -import { SendButton as SendButtonDefault } from '../MessageInput/components/OutputButtons/SendButton'; -import { MessageComposerLeadingView as MessageComposerLeadingViewDefault } from '../MessageInput/MessageComposerLeadingView'; -import { MessageComposerTrailingView as MessageComposerTrailingViewDefault } from '../MessageInput/MessageComposerTrailingView'; -import { MessageInputFooterView as MessageInputFooterViewDefault } from '../MessageInput/MessageInputFooterView'; -import { MessageInputHeaderView as MessageInputHeaderViewDefault } from '../MessageInput/MessageInputHeaderView'; -import { MessageInputLeadingView as MessageInputLeadingViewDefault } from '../MessageInput/MessageInputLeadingView'; -import { MessageInputTrailingView as MessageInputTrailingViewDefault } from '../MessageInput/MessageInputTrailingView'; -import { SendMessageDisallowedIndicator as SendMessageDisallowedIndicatorDefault } from '../MessageInput/SendMessageDisallowedIndicator'; -import { ShowThreadMessageInChannelButton as ShowThreadMessageInChannelButtonDefault } from '../MessageInput/ShowThreadMessageInChannelButton'; -import { StopMessageStreamingButton as DefaultStopMessageStreamingButton } from '../MessageInput/StopMessageStreamingButton'; -import { DateHeader as DateHeaderDefault } from '../MessageList/DateHeader'; -import { InlineDateSeparator as InlineDateSeparatorDefault } from '../MessageList/InlineDateSeparator'; -import { InlineUnreadIndicator as InlineUnreadIndicatorDefault } from '../MessageList/InlineUnreadIndicator'; -import { MessageList as MessageListDefault } from '../MessageList/MessageList'; -import { MessageSystem as MessageSystemDefault } from '../MessageList/MessageSystem'; -import { NetworkDownIndicator as NetworkDownIndicatorDefault } from '../MessageList/NetworkDownIndicator'; -import { ScrollToBottomButton as ScrollToBottomButtonDefault } from '../MessageList/ScrollToBottomButton'; -import { StickyHeader as StickyHeaderDefault } from '../MessageList/StickyHeader'; -import { TypingIndicator as TypingIndicatorDefault } from '../MessageList/TypingIndicator'; -import { TypingIndicatorContainer as TypingIndicatorContainerDefault } from '../MessageList/TypingIndicatorContainer'; -import { UnreadMessagesNotification as UnreadMessagesNotificationDefault } from '../MessageList/UnreadMessagesNotification'; +import type { KeyboardCompatibleViewProps } from '../KeyboardCompatibleView/KeyboardControllerAvoidingView'; import { Emoji } from '../MessageMenu/EmojiPickerList'; import { emojis } from '../MessageMenu/emojis'; -import { MessageActionList as MessageActionListDefault } from '../MessageMenu/MessageActionList'; -import { MessageActionListItem as MessageActionListItemDefault } from '../MessageMenu/MessageActionListItem'; -import { MessageMenu as MessageMenuDefault } from '../MessageMenu/MessageMenu'; -import { MessageReactionPicker as MessageReactionPickerDefault } from '../MessageMenu/MessageReactionPicker'; -import { MessageUserReactions as MessageUserReactionsDefault } from '../MessageMenu/MessageUserReactions'; -import { MessageUserReactionsAvatar as MessageUserReactionsAvatarDefault } from '../MessageMenu/MessageUserReactionsAvatar'; -import { MessageUserReactionsItem as MessageUserReactionsItemDefault } from '../MessageMenu/MessageUserReactionsItem'; import { toUnicodeScalarString } from '../MessageMenu/utils/toUnicodeScalarString'; -import { Reply as ReplyDefault } from '../Reply/Reply'; export type MarkReadFunctionOptions = { /** @@ -288,30 +184,47 @@ export type ChannelPropsWithContext = Pick & | 'bottomInset' | 'topInset' | 'disableAttachmentPicker' - | 'ImageOverlaySelectedComponent' | 'numberOfAttachmentPickerImageColumns' - | 'AttachmentPickerIOSSelectMorePhotos' | 'numberOfAttachmentImagesToLoadPerCall' - | 'AttachmentPickerContent' > > & Partial< Pick< ChannelContextValue, - | 'EmptyStateIndicator' | 'enableMessageGroupingByUser' | 'enforceUniqueReaction' | 'hideStickyDateHeader' | 'hideDateSeparators' - | 'LoadingIndicator' | 'maxTimeBetweenGroupedMessages' - | 'NetworkDownIndicator' - | 'StickyHeader' | 'maximumMessageLimit' > > & Pick & - Partial> & + Partial< + Pick< + InputMessageInputContextValue, + | 'additionalTextInputProps' + | 'allowSendBeforeAttachmentsUpload' + | 'asyncMessagesLockDistance' + | 'asyncMessagesMinimumPressDuration' + | 'audioRecordingSendOnComplete' + | 'asyncMessagesSlideToCancelDistance' + | 'attachmentPickerBottomSheetHeight' + | 'attachmentSelectionBarHeight' + | 'audioRecordingEnabled' + | 'compressImageQuality' + | 'createPollOptionGap' + | 'doFileUploadRequest' + | 'handleAttachButtonPress' + | 'hasCameraPicker' + | 'hasCommands' + | 'hasFilePicker' + | 'hasImagePicker' + | 'messageInputFloating' + | 'openPollCreationDialog' + | 'setInputRef' + > + > & Pick & Partial< Pick @@ -321,25 +234,15 @@ export type ChannelPropsWithContext = Pick & Pick< MessagesContextValue, | 'additionalPressableProps' - | 'Attachment' - | 'AudioAttachment' | 'customMessageSwipeAction' - | 'DateHeader' | 'deletedMessagesVisibilityType' | 'disableTypingIndicator' | 'dismissKeyboardOnMessageTouch' | 'enableSwipeToReply' | 'urlPreviewType' - | 'UnsupportedAttachment' - | 'FileAttachment' - | 'FileAttachmentIcon' - | 'FileAttachmentGroup' - | 'FilePreview' | 'FlatList' | 'forceAlignMessages' - | 'Gallery' | 'getMessageGroupStyle' - | 'Giphy' | 'giphyVersion' | 'handleBan' | 'handleCopy' @@ -355,77 +258,22 @@ export type ChannelPropsWithContext = Pick & | 'handleRetry' | 'handleThreadReply' | 'handleBlockUser' - | 'InlineDateSeparator' - | 'InlineUnreadIndicator' | 'isAttachmentEqual' - | 'ImageLoadingFailedIndicator' - | 'ImageLoadingIndicator' | 'markdownRules' - | 'Message' - | 'MessageActionList' - | 'MessageActionListItem' | 'messageActions' - | 'MessageAuthor' - | 'MessageBounce' - | 'MessageBlocked' - | 'MessageContent' - | 'MessageContentBottomView' - | 'MessageContentLeadingView' | 'messageContentOrder' - | 'MessageContentTrailingView' - | 'MessageContentTopView' - | 'MessageDeleted' - | 'MessageError' - | 'MessageFooter' - | 'MessageHeader' - | 'MessageList' - | 'MessageLocation' - | 'MessageMenu' - | 'MessagePinnedHeader' - | 'MessageReminderHeader' - | 'MessageSavedForLaterHeader' - | 'SentToChannelHeader' - | 'MessageReplies' - | 'MessageRepliesAvatars' - | 'MessageSpacer' - | 'MessageItemView' - | 'MessageStatus' - | 'MessageSystem' - | 'MessageText' | 'messageTextNumberOfLines' - | 'MessageTimestamp' - | 'MessageUserReactions' - | 'MessageSwipeContent' | 'messageSwipeToReplyHitSlop' | 'myMessageTheme' | 'onLongPressMessage' | 'onPressInMessage' | 'onPressMessage' - | 'MessageReactionPicker' - | 'MessageUserReactionsAvatar' - | 'MessageUserReactionsItem' - | 'ReactionListBottom' | 'reactionListPosition' | 'reactionListType' - | 'ReactionListTop' - | 'ReactionListClustered' - | 'ReactionListItem' - | 'ReactionListItemWrapper' - | 'ReactionListCountItem' - | 'Reply' | 'shouldShowUnreadUnderlay' - | 'ScrollToBottomButton' | 'selectReaction' | 'supportedReactions' - | 'TypingIndicator' - | 'TypingIndicatorContainer' - | 'UrlPreview' - | 'URLPreviewCompact' - | 'VideoThumbnail' - | 'PollContent' | 'hasCreatePoll' - | 'UnreadMessagesNotification' - | 'StreamingMessageView' > > & Partial> & @@ -494,31 +342,7 @@ export type ChannelPropsWithContext = Pick & */ initialScrollToFirstUnreadMessage?: boolean; keyboardBehavior?: KeyboardCompatibleViewProps['behavior']; - /** - * Custom wrapper component that handles height adjustment of Channel component when keyboard is opened or dismissed - * Default component (accepts the same props): [KeyboardCompatibleView](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/KeyboardCompatibleView/KeyboardCompatibleView.tsx) - * - * **Example:** - * - * ``` - * { - * return ( - * - * {props.children} - * - * ) - * }} - * /> - * ``` - */ - KeyboardCompatibleView?: React.ComponentType; keyboardVerticalOffset?: number; - /** - * Custom loading error indicator to override the Stream default - */ - LoadingErrorIndicator?: React.ComponentType; /** * Boolean flag to enable/disable marking the channel as read on mount */ @@ -554,15 +378,7 @@ export type ChannelPropsWithContext = Pick & * is sent). */ initializeOnMount?: boolean; - } & Partial< - Pick< - InputMessageInputContextValue, - | 'openPollCreationDialog' - | 'CreatePollContent' - | 'StopMessageStreamingButton' - | 'allowSendBeforeAttachmentsUpload' - > - >; + }; const ChannelWithContext = (props: PropsWithChildren) => { const { @@ -576,24 +392,9 @@ const ChannelWithContext = (props: PropsWithChildren) = asyncMessagesMinimumPressDuration = 500, asyncMessagesSlideToCancelDistance = 75, audioRecordingSendOnComplete = false, - AttachButton = AttachButtonDefault, - Attachment = AttachmentDefault, attachmentPickerBottomSheetHeight = disableAttachmentPicker ? 72 : 333, - AttachmentPickerSelectionBar = DefaultAttachmentPickerSelectionBar, attachmentSelectionBarHeight = 72, - AudioAttachment = AudioAttachmentDefault, - AudioAttachmentUploadPreview = AudioAttachmentUploadPreviewDefault, - AudioRecorder = AudioRecorderDefault, audioRecordingEnabled = false, - AudioRecordingInProgress = AudioRecordingInProgressDefault, - AudioRecordingLockIndicator = AudioRecordingLockIndicatorDefault, - AudioRecordingPreview = AudioRecordingPreviewDefault, - AudioRecordingWaveform = AudioRecordingWaveformDefault, - AutoCompleteSuggestionHeader = AutoCompleteSuggestionHeaderDefault, - AutoCompleteSuggestionItem = AutoCompleteSuggestionItemDefault, - AutoCompleteSuggestionList = AutoCompleteSuggestionListDefault, - AttachmentUploadPreviewList = AttachmentUploadPreviewDefault, - ImageOverlaySelectedComponent = DefaultImageOverlaySelectedComponent, numberOfAttachmentImagesToLoadPerCall = 25, numberOfAttachmentPickerImageColumns = 3, giphyVersion = 'fixed_height', @@ -602,11 +403,8 @@ const ChannelWithContext = (props: PropsWithChildren) = children, client, compressImageQuality, - CooldownTimer = CooldownTimerDefault, - CreatePollContent, createPollOptionGap, customMessageSwipeAction, - DateHeader = DateHeaderDefault, deletedMessagesVisibilityType = 'always', disableKeyboardCompatibleView = false, disableTypingIndicator, @@ -616,28 +414,14 @@ const ChannelWithContext = (props: PropsWithChildren) = doSendMessageRequest, preSendMessageRequest, doUpdateMessageRequest, - EmptyStateIndicator = EmptyStateIndicatorDefault, enableMessageGroupingByUser = true, enableOfflineSupport, allowSendBeforeAttachmentsUpload = enableOfflineSupport, enableSwipeToReply = true, enforceUniqueReaction = false, - FileAttachment = FileAttachmentDefault, - FileAttachmentUploadPreview = FileAttachmentUploadPreviewDefault, - FileAttachmentGroup = FileAttachmentGroupDefault, - FileAttachmentIcon = FileIconDefault, - FilePreview = FilePreviewDefault, - FileUploadInProgressIndicator = FileUploadInProgressIndicatorDefault, - FileUploadRetryIndicator = FileUploadRetryIndicatorDefault, - FileUploadNotSupportedIndicator = FileUploadNotSupportedIndicatorDefault, - ImageUploadInProgressIndicator = ImageUploadInProgressIndicatorDefault, - ImageUploadRetryIndicator = ImageUploadRetryIndicatorDefault, - ImageUploadNotSupportedIndicator = ImageUploadNotSupportedIndicatorDefault, FlatList = NativeHandlers.FlatList, forceAlignMessages, - Gallery = GalleryDefault, getMessageGroupStyle, - Giphy = GiphyDefault, handleAttachButtonPress, handleBan, handleCopy, @@ -661,39 +445,17 @@ const ChannelWithContext = (props: PropsWithChildren) = hasImagePicker = isImagePickerAvailable() || isImageMediaLibraryAvailable(), hideDateSeparators = false, hideStickyDateHeader = false, - ImageAttachmentUploadPreview = ImageAttachmentUploadPreviewDefault, - ImageLoadingFailedIndicator = ImageLoadingFailedIndicatorDefault, - ImageLoadingIndicator = ImageLoadingIndicatorDefault, initialScrollToFirstUnreadMessage = false, - InlineDateSeparator = InlineDateSeparatorDefault, - InlineUnreadIndicator = InlineUnreadIndicatorDefault, - Input, - InputView = InputViewDefault, - InputButtons = InputButtonsDefault, - MessageComposerLeadingView = MessageComposerLeadingViewDefault, - MessageComposerTrailingView = MessageComposerTrailingViewDefault, isAttachmentEqual, isMessageAIGenerated = () => false, keyboardBehavior, - KeyboardCompatibleView = KeyboardCompatibleViewDefault, keyboardVerticalOffset, - LoadingErrorIndicator = LoadingErrorIndicatorDefault, - LoadingIndicator = LoadingIndicatorDefault, loadingMore: loadingMoreProp, loadingMoreRecent: loadingMoreRecentProp, markdownRules, markReadOnMount = true, maxTimeBetweenGroupedMessages, - Message = MessageDefault, - MessageActionList = MessageActionListDefault, - MessageActionListItem = MessageActionListItemDefault, messageActions, - MessageAuthor = MessageAuthorDefault, - MessageBlocked = MessageBlockedDefault, - MessageBounce = MessageBounceDefault, - MessageContent = MessageContentDefault, - MessageContentBottomView, - MessageContentLeadingView, messageContentOrder = [ 'quoted_reply', 'gallery', @@ -704,42 +466,11 @@ const ChannelWithContext = (props: PropsWithChildren) = 'text', 'location', ], - MessageContentTrailingView, - MessageContentTopView, - MessageDeleted = MessageDeletedDefault, - MessageError = MessageErrorDefault, messageInputFloating = false, - MessageInputFooterView = MessageInputFooterViewDefault, - MessageInputHeaderView = MessageInputHeaderViewDefault, - MessageInputLeadingView = MessageInputLeadingViewDefault, - MessageInputTrailingView = MessageInputTrailingViewDefault, - MessageFooter = MessageFooterDefault, - MessageHeader = MessageHeaderDefault, messageId, - MessageList = MessageListDefault, - MessageLocation, - MessageMenu = MessageMenuDefault, - MessagePinnedHeader = MessagePinnedHeaderDefault, - MessageReminderHeader = MessageReminderHeaderDefault, - MessageSavedForLaterHeader = MessageSavedForLaterHeaderDefault, - SentToChannelHeader = SentToChannelHeaderDefault, - MessageReactionPicker = MessageReactionPickerDefault, - MessageReplies = MessageRepliesDefault, - MessageRepliesAvatars = MessageRepliesAvatarsDefault, - MessageSpacer, - MessageItemView = MessageItemViewDefault, - MessageStatus = MessageStatusDefault, - MessageSwipeContent = MessageSwipeContentDefault, messageSwipeToReplyHitSlop, - MessageSystem = MessageSystemDefault, - MessageText, messageTextNumberOfLines, - MessageTimestamp = MessageTimestampDefault, - MessageUserReactions = MessageUserReactionsDefault, - MessageUserReactionsAvatar = MessageUserReactionsAvatarDefault, - MessageUserReactionsItem = MessageUserReactionsItemDefault, myMessageTheme, - NetworkDownIndicator = NetworkDownIndicatorDefault, // TODO: Think about this one newMessageStateUpdateThrottleInterval = defaultThrottleInterval, onLongPressMessage, @@ -748,56 +479,30 @@ const ChannelWithContext = (props: PropsWithChildren) = onAlsoSentToChannelHeaderPress, openPollCreationDialog, overrideOwnCapabilities, - PollContent, - ReactionListBottom = ReactionListBottomDefault, reactionListPosition = 'top', reactionListType = 'clustered', - ReactionListTop = ReactionListTopDefault, - ReactionListClustered = ReactionListClusteredDefault, - ReactionListItem = ReactionListItemDefault, - ReactionListItemWrapper = ReactionListItemWrapperDefault, - ReactionListCountItem = ReactionListCountItemDefault, - Reply = ReplyDefault, - ScrollToBottomButton = ScrollToBottomButtonDefault, selectReaction, - SendButton = SendButtonDefault, - SendMessageDisallowedIndicator = SendMessageDisallowedIndicatorDefault, setInputRef, setThreadMessages, shouldShowUnreadUnderlay = true, shouldSyncChannel, - ShowThreadMessageInChannelButton = ShowThreadMessageInChannelButtonDefault, - StartAudioRecordingButton = AudioRecordingButtonDefault, stateUpdateThrottleInterval = defaultThrottleInterval, - StickyHeader = StickyHeaderDefault, - StopMessageStreamingButton: StopMessageStreamingButtonOverride, - StreamingMessageView = DefaultStreamingMessageView, supportedReactions = reactionData, t, thread: threadFromProps, threadList, threadMessages, topInset, - TypingIndicator = TypingIndicatorDefault, - TypingIndicatorContainer = TypingIndicatorContainerDefault, - UnreadMessagesNotification = UnreadMessagesNotificationDefault, - UrlPreview = URLPreviewDefault, - URLPreviewCompact = URLPreviewCompactDefault, - VideoAttachmentUploadPreview = VideoAttachmentUploadPreviewDefault, - VideoThumbnail = VideoThumbnailDefault, isOnline, maximumMessageLimit, initializeOnMount = true, - AttachmentPickerContent = DefaultAttachmentPickerContent, urlPreviewType = 'full', - UnsupportedAttachment = UnsupportedAttachmentDefault, } = props; + const components = useComponentsContext(); + const { KeyboardCompatibleView, LoadingErrorIndicator } = components; + const { thread: threadProps, threadInstance } = threadFromProps; - const StopMessageStreamingButton = - StopMessageStreamingButtonOverride === undefined - ? DefaultStopMessageStreamingButton - : StopMessageStreamingButtonOverride; const styles = useStyles(); const [deleted, setDeleted] = useState(false); @@ -1815,13 +1520,10 @@ const ChannelWithContext = (props: PropsWithChildren) = disableAttachmentPicker, openPicker: handleOpenPicker, topInset, - ImageOverlaySelectedComponent, - AttachmentPickerSelectionBar, numberOfAttachmentPickerImageColumns, attachmentPickerBottomSheetHeight, attachmentSelectionBarHeight, numberOfAttachmentImagesToLoadPerCall, - AttachmentPickerContent, }), [ bottomInset, @@ -1830,13 +1532,10 @@ const ChannelWithContext = (props: PropsWithChildren) = disableAttachmentPicker, handleOpenPicker, topInset, - ImageOverlaySelectedComponent, - AttachmentPickerSelectionBar, numberOfAttachmentPickerImageColumns, attachmentPickerBottomSheetHeight, attachmentSelectionBarHeight, numberOfAttachmentImagesToLoadPerCall, - AttachmentPickerContent, ], ); @@ -1849,7 +1548,6 @@ const ChannelWithContext = (props: PropsWithChildren) = channel, channelUnreadStateStore, disabled: !!channel?.data?.frozen, - EmptyStateIndicator, enableMessageGroupingByUser, enforceUniqueReaction, error, @@ -1861,19 +1559,16 @@ const ChannelWithContext = (props: PropsWithChildren) = loadChannelAroundMessage, loadChannelAtFirstUnreadMessage, loading: channelMessagesState.loading, - LoadingIndicator, markRead, maximumMessageLimit, maxTimeBetweenGroupedMessages, members: channelState.members ?? {}, - NetworkDownIndicator, read: channelState.read ?? {}, reloadChannel, scrollToFirstUnreadThreshold, setChannelUnreadState, setLastRead, setTargetedMessage, - StickyHeader, targetedMessage, threadList, uploadAbortControllerRef, @@ -1901,61 +1596,24 @@ const ChannelWithContext = (props: PropsWithChildren) = asyncMessagesMinimumPressDuration, audioRecordingSendOnComplete, asyncMessagesSlideToCancelDistance, - AttachButton, attachmentPickerBottomSheetHeight, - AttachmentPickerSelectionBar, attachmentSelectionBarHeight, - AttachmentUploadPreviewList, - AudioAttachmentUploadPreview, - AudioRecorder, audioRecordingEnabled, - AudioRecordingInProgress, - AudioRecordingLockIndicator, - AudioRecordingPreview, - AudioRecordingWaveform, - AutoCompleteSuggestionHeader, - AutoCompleteSuggestionItem, - AutoCompleteSuggestionList, channelId, compressImageQuality, - CooldownTimer, - CreatePollContent, createPollOptionGap, doFileUploadRequest, editMessage, - FileAttachmentUploadPreview, - FileUploadInProgressIndicator, - FileUploadRetryIndicator, - FileUploadNotSupportedIndicator, - ImageUploadInProgressIndicator, - ImageUploadRetryIndicator, - ImageUploadNotSupportedIndicator, handleAttachButtonPress, hasCameraPicker, hasCommands: hasCommands ?? !!clientChannelConfig?.commands?.length, hasFilePicker, hasImagePicker, - ImageAttachmentUploadPreview, - Input, - InputView, - InputButtons, - MessageComposerLeadingView, - MessageComposerTrailingView, messageInputFloating, messageInputHeightStore, - MessageInputFooterView, - MessageInputHeaderView, - MessageInputLeadingView, - MessageInputTrailingView, openPollCreationDialog, - SendButton, sendMessage, - SendMessageDisallowedIndicator, setInputRef, - ShowThreadMessageInChannelButton, - StartAudioRecordingButton, - StopMessageStreamingButton, - VideoAttachmentUploadPreview, }); const messageListContext = useCreatePaginatedMessageListContext({ @@ -1975,11 +1633,8 @@ const ChannelWithContext = (props: PropsWithChildren) = const messagesContext = useCreateMessagesContext({ additionalPressableProps, - Attachment, - AudioAttachment, channelId, customMessageSwipeAction, - DateHeader, deletedMessagesVisibilityType, deleteMessage, deleteReaction, @@ -1987,15 +1642,9 @@ const ChannelWithContext = (props: PropsWithChildren) = dismissKeyboardOnMessageTouch, enableMessageGroupingByUser, enableSwipeToReply, - FileAttachment, - FileAttachmentGroup, - FileAttachmentIcon, - FilePreview, FlatList, forceAlignMessages, - Gallery, getMessageGroupStyle, - Giphy, giphyVersion, handleBan, handleCopy, @@ -2013,85 +1662,29 @@ const ChannelWithContext = (props: PropsWithChildren) = handleBlockUser, hasCreatePoll: hasCreatePoll === undefined ? pollCreationEnabled : hasCreatePoll && pollCreationEnabled, - ImageLoadingFailedIndicator, - ImageLoadingIndicator, initialScrollToFirstUnreadMessage: !messageId && initialScrollToFirstUnreadMessage, // when messageId is set, we scroll to the messageId instead of first unread - InlineDateSeparator, - InlineUnreadIndicator, isAttachmentEqual, isMessageAIGenerated, markdownRules, - Message, - MessageActionList, - MessageActionListItem, messageActions, - MessageAuthor, - MessageBlocked, - MessageBounce, - MessageContent, - MessageContentBottomView, - MessageContentLeadingView, messageContentOrder, - MessageContentTrailingView, - MessageContentTopView, - MessageDeleted, - MessageError, - MessageFooter, - MessageHeader, - MessageList, - MessageLocation, - MessageMenu, - MessagePinnedHeader, - MessageReminderHeader, - MessageSavedForLaterHeader, - SentToChannelHeader, - MessageReactionPicker, - MessageReplies, - MessageRepliesAvatars, - MessageSpacer, - MessageItemView, - MessageStatus, - MessageSwipeContent, messageSwipeToReplyHitSlop, - MessageSystem, - MessageText, messageTextNumberOfLines, - MessageTimestamp, - MessageUserReactions, - MessageUserReactionsAvatar, - MessageUserReactionsItem, myMessageTheme, onLongPressMessage, onPressInMessage, onPressMessage, - PollContent, - ReactionListBottom, reactionListPosition, reactionListType, - ReactionListTop, - ReactionListClustered, - ReactionListItem, - ReactionListItemWrapper, - ReactionListCountItem, removeMessage, - Reply, retrySendMessage, - ScrollToBottomButton, selectReaction, sendReaction, shouldShowUnreadUnderlay, - StreamingMessageView, supportedReactions, targetedMessage, - TypingIndicator, - TypingIndicatorContainer, - UnreadMessagesNotification, updateMessage, - UrlPreview, - URLPreviewCompact, - VideoThumbnail, urlPreviewType, - UnsupportedAttachment, }); const threadContext = useCreateThreadContext({ diff --git a/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.js b/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.js index d56cbb0ac4..7c02654712 100644 --- a/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.js +++ b/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.js @@ -4,6 +4,7 @@ import { Text } from 'react-native'; import { act, cleanup, render, waitFor } from '@testing-library/react-native'; +import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext'; import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider'; import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; @@ -61,17 +62,19 @@ describe('isAttachmentEqualHandler', () => { return ( - { - if (type === 'test') { - return {customField}; - } + { + if (type === 'test') { + return {customField}; + } + }, }} - channel={channel} - isAttachmentEqual={isAttachmentEqualHandler} > - - + + + + ); diff --git a/package/src/components/Channel/hooks/useCreateChannelContext.ts b/package/src/components/Channel/hooks/useCreateChannelContext.ts index 824f30cab5..5c7c9e33ca 100644 --- a/package/src/components/Channel/hooks/useCreateChannelContext.ts +++ b/package/src/components/Channel/hooks/useCreateChannelContext.ts @@ -6,7 +6,6 @@ export const useCreateChannelContext = ({ channel, channelUnreadStateStore, disabled, - EmptyStateIndicator, enableMessageGroupingByUser, enforceUniqueReaction, error, @@ -18,19 +17,16 @@ export const useCreateChannelContext = ({ loadChannelAroundMessage, loadChannelAtFirstUnreadMessage, loading, - LoadingIndicator, markRead, maxTimeBetweenGroupedMessages, maximumMessageLimit, members, - NetworkDownIndicator, read, reloadChannel, scrollToFirstUnreadThreshold, setChannelUnreadState, setLastRead, setTargetedMessage, - StickyHeader, targetedMessage, threadList, uploadAbortControllerRef, @@ -52,7 +48,6 @@ export const useCreateChannelContext = ({ channel, channelUnreadStateStore, disabled, - EmptyStateIndicator, enableMessageGroupingByUser, enforceUniqueReaction, error, @@ -64,19 +59,16 @@ export const useCreateChannelContext = ({ loadChannelAroundMessage, loadChannelAtFirstUnreadMessage, loading, - LoadingIndicator, markRead, maximumMessageLimit, maxTimeBetweenGroupedMessages, members, - NetworkDownIndicator, read, reloadChannel, scrollToFirstUnreadThreshold, setChannelUnreadState, setLastRead, setTargetedMessage, - StickyHeader, targetedMessage, threadList, uploadAbortControllerRef, diff --git a/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts b/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts index 847a43851f..2ea779f443 100644 --- a/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts +++ b/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts @@ -9,62 +9,25 @@ export const useCreateInputMessageInputContext = ({ asyncMessagesMinimumPressDuration, asyncMessagesSlideToCancelDistance, audioRecordingSendOnComplete, - AttachButton, attachmentPickerBottomSheetHeight, - AttachmentPickerSelectionBar, attachmentSelectionBarHeight, - AttachmentUploadPreviewList, - AudioAttachmentUploadPreview, - AudioRecorder, audioRecordingEnabled, - AudioRecordingInProgress, - AudioRecordingLockIndicator, - AudioRecordingPreview, - AudioRecordingWaveform, - AutoCompleteSuggestionHeader, - AutoCompleteSuggestionItem, - AutoCompleteSuggestionList, channelId, compressImageQuality, - CooldownTimer, - CreatePollContent, createPollOptionGap, doFileUploadRequest, editMessage, - FileAttachmentUploadPreview, - FileUploadInProgressIndicator, - FileUploadRetryIndicator, - FileUploadNotSupportedIndicator, - ImageUploadInProgressIndicator, - ImageUploadRetryIndicator, - ImageUploadNotSupportedIndicator, handleAttachButtonPress, hasCameraPicker, hasCommands, hasFilePicker, hasImagePicker, - ImageAttachmentUploadPreview, - Input, - InputView, - InputButtons, - MessageComposerLeadingView, - MessageComposerTrailingView, messageInputFloating, messageInputHeightStore, - MessageInputFooterView, - MessageInputHeaderView, - MessageInputLeadingView, - MessageInputTrailingView, openPollCreationDialog, - SendButton, sendMessage, - SendMessageDisallowedIndicator, setInputRef, showPollCreationDialog, - ShowThreadMessageInChannelButton, - StartAudioRecordingButton, - StopMessageStreamingButton, - VideoAttachmentUploadPreview, }: InputMessageInputContextValue & { /** * To ensure we allow re-render, when channel is changed @@ -79,70 +42,27 @@ export const useCreateInputMessageInputContext = ({ asyncMessagesMinimumPressDuration, asyncMessagesSlideToCancelDistance, audioRecordingSendOnComplete, - AttachButton, attachmentPickerBottomSheetHeight, - AttachmentPickerSelectionBar, attachmentSelectionBarHeight, - AttachmentUploadPreviewList, - AudioAttachmentUploadPreview, - AudioRecorder, audioRecordingEnabled, - AudioRecordingInProgress, - AudioRecordingLockIndicator, - AudioRecordingPreview, - AudioRecordingWaveform, - AutoCompleteSuggestionHeader, - AutoCompleteSuggestionItem, - AutoCompleteSuggestionList, compressImageQuality, - CooldownTimer, - CreatePollContent, createPollOptionGap, doFileUploadRequest, editMessage, - FileAttachmentUploadPreview, - FileUploadInProgressIndicator, - FileUploadRetryIndicator, - FileUploadNotSupportedIndicator, - ImageUploadInProgressIndicator, - ImageUploadRetryIndicator, - ImageUploadNotSupportedIndicator, handleAttachButtonPress, hasCameraPicker, hasCommands, hasFilePicker, hasImagePicker, - ImageAttachmentUploadPreview, - Input, - InputView, - InputButtons, - MessageComposerLeadingView, - MessageComposerTrailingView, messageInputFloating, messageInputHeightStore, - MessageInputFooterView, - MessageInputHeaderView, - MessageInputLeadingView, - MessageInputTrailingView, openPollCreationDialog, - SendButton, sendMessage, - SendMessageDisallowedIndicator, setInputRef, showPollCreationDialog, - ShowThreadMessageInChannelButton, - StartAudioRecordingButton, - StopMessageStreamingButton, - VideoAttachmentUploadPreview, }), // eslint-disable-next-line react-hooks/exhaustive-deps - [ - compressImageQuality, - channelId, - CreatePollContent, - showPollCreationDialog, - allowSendBeforeAttachmentsUpload, - ], + [compressImageQuality, channelId, showPollCreationDialog, allowSendBeforeAttachmentsUpload], ); return inputMessageInputContext; diff --git a/package/src/components/Channel/hooks/useCreateMessagesContext.ts b/package/src/components/Channel/hooks/useCreateMessagesContext.ts index 2f21318b6d..62c375cec6 100644 --- a/package/src/components/Channel/hooks/useCreateMessagesContext.ts +++ b/package/src/components/Channel/hooks/useCreateMessagesContext.ts @@ -4,11 +4,8 @@ import type { MessagesContextValue } from '../../../contexts/messagesContext/Mes export const useCreateMessagesContext = ({ additionalPressableProps, - Attachment, - AudioAttachment, channelId, customMessageSwipeAction, - DateHeader, deletedMessagesVisibilityType, deleteMessage, deleteReaction, @@ -16,15 +13,9 @@ export const useCreateMessagesContext = ({ dismissKeyboardOnMessageTouch, enableMessageGroupingByUser, enableSwipeToReply, - FileAttachment, - FileAttachmentGroup, - FileAttachmentIcon, - FilePreview, FlatList, forceAlignMessages, - Gallery, getMessageGroupStyle, - Giphy, giphyVersion, handleBan, handleCopy, @@ -41,85 +32,29 @@ export const useCreateMessagesContext = ({ handleThreadReply, handleBlockUser, hasCreatePoll, - ImageLoadingFailedIndicator, - ImageLoadingIndicator, initialScrollToFirstUnreadMessage, - InlineDateSeparator, - InlineUnreadIndicator, isAttachmentEqual, isMessageAIGenerated, markdownRules, - Message, - MessageActionList, - MessageActionListItem, messageActions, - MessageAuthor, - MessageBlocked, - MessageBounce, - MessageContent, - MessageContentBottomView, - MessageContentLeadingView, messageContentOrder, - MessageContentTrailingView, - MessageContentTopView, - MessageDeleted, - MessageError, - MessageFooter, - MessageHeader, - MessageList, - MessageLocation, - MessageMenu, - MessagePinnedHeader, - MessageReminderHeader, - MessageSavedForLaterHeader, - SentToChannelHeader, - MessageReactionPicker, - MessageReplies, - MessageRepliesAvatars, - MessageSpacer, - MessageItemView, - MessageStatus, - MessageSwipeContent, messageSwipeToReplyHitSlop, - MessageSystem, - MessageText, messageTextNumberOfLines, - MessageTimestamp, - MessageUserReactions, - MessageUserReactionsAvatar, - MessageUserReactionsItem, myMessageTheme, onLongPressMessage, onPressInMessage, onPressMessage, - PollContent, - ReactionListBottom, reactionListPosition, reactionListType, - ReactionListTop, - ReactionListClustered, - ReactionListItem, - ReactionListItemWrapper, - ReactionListCountItem, removeMessage, - Reply, retrySendMessage, - ScrollToBottomButton, selectReaction, sendReaction, shouldShowUnreadUnderlay, - StreamingMessageView, supportedReactions, targetedMessage, - TypingIndicator, - TypingIndicatorContainer, - UnreadMessagesNotification, updateMessage, - UrlPreview, - URLPreviewCompact, - VideoThumbnail, urlPreviewType, - UnsupportedAttachment, }: MessagesContextValue & { /** * To ensure we allow re-render, when channel is changed @@ -134,10 +69,7 @@ export const useCreateMessagesContext = ({ const messagesContext: MessagesContextValue = useMemo( () => ({ additionalPressableProps, - Attachment, - AudioAttachment, customMessageSwipeAction, - DateHeader, deletedMessagesVisibilityType, deleteMessage, deleteReaction, @@ -145,15 +77,9 @@ export const useCreateMessagesContext = ({ dismissKeyboardOnMessageTouch, enableMessageGroupingByUser, enableSwipeToReply, - FileAttachment, - FileAttachmentGroup, - FileAttachmentIcon, - FilePreview, FlatList, forceAlignMessages, - Gallery, getMessageGroupStyle, - Giphy, giphyVersion, handleBan, handleCopy, @@ -170,85 +96,29 @@ export const useCreateMessagesContext = ({ handleThreadReply, handleBlockUser, hasCreatePoll, - ImageLoadingFailedIndicator, - ImageLoadingIndicator, initialScrollToFirstUnreadMessage, - InlineDateSeparator, - InlineUnreadIndicator, isAttachmentEqual, isMessageAIGenerated, markdownRules, - Message, - MessageActionList, - MessageActionListItem, messageActions, - MessageAuthor, - MessageBlocked, - MessageBounce, - MessageContent, - MessageContentBottomView, - MessageContentLeadingView, messageContentOrder, - MessageContentTrailingView, - MessageContentTopView, - MessageDeleted, - MessageError, - MessageFooter, - MessageHeader, - MessageList, - MessageLocation, - MessageMenu, - MessagePinnedHeader, - MessageReminderHeader, - MessageSavedForLaterHeader, - SentToChannelHeader, - MessageReactionPicker, - MessageReplies, - MessageRepliesAvatars, - MessageSpacer, - MessageItemView, - MessageStatus, - MessageSwipeContent, messageSwipeToReplyHitSlop, - MessageSystem, - MessageText, messageTextNumberOfLines, - MessageTimestamp, - MessageUserReactions, - MessageUserReactionsAvatar, - MessageUserReactionsItem, myMessageTheme, onLongPressMessage, onPressInMessage, onPressMessage, - PollContent, - ReactionListBottom, reactionListPosition, reactionListType, - ReactionListTop, - ReactionListClustered, - ReactionListItem, - ReactionListItemWrapper, - ReactionListCountItem, removeMessage, - Reply, retrySendMessage, - ScrollToBottomButton, selectReaction, sendReaction, shouldShowUnreadUnderlay, - StreamingMessageView, supportedReactions, targetedMessage, - TypingIndicator, - TypingIndicatorContainer, - UnreadMessagesNotification, updateMessage, - UrlPreview, - URLPreviewCompact, - VideoThumbnail, urlPreviewType, - UnsupportedAttachment, }), // eslint-disable-next-line react-hooks/exhaustive-deps [ diff --git a/package/src/components/ChannelList/ChannelList.tsx b/package/src/components/ChannelList/ChannelList.tsx index 79196f1bb7..9bd0055f59 100644 --- a/package/src/components/ChannelList/ChannelList.tsx +++ b/package/src/components/ChannelList/ChannelList.tsx @@ -11,15 +11,10 @@ import { QueryChannelsRequestType, } from 'stream-chat'; -import { ChannelListFooterLoadingIndicator } from './ChannelListFooterLoadingIndicator'; -import { ChannelListHeaderErrorIndicator } from './ChannelListHeaderErrorIndicator'; -import { ChannelListHeaderNetworkDownIndicator } from './ChannelListHeaderNetworkDownIndicator'; -import { ChannelListLoadingIndicator } from './ChannelListLoadingIndicator'; -import { ChannelListView, ChannelListViewProps } from './ChannelListView'; +import { ChannelListView } from './ChannelListView'; import { useChannelUpdated } from './hooks/listeners/useChannelUpdated'; import { useCreateChannelsContext } from './hooks/useCreateChannelsContext'; import { usePaginatedChannels } from './hooks/usePaginatedChannels'; -import { Skeleton as SkeletonDefault } from './Skeleton'; import { ChannelsContextValue, @@ -28,38 +23,16 @@ import { import { useChatContext } from '../../contexts/chatContext/ChatContext'; import { SwipeRegistryProvider } from '../../contexts/swipeableContext/SwipeRegistryContext'; import type { ChannelListEventListenerOptions } from '../../types/types'; -import { ChannelPreviewView } from '../ChannelPreview/ChannelPreviewView'; -import { EmptyStateIndicator as EmptyStateIndicatorDefault } from '../Indicators/EmptyStateIndicator'; -import { LoadingErrorIndicator as LoadingErrorIndicatorDefault } from '../Indicators/LoadingErrorIndicator'; export type ChannelListProps = Partial< Pick< ChannelsContextValue, | 'additionalFlatListProps' - | 'EmptyStateIndicator' - | 'FooterLoadingIndicator' - | 'HeaderErrorIndicator' - | 'HeaderNetworkDownIndicator' - | 'LoadingErrorIndicator' - | 'LoadingIndicator' - | 'Preview' | 'setFlatListRef' - | 'ListHeaderComponent' | 'onSelect' - | 'PreviewAvatar' - | 'PreviewMessage' - | 'PreviewMutedStatus' - | 'PreviewStatus' - | 'PreviewTitle' - | 'PreviewLastMessage' - | 'PreviewUnreadCount' - | 'PreviewTypingIndicator' - | 'PreviewMessageDeliveryStatus' - | 'ChannelDetailsBottomSheet' | 'getChannelActionItems' | 'swipeActionsEnabled' | 'loadMoreThreshold' - | 'Skeleton' | 'maxUnreadCount' | 'numberOfSkeletons' | 'mutedStatusPosition' @@ -75,12 +48,6 @@ export type ChannelListProps = Partial< * @overrideType object * */ filters?: ChannelFilters; - /** - * Custom UI component to display the list of channels - * - * Default: [ChannelListView](https://getstream.io/chat/docs/sdk/reactnative/ui-components/channel-list-view/) - */ - List?: React.ComponentType; /** * If set to true, channels won't dynamically sort by most recent message, defaults to false */ @@ -247,8 +214,7 @@ const DEFAULT_SORT = {}; /** * This component fetches a list of channels, allowing you to select the channel you want to open. - * The ChannelList doesn't provide any UI for the underlying React Native FlatList. UI is determined by the `List` component which is - * provided to the ChannelList component as a prop. By default, the ChannelListView component is used as the list UI. + * The ChannelList renders a ChannelListView which provides the UI for the underlying React Native FlatList. * * @example ./ChannelList.md */ @@ -256,15 +222,7 @@ export const ChannelList = (props: ChannelListProps) => { const { additionalFlatListProps = {}, channelRenderFilterFn, - EmptyStateIndicator = EmptyStateIndicatorDefault, filters = DEFAULT_FILTERS, - FooterLoadingIndicator = ChannelListFooterLoadingIndicator, - HeaderErrorIndicator = ChannelListHeaderErrorIndicator, - HeaderNetworkDownIndicator = ChannelListHeaderNetworkDownIndicator, - List = ChannelListView, - ListHeaderComponent, - LoadingErrorIndicator = LoadingErrorIndicatorDefault, - LoadingIndicator = ChannelListLoadingIndicator, // https://stackoverflow.com/a/60666252/10826415 loadMoreThreshold = 0.1, lockChannelOrder = false, @@ -282,20 +240,8 @@ export const ChannelList = (props: ChannelListProps) => { onRemovedFromChannel, onSelect, options = DEFAULT_OPTIONS, - Preview = ChannelPreviewView, getChannelActionItems, - PreviewAvatar, - PreviewMessage, - PreviewMutedStatus, - PreviewLastMessage, - PreviewStatus, - PreviewTitle, - PreviewUnreadCount, - PreviewTypingIndicator, - PreviewMessageDeliveryStatus, - ChannelDetailsBottomSheet, setFlatListRef, - Skeleton = SkeletonDefault, sort = DEFAULT_SORT, queryChannelsOverride, mutedStatusPosition = 'inlineTitle', @@ -398,35 +344,17 @@ export const ChannelList = (props: ChannelListProps) => { additionalFlatListProps, channelListInitialized, channels: channelRenderFilterFn ? channelRenderFilterFn(channels ?? []) : channels, - EmptyStateIndicator, error, - FooterLoadingIndicator, forceUpdate, hasNextPage, - HeaderErrorIndicator, - HeaderNetworkDownIndicator, - ListHeaderComponent, loadingChannels, - LoadingErrorIndicator, - LoadingIndicator, loadingNextPage, loadMoreThreshold, loadNextPage, maxUnreadCount, numberOfSkeletons, onSelect, - Preview, getChannelActionItems, - PreviewAvatar, - PreviewMessage, - PreviewMutedStatus, - PreviewStatus, - PreviewTitle, - PreviewUnreadCount, - PreviewTypingIndicator, - PreviewMessageDeliveryStatus, - ChannelDetailsBottomSheet, - PreviewLastMessage, swipeActionsEnabled, refreshing, refreshList, @@ -436,14 +364,13 @@ export const ChannelList = (props: ChannelListProps) => { setFlatListRef(ref); } }, - Skeleton, mutedStatusPosition, }); return ( - + ); diff --git a/package/src/components/ChannelList/ChannelListLoadingIndicator.tsx b/package/src/components/ChannelList/ChannelListLoadingIndicator.tsx index 2780811312..5949a4bf53 100644 --- a/package/src/components/ChannelList/ChannelListLoadingIndicator.tsx +++ b/package/src/components/ChannelList/ChannelListLoadingIndicator.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; import { useChannelsContext } from '../../contexts/channelsContext/ChannelsContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; const styles = StyleSheet.create({ @@ -16,7 +17,8 @@ export const ChannelListLoadingIndicator = () => { channelListLoadingIndicator: { container }, }, } = useTheme(); - const { numberOfSkeletons, Skeleton } = useChannelsContext(); + const { numberOfSkeletons } = useChannelsContext(); + const { Skeleton } = useComponentsContext(); return ( diff --git a/package/src/components/ChannelList/ChannelListView.tsx b/package/src/components/ChannelList/ChannelListView.tsx index 5655904571..23814ae7a1 100644 --- a/package/src/components/ChannelList/ChannelListView.tsx +++ b/package/src/components/ChannelList/ChannelListView.tsx @@ -10,6 +10,7 @@ import { useChannelsContext, } from '../../contexts/channelsContext/ChannelsContext'; import { useChatContext } from '../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useDebugContext } from '../../contexts/debugContext/DebugContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; @@ -18,24 +19,14 @@ import { ChannelPreview } from '../ChannelPreview/ChannelPreview'; export type ChannelListViewPropsWithContext = Omit< ChannelsContextValue, - | 'HeaderErrorIndicator' - | 'HeaderNetworkDownIndicator' - | 'maxUnreadCount' - | 'numberOfSkeletons' - | 'onSelect' - | 'Preview' - | 'PreviewTitle' - | 'PreviewStatus' - | 'PreviewAvatar' - | 'previewMessage' - | 'Skeleton' + 'maxUnreadCount' | 'numberOfSkeletons' | 'onSelect' >; const StatusIndicator = () => { const { isOnline } = useChatContext(); const styles = useStyles(); - const { error, HeaderErrorIndicator, HeaderNetworkDownIndicator, loadingChannels, refreshList } = - useChannelsContext(); + const { error, loadingChannels, refreshList } = useChannelsContext(); + const { HeaderErrorIndicator, HeaderNetworkDownIndicator } = useComponentsContext(); if (loadingChannels) { return null; @@ -67,15 +58,10 @@ const ChannelListViewWithContext = (props: ChannelListViewPropsWithContext) => { additionalFlatListProps, channelListInitialized, channels, - EmptyStateIndicator, error, - FooterLoadingIndicator, forceUpdate, hasNextPage, - ListHeaderComponent, loadingChannels, - LoadingErrorIndicator, - LoadingIndicator, loadingNextPage, loadMoreThreshold, loadNextPage, @@ -84,6 +70,13 @@ const ChannelListViewWithContext = (props: ChannelListViewPropsWithContext) => { reloadList, setFlatListRef, } = props; + const { + EmptyStateIndicator, + FooterLoadingIndicator, + ListHeaderComponent, + LoadingErrorIndicator, + ChannelListLoadingIndicator: LoadingIndicator, + } = useComponentsContext(); /** * In order to prevent the EmptyStateIndicator component from showing up briefly on mount, @@ -143,11 +136,7 @@ const ChannelListViewWithContext = (props: ChannelListViewPropsWithContext) => { extraData={forceUpdate} keyExtractor={keyExtractor} ListEmptyComponent={ - loading ? ( - - ) : ( - - ) + loading ? : } ListFooterComponent={loadingNextPage ? : undefined} ListHeaderComponent={ListHeaderComponent} @@ -180,15 +169,10 @@ export const ChannelListView = (props: ChannelListViewProps) => { additionalFlatListProps, channelListInitialized, channels, - EmptyStateIndicator, error, - FooterLoadingIndicator, forceUpdate, hasNextPage, - ListHeaderComponent, loadingChannels, - LoadingErrorIndicator, - LoadingIndicator, loadingNextPage, loadMoreThreshold, loadNextPage, @@ -204,15 +188,10 @@ export const ChannelListView = (props: ChannelListViewProps) => { additionalFlatListProps, channelListInitialized, channels, - EmptyStateIndicator, error, - FooterLoadingIndicator, forceUpdate, hasNextPage, - ListHeaderComponent, loadingChannels, - LoadingErrorIndicator, - LoadingIndicator, loadingNextPage, loadMoreThreshold, loadNextPage, diff --git a/package/src/components/ChannelList/__tests__/ChannelList.test.js b/package/src/components/ChannelList/__tests__/ChannelList.test.js index d166544fa7..5700d93027 100644 --- a/package/src/components/ChannelList/__tests__/ChannelList.test.js +++ b/package/src/components/ChannelList/__tests__/ChannelList.test.js @@ -12,6 +12,10 @@ import { } from '@testing-library/react-native'; import { useChannelsContext } from '../../../contexts/channelsContext/ChannelsContext'; +import { + useComponentsContext, + WithComponents, +} from '../../../contexts/componentsContext/ComponentsContext'; import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; import { queryChannelsApi } from '../../../mock-builders/api/queryChannels'; @@ -30,7 +34,6 @@ import { generateChannel, generateChannelResponse } from '../../../mock-builders import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; import { getTestClientWithUser } from '../../../mock-builders/mock'; -import { ChannelPreview } from '../../ChannelPreview/ChannelPreview'; import { Chat } from '../../Chat/Chat'; import { ChannelList } from '../ChannelList'; @@ -43,60 +46,38 @@ jest.mock('../../ChannelPreview/ChannelSwipableWrapper', () => ({ })); /** - * We are gonna use following custom UI components for preview and list. - * If we use ChannelPreviewView or ChannelPreviewLastMessage here, then changes - * to those components might end up breaking tests for ChannelList, which will be quite painful - * to debug. + * Custom Preview component used via WithComponents to verify channel rendering. + * Receives { channel, muted, unread, lastMessage } from ChannelPreview. */ -const ChannelPreviewComponent = ({ channel, setActiveChannel }) => ( - +const ChannelPreviewComponent = ({ channel }) => ( + {channel.data?.name} {channel.state.messages[0]?.text} ); -const ChannelListComponent = (props) => { - const { channels, onSelect } = useChannelsContext(); - return ( - - {channels?.map((channel) => ( - - ))} - - ); -}; - -const ChannelListSwipeActionsProbe = () => { +/** + * Probe that reads swipeActionsEnabled from ChannelsContext. + * Used as a Preview override to inspect context values. + */ +const SwipeActionsProbe = () => { const { swipeActionsEnabled } = useChannelsContext(); return {`${swipeActionsEnabled}`}; }; -const ChannelListRefreshingProbe = () => { +/** + * Probe that reads refreshing from ChannelsContext. + */ +const RefreshingProbe = () => { const { refreshing } = useChannelsContext(); return {`${refreshing}`}; }; const ChannelPreviewContent = ({ unread }) => {`${unread}`}; -const ChannelListWithChannelPreview = () => { - const { channels } = useChannelsContext(); - return ( - - {channels?.map((channel) => ( - - ))} - - ); -}; - let expectedChannelDetailsBottomSheetOverride; -const ChannelListChannelDetailsBottomSheetProbe = () => { - const { ChannelDetailsBottomSheet } = useChannelsContext(); +const ChannelDetailsBottomSheetProbe = () => { + const { ChannelDetailsBottomSheet } = useComponentsContext(); return ( {`${ChannelDetailsBottomSheet === expectedChannelDetailsBottomSheetOverride}`} @@ -120,8 +101,6 @@ describe('ChannelList', () => { let testChannel3; const props = { filters: {}, - List: ChannelListComponent, - Preview: ChannelPreviewComponent, }; beforeEach(async () => { @@ -140,11 +119,13 @@ describe('ChannelList', () => { const { getByTestId } = render( - + + + , ); - await waitFor(() => expect(getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(getByTestId('channel-list-view')).toBeTruthy()); }); it('should render a preview of each channel', async () => { @@ -152,7 +133,9 @@ describe('ChannelList', () => { const { getByTestId } = render( - + + + , ); @@ -164,12 +147,14 @@ describe('ChannelList', () => { render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); expect(screen.getByTestId(testChannel1.channel.id)).toBeTruthy(); }); @@ -177,7 +162,9 @@ describe('ChannelList', () => { screen.rerender( - + + + , ); @@ -191,8 +178,22 @@ describe('ChannelList', () => { const deferredCallForFreshFilter = new DeferredPromise(); const staleFilter = { 'initial-filter': { a: { $gt: 'c' } } }; const freshFilter = { 'new-filter': { a: { $gt: 'c' } } }; - const staleChannel = [generateChannel({ id: 'stale-channel' })]; - const freshChannel = [generateChannel({ id: 'new-channel' })]; + const createMockChannel = (id) => { + const channel = generateChannel({ + data: { name: id }, + id, + state: { latestMessages: [], members: {}, messages: [], setIsUpToDate: jest.fn() }, + }); + channel.countUnread = () => 0; + channel.muteStatus = () => ({ muted: false }); + channel.on = jest.fn(() => ({ unsubscribe: jest.fn() })); + channel.messageComposer = { + registerDraftEventSubscriptions: jest.fn(() => jest.fn()), + }; + return channel; + }; + const staleChannel = [createMockChannel('stale-channel')]; + const freshChannel = [createMockChannel('new-channel')]; const spy = jest.spyOn(chatClient, 'queryChannels'); spy.mockImplementation((filters = {}) => { if (Object.prototype.hasOwnProperty.call(filters, 'new-filter')) { @@ -203,7 +204,9 @@ describe('ChannelList', () => { const { rerender, queryByTestId } = render( - + + + , ); @@ -216,12 +219,14 @@ describe('ChannelList', () => { ); await waitFor(() => { - expect(queryByTestId('channel-list')).toBeTruthy(); + expect(queryByTestId('channel-list-view')).toBeTruthy(); }); rerender( - + + + , ); @@ -238,7 +243,7 @@ describe('ChannelList', () => { deferredCallForFreshFilter.resolve(freshChannel); }); await waitFor(() => { - expect(queryByTestId('channel-list')).toBeTruthy(); + expect(queryByTestId('channel-list-view')).toBeTruthy(); expect(queryByTestId('new-channel')).toBeTruthy(); }); }); @@ -249,7 +254,9 @@ describe('ChannelList', () => { render( - + + + , ); @@ -269,7 +276,9 @@ describe('ChannelList', () => { const { getByTestId } = render( - + + + , ); @@ -282,7 +291,9 @@ describe('ChannelList', () => { const { getByTestId } = render( - + + + , ); @@ -295,11 +306,13 @@ describe('ChannelList', () => { const { getByTestId, queryByTestId } = render( - + + + , ); - await waitFor(() => expect(getByTestId('channel-list-with-channel-preview')).toBeTruthy()); + await waitFor(() => expect(getByTestId('channel-list-view')).toBeTruthy()); expect(getByTestId('preview-unread')).toHaveTextContent('0'); expect(queryByTestId('swipe-wrapper')).toBeNull(); expect(mockChannelSwipableWrapper).not.toHaveBeenCalled(); @@ -310,27 +323,32 @@ describe('ChannelList', () => { const { getByTestId } = render( - + + + , ); - await waitFor(() => expect(getByTestId('channel-list-with-channel-preview')).toBeTruthy()); + await waitFor(() => expect(getByTestId('channel-list-view')).toBeTruthy()); expect(getByTestId('swipe-wrapper')).toBeTruthy(); expect(mockChannelSwipableWrapper).toHaveBeenCalledTimes(1); }); - it('should expose ChannelDetailsBottomSheet override in ChannelsContext', async () => { + it('should expose ChannelDetailsBottomSheet override via WithComponents', async () => { useMockedApis(chatClient, [queryChannelsApi([testChannel1])]); const ChannelDetailsBottomSheetOverride = () => null; expectedChannelDetailsBottomSheetOverride = ChannelDetailsBottomSheetOverride; const { getByTestId } = render( - + + + , ); @@ -341,24 +359,23 @@ describe('ChannelList', () => { it('should pass ChannelDetailsBottomSheet override to ChannelSwipableWrapper', async () => { useMockedApis(chatClient, [queryChannelsApi([testChannel1])]); const ChannelDetailsBottomSheetOverride = () => null; + expectedChannelDetailsBottomSheetOverride = ChannelDetailsBottomSheetOverride; - render( + const { getByTestId } = render( - + + + , ); - await waitFor(() => expect(mockChannelSwipableWrapper).toHaveBeenCalled()); - const swipableWrapperProps = mockChannelSwipableWrapper.mock.calls[0]?.[0]; - expect(swipableWrapperProps).toEqual( - expect.objectContaining({ - ChannelDetailsBottomSheet: ChannelDetailsBottomSheetOverride, - }), - ); + await waitFor(() => expect(getByTestId('channel-details-bottom-sheet-override')).toBeTruthy()); + expect(getByTestId('channel-details-bottom-sheet-override')).toHaveTextContent('true'); }); describe('Event handling', () => { @@ -378,11 +395,13 @@ describe('ChannelList', () => { it('should move channel to top of the list by default', async () => { render( - + + + , ); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const newMessage = sendNewMessageOnChannel3(); @@ -400,11 +419,13 @@ describe('ChannelList', () => { it('should add channel to top if channel is hidden from the list', async () => { render( - + + + , ); - await waitFor(() => expect(screen.getByTestId('channel-list')).toBeTruthy()); + await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); act(() => dispatchChannelHiddenEvent(chatClient, testChannel3.channel)); const newItems = screen.getAllByLabelText('list-item'); @@ -428,7 +449,9 @@ describe('ChannelList', () => { it('should not alter order if `lockChannelOrder` prop is true', async () => { render( - + + + , ); @@ -452,12 +475,14 @@ describe('ChannelList', () => { const onNewMessage = jest.fn(); render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchMessageNewEvent(chatClient, testChannel2.channel)); @@ -479,11 +504,13 @@ describe('ChannelList', () => { it('should move a channel to top of the list by default', async () => { render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchNotificationMessageNewEvent(chatClient, testChannel3.channel)); @@ -501,12 +528,14 @@ describe('ChannelList', () => { const onNewMessage = jest.fn(); render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchMessageNewEvent(chatClient, testChannel2.channel)); @@ -520,12 +549,14 @@ describe('ChannelList', () => { const onNewMessageNotification = jest.fn(); render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchNotificationMessageNewEvent(chatClient, testChannel2.channel)); @@ -547,12 +578,14 @@ describe('ChannelList', () => { it('should move a channel to top of the list by default', async () => { render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchNotificationAddedToChannelEvent(chatClient, testChannel3.channel)); @@ -572,12 +605,14 @@ describe('ChannelList', () => { const onAddedToChannel = jest.fn(); render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchNotificationAddedToChannelEvent(chatClient, testChannel3.channel)); @@ -596,12 +631,14 @@ describe('ChannelList', () => { it('should remove the channel from list by default', async () => { render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); const items = screen.getAllByLabelText('list-item'); @@ -621,12 +658,14 @@ describe('ChannelList', () => { const onRemovedFromChannel = jest.fn(); render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchNotificationRemovedFromChannel(chatClient, testChannel3.channel)); @@ -645,12 +684,14 @@ describe('ChannelList', () => { it('should update a channel in the list by default', async () => { render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => @@ -669,12 +710,14 @@ describe('ChannelList', () => { const onChannelUpdated = jest.fn(); render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => @@ -698,12 +741,14 @@ describe('ChannelList', () => { it('should remove a channel from the list by default', async () => { render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); const items = screen.getAllByLabelText('list-item'); @@ -723,12 +768,14 @@ describe('ChannelList', () => { const onChannelDeleted = jest.fn(); render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchChannelDeletedEvent(chatClient, testChannel2.channel)); @@ -747,12 +794,14 @@ describe('ChannelList', () => { it('should hide a channel from the list by default', async () => { render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); const items = screen.getAllByLabelText('list-item'); @@ -772,12 +821,14 @@ describe('ChannelList', () => { const onChannelHidden = jest.fn(); render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchChannelHiddenEvent(chatClient, testChannel2.channel)); @@ -795,12 +846,14 @@ describe('ChannelList', () => { render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchConnectionRecoveredEvent(chatClient)); @@ -821,7 +874,9 @@ describe('ChannelList', () => { render( - + + + , ); @@ -854,12 +909,14 @@ describe('ChannelList', () => { const onChannelTruncated = jest.fn(); render( - + + + , ); await waitFor(() => { - expect(screen.getByTestId('channel-list')).toBeTruthy(); + expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); act(() => dispatchChannelTruncatedEvent(chatClient, testChannel1.channel)); diff --git a/package/src/components/ChannelList/__tests__/ChannelListView.test.js b/package/src/components/ChannelList/__tests__/ChannelListView.test.js index 17491b1629..73b800cf23 100644 --- a/package/src/components/ChannelList/__tests__/ChannelListView.test.js +++ b/package/src/components/ChannelList/__tests__/ChannelListView.test.js @@ -1,11 +1,8 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { cleanup, render, waitFor } from '@testing-library/react-native'; -import { - ChannelsProvider, - useChannelsContext, -} from '../../../contexts/channelsContext/ChannelsContext'; +import { ChannelsProvider } from '../../../contexts/channelsContext/ChannelsContext'; import { ChatContext, ChatProvider } from '../../../contexts/chatContext/ChatContext'; import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; import { queryChannelsApi } from '../../../mock-builders/api/queryChannels'; @@ -16,43 +13,67 @@ import { Chat } from '../../Chat/Chat'; import { ChannelList } from '../ChannelList'; import { ChannelListView } from '../ChannelListView'; -let mockChannels; let chatClient; -const ListMessenger = ({ error, loadingChannels, ...props }) => { - const channelsContext = useChannelsContext(); +/** + * Renders the full ChannelList (which now always uses ChannelListView internally). + */ +const Component = () => ( + + + {(context) => ( + + + + )} + + +); - return ( - - - - ); -}; +const noop = () => {}; -const Component = ({ error = false, loadingChannels = false }) => { - const List = useCallback( - (...props) => , - [error, loadingChannels], - ); - return ( - - - {(context) => ( - - - - )} - - - ); -}; +/** + * Renders ChannelListView directly with a mock ChannelsContext for testing + * error and loading states. + */ +const ComponentWithContextOverrides = ({ error, loadingChannels }) => ( + + + {(context) => ( + + + + + + )} + + +); describe('ChannelListView', () => { beforeAll(async () => { @@ -77,7 +98,7 @@ describe('ChannelListView', () => { it('renders the `EmptyStateIndicator` when no channels are present', async () => { useMockedApis(chatClient, [queryChannelsApi([])]); - const { getByTestId } = render(); + const { getByTestId } = render(); await waitFor(() => { expect(getByTestId('empty-channel-state-title')).toBeTruthy(); }); @@ -85,7 +106,7 @@ describe('ChannelListView', () => { it('renders the `LoadingErrorIndicator` when `error` prop is true', async () => { const { getByTestId } = render( - , + , ); await waitFor(() => { expect(getByTestId('loading-error')).toBeTruthy(); @@ -93,7 +114,9 @@ describe('ChannelListView', () => { }); it('renders the `LoadingIndicator` when when channels have not yet loaded', async () => { - const { getByTestId } = render(); + const { getByTestId } = render( + , + ); await waitFor(() => { expect(getByTestId('channel-list-loading-indicator')).toBeTruthy(); }); diff --git a/package/src/components/ChannelList/hooks/useCreateChannelsContext.ts b/package/src/components/ChannelList/hooks/useCreateChannelsContext.ts index 7badceb7a0..8ef2683e30 100644 --- a/package/src/components/ChannelList/hooks/useCreateChannelsContext.ts +++ b/package/src/components/ChannelList/hooks/useCreateChannelsContext.ts @@ -6,41 +6,22 @@ export const useCreateChannelsContext = ({ additionalFlatListProps, channelListInitialized, channels, - EmptyStateIndicator, error, - FooterLoadingIndicator, forceUpdate, hasNextPage, - HeaderErrorIndicator, - HeaderNetworkDownIndicator, - ListHeaderComponent, loadingChannels, - LoadingErrorIndicator, - LoadingIndicator, loadingNextPage, loadMoreThreshold, loadNextPage, maxUnreadCount, numberOfSkeletons, onSelect, - Preview, getChannelActionItems, - PreviewAvatar, - PreviewMessage, - PreviewMutedStatus, - PreviewStatus, - PreviewTitle, - PreviewLastMessage, - PreviewTypingIndicator, - PreviewMessageDeliveryStatus, - PreviewUnreadCount, - ChannelDetailsBottomSheet, swipeActionsEnabled, refreshing, refreshList, reloadList, setFlatListRef, - Skeleton, mutedStatusPosition, }: ChannelsContextValue) => { const channelValueString = channels @@ -58,41 +39,22 @@ export const useCreateChannelsContext = ({ additionalFlatListProps, channelListInitialized, channels, - EmptyStateIndicator, error, - FooterLoadingIndicator, forceUpdate, hasNextPage, - HeaderErrorIndicator, - HeaderNetworkDownIndicator, - ListHeaderComponent, loadingChannels, - LoadingErrorIndicator, - LoadingIndicator, loadingNextPage, loadMoreThreshold, loadNextPage, maxUnreadCount, numberOfSkeletons, onSelect, - Preview, getChannelActionItems, - PreviewAvatar, - PreviewMessage, - PreviewMutedStatus, - PreviewStatus, - PreviewTitle, - PreviewUnreadCount, - PreviewTypingIndicator, - PreviewMessageDeliveryStatus, - PreviewLastMessage, - ChannelDetailsBottomSheet, swipeActionsEnabled, refreshing, refreshList, reloadList, setFlatListRef, - Skeleton, mutedStatusPosition, }), // eslint-disable-next-line react-hooks/exhaustive-deps @@ -104,7 +66,6 @@ export const useCreateChannelsContext = ({ loadingChannels, loadingNextPage, channelListInitialized, - ChannelDetailsBottomSheet, swipeActionsEnabled, refreshing, mutedStatusPosition, diff --git a/package/src/components/ChannelPreview/ChannelDetailsBottomSheet.tsx b/package/src/components/ChannelPreview/ChannelDetailsBottomSheet.tsx index 26ecdb2ccf..e2fde979d4 100644 --- a/package/src/components/ChannelPreview/ChannelDetailsBottomSheet.tsx +++ b/package/src/components/ChannelPreview/ChannelDetailsBottomSheet.tsx @@ -11,6 +11,7 @@ import { ChannelPreviewTitle } from './ChannelPreviewTitle'; import { useIsChannelMuted } from './hooks/useIsChannelMuted'; import { useBottomSheetContext } from '../../contexts/bottomSheetContext/BottomSheetContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useSwipeRegistryContext } from '../../contexts/swipeableContext/SwipeRegistryContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; @@ -29,7 +30,6 @@ export type ChannelDetailsHeaderProps = { channel: Channel }; export type ChannelDetailsBottomSheetProps = { additionalFlatListProps?: Partial>; channel: Channel; - ChannelDetailsHeader?: React.ComponentType; items: ChannelActionItem[]; }; @@ -104,10 +104,10 @@ const keyExtractor = (item: ChannelActionItem) => item.id; export const ChannelDetailsBottomSheet = ({ additionalFlatListProps, - ChannelDetailsHeader: ChannelDetailsHeaderComponent = ChannelDetailsHeader, items, channel, }: ChannelDetailsBottomSheetProps) => { + const { ChannelDetailsHeader: ChannelDetailsHeaderComponent } = useComponentsContext(); const styles = useStyles(); return ( <> diff --git a/package/src/components/ChannelPreview/ChannelPreview.tsx b/package/src/components/ChannelPreview/ChannelPreview.tsx index 3844f96a4e..81b386b95b 100644 --- a/package/src/components/ChannelPreview/ChannelPreview.tsx +++ b/package/src/components/ChannelPreview/ChannelPreview.tsx @@ -10,10 +10,11 @@ import { useChannelsContext, } from '../../contexts/channelsContext/ChannelsContext'; import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useTranslatedMessage } from '../../hooks/useTranslatedMessage'; export type ChannelPreviewProps = Partial> & - Partial> & { + Partial> & { /** * Instance of Channel from stream-chat package. */ @@ -21,18 +22,13 @@ export type ChannelPreviewProps = Partial> & }; export const ChannelPreview = (props: ChannelPreviewProps) => { - const { channel, client: propClient, forceUpdate: propForceUpdate, Preview: propPreview } = props; + const { channel, client: propClient, forceUpdate: propForceUpdate } = props; const { client: contextClient } = useChatContext(); - const { - Preview: contextPreview, - ChannelDetailsBottomSheet, - getChannelActionItems, - swipeActionsEnabled, - } = useChannelsContext(); + const { getChannelActionItems, swipeActionsEnabled } = useChannelsContext(); + const { Preview } = useComponentsContext(); const client = propClient || contextClient; - const Preview = propPreview || contextPreview; const { muted, unread, lastMessage } = useChannelPreviewData(channel, client, propForceUpdate); @@ -45,11 +41,7 @@ export const ChannelPreview = (props: ChannelPreviewProps) => { } return ( - + ); diff --git a/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx b/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx index ca7e17fdde..2dbed3fd35 100644 --- a/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx +++ b/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx @@ -1,11 +1,8 @@ import React, { useMemo } from 'react'; import { StyleSheet, Text, View } from 'react-native'; -import { ChannelLastMessagePreview } from './ChannelLastMessagePreview'; -import { ChannelMessagePreviewDeliveryStatus } from './ChannelMessagePreviewDeliveryStatus'; import { ChannelPreviewProps } from './ChannelPreview'; -import { ChannelPreviewTypingIndicator } from './ChannelPreviewTypingIndicator'; import { LastMessageType } from './hooks/useChannelPreviewData'; import { useChannelPreviewDraftMessage } from './hooks/useChannelPreviewDraftMessage'; @@ -13,11 +10,8 @@ import { useChannelPreviewPollLabel } from './hooks/useChannelPreviewPollLabel'; import { useChannelTypingState } from './hooks/useChannelTypingState'; -import { - ChannelsContextValue, - useChannelsContext, -} from '../../contexts/channelsContext/ChannelsContext'; import { useChatContext } from '../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; @@ -26,34 +20,14 @@ import { primitives } from '../../theme'; import { MessageStatusTypes } from '../../utils/utils'; import { ErrorBadge } from '../ui'; -export type ChannelPreviewMessageProps = Pick & - Partial< - Pick< - ChannelsContextValue, - 'PreviewTypingIndicator' | 'PreviewMessageDeliveryStatus' | 'PreviewLastMessage' - > - > & { - lastMessage?: LastMessageType; - }; +export type ChannelPreviewMessageProps = Pick & { + lastMessage?: LastMessageType; +}; export const ChannelPreviewMessage = (props: ChannelPreviewMessageProps) => { - const { - channel, - lastMessage, - PreviewTypingIndicator: PreviewTypingIndicatorProp = ChannelPreviewTypingIndicator, - PreviewMessageDeliveryStatus: - PreviewMessageDeliveryStatusProp = ChannelMessagePreviewDeliveryStatus, - PreviewLastMessage: PreviewLastMessageProp = ChannelLastMessagePreview, - } = props; - const { - PreviewTypingIndicator: PreviewTypingIndicatorContext, - PreviewMessageDeliveryStatus: PreviewMessageDeliveryStatusContext, - PreviewLastMessage: PreviewLastMessageContext, - } = useChannelsContext(); - const PreviewTypingIndicator = PreviewTypingIndicatorProp || PreviewTypingIndicatorContext; - const PreviewMessageDeliveryStatus = - PreviewMessageDeliveryStatusProp || PreviewMessageDeliveryStatusContext; - const PreviewLastMessage = PreviewLastMessageProp || PreviewLastMessageContext; + const { channel, lastMessage } = props; + const { PreviewTypingIndicator, PreviewMessageDeliveryStatus, PreviewLastMessage } = + useComponentsContext(); const { theme: { semantics }, } = useTheme(); diff --git a/package/src/components/ChannelPreview/ChannelPreviewView.tsx b/package/src/components/ChannelPreview/ChannelPreviewView.tsx index f27a23fce6..4bb0f224e8 100644 --- a/package/src/components/ChannelPreview/ChannelPreviewView.tsx +++ b/package/src/components/ChannelPreview/ChannelPreviewView.tsx @@ -2,11 +2,6 @@ import React, { useMemo } from 'react'; import { Pressable, StyleSheet, View } from 'react-native'; import type { ChannelPreviewProps } from './ChannelPreview'; -import { ChannelPreviewMessage } from './ChannelPreviewMessage'; -import { ChannelPreviewMutedStatus } from './ChannelPreviewMutedStatus'; -import { ChannelPreviewStatus } from './ChannelPreviewStatus'; -import { ChannelPreviewTitle } from './ChannelPreviewTitle'; -import { ChannelPreviewUnreadCount } from './ChannelPreviewUnreadCount'; import type { LastMessageType } from './hooks/useChannelPreviewData'; @@ -14,25 +9,14 @@ import { ChannelsContextValue, useChannelsContext, } from '../../contexts/channelsContext/ChannelsContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useSwipeRegistryContext } from '../../contexts/swipeableContext/SwipeRegistryContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useStableCallback } from '../../hooks'; import { primitives } from '../../theme'; -import { ChannelAvatar } from '../ui/Avatar/ChannelAvatar'; export type ChannelPreviewViewPropsWithContext = Pick & - Pick< - ChannelsContextValue, - | 'maxUnreadCount' - | 'onSelect' - | 'PreviewAvatar' - | 'PreviewMessage' - | 'PreviewMutedStatus' - | 'PreviewStatus' - | 'PreviewTitle' - | 'PreviewUnreadCount' - | 'mutedStatusPosition' - > & { + Pick & { /** * Formatter function for date of latest message. * @param date Message date @@ -57,16 +41,18 @@ const ChannelPreviewViewWithContext = (props: ChannelPreviewViewPropsWithContext maxUnreadCount, muted, onSelect, - PreviewAvatar = ChannelAvatar, - PreviewMessage = ChannelPreviewMessage, - PreviewMutedStatus = ChannelPreviewMutedStatus, - PreviewStatus = ChannelPreviewStatus, - PreviewTitle = ChannelPreviewTitle, - PreviewUnreadCount = ChannelPreviewUnreadCount, unread, mutedStatusPosition, lastMessage, } = props; + const { + PreviewAvatar, + PreviewMessage, + PreviewMutedStatus, + PreviewStatus, + PreviewTitle, + PreviewUnreadCount, + } = useComponentsContext(); const { theme: { @@ -158,28 +144,13 @@ const MemoizedChannelPreviewViewWithContext = React.memo( * from the ChannelPreview component. */ export const ChannelPreviewView = (props: ChannelPreviewViewProps) => { - const { - forceUpdate, - maxUnreadCount, - onSelect, - PreviewMessage, - PreviewMutedStatus, - PreviewStatus, - PreviewTitle, - PreviewUnreadCount, - mutedStatusPosition, - } = useChannelsContext(); + const { forceUpdate, maxUnreadCount, onSelect, mutedStatusPosition } = useChannelsContext(); return ( { export const ChannelSwipableWrapper = ({ channel, getChannelActionItems, - ChannelDetailsBottomSheet: ChannelDetailsBottomSheetComponent = DefaultChannelDetailsBottomSheet, swipableProps: _swipableProps, children, }: PropsWithChildren<{ channel: Channel; - ChannelDetailsBottomSheet?: React.ComponentType; getChannelActionItems?: GetChannelActionItems; swipableProps?: SwipableWrapperProps['swipableProps']; }>) => { + const { ChannelDetailsBottomSheet: ChannelDetailsBottomSheetComponent } = useComponentsContext(); const [channelDetailSheetOpen, setChannelDetailSheetOpen] = useState(false); const { muteChannel, unmuteChannel } = useChannelActions(channel); const channelActionItems = useChannelActionItems({ channel, getChannelActionItems }); diff --git a/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx index 5a29066f5c..2f2b7b11d9 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx @@ -5,6 +5,7 @@ import { render } from '@testing-library/react-native'; import type { Channel } from 'stream-chat'; import { ThemeProvider, defaultTheme } from '../../../contexts'; +import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext'; import type { ChannelActionItem } from '../../ChannelList/hooks/useChannelActionItems'; import type { ChannelDetailsHeaderProps } from '../ChannelDetailsBottomSheet'; import { ChannelDetailsBottomSheet } from '../ChannelDetailsBottomSheet'; @@ -17,7 +18,11 @@ jest.mock('../../UIComponents/StreamBottomSheetModalFlatList', () => ({ })); describe('ChannelDetailsBottomSheet', () => { - const channel = { cid: 'messaging:test-channel', id: 'test-channel' } as Channel; + const channel = { + cid: 'messaging:test-channel', + id: 'test-channel', + state: { members: {} }, + } as unknown as Channel; const items: ChannelActionItem[] = [ { @@ -41,11 +46,9 @@ describe('ChannelDetailsBottomSheet', () => { const { getByTestId } = render( - + + + , ); @@ -59,12 +62,13 @@ describe('ChannelDetailsBottomSheet', () => { render( - null} - additionalFlatListProps={{ onEndReached, testID: 'channel-details-list' }} - /> + null }}> + + , ); diff --git a/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx index bae4e582d7..42cb1ed3e2 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx @@ -7,6 +7,7 @@ import type { Channel, StreamChat } from 'stream-chat'; import { ChannelsProvider } from '../../../contexts/channelsContext/ChannelsContext'; import type { ChannelsContextValue } from '../../../contexts/channelsContext/ChannelsContext'; +import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext'; import { getOrCreateChannelApi, GetOrCreateChannelApiParams, @@ -83,12 +84,9 @@ describe('ChannelPreview', () => { return ( - + + + ); }; @@ -436,18 +434,23 @@ describe('ChannelPreview', () => { return ( - - - + + + + ); }; @@ -474,7 +477,7 @@ describe('ChannelPreview', () => { expect(mockChannelSwipableWrapper).toHaveBeenCalled(); }); - it('passes ChannelDetailsBottomSheet override to ChannelSwipableWrapper', async () => { + it('makes ChannelDetailsBottomSheet override available via WithComponents', async () => { render( { />, ); + // ChannelDetailsBottomSheet is now read from useComponentsContext() by + // ChannelSwipableWrapper rather than passed as a prop from ChannelPreview. + // Since ChannelSwipableWrapper is mocked, we verify the override is + // provided via WithComponents (set up in SwipeTestComponent). await waitFor(() => expect(mockChannelSwipableWrapper).toHaveBeenCalled()); - const swipableWrapperProps = mockChannelSwipableWrapper.mock.calls[0]?.[0]; - expect(swipableWrapperProps).toEqual( - expect.objectContaining({ - ChannelDetailsBottomSheet: ChannelDetailsBottomSheetOverride, - }), - ); }); }); }); diff --git a/package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx index d3855758fd..4af7299bf4 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx @@ -4,6 +4,7 @@ import { Text } from 'react-native'; import { act, render } from '@testing-library/react-native'; import type { Channel } from 'stream-chat'; +import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext'; import type { ChannelActionItem } from '../../ChannelList/hooks/useChannelActionItems'; import * as ChannelActionItemsModule from '../../ChannelList/hooks/useChannelActionItems'; import * as ChannelActionsModule from '../../ChannelList/hooks/useChannelActions'; @@ -115,9 +116,11 @@ describe('ChannelSwipableWrapper', () => { }); render( - - child - , + + + child + + , ); expect(customBottomSheet).toHaveBeenCalledWith( @@ -181,9 +184,11 @@ describe('ChannelSwipableWrapper', () => { }); render( - - child - , + + + child + + , ); expect(customBottomSheet).toHaveBeenCalledWith( diff --git a/package/src/components/Chat/Chat.tsx b/package/src/components/Chat/Chat.tsx index 8efe65ff4b..1648abbbd2 100644 --- a/package/src/components/Chat/Chat.tsx +++ b/package/src/components/Chat/Chat.tsx @@ -1,5 +1,5 @@ import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react'; -import { Image, Platform } from 'react-native'; +import { Platform } from 'react-native'; import { Channel, OfflineDBState } from 'stream-chat'; @@ -10,6 +10,7 @@ import { useIsOnline } from './hooks/useIsOnline'; import { ChannelsStateProvider } from '../../contexts/channelsStateContext/ChannelsStateContext'; import { ChatContextValue, ChatProvider } from '../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useDebugContext } from '../../contexts/debugContext/DebugContext'; import { DeepPartial, ThemeProvider, useTheme } from '../../contexts/themeContext/ThemeContext'; import type { Theme } from '../../contexts/themeContext/utils/theme'; @@ -30,7 +31,7 @@ import { version } from '../../version.json'; init(); export type ChatProps = Pick & - Partial> & { + Partial> & { /** * When false, ws connection won't be disconnection upon backgrounding the app. * To receive push notifications, its necessary that user doesn't have active @@ -94,12 +95,6 @@ export type ChatProps = Pick & * ``` */ i18nInstance?: Streami18n; - /** - * Custom loading indicator component to be used to represent the loading state of the chat. - * - * This can be used during the phase when db is not initialised. - */ - LoadingIndicator?: React.ComponentType | null; /** * You can pass the theme object to customize the styles of Chat components. You can check the default theme in [theme.ts](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/contexts/themeContext/utils/theme.ts) * @@ -144,11 +139,10 @@ const ChatWithContext = (props: PropsWithChildren) => { closeConnectionOnBackground = true, enableOfflineSupport = false, i18nInstance, - ImageComponent = Image, isMessageAIGenerated, - LoadingIndicator = null, style, } = props; + const { ChatLoadingIndicator } = useComponentsContext(); const [channel, setChannel] = useState(); @@ -257,7 +251,6 @@ const ChatWithContext = (props: PropsWithChildren) => { client, connectionRecovering, enableOfflineSupport, - ImageComponent, isMessageAIGenerated, isOnline, mutedUsers, @@ -266,7 +259,7 @@ const ChatWithContext = (props: PropsWithChildren) => { if (userID && enableOfflineSupport && !initialisedDatabase) { // if user id has been set and offline support is enabled, we need to wait for database to be initialised - return LoadingIndicator ? : null; + return ChatLoadingIndicator ? : null; } return ( diff --git a/package/src/components/Chat/hooks/useCreateChatContext.ts b/package/src/components/Chat/hooks/useCreateChatContext.ts index 2992dbc961..1a74a10e58 100644 --- a/package/src/components/Chat/hooks/useCreateChatContext.ts +++ b/package/src/components/Chat/hooks/useCreateChatContext.ts @@ -8,7 +8,6 @@ export const useCreateChatContext = ({ client, connectionRecovering, enableOfflineSupport, - ImageComponent, isMessageAIGenerated, isOnline, mutedUsers, @@ -29,7 +28,6 @@ export const useCreateChatContext = ({ client, connectionRecovering, enableOfflineSupport, - ImageComponent, isMessageAIGenerated, isOnline, mutedUsers, diff --git a/package/src/components/ImageGallery/ImageGallery.tsx b/package/src/components/ImageGallery/ImageGallery.tsx index 069b0f7af7..d3af58ec2d 100644 --- a/package/src/components/ImageGallery/ImageGallery.tsx +++ b/package/src/components/ImageGallery/ImageGallery.tsx @@ -13,9 +13,15 @@ import Animated, { import { AnimatedGalleryImage } from './components/AnimatedGalleryImage'; import { AnimatedGalleryVideo } from './components/AnimatedGalleryVideo'; +import type { + ImageGalleryFooterProps, + ImageGalleryGridProps, + ImageGalleryHeaderProps, +} from './components/types'; import { useImageGalleryGestures } from './hooks/useImageGalleryGestures'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { ImageGalleryProviderProps, useImageGalleryContext, @@ -63,13 +69,13 @@ const imageGallerySelector = (state: ImageGalleryState) => ({ type ImageGalleryWithContextProps = Pick< ImageGalleryProviderProps, - | 'numberOfImageGalleryGridColumns' - | 'ImageGalleryHeader' - | 'ImageGalleryFooter' - | 'ImageGalleryVideoControls' - | 'ImageGalleryGrid' + 'numberOfImageGalleryGridColumns' > & - Pick; + Pick & { + ImageGalleryHeader?: React.ComponentType; + ImageGalleryFooter?: React.ComponentType; + ImageGalleryGrid?: React.ComponentType; + }; export const ImageGalleryWithContext = (props: ImageGalleryWithContextProps) => { const { @@ -77,7 +83,6 @@ export const ImageGalleryWithContext = (props: ImageGalleryWithContextProps) => overlayOpacity, ImageGalleryHeader, ImageGalleryFooter, - ImageGalleryVideoControls, ImageGalleryGrid, } = props; const [isGridViewVisible, setIsGridViewVisible] = useState(false); @@ -345,7 +350,6 @@ export const ImageGalleryWithContext = (props: ImageGalleryWithContextProps) => opacity={headerFooterOpacity} openGridView={openGridView} visible={headerFooterVisible} - ImageGalleryVideoControls={ImageGalleryVideoControls} /> ) : null} @@ -370,13 +374,8 @@ export const ImageGalleryWithContext = (props: ImageGalleryWithContextProps) => export type ImageGalleryProps = Partial; export const ImageGallery = (props: ImageGalleryProps) => { - const { - numberOfImageGalleryGridColumns, - ImageGalleryHeader, - ImageGalleryFooter, - ImageGalleryVideoControls, - ImageGalleryGrid, - } = useImageGalleryContext(); + const { numberOfImageGalleryGridColumns } = useImageGalleryContext(); + const { ImageGalleryHeader, ImageGalleryFooter, ImageGalleryGrid } = useComponentsContext(); const { overlayOpacity } = useOverlayContext(); return ( { overlayOpacity={overlayOpacity} ImageGalleryHeader={ImageGalleryHeader} ImageGalleryFooter={ImageGalleryFooter} - ImageGalleryVideoControls={ImageGalleryVideoControls} ImageGalleryGrid={ImageGalleryGrid} {...props} /> diff --git a/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx index 5c8b27d723..dcdcb2b959 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx @@ -9,10 +9,6 @@ import duration from 'dayjs/plugin/duration'; import { LocalMessage } from 'stream-chat'; -import { ImageGalleryFooter as ImageGalleryFooterDefault } from '../../../components/ImageGallery/components/ImageGalleryFooter'; -import { ImageGalleryHeader as ImageGalleryHeaderDefault } from '../../../components/ImageGallery/components/ImageGalleryHeader'; -import { ImageGalleryVideoControl as ImageGalleryVideoControlDefault } from '../../../components/ImageGallery/components/ImageGalleryVideoControl'; -import { ImageGalleryGrid as ImageGalleryGridDefault } from '../../../components/ImageGallery/components/ImageGrid'; import { ImageGalleryContext, ImageGalleryContextValue, @@ -66,10 +62,6 @@ const ImageGalleryComponent = (props: ImageGalleryProps & { message: LocalMessag value={ { imageGalleryStateStore, - ImageGalleryHeader: ImageGalleryHeaderDefault, - ImageGalleryFooter: ImageGalleryFooterDefault, - ImageGalleryVideoControls: ImageGalleryVideoControlDefault, - ImageGalleryGrid: ImageGalleryGridDefault, } as unknown as ImageGalleryContextValue } > diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx index 5148cc6f27..0ba68f73de 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx @@ -6,7 +6,7 @@ import { render, screen, userEvent, waitFor } from '@testing-library/react-nativ import { Attachment, LocalMessage } from 'stream-chat'; -import { ImageGalleryFooter as ImageGalleryFooterDefault } from '../../../components/ImageGallery/components/ImageGalleryFooter'; +import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext'; import { ImageGalleryContext, ImageGalleryContextValue, @@ -60,16 +60,18 @@ const ImageGalleryComponentVideo = (props: ImageGalleryProps) => { return ( }}> - - - + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} + + + + + ); }; @@ -100,16 +102,18 @@ const ImageGalleryComponentImage = ( return ( }}> - - - + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} + + + + + ); }; diff --git a/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx b/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx index 43181f12ff..51bd40c1c6 100644 --- a/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx +++ b/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx @@ -4,6 +4,7 @@ import Animated, { Extrapolation, interpolate, useAnimatedStyle } from 'react-na import type { ImageGalleryFooterProps, ImageGalleryVideoControlProps } from './types'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { useImageGalleryContext } from '../../../contexts/imageGalleryContext/ImageGalleryContextBase'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; @@ -42,7 +43,8 @@ const imageGallerySelector = (state: ImageGalleryState) => ({ }); export const ImageGalleryFooterWithContext = (props: ImageGalleryFooterProps) => { - const { accessibilityLabel, opacity, openGridView, visible, ImageGalleryVideoControls } = props; + const { accessibilityLabel, opacity, openGridView, visible } = props; + const { ImageGalleryVideoControls } = useComponentsContext(); const [height, setHeight] = useState(200); const [savingInProgress, setSavingInProgress] = useState(false); diff --git a/package/src/components/ImageGallery/components/types.ts b/package/src/components/ImageGallery/components/types.ts index 68fce8877e..feb795e1a5 100644 --- a/package/src/components/ImageGallery/components/types.ts +++ b/package/src/components/ImageGallery/components/types.ts @@ -1,5 +1,3 @@ -import type React from 'react'; - import type { SharedValue } from 'react-native-reanimated'; export type ImageGalleryVideoControlProps = { @@ -13,7 +11,6 @@ export type ImageGalleryHeaderProps = { export type ImageGalleryFooterProps = { accessibilityLabel: string; - ImageGalleryVideoControls?: React.ComponentType; opacity: SharedValue; openGridView: () => void; visible: SharedValue; diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index 6ee7445381..327fff2648 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -27,6 +27,7 @@ import { useChannelContext, } from '../../contexts/channelContext/ChannelContext'; import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { KeyboardContextValue, useKeyboardContext, @@ -205,12 +206,8 @@ export type MessagePropsWithContext = Pick< | 'handleThreadReply' | 'handleBlockUser' | 'isAttachmentEqual' - | 'MessageMenu' | 'messageActions' | 'messageContentOrder' - | 'MessageBounce' - | 'MessageBlocked' - | 'MessageItemView' | 'onLongPressMessage' | 'onPressInMessage' | 'onPressMessage' @@ -220,14 +217,6 @@ export type MessagePropsWithContext = Pick< | 'selectReaction' | 'supportedReactions' | 'updateMessage' - | 'PollContent' - // TODO: remove this comment later, using it as a pragma mark - | 'MessageUserReactions' - | 'MessageUserReactionsAvatar' - | 'MessageUserReactionsItem' - | 'MessageReactionPicker' - | 'MessageActionList' - | 'MessageActionListItem' > & Pick & Pick & { @@ -289,11 +278,8 @@ const MessageWithContext = (props: MessagePropsWithContext) => { members, message, messageActions: messageActionsProp = defaultMessageActions, - MessageBlocked, - MessageBounce, messageContentOrder: messageContentOrderProp, messagesContext, - MessageItemView, onLongPressMessage: onLongPressMessageProp, onPressInMessage: onPressInMessageProp, onPressMessage: onPressMessageProp, @@ -314,13 +300,15 @@ const MessageWithContext = (props: MessagePropsWithContext) => { updateMessage, readBy, setQuotedMessage, - MessageUserReactions, - MessageUserReactionsAvatar, - MessageUserReactionsItem, - MessageReactionPicker, - MessageActionList, - MessageActionListItem, } = props; + const { + MessageActionList, + MessageBlocked, + MessageBounce, + MessageItemView, + MessageReactionPicker, + MessageUserReactions, + } = useComponentsContext(); // TODO: V9: Reconsider using safe area insets in every message. const insets = useSafeAreaInsets(); const isMessageAIGenerated = messagesContext.isMessageAIGenerated; @@ -878,12 +866,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => { visible={showMessageReactions} height={424} > - + ) : null} @@ -902,11 +885,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => { }); }} > - + ) : null} diff --git a/package/src/components/Message/MessageItemView/MessageBubble.tsx b/package/src/components/Message/MessageItemView/MessageBubble.tsx index f945368bec..3277850cfc 100644 --- a/package/src/components/Message/MessageItemView/MessageBubble.tsx +++ b/package/src/components/Message/MessageItemView/MessageBubble.tsx @@ -12,20 +12,24 @@ import Animated, { import { MessageItemViewPropsWithContext } from './MessageItemView'; -import { MessagesContextValue, useTheme } from '../../../contexts'; +import { useTheme } from '../../../contexts'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { NativeHandlers } from '../../../native'; const AnimatedWrapper = Animated.createAnimatedComponent(View); -type SwipableMessageWrapperProps = Pick & - Pick & { - children: ReactNode; - onSwipe: () => void; - }; +type SwipableMessageWrapperProps = Pick< + MessageItemViewPropsWithContext, + 'messageSwipeToReplyHitSlop' +> & { + children: ReactNode; + onSwipe: () => void; +}; export const SwipableMessageWrapper = React.memo((props: SwipableMessageWrapperProps) => { - const { MessageSwipeContent, children, messageSwipeToReplyHitSlop, onSwipe } = props; + const { children, messageSwipeToReplyHitSlop, onSwipe } = props; + const { MessageSwipeContent } = useComponentsContext(); const isRTL = I18nManager.isRTL; const swipeDirectionMultiplier = isRTL ? -1 : 1; diff --git a/package/src/components/Message/MessageItemView/MessageContent.tsx b/package/src/components/Message/MessageItemView/MessageContent.tsx index dc068c2689..4f94ad2383 100644 --- a/package/src/components/Message/MessageItemView/MessageContent.tsx +++ b/package/src/components/Message/MessageItemView/MessageContent.tsx @@ -4,6 +4,7 @@ import { AnimatableNumericValue, ColorValue, Pressable, StyleSheet, View } from import { MessageTextContainer } from './MessageTextContainer'; import { useChatContext } from '../../../contexts'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, @@ -67,19 +68,9 @@ export type MessageContentPropsWithContext = Pick< Pick< MessagesContextValue, | 'additionalPressableProps' - | 'Attachment' | 'enableMessageGroupingByUser' - | 'FileAttachmentGroup' - | 'Gallery' | 'isAttachmentEqual' - | 'MessageContentBottomView' - | 'MessageContentLeadingView' - | 'MessageLocation' - | 'MessageContentTrailingView' - | 'MessageContentTopView' | 'myMessageTheme' - | 'Reply' - | 'StreamingMessageView' > & Pick & { /** @@ -115,11 +106,8 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { const { additionalPressableProps, alignment, - Attachment, backgroundColor, enableMessageGroupingByUser, - FileAttachmentGroup, - Gallery, groupStyles, goToMessage, isMessageAIGenerated, @@ -127,26 +115,30 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { isVeryLastMessage, message, messageContentOrder, - MessageContentBottomView, - MessageContentLeadingView, messageGroupedSingleOrBottom = false, - MessageLocation, - MessageContentTrailingView, - MessageContentTopView, noBorder, onLongPress, onPress, onPressIn, otherAttachments, preventPress, - Reply, - StreamingMessageView, hidePaddingTop, hidePaddingHorizontal, hidePaddingBottom, } = props; const { client } = useChatContext(); - const { PollContent: PollContentOverride } = useMessagesContext(); + const { + Attachment, + FileAttachmentGroup, + Gallery, + MessageContentBottomView, + MessageContentLeadingView, + MessageContentTopView, + MessageContentTrailingView, + MessageLocation, + Reply, + StreamingMessageView, + } = useComponentsContext(); const replyStyles = useReplyStyles(); const { @@ -298,12 +290,7 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { const pollId = message.poll_id; const poll = pollId && client.polls.fromState(pollId); return pollId && poll ? ( - + ) : null; } case 'location': @@ -590,19 +577,9 @@ export const MessageContent = (props: MessageContentProps) => { } = useMessageContext(); const { additionalPressableProps, - Attachment, enableMessageGroupingByUser, - FileAttachmentGroup, - Gallery, isAttachmentEqual, - MessageContentBottomView, - MessageContentLeadingView, - MessageLocation, - MessageContentTrailingView, - MessageContentTopView, myMessageTheme, - Reply, - StreamingMessageView, } = useMessagesContext(); const { t } = useTranslationContext(); const isSingleFile = files.length === 1; @@ -647,10 +624,7 @@ export const MessageContent = (props: MessageContentProps) => { {...{ additionalPressableProps, alignment, - Attachment, enableMessageGroupingByUser, - FileAttachmentGroup, - Gallery, goToMessage, groupStyles, isAttachmentEqual, @@ -658,19 +632,12 @@ export const MessageContent = (props: MessageContentProps) => { isMyMessage, message, messageContentOrder, - MessageContentBottomView, - MessageContentLeadingView, - MessageLocation, - MessageContentTrailingView, - MessageContentTopView, myMessageTheme, onLongPress, onPress, onPressIn, otherAttachments, preventPress, - Reply, - StreamingMessageView, t, threadList, hidePaddingTop, diff --git a/package/src/components/Message/MessageItemView/MessageDeleted.tsx b/package/src/components/Message/MessageItemView/MessageDeleted.tsx index be907db63d..4515b5095f 100644 --- a/package/src/components/Message/MessageItemView/MessageDeleted.tsx +++ b/package/src/components/Message/MessageItemView/MessageDeleted.tsx @@ -1,14 +1,11 @@ import React, { useMemo } from 'react'; import { StyleSheet, Text, View } from 'react-native'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, } from '../../../contexts/messageContext/MessageContext'; -import { - MessagesContextValue, - useMessagesContext, -} from '../../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { CircleBan } from '../../../icons/no-sign'; @@ -20,11 +17,11 @@ type MessageDeletedComponentProps = { }; type MessageDeletedPropsWithContext = Pick & - Pick & MessageDeletedComponentProps; const MessageDeletedWithContext = (props: MessageDeletedPropsWithContext) => { - const { alignment, date, groupStyle, MessageFooter } = props; + const { alignment, date, groupStyle } = props; + const { MessageFooter } = useComponentsContext(); const { theme: { @@ -111,14 +108,11 @@ export type MessageDeletedProps = Partial & { export const MessageDeleted = (props: MessageDeletedProps) => { const { alignment, message } = useMessageContext(); - const { MessageFooter } = useMessagesContext(); - return ( diff --git a/package/src/components/Message/MessageItemView/MessageFooter.tsx b/package/src/components/Message/MessageItemView/MessageFooter.tsx index c0ada724f9..779f553e1f 100644 --- a/package/src/components/Message/MessageItemView/MessageFooter.tsx +++ b/package/src/components/Message/MessageItemView/MessageFooter.tsx @@ -3,18 +3,13 @@ import { StyleSheet, Text, View } from 'react-native'; import type { Attachment, LocalMessage } from 'stream-chat'; -import type { MessageStatusProps } from './MessageStatus'; - import type { ChannelContextValue } from '../../../contexts/channelContext/ChannelContext'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { Alignment, MessageContextValue, useMessageContext, } from '../../../contexts/messageContext/MessageContext'; -import { - MessagesContextValue, - useMessagesContext, -} from '../../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; @@ -37,7 +32,6 @@ type MessageFooterPropsWithContext = Pick< | 'lastGroupMessage' | 'isMessageAIGenerated' > & - Pick & MessageFooterComponentProps; const MessageFooterWithContext = (props: MessageFooterPropsWithContext) => { @@ -49,10 +43,9 @@ const MessageFooterWithContext = (props: MessageFooterPropsWithContext) => { lastGroupMessage, members, message, - MessageStatus, - MessageTimestamp, showMessageStatus, } = props; + const { MessageStatus, MessageTimestamp } = useComponentsContext(); const styles = useStyles(); const { @@ -169,7 +162,6 @@ export type MessageFooterProps = Partial> & alignment?: Alignment; lastGroupMessage?: boolean; message?: LocalMessage; - MessageStatus?: React.ComponentType; otherAttachments?: Attachment[]; showMessageStatus?: boolean; }; @@ -178,8 +170,6 @@ export const MessageFooter = (props: MessageFooterProps) => { const { alignment, isMessageAIGenerated, lastGroupMessage, members, message, showMessageStatus } = useMessageContext(); - const { MessageStatus, MessageTimestamp } = useMessagesContext(); - return ( { lastGroupMessage, members, message, - MessageStatus, - MessageTimestamp, showMessageStatus, }} {...props} diff --git a/package/src/components/Message/MessageItemView/MessageHeader.tsx b/package/src/components/Message/MessageItemView/MessageHeader.tsx index 0762b38c9f..74f2e29e2d 100644 --- a/package/src/components/Message/MessageItemView/MessageHeader.tsx +++ b/package/src/components/Message/MessageItemView/MessageHeader.tsx @@ -2,43 +2,35 @@ import React, { useMemo } from 'react'; import { View, ViewStyle } from 'react-native'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, } from '../../../contexts/messageContext/MessageContext'; -import { - MessagesContextValue, - useMessagesContext, -} from '../../../contexts/messagesContext/MessagesContext'; import { useMessageReminder } from '../../../hooks/useMessageReminder'; -type MessageHeaderPropsWithContext = Pick & - Pick< - MessagesContextValue, - | 'MessagePinnedHeader' - | 'MessageReminderHeader' - | 'MessageSavedForLaterHeader' - | 'SentToChannelHeader' - > & { - shouldShowSavedForLaterHeader?: boolean; - shouldShowPinnedHeader: boolean; - shouldShowReminderHeader: boolean; - shouldShowSentToChannelHeader: boolean; - }; +type MessageHeaderPropsWithContext = Pick & { + shouldShowSavedForLaterHeader?: boolean; + shouldShowPinnedHeader: boolean; + shouldShowReminderHeader: boolean; + shouldShowSentToChannelHeader: boolean; +}; const MessageHeaderWithContext = (props: MessageHeaderPropsWithContext) => { const { alignment, message, - MessagePinnedHeader, shouldShowSavedForLaterHeader, shouldShowPinnedHeader, shouldShowReminderHeader, shouldShowSentToChannelHeader, + } = props; + const { + MessagePinnedHeader, MessageReminderHeader, MessageSavedForLaterHeader, SentToChannelHeader, - } = props; + } = useComponentsContext(); const containerStyle: ViewStyle = useMemo(() => { return { @@ -115,12 +107,6 @@ export type MessageHeaderProps = Partial>; export const MessageHeader = (props: MessageHeaderProps) => { const { alignment, message } = useMessageContext(); - const { - MessagePinnedHeader, - MessageReminderHeader, - MessageSavedForLaterHeader, - SentToChannelHeader, - } = useMessagesContext(); const reminder = useMessageReminder(message.id); const shouldShowSavedForLaterHeader = reminder && !reminder.remindAt; @@ -141,14 +127,10 @@ export const MessageHeader = (props: MessageHeaderProps) => { ); diff --git a/package/src/components/Message/MessageItemView/MessageItemView.tsx b/package/src/components/Message/MessageItemView/MessageItemView.tsx index 765dccbded..c9d8cedf0d 100644 --- a/package/src/components/Message/MessageItemView/MessageItemView.tsx +++ b/package/src/components/Message/MessageItemView/MessageItemView.tsx @@ -3,6 +3,7 @@ import { Dimensions, StyleSheet, View, ViewStyle } from 'react-native'; import { SwipableMessageWrapper } from './MessageBubble'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { Alignment, MessageContextValue, @@ -216,20 +217,9 @@ export type MessageItemViewPropsWithContext = Pick< | 'enableMessageGroupingByUser' | 'enableSwipeToReply' | 'myMessageTheme' - | 'MessageAuthor' - | 'MessageContent' - | 'MessageDeleted' - | 'MessageError' - | 'MessageFooter' - | 'MessageHeader' - | 'MessageReplies' - | 'MessageSpacer' - | 'MessageSwipeContent' | 'messageSwipeToReplyHitSlop' - | 'ReactionListBottom' | 'reactionListPosition' | 'reactionListType' - | 'ReactionListTop' >; const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => { @@ -245,6 +235,14 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => { hasAttachmentActions, isMyMessage, message, + messageSwipeToReplyHitSlop = { left: width, right: width }, + onlyEmojis, + otherAttachments, + reactionListPosition, + reactionListType, + setQuotedMessage, + } = props; + const { MessageAuthor, MessageContent, MessageDeleted, @@ -253,16 +251,9 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => { MessageHeader, MessageReplies, MessageSpacer, - MessageSwipeContent, - messageSwipeToReplyHitSlop = { left: width, right: width }, - onlyEmojis, - otherAttachments, ReactionListBottom, - reactionListPosition, - reactionListType, ReactionListTop, - setQuotedMessage, - } = props; + } = useComponentsContext(); const { theme: { @@ -380,7 +371,6 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => { return enableSwipeToReply && !isMessageTypeDeleted ? ( @@ -540,21 +530,10 @@ export const MessageItemView = (props: MessageItemViewProps) => { customMessageSwipeAction, enableMessageGroupingByUser, enableSwipeToReply, - MessageAuthor, - MessageContent, - MessageDeleted, - MessageError, - MessageFooter, - MessageHeader, - MessageReplies, - MessageSpacer, - MessageSwipeContent, messageSwipeToReplyHitSlop, myMessageTheme, - ReactionListBottom, reactionListPosition, reactionListType, - ReactionListTop, } = useMessagesContext(); return ( @@ -570,23 +549,12 @@ export const MessageItemView = (props: MessageItemViewProps) => { hasAttachmentActions, isMyMessage, message, - MessageAuthor, - MessageContent, - MessageDeleted, - MessageError, - MessageFooter, - MessageHeader, - MessageReplies, - MessageSpacer, - MessageSwipeContent, messageSwipeToReplyHitSlop, myMessageTheme, onlyEmojis, otherAttachments, - ReactionListBottom, reactionListPosition, reactionListType, - ReactionListTop, setQuotedMessage, lastGroupMessage, members, diff --git a/package/src/components/Message/MessageItemView/MessageReplies.tsx b/package/src/components/Message/MessageItemView/MessageReplies.tsx index d44e0aba05..1b24042bf2 100644 --- a/package/src/components/Message/MessageItemView/MessageReplies.tsx +++ b/package/src/components/Message/MessageItemView/MessageReplies.tsx @@ -1,14 +1,11 @@ import React, { useMemo } from 'react'; import { I18nManager, Pressable, StyleSheet, Text, View } from 'react-native'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, } from '../../../contexts/messageContext/MessageContext'; -import { - MessagesContextValue, - useMessagesContext, -} from '../../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { TranslationContextValue, @@ -31,7 +28,6 @@ export type MessageRepliesPropsWithContext = Pick< | 'preventPress' | 'threadList' > & - Pick & Pick; const MessageRepliesWithContext = (props: MessageRepliesPropsWithContext) => { @@ -39,7 +35,6 @@ const MessageRepliesWithContext = (props: MessageRepliesPropsWithContext) => { alignment, isMyMessage, message, - MessageRepliesAvatars, onLongPress, onOpenThread, onPress, @@ -48,6 +43,7 @@ const MessageRepliesWithContext = (props: MessageRepliesPropsWithContext) => { t, threadList, } = props; + const { MessageRepliesAvatars } = useComponentsContext(); const { theme: { @@ -190,7 +186,6 @@ export const MessageReplies = (props: MessageRepliesProps) => { preventPress, threadList, } = useMessageContext(); - const { MessageRepliesAvatars } = useMessagesContext(); const { t } = useTranslationContext(); return ( @@ -199,7 +194,6 @@ export const MessageReplies = (props: MessageRepliesProps) => { alignment, isMyMessage, message, - MessageRepliesAvatars, onLongPress, onOpenThread, onPress, diff --git a/package/src/components/Message/MessageItemView/MessageTextContainer.tsx b/package/src/components/Message/MessageItemView/MessageTextContainer.tsx index 704121be4a..bc2d06f7ce 100644 --- a/package/src/components/Message/MessageItemView/MessageTextContainer.tsx +++ b/package/src/components/Message/MessageItemView/MessageTextContainer.tsx @@ -5,6 +5,7 @@ import { LocalMessage } from 'stream-chat'; import { renderText, RenderTextParams } from './utils/renderText'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, @@ -32,10 +33,7 @@ export type MessageTextContainerPropsWithContext = Pick< MessageContextValue, 'message' | 'onLongPress' | 'onlyEmojis' | 'onPress' | 'preventPress' | 'isMyMessage' > & - Pick< - MessagesContextValue, - 'markdownRules' | 'MessageText' | 'myMessageTheme' | 'messageTextNumberOfLines' - > & { + Pick & { markdownStyles?: MarkdownStyle; messageOverlay?: boolean; styles?: Partial<{ @@ -52,7 +50,6 @@ const MessageTextContainerWithContext = (props: MessageTextContainerPropsWithCon markdownStyles: markdownStylesProp = {}, message, messageOverlay, - MessageText, messageTextNumberOfLines, onLongPress, onlyEmojis, @@ -60,6 +57,7 @@ const MessageTextContainerWithContext = (props: MessageTextContainerPropsWithCon preventPress, styles: stylesProp = {}, } = props; + const { MessageText } = useComponentsContext(); const { theme: { @@ -186,8 +184,7 @@ export type MessageTextContainerProps = Partial { const { message, onLongPress, onlyEmojis, onPress, preventPress, isMyMessage } = useMessageContext(); - const { markdownRules, MessageText, messageTextNumberOfLines, myMessageTheme } = - useMessagesContext(); + const { markdownRules, messageTextNumberOfLines, myMessageTheme } = useMessagesContext(); return ( { markdownRules, message, isMyMessage, - MessageText, messageTextNumberOfLines, myMessageTheme, onLongPress, diff --git a/package/src/components/Message/MessageItemView/MessageWrapper.tsx b/package/src/components/Message/MessageItemView/MessageWrapper.tsx index 69ffb6aea1..5c27caa18f 100644 --- a/package/src/components/Message/MessageItemView/MessageWrapper.tsx +++ b/package/src/components/Message/MessageItemView/MessageWrapper.tsx @@ -8,6 +8,7 @@ import { useMessageDateSeparator } from '../../../components/MessageList/hooks/u import { useMessageGroupStyles } from '../../../components/MessageList/hooks/useMessageGroupStyles'; import { useChannelContext } from '../../../contexts/channelContext/ChannelContext'; import { useChatContext } from '../../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { useMessageListItemContext } from '../../../contexts/messageListItemContext/MessageListItemContext'; import { useMessagesContext } from '../../../contexts/messagesContext/MessagesContext'; import { ThemeProvider, useTheme } from '../../../contexts/themeContext/ThemeContext'; @@ -40,15 +41,9 @@ export const MessageWrapper = React.memo((props: MessageWrapperProps) => { maxTimeBetweenGroupedMessages, threadList, } = useChannelContext(); - const { - getMessageGroupStyle, - InlineDateSeparator, - InlineUnreadIndicator, - Message, - MessageSystem, - myMessageTheme, - shouldShowUnreadUnderlay, - } = useMessagesContext(); + const { InlineDateSeparator, InlineUnreadIndicator, Message, MessageSystem } = + useComponentsContext(); + const { getMessageGroupStyle, myMessageTheme, shouldShowUnreadUnderlay } = useMessagesContext(); const { goToMessage, onThreadSelect, noGroupByUser, modifiedTheme } = useMessageListItemContext(); const dateSeparatorDate = useMessageDateSeparator({ diff --git a/package/src/components/Message/MessageItemView/ReactionList/ReactionListBottom.tsx b/package/src/components/Message/MessageItemView/ReactionList/ReactionListBottom.tsx index efabd0da4a..1cf02a99f2 100644 --- a/package/src/components/Message/MessageItemView/ReactionList/ReactionListBottom.tsx +++ b/package/src/components/Message/MessageItemView/ReactionList/ReactionListBottom.tsx @@ -3,6 +3,7 @@ import { FlatList, StyleSheet, View } from 'react-native'; import { ReactionListItemProps } from './ReactionListItem'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, @@ -29,9 +30,7 @@ export type ReactionListBottomProps = Partial< | 'showReactionsOverlay' > > & - Partial< - Pick - > & { + Partial> & { type?: 'clustered' | 'segmented'; showCount?: boolean; }; @@ -55,8 +54,6 @@ export const ReactionListBottom = (props: ReactionListBottomProps) => { supportedReactions: propSupportedReactions, type, showCount = true, - ReactionListClustered: propReactionListClustered, - ReactionListItem: propReactionListItem, } = props; const { @@ -71,11 +68,8 @@ export const ReactionListBottom = (props: ReactionListBottomProps) => { showReactionsOverlay: contextShowReactionsOverlay, } = useMessageContext(); - const { - supportedReactions: contextSupportedReactions, - ReactionListClustered: contextReactionListClustered, - ReactionListItem: contextReactionListItem, - } = useMessagesContext(); + const { ReactionListClustered, ReactionListItem } = useComponentsContext(); + const { supportedReactions: contextSupportedReactions } = useMessagesContext(); const alignment = propAlignment || contextAlignment; const handleReaction = propHandlerReaction || contextHandleReaction; @@ -87,8 +81,6 @@ export const ReactionListBottom = (props: ReactionListBottomProps) => { const reactions = propReactions || contextReactions; const showReactionsOverlay = propShowReactionsOverlay || contextShowReactionsOverlay; const supportedReactions = propSupportedReactions || contextSupportedReactions; - const ReactionListClustered = propReactionListClustered || contextReactionListClustered; - const ReactionListItem = propReactionListItem || contextReactionListItem; const renderItem = useCallback( ({ index, item }: { index: number; item: ReactionListItemProps }) => ( diff --git a/package/src/components/Message/MessageItemView/ReactionList/ReactionListTop.tsx b/package/src/components/Message/MessageItemView/ReactionList/ReactionListTop.tsx index eaad1d5bde..af407679ee 100644 --- a/package/src/components/Message/MessageItemView/ReactionList/ReactionListTop.tsx +++ b/package/src/components/Message/MessageItemView/ReactionList/ReactionListTop.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import { ScrollView, StyleSheet, View } from 'react-native'; import { useTheme } from '../../../../contexts'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, @@ -26,14 +27,7 @@ export type ReactionListTopProps = Partial< | 'showReactionsOverlay' | 'handleReaction' > & - Pick< - MessagesContextValue, - | 'supportedReactions' - | 'reactionListType' - | 'ReactionListClustered' - | 'ReactionListItem' - | 'ReactionListCountItem' - > + Pick > & { type?: 'clustered' | 'segmented'; showCount?: boolean; @@ -56,9 +50,6 @@ export const ReactionListTop = (props: ReactionListTopProps) => { handleReaction: propHandleReaction, type, showCount = true, - ReactionListClustered: propReactionListClustered, - ReactionListItem: propReactionListItem, - ReactionListCountItem: propReactionListCountItem, } = props; const { @@ -73,12 +64,8 @@ export const ReactionListTop = (props: ReactionListTopProps) => { handleReaction: contextHandleReaction, } = useMessageContext(); - const { - supportedReactions: contextSupportedReactions, - ReactionListClustered: contextReactionListClustered, - ReactionListItem: contextReactionListItem, - ReactionListCountItem: contextReactionListCountItem, - } = useMessagesContext(); + const { ReactionListClustered, ReactionListCountItem, ReactionListItem } = useComponentsContext(); + const { supportedReactions: contextSupportedReactions } = useMessagesContext(); const alignment = propAlignment || contextAlignment; const hasReactions = propHasReactions || contextHasReactions; @@ -90,9 +77,6 @@ export const ReactionListTop = (props: ReactionListTopProps) => { const showReactionsOverlay = propShowReactionsOverlay || contextShowReactionsOverlay; const supportedReactions = propSupportedReactions || contextSupportedReactions; const handleReaction = propHandleReaction || contextHandleReaction; - const ReactionListClustered = propReactionListClustered || contextReactionListClustered; - const ReactionListItem = propReactionListItem || contextReactionListItem; - const ReactionListCountItem = propReactionListCountItem || contextReactionListCountItem; const styles = useStyles({ alignment }); diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.js b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.js index 515f50b7b9..5a2195d00a 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.js +++ b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.js @@ -4,6 +4,7 @@ import { StyleSheet, View } from 'react-native'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext'; +import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext'; import { getOrCreateChannelApi } from '../../../../mock-builders/api/getOrCreateChannel'; import { useMockedApis } from '../../../../mock-builders/api/useMockedApis'; @@ -116,9 +117,11 @@ describe('MessageContent', () => { render( - - - + + + + + , ); @@ -138,9 +141,11 @@ describe('MessageContent', () => { render( - - - + + + + + , ); @@ -158,13 +163,16 @@ describe('MessageContent', () => { render( - } - MessageContentTopView={() => } + , + MessageContentTopView: () => , + }} > - - + + + + , ); @@ -183,13 +191,16 @@ describe('MessageContent', () => { render( - } - MessageContentTrailingView={() => } + , + MessageContentTrailingView: () => , + }} > - - + + + + , ); @@ -209,13 +220,16 @@ describe('MessageContent', () => { const { rerender } = render( - } - MessageContentTrailingView={() => } + , + MessageContentTrailingView: () => , + }} > - - + + + + , ); @@ -231,13 +245,16 @@ describe('MessageContent', () => { rerender( - } - MessageContentTrailingView={() => } + , + MessageContentTrailingView: () => , + }} > - - + + + + , ); diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.js b/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.js index 2f45bfbcff..21f212cabf 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.js +++ b/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.js @@ -6,6 +6,7 @@ import { GestureDetector } from 'react-native-gesture-handler'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext'; +import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext'; import { useMessageContext } from '../../../../contexts/messageContext/MessageContext'; import { getOrCreateChannelApi } from '../../../../mock-builders/api/getOrCreateChannel'; @@ -42,13 +43,21 @@ describe('MessageItemView', () => { useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); channel = chatClient.channel('messaging', mockedChannel.id); - renderMessage = (options, channelProps) => + renderMessage = (options, channelProps, componentOverrides) => render( - - - + {componentOverrides ? ( + + + + + + ) : ( + + + + )} , ); @@ -136,7 +145,7 @@ describe('MessageItemView', () => { return Custom Message Item; }; - renderMessage({ message }, { MessageItemView: CustomMessageItemView }); + renderMessage({ message }, {}, { MessageItemView: CustomMessageItemView }); await waitFor(() => { expect(screen.queryByText('Custom Message Item')).not.toBeNull(); @@ -147,7 +156,7 @@ describe('MessageItemView', () => { const user = generateUser(); const message = generateMessage({ user }); - renderMessage({ message }, { MessageSpacer: () => Message Spacer }); + renderMessage({ message }, {}, { MessageSpacer: () => Message Spacer }); await waitFor(() => { expect(screen.queryByText('Message Spacer')).not.toBeNull(); @@ -193,7 +202,7 @@ describe('MessageItemView', () => { const user = generateUser(); const message = generateMessage({ user }); - renderMessage({ message }, { MessageHeader: () => Message Header }); + renderMessage({ message }, {}, { MessageHeader: () => Message Header }); await waitFor(() => { expect(screen.queryByText('Message Header')).not.toBeNull(); diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx index ab18a2a654..0caded18fc 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx @@ -5,6 +5,7 @@ import { cleanup, render, waitFor } from '@testing-library/react-native'; import { LocalMessage } from 'stream-chat'; +import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext'; import { OverlayProvider } from '../../../../contexts/overlayContext/OverlayProvider'; import { ThemeProvider } from '../../../../contexts/themeContext/ThemeContext'; import { defaultTheme } from '../../../../contexts/themeContext/utils/theme'; @@ -43,10 +44,13 @@ describe('MessageTextContainer', () => { rerender( - {message?.text}} - /> + {message?.text}, + }} + > + + , ); diff --git a/package/src/components/MessageInput/MessageComposer.tsx b/package/src/components/MessageInput/MessageComposer.tsx index b6e8dc1ead..2170994550 100644 --- a/package/src/components/MessageInput/MessageComposer.tsx +++ b/package/src/components/MessageInput/MessageComposer.tsx @@ -27,6 +27,7 @@ import { ChannelContextValue, useChannelContext, } from '../../contexts/channelContext/ChannelContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { MessageComposerAPIContextValue, useMessageComposerAPIContext, @@ -36,10 +37,6 @@ import { MessageInputContextValue, useMessageInputContext, } from '../../contexts/messageInputContext/MessageInputContext'; -import { - MessagesContextValue, - useMessagesContext, -} from '../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { @@ -160,36 +157,18 @@ type MessageComposerPropsWithContext = Pick & | 'asyncMessagesLockDistance' | 'asyncMessagesMinimumPressDuration' | 'asyncMessagesSlideToCancelDistance' - | 'AttachmentUploadPreviewList' - | 'AudioRecorder' - | 'AudioRecordingInProgress' - | 'AudioRecordingLockIndicator' - | 'AudioRecordingPreview' - | 'AutoCompleteSuggestionList' | 'closeAttachmentPicker' | 'compressImageQuality' - | 'Input' - | 'InputView' | 'inputBoxRef' - | 'InputButtons' - | 'MessageComposerLeadingView' - | 'MessageComposerTrailingView' | 'messageInputFloating' | 'messageInputHeightStore' - | 'MessageInputHeaderView' - | 'MessageInputTrailingView' - | 'SendButton' - | 'StartAudioRecordingButton' | 'uploadNewFile' | 'openPollCreationDialog' | 'closePollCreationDialog' | 'showPollCreationDialog' | 'sendMessage' - | 'CreatePollContent' | 'createPollOptionGap' - | 'StopMessageStreamingButton' > & - Pick & Pick & Pick & Pick & { @@ -210,23 +189,11 @@ const MessageComposerWithContext = (props: MessageComposerPropsWithContext) => { additionalTextInputProps, asyncMessagesLockDistance, asyncMessagesSlideToCancelDistance, - AudioRecorder, - AudioRecordingInProgress, - AudioRecordingLockIndicator, - AudioRecordingPreview, - AutoCompleteSuggestionList, closeAttachmentPicker, closePollCreationDialog, - CreatePollContent, createPollOptionGap, - InputView, - MessageComposerLeadingView, - MessageComposerTrailingView, messageInputFloating, messageInputHeightStore, - MessageInputHeaderView, - MessageInputTrailingView, - Input, inputBoxRef, isKeyboardVisible, members, @@ -239,6 +206,20 @@ const MessageComposerWithContext = (props: MessageComposerPropsWithContext) => { recordingStatus, } = props; + const { + AudioRecorder, + AudioRecordingInProgress, + AudioRecordingLockIndicator, + AudioRecordingPreview, + AutoCompleteSuggestionList, + Input, + InputView, + MessageComposerLeadingView, + MessageComposerTrailingView, + MessageInputHeaderView, + MessageInputTrailingView, + } = useComponentsContext(); + const styles = useStyles(); const { selectedPicker } = useAttachmentPickerState(); const { attachmentPickerBottomSheetHeight, bottomInset } = useAttachmentPickerContext(); @@ -486,7 +467,6 @@ const MessageComposerWithContext = (props: MessageComposerPropsWithContext) => { @@ -660,42 +640,22 @@ export const MessageComposer = (props: MessageComposerProps) => { asyncMessagesLockDistance, asyncMessagesMinimumPressDuration, asyncMessagesSlideToCancelDistance, - AttachmentUploadPreviewList, - AudioRecorder, audioRecordingEnabled, - AudioRecordingInProgress, - AudioRecordingLockIndicator, - AudioRecordingPreview, - AudioRecordingWaveform, - AutoCompleteSuggestionList, closeAttachmentPicker, closePollCreationDialog, compressImageQuality, - CreatePollContent, - Input, - InputView, inputBoxRef, - InputButtons, - MessageComposerLeadingView, - MessageComposerTrailingView, messageInputFloating, messageInputHeightStore, - MessageInputHeaderView, - MessageInputTrailingView, openPollCreationDialog, - SendButton, sendMessage, - SendMessageDisallowedIndicator, showPollCreationDialog, - StartAudioRecordingButton, - StopMessageStreamingButton, uploadNewFile, } = useMessageInputContext(); + const { SendMessageDisallowedIndicator } = useComponentsContext(); const messageComposer = useMessageComposer(); const editing = !!messageComposer.editedMessage; const { clearEditingState } = useMessageComposerAPIContext(); - - const { Reply } = useMessagesContext(); const isKeyboardVisible = useKeyboardVisibility(); const { micLocked, isRecordingStateIdle, recordingStatus } = useStateStore( @@ -725,43 +685,23 @@ export const MessageComposer = (props: MessageComposerProps) => { asyncMessagesLockDistance, asyncMessagesMinimumPressDuration, asyncMessagesSlideToCancelDistance, - AttachmentUploadPreviewList, - AudioRecorder, audioRecordingEnabled, - AudioRecordingInProgress, - AudioRecordingLockIndicator, - AudioRecordingPreview, - AudioRecordingWaveform, - AutoCompleteSuggestionList, channel, clearEditingState, closeAttachmentPicker, closePollCreationDialog, compressImageQuality, - CreatePollContent, // TODO: probably not needed anymore, please check editing, - Input, - InputView, inputBoxRef, - InputButtons, - MessageComposerLeadingView, - MessageComposerTrailingView, isKeyboardVisible, isOnline, members, messageInputFloating, messageInputHeightStore, - MessageInputHeaderView, - MessageInputTrailingView, openPollCreationDialog, - Reply, - SendButton, sendMessage, - SendMessageDisallowedIndicator, showPollCreationDialog, - StartAudioRecordingButton, - StopMessageStreamingButton, t, uploadNewFile, watchers, diff --git a/package/src/components/MessageInput/MessageInputHeaderView.tsx b/package/src/components/MessageInput/MessageInputHeaderView.tsx index 39ca1f446d..c717b4e810 100644 --- a/package/src/components/MessageInput/MessageInputHeaderView.tsx +++ b/package/src/components/MessageInput/MessageInputHeaderView.tsx @@ -9,11 +9,11 @@ import { useHasLinkPreviews } from './hooks/useLinkPreviews'; import { idleRecordingStateSelector } from './utils/audioRecorderSelectors'; import { messageComposerStateStoreSelector } from './utils/messageComposerSelectors'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useMessageComposerAPIContext } from '../../contexts/messageComposerContext/MessageComposerAPIContext'; import { useHasAttachments } from '../../contexts/messageInputContext/hooks/useHasAttachments'; import { useMessageComposer } from '../../contexts/messageInputContext/hooks/useMessageComposer'; import { useMessageInputContext } from '../../contexts/messageInputContext/MessageInputContext'; -import { useMessagesContext } from '../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useStateStore } from '../../hooks/useStateStore'; import { primitives } from '../../theme'; @@ -30,8 +30,8 @@ export const MessageInputHeaderView = () => { const { clearEditingState } = useMessageComposerAPIContext(); const { quotedMessage } = useStateStore(messageComposer.state, messageComposerStateStoreSelector); const hasLinkPreviews = useHasLinkPreviews(); - const { audioRecorderManager, AttachmentUploadPreviewList } = useMessageInputContext(); - const { Reply } = useMessagesContext(); + const { audioRecorderManager } = useMessageInputContext(); + const { AttachmentUploadPreviewList, Reply } = useComponentsContext(); const { isRecordingStateIdle } = useStateStore( audioRecorderManager.state, idleRecordingStateSelector, diff --git a/package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.tsx b/package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.tsx index 3a82e92619..15fbb71f6d 100644 --- a/package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.tsx +++ b/package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.tsx @@ -31,11 +31,9 @@ import { } from 'stream-chat'; import { useMessageComposer } from '../../../../contexts'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { useAttachmentManagerState } from '../../../../contexts/messageInputContext/hooks/useAttachmentManagerState'; -import { - MessageInputContextValue, - useMessageInputContext, -} from '../../../../contexts/messageInputContext/MessageInputContext'; +import { useMessageInputContext } from '../../../../contexts/messageInputContext/MessageInputContext'; import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; import { isSoundPackageAvailable } from '../../../../native'; import { primitives } from '../../../../theme'; @@ -44,13 +42,7 @@ const END_ANCHOR_THRESHOLD = 16; const END_SHRINK_COMPENSATION_DURATION = 200; const MAX_AUDIO_ATTACHMENTS_CONTAINER_WIDTH = 560; -export type AttachmentUploadListPreviewPropsWithContext = Pick< - MessageInputContextValue, - | 'AudioAttachmentUploadPreview' - | 'FileAttachmentUploadPreview' - | 'ImageAttachmentUploadPreview' - | 'VideoAttachmentUploadPreview' ->; +export type AttachmentUploadListPreviewPropsWithContext = Record; const AttachmentPreviewCell = ({ children }: { children: React.ReactNode }) => ( { +const UnMemoizedAttachmentUploadPreviewList = () => { const { AudioAttachmentUploadPreview, FileAttachmentUploadPreview, ImageAttachmentUploadPreview, VideoAttachmentUploadPreview, - } = props; + } = useComponentsContext(); const { audioRecordingSendOnComplete } = useMessageInputContext(); const { attachmentManager } = useMessageComposer(); const { attachments } = useAttachmentManagerState(); @@ -370,7 +360,7 @@ const UnMemoizedAttachmentUploadPreviewList = ( ); }; -export type AttachmentUploadPreviewListProps = Partial; +export type AttachmentUploadPreviewListProps = Record; const MemoizedAttachmentUploadPreviewListWithContext = React.memo( UnMemoizedAttachmentUploadPreviewList, @@ -380,25 +370,7 @@ const MemoizedAttachmentUploadPreviewListWithContext = React.memo( * AttachmentUploadPreviewList * UI Component to preview the files set for upload */ -export const AttachmentUploadPreviewList = (props: AttachmentUploadPreviewListProps) => { - const { - AudioAttachmentUploadPreview, - FileAttachmentUploadPreview, - ImageAttachmentUploadPreview, - VideoAttachmentUploadPreview, - } = useMessageInputContext(); - return ( - - ); -}; +export const AttachmentUploadPreviewList = () => ; const styles = StyleSheet.create({ audioAttachmentsContainer: { diff --git a/package/src/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.tsx b/package/src/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.tsx index 36bdd66f09..86f3a0442c 100644 --- a/package/src/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.tsx +++ b/package/src/components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview.tsx @@ -8,7 +8,7 @@ import { AttachmentRemoveControl } from './AttachmentRemoveControl'; import { FilePreview } from '../../../../components/Attachment/FilePreview'; import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; -import { useMessageInputContext } from '../../../../contexts/messageInputContext/MessageInputContext'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; import { primitives } from '../../../../theme'; import { UploadAttachmentPreviewProps } from '../../../../types/types'; @@ -31,7 +31,7 @@ export const FileAttachmentUploadPreview = ({ FileUploadInProgressIndicator, FileUploadRetryIndicator, FileUploadNotSupportedIndicator, - } = useMessageInputContext(); + } = useComponentsContext(); const { enableOfflineSupport } = useChatContext(); const indicatorType = getIndicatorTypeForFileState( attachment.localMetadata.uploadState, diff --git a/package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx b/package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx index b2e40dc862..ff47d481fe 100644 --- a/package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx +++ b/package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx @@ -7,7 +7,7 @@ import { LocalImageAttachment } from 'stream-chat'; import { AttachmentRemoveControl } from './AttachmentRemoveControl'; import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; -import { useMessageInputContext } from '../../../../contexts/messageInputContext/MessageInputContext'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; import { primitives } from '../../../../theme'; import { UploadAttachmentPreviewProps } from '../../../../types/types'; @@ -29,7 +29,7 @@ export const ImageAttachmentUploadPreview = ({ ImageUploadInProgressIndicator, ImageUploadRetryIndicator, ImageUploadNotSupportedIndicator, - } = useMessageInputContext(); + } = useComponentsContext(); const indicatorType = loading ? ProgressIndicatorTypes.IN_PROGRESS : getIndicatorTypeForFileState(attachment.localMetadata.uploadState, enableOfflineSupport); diff --git a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx index 1ef8c1bf76..de0e9f667c 100644 --- a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx +++ b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx @@ -3,6 +3,7 @@ import { StyleSheet, Text, View } from 'react-native'; import dayjs from 'dayjs'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { MessageInputContextValue, useMessageInputContext, @@ -15,7 +16,7 @@ import { primitives } from '../../../../theme'; type AudioRecordingInProgressPropsWithContext = Pick< MessageInputContextValue, - 'audioRecorderManager' | 'AudioRecordingWaveform' + 'audioRecorderManager' > & Pick & { /** @@ -25,7 +26,8 @@ type AudioRecordingInProgressPropsWithContext = Pick< }; const AudioRecordingInProgressWithContext = (props: AudioRecordingInProgressPropsWithContext) => { - const { AudioRecordingWaveform, maxDataPointsDrawn = 60, duration, waveformData } = props; + const { maxDataPointsDrawn = 60, duration, waveformData } = props; + const { AudioRecordingWaveform } = useComponentsContext(); const styles = useStyles(); @@ -69,7 +71,7 @@ const audioRecorderSelector = (state: AudioRecorderManagerState) => ({ * Component displayed when the audio is in the recording state. */ export const AudioRecordingInProgress = (props: AudioRecordingInProgressProps) => { - const { audioRecorderManager, AudioRecordingWaveform } = useMessageInputContext(); + const { audioRecorderManager } = useMessageInputContext(); const { duration, waveformData } = useStateStore( audioRecorderManager.state, @@ -78,7 +80,7 @@ export const AudioRecordingInProgress = (props: AudioRecordingInProgressProps) = return ( ); diff --git a/package/src/components/MessageInput/components/InputButtons/index.tsx b/package/src/components/MessageInput/components/InputButtons/index.tsx index bb792302f0..e9f3cb7739 100644 --- a/package/src/components/MessageInput/components/InputButtons/index.tsx +++ b/package/src/components/MessageInput/components/InputButtons/index.tsx @@ -10,6 +10,7 @@ import Animated, { } from 'react-native-reanimated'; import { OwnCapabilitiesContextValue } from '../../../../contexts'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { useActiveCommand } from '../../../../contexts/messageInputContext/hooks/useActiveCommand'; import { MessageInputContextValue, @@ -23,19 +24,19 @@ export type InputButtonsProps = Partial; export type InputButtonsWithContextProps = Pick< MessageInputContextValue, - 'AttachButton' | 'hasCameraPicker' | 'hasCommands' | 'hasFilePicker' | 'hasImagePicker' + 'hasCameraPicker' | 'hasCommands' | 'hasFilePicker' | 'hasImagePicker' > & Pick; export const InputButtonsWithContext = (props: InputButtonsWithContextProps) => { const { - AttachButton, hasCameraPicker, hasCommands, hasFilePicker, hasImagePicker, uploadFile: ownCapabilitiesUploadFile, } = props; + const { AttachButton } = useComponentsContext(); const { selectedPicker } = useAttachmentPickerState(); const rotation = useSharedValue(0); const command = useActiveCommand(); @@ -107,14 +108,12 @@ const MemoizedInputButtonsWithContext = React.memo( ) as typeof InputButtonsWithContext; export const InputButtons = (props: InputButtonsProps) => { - const { AttachButton, hasCameraPicker, hasCommands, hasFilePicker, hasImagePicker } = - useMessageInputContext(); + const { hasCameraPicker, hasCommands, hasFilePicker, hasImagePicker } = useMessageInputContext(); const { uploadFile } = useOwnCapabilitiesContext(); return ( { const styles = useStyles(); - const { ImageComponent } = useChatContext(); + const { ImageComponent } = useComponentsContext(); const { linkPreviewsManager } = useMessageComposer(); const { image_url, thumb_url, title, text, og_scrape_url } = linkPreview; diff --git a/package/src/components/MessageInput/components/OutputButtons/index.tsx b/package/src/components/MessageInput/components/OutputButtons/index.tsx index 283a9dc7a7..b550321082 100644 --- a/package/src/components/MessageInput/components/OutputButtons/index.tsx +++ b/package/src/components/MessageInput/components/OutputButtons/index.tsx @@ -14,6 +14,7 @@ import { useMessageComposerHasSendableData, useTheme, } from '../../../../contexts'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { useHasAttachments } from '../../../../contexts/messageInputContext/hooks/useHasAttachments'; import { useMessageComposer } from '../../../../contexts/messageInputContext/hooks/useMessageComposer'; import { @@ -36,10 +37,6 @@ export type OutputButtonsWithContextProps = Pick & | 'asyncMessagesLockDistance' | 'audioRecordingSendOnComplete' | 'audioRecordingEnabled' - | 'CooldownTimer' - | 'SendButton' - | 'StopMessageStreamingButton' - | 'StartAudioRecordingButton' > & { cooldownIsActive: boolean; hasAttachments: boolean; @@ -51,17 +48,9 @@ const textComposerStateSelector = (state: TextComposerState) => ({ }); export const OutputButtonsWithContext = (props: OutputButtonsWithContextProps) => { - const { - audioRecordingEnabled, - channel, - CooldownTimer, - cooldownIsActive, - isOnline, - SendButton, - StopMessageStreamingButton, - StartAudioRecordingButton, - hasAttachments, - } = props; + const { audioRecordingEnabled, channel, cooldownIsActive, isOnline, hasAttachments } = props; + const { CooldownTimer, SendButton, StartAudioRecordingButton, StopMessageStreamingButton } = + useComponentsContext(); const { theme: { messageComposer: { @@ -180,10 +169,6 @@ export const OutputButtons = (props: OutputButtonsProps) => { asyncMessagesSlideToCancelDistance, asyncMessagesLockDistance, audioRecordingSendOnComplete, - CooldownTimer, - SendButton, - StopMessageStreamingButton, - StartAudioRecordingButton, } = useMessageInputContext(); const cooldownIsActive = useIsCooldownActive(); const hasAttachments = useHasAttachments(); @@ -198,11 +183,7 @@ export const OutputButtons = (props: OutputButtonsProps) => { audioRecordingEnabled, channel, cooldownIsActive, - CooldownTimer, isOnline, - SendButton, - StartAudioRecordingButton, - StopMessageStreamingButton, hasAttachments, }} {...props} diff --git a/package/src/components/MessageList/MessageFlashList.tsx b/package/src/components/MessageList/MessageFlashList.tsx index 6faae7522e..f3f7318933 100644 --- a/package/src/components/MessageList/MessageFlashList.tsx +++ b/package/src/components/MessageList/MessageFlashList.tsx @@ -29,6 +29,7 @@ import { useChannelContext, } from '../../contexts/channelContext/ChannelContext'; import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { MessageInputContextValue, useMessageInputContext, @@ -121,19 +122,15 @@ type MessageFlashListPropsWithContext = Pick< | 'channel' | 'channelUnreadStateStore' | 'disabled' - | 'EmptyStateIndicator' | 'hideStickyDateHeader' | 'highlightedMessageId' | 'loadChannelAroundMessage' | 'loading' - | 'LoadingIndicator' | 'markRead' - | 'NetworkDownIndicator' | 'reloadChannel' | 'scrollToFirstUnreadThreshold' | 'setChannelUnreadState' | 'setTargetedMessage' - | 'StickyHeader' | 'targetedMessage' | 'threadList' | 'maximumMessageLimit' @@ -143,19 +140,7 @@ type MessageFlashListPropsWithContext = Pick< Pick & Pick< MessagesContextValue, - | 'DateHeader' - | 'disableTypingIndicator' - | 'FlatList' - | 'InlineDateSeparator' - | 'InlineUnreadIndicator' - | 'Message' - | 'ScrollToBottomButton' - | 'MessageSystem' - | 'myMessageTheme' - | 'shouldShowUnreadUnderlay' - | 'TypingIndicator' - | 'TypingIndicatorContainer' - | 'UnreadMessagesNotification' + 'disableTypingIndicator' | 'FlatList' | 'myMessageTheme' | 'shouldShowUnreadUnderlay' > & Pick< ThreadContextValue, @@ -277,10 +262,8 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => channelUnreadStateStore, client, closePicker, - DateHeader, disabled, disableTypingIndicator, - EmptyStateIndicator, // FlatList, FooterComponent, HeaderComponent = InlineLoadingMoreIndicator, @@ -288,7 +271,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => isLiveStreaming = false, loadChannelAroundMessage, loading, - LoadingIndicator, loadMore, loadMoreRecent, loadMoreRecentThread, @@ -299,24 +281,28 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => messageInputHeightStore, myMessageTheme, readEvents, - NetworkDownIndicator, noGroupByUser, onListScroll, onThreadSelect, reloadChannel, - ScrollToBottomButton, setChannelUnreadState, setFlatListRef, setTargetedMessage, - StickyHeader, targetedMessage, thread, threadInstance, threadList = false, + } = props; + const { + EmptyStateIndicator, + MessageListLoadingIndicator: LoadingIndicator, + NetworkDownIndicator, + ScrollToBottomButton, + StickyHeader, TypingIndicator, TypingIndicatorContainer, UnreadMessagesNotification, - } = props; + } = useComponentsContext(); const flashListRef = useRef | null>(null); const { height: messageInputHeight } = useStateStore( @@ -1091,7 +1077,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => )} {messageListLengthAfterUpdate && StickyHeader ? ( - + ) : null} { channel, channelUnreadStateStore, disabled, - EmptyStateIndicator, enableMessageGroupingByUser, error, hideStickyDateHeader, @@ -1189,34 +1174,18 @@ export const MessageFlashList = (props: MessageFlashListProps) => { isChannelActive, loadChannelAroundMessage, loading, - LoadingIndicator, markRead, maximumMessageLimit, - NetworkDownIndicator, reloadChannel, scrollToFirstUnreadThreshold, setChannelUnreadState, setTargetedMessage, - StickyHeader, targetedMessage, threadList, } = useChannelContext(); const { client } = useChatContext(); - const { - DateHeader, - disableTypingIndicator, - FlatList, - InlineDateSeparator, - InlineUnreadIndicator, - Message, - MessageSystem, - myMessageTheme, - ScrollToBottomButton, - shouldShowUnreadUnderlay, - TypingIndicator, - TypingIndicatorContainer, - UnreadMessagesNotification, - } = useMessagesContext(); + const { disableTypingIndicator, FlatList, myMessageTheme, shouldShowUnreadUnderlay } = + useMessagesContext(); const { loadMore, loadMoreRecent } = usePaginatedMessageListContext(); const { loadMoreRecentThread, loadMoreThread, thread, threadInstance } = useThreadContext(); const { readEvents } = useOwnCapabilitiesContext(); @@ -1230,48 +1199,35 @@ export const MessageFlashList = (props: MessageFlashListProps) => { channelUnreadStateStore, client, closePicker, - DateHeader, disabled, disableTypingIndicator, - EmptyStateIndicator, enableMessageGroupingByUser, error, FlatList, hideStickyDateHeader, highlightedMessageId, - InlineDateSeparator, - InlineUnreadIndicator, isListActive: isChannelActive, loadChannelAroundMessage, loading, - LoadingIndicator, loadMore, loadMoreRecent, loadMoreRecentThread, loadMoreThread, markRead, maximumMessageLimit, - Message, messageInputFloating, messageInputHeightStore, - MessageSystem, myMessageTheme, - NetworkDownIndicator, readEvents, reloadChannel, - ScrollToBottomButton, scrollToFirstUnreadThreshold, setChannelUnreadState, setTargetedMessage, shouldShowUnreadUnderlay, - StickyHeader, targetedMessage, thread, threadInstance, threadList, - TypingIndicator, - TypingIndicatorContainer, - UnreadMessagesNotification, }} {...props} noGroupByUser={!enableMessageGroupingByUser || props.noGroupByUser} diff --git a/package/src/components/MessageList/MessageList.tsx b/package/src/components/MessageList/MessageList.tsx index 9ea0b3bdba..b2696e3d97 100644 --- a/package/src/components/MessageList/MessageList.tsx +++ b/package/src/components/MessageList/MessageList.tsx @@ -38,6 +38,7 @@ import { useChannelContext, } from '../../contexts/channelContext/ChannelContext'; import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useDebugContext } from '../../contexts/debugContext/DebugContext'; import { @@ -196,18 +197,14 @@ type MessageListPropsWithContext = Pick< | 'channel' | 'channelUnreadStateStore' | 'disabled' - | 'EmptyStateIndicator' | 'hideStickyDateHeader' | 'loadChannelAroundMessage' | 'loading' - | 'LoadingIndicator' | 'markRead' - | 'NetworkDownIndicator' | 'reloadChannel' | 'scrollToFirstUnreadThreshold' | 'setChannelUnreadState' | 'setTargetedMessage' - | 'StickyHeader' | 'targetedMessage' | 'threadList' | 'maximumMessageLimit' @@ -216,17 +213,7 @@ type MessageListPropsWithContext = Pick< Pick & Pick< MessagesContextValue, - | 'DateHeader' - | 'disableTypingIndicator' - | 'FlatList' - | 'InlineDateSeparator' - | 'InlineUnreadIndicator' - | 'Message' - | 'ScrollToBottomButton' - | 'myMessageTheme' - | 'TypingIndicator' - | 'TypingIndicatorContainer' - | 'UnreadMessagesNotification' + 'disableTypingIndicator' | 'FlatList' | 'myMessageTheme' | 'shouldShowUnreadUnderlay' > & Pick & Pick< @@ -326,10 +313,8 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { channelUnreadStateStore, client, closePicker, - DateHeader, disabled, disableTypingIndicator, - EmptyStateIndicator, FlatList, FooterComponent = InlineLoadingMoreIndicator, HeaderComponent, @@ -338,7 +323,6 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { isLiveStreaming = false, loadChannelAroundMessage, loading, - LoadingIndicator, loadMore, loadMoreRecent, loadMoreRecentThread, @@ -348,27 +332,31 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { messageInputFloating, messageInputHeightStore, myMessageTheme, - NetworkDownIndicator, noGroupByUser, onListScroll, onThreadSelect, readEvents, reloadChannel, - ScrollToBottomButton, setChannelUnreadState, setFlatListRef, setTargetedMessage, - StickyHeader, targetedMessage, thread, threadInstance, threadList = false, - TypingIndicator, - TypingIndicatorContainer, - UnreadMessagesNotification, hasMore, threadHasMore, } = props; + const { + EmptyStateIndicator, + MessageListLoadingIndicator: LoadingIndicator, + NetworkDownIndicator, + ScrollToBottomButton, + StickyHeader, + TypingIndicator, + TypingIndicatorContainer, + UnreadMessagesNotification, + } = useComponentsContext(); const [isUnreadNotificationOpen, setIsUnreadNotificationOpen] = useState(false); const { theme } = useTheme(); const styles = useStyles(); @@ -1306,7 +1294,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { )} {messageListLengthAfterUpdate && StickyHeader ? ( - + ) : null} {scrollToBottomButtonVisible ? ( @@ -1350,42 +1338,25 @@ export const MessageList = (props: MessageListProps) => { channel, channelUnreadStateStore, disabled, - EmptyStateIndicator, enableMessageGroupingByUser, error, hideStickyDateHeader, highlightedMessageId, loadChannelAroundMessage, loading, - LoadingIndicator, maximumMessageLimit, markRead, - NetworkDownIndicator, reloadChannel, scrollToFirstUnreadThreshold, setChannelUnreadState, setTargetedMessage, - StickyHeader, targetedMessage, threadList, } = useChannelContext(); const { client } = useChatContext(); const { readEvents } = useOwnCapabilitiesContext(); - const { - DateHeader, - disableTypingIndicator, - FlatList, - InlineDateSeparator, - InlineUnreadIndicator, - Message, - MessageSystem, - myMessageTheme, - ScrollToBottomButton, - shouldShowUnreadUnderlay, - TypingIndicator, - TypingIndicatorContainer, - UnreadMessagesNotification, - } = useMessagesContext(); + const { disableTypingIndicator, FlatList, myMessageTheme, shouldShowUnreadUnderlay } = + useMessagesContext(); const { messageInputFloating, messageInputHeightStore } = useMessageInputContext(); const { loadMore, loadMoreRecent, hasMore } = usePaginatedMessageListContext(); const { loadMoreRecentThread, loadMoreThread, threadHasMore, thread, threadInstance } = @@ -1399,47 +1370,34 @@ export const MessageList = (props: MessageListProps) => { channelUnreadStateStore, client, closePicker, - DateHeader, disabled, disableTypingIndicator, - EmptyStateIndicator, enableMessageGroupingByUser, error, FlatList, hideStickyDateHeader, highlightedMessageId, - InlineDateSeparator, - InlineUnreadIndicator, loadChannelAroundMessage, loading, - LoadingIndicator, loadMore, loadMoreRecent, loadMoreRecentThread, loadMoreThread, markRead, maximumMessageLimit, - Message, messageInputFloating, messageInputHeightStore, - MessageSystem, myMessageTheme, - NetworkDownIndicator, readEvents, reloadChannel, - ScrollToBottomButton, scrollToFirstUnreadThreshold, setChannelUnreadState, setTargetedMessage, shouldShowUnreadUnderlay, - StickyHeader, targetedMessage, thread, threadInstance, threadList, - TypingIndicator, - TypingIndicatorContainer, - UnreadMessagesNotification, hasMore, threadHasMore, }} diff --git a/package/src/components/MessageList/StickyHeader.tsx b/package/src/components/MessageList/StickyHeader.tsx index 9ecaefac70..746f95e4a0 100644 --- a/package/src/components/MessageList/StickyHeader.tsx +++ b/package/src/components/MessageList/StickyHeader.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; -import { MessagesContextValue } from '../../contexts/messagesContext/MessagesContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; import { getDateString } from '../../utils/i18n/getDateString'; @@ -8,7 +8,7 @@ import { getDateString } from '../../utils/i18n/getDateString'; /** * Props for the StickyHeader component. */ -export type StickyHeaderProps = Pick & { +export type StickyHeaderProps = { /** * Date to be displayed in the sticky header. */ @@ -19,7 +19,8 @@ export type StickyHeaderProps = Pick & { dateString?: string | number; }; -export const StickyHeader = ({ date, DateHeader, dateString }: StickyHeaderProps) => { +export const StickyHeader = ({ date, dateString }: StickyHeaderProps) => { + const { DateHeader } = useComponentsContext(); const { t, tDateTimeParser } = useTranslationContext(); const stickyHeaderDateString = useMemo(() => { diff --git a/package/src/components/MessageMenu/MessageActionList.tsx b/package/src/components/MessageMenu/MessageActionList.tsx index 47c89fbf9a..a37bac7a3c 100644 --- a/package/src/components/MessageMenu/MessageActionList.tsx +++ b/package/src/components/MessageMenu/MessageActionList.tsx @@ -5,11 +5,11 @@ import { ScrollView } from 'react-native-gesture-handler'; import { MessageActionType } from './MessageActionListItem'; -import { MessagesContextValue } from '../../contexts/messagesContext/MessagesContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { primitives } from '../../theme'; -export type MessageActionListProps = Pick & { +export type MessageActionListProps = { /** * Function to close the message actions bottom sheet * @returns void @@ -22,7 +22,8 @@ export type MessageActionListProps = Pick { - const { MessageActionListItem, messageActions } = props; + const { messageActions } = props; + const { MessageActionListItem } = useComponentsContext(); const { theme: { messageMenu: { diff --git a/package/src/components/MessageMenu/MessageMenu.tsx b/package/src/components/MessageMenu/MessageMenu.tsx index 28d1484169..bf0564e020 100644 --- a/package/src/components/MessageMenu/MessageMenu.tsx +++ b/package/src/components/MessageMenu/MessageMenu.tsx @@ -5,58 +5,46 @@ import { useWindowDimensions } from 'react-native'; import { MessageActionType } from './MessageActionListItem'; import { MessageContextValue } from '../../contexts/messageContext/MessageContext'; -import { MessagesContextValue } from '../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { BottomSheetModal } from '../UIComponents/BottomSheetModal'; export type MessageMenuProps = PropsWithChildren< - Partial< - Pick< - MessagesContextValue, - | 'MessageActionList' - | 'MessageActionListItem' - | 'MessageReactionPicker' - | 'MessageUserReactions' - | 'MessageUserReactionsAvatar' - | 'MessageUserReactionsItem' - > - > & - Partial> & { - /** - * Function to close the message actions bottom sheet - * @returns void - */ - dismissOverlay: () => void; - /** - * An array of message actions to render - */ - messageActions: MessageActionType[]; - /** - * Boolean to determine if there are message actions - */ - showMessageReactions: boolean; - /** - * Boolean to determine if the overlay is visible. - */ - visible: boolean; - /** - * Function to handle reaction on press - * @param reactionType - * @returns - */ - handleReaction?: (reactionType: string) => Promise; - /** - * The selected reaction - */ - selectedReaction?: string; + Partial> & { + /** + * Function to close the message actions bottom sheet + * @returns void + */ + dismissOverlay: () => void; + /** + * An array of message actions to render + */ + messageActions: MessageActionType[]; + /** + * Boolean to determine if there are message actions + */ + showMessageReactions: boolean; + /** + * Boolean to determine if the overlay is visible. + */ + visible: boolean; + /** + * Function to handle reaction on press + * @param reactionType + * @returns + */ + handleReaction?: (reactionType: string) => Promise; + /** + * The selected reaction + */ + selectedReaction?: string; - layout: { - x: number; - y: number; - w: number; - h: number; - }; - } + layout: { + x: number; + y: number; + w: number; + h: number; + }; + } >; // TODO: V9: Either remove this or refactor it so that it's useful again, as its logic diff --git a/package/src/components/MessageMenu/MessageUserReactions.tsx b/package/src/components/MessageMenu/MessageUserReactions.tsx index 6163a0caa7..0a001c9b8e 100644 --- a/package/src/components/MessageMenu/MessageUserReactions.tsx +++ b/package/src/components/MessageMenu/MessageUserReactions.tsx @@ -11,6 +11,7 @@ import { useFetchReactions } from './hooks/useFetchReactions'; import { ReactionButton } from './ReactionButton'; import { useBottomSheetContext } from '../../contexts'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, @@ -39,12 +40,7 @@ const getItemLayout = (_, index: number) => ({ index, }); -export type MessageUserReactionsProps = Partial< - Pick< - MessagesContextValue, - 'MessageUserReactionsAvatar' | 'MessageUserReactionsItem' | 'supportedReactions' - > -> & +export type MessageUserReactionsProps = Partial> & Partial> & { /** * An array of reactions @@ -99,13 +95,7 @@ const reactionSelectorKeyExtractor = (item: ReactionSelectorItemType) => item.ty export const MessageUserReactions = (props: MessageUserReactionsProps) => { const styles = useStyles(); const [showMoreReactions, setShowMoreReactions] = useState(false); - const { - message, - MessageUserReactionsAvatar: propMessageUserReactionsAvatar, - MessageUserReactionsItem: propMessageUserReactionsItem, - reactions: propReactions, - supportedReactions: propSupportedReactions, - } = props; + const { message, reactions: propReactions, supportedReactions: propSupportedReactions } = props; const selectorListRef = useRef(null); const { close } = useBottomSheetContext(); const reactionTypes = useMemo( @@ -113,16 +103,10 @@ export const MessageUserReactions = (props: MessageUserReactionsProps) => { [message?.reaction_groups], ); const [selectedReaction, setSelectedReaction] = useState(undefined); - const { - MessageUserReactionsAvatar: contextMessageUserReactionsAvatar, - MessageUserReactionsItem: contextMessageUserReactionsItem, - supportedReactions: contextSupportedReactions, - } = useMessagesContext(); + const { supportedReactions: contextSupportedReactions } = useMessagesContext(); + const { MessageUserReactionsItem } = useComponentsContext(); const { handleReaction } = useMessageContext(); const supportedReactions = propSupportedReactions ?? contextSupportedReactions; - const MessageUserReactionsAvatar = - propMessageUserReactionsAvatar ?? contextMessageUserReactionsAvatar; - const MessageUserReactionsItem = propMessageUserReactionsItem ?? contextMessageUserReactionsItem; const onSelectReaction = useStableCallback((reactionType: string) => { setSelectedReaction((currentReaction) => @@ -225,13 +209,9 @@ export const MessageUserReactions = (props: MessageUserReactionsProps) => { const renderItem = useCallback( ({ item }: { item: Reaction }) => ( - + ), - [MessageUserReactionsAvatar, MessageUserReactionsItem, supportedReactions], + [MessageUserReactionsItem, supportedReactions], ); const handleSelectReaction = useStableCallback((emoji: string) => { diff --git a/package/src/components/MessageMenu/MessageUserReactionsItem.tsx b/package/src/components/MessageMenu/MessageUserReactionsItem.tsx index 5db6c9a39c..dea19586fc 100644 --- a/package/src/components/MessageMenu/MessageUserReactionsItem.tsx +++ b/package/src/components/MessageMenu/MessageUserReactionsItem.tsx @@ -4,7 +4,7 @@ import { Pressable, StyleSheet, Text, View } from 'react-native'; import { useMessageContext } from '../../contexts'; import { useChatContext } from '../../contexts/chatContext/ChatContext'; -import { MessagesContextValue } from '../../contexts/messagesContext/MessagesContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; import { useStableCallback } from '../../hooks'; @@ -14,10 +14,7 @@ import { primitives } from '../../theme'; import type { Reaction } from '../../types/types'; import { ReactionData } from '../../utils/utils'; -export type MessageUserReactionsItemProps = Pick< - MessagesContextValue, - 'MessageUserReactionsAvatar' -> & { +export type MessageUserReactionsItemProps = { /** * The reaction object */ @@ -29,10 +26,10 @@ export type MessageUserReactionsItemProps = Pick< }; export const MessageUserReactionsItem = ({ - MessageUserReactionsAvatar, reaction, supportedReactions, }: MessageUserReactionsItemProps) => { + const { MessageUserReactionsAvatar } = useComponentsContext(); const { id, name, type } = reaction; const { theme: { diff --git a/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx b/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx index c7312dccee..a7272b0344 100644 --- a/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx @@ -6,6 +6,7 @@ import { fireEvent, render } from '@testing-library/react-native'; import { LocalMessage, ReactionResponse } from 'stream-chat'; +import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext'; import { MessagesContextValue, MessagesProvider, @@ -42,18 +43,18 @@ const renderComponent = (props = {}) => render( - null, - MessageUserReactionsItem: (props: MessageUserReactionsItemProps) => ( - {props.reaction.id + ' ' + props.reaction.type} - ), - } as unknown as MessagesContextValue - } + null, + MessageUserReactionsItem: (itemProps: MessageUserReactionsItemProps) => ( + {itemProps.reaction.id + ' ' + itemProps.reaction.type} + ), + }} > - - + + + + , ); diff --git a/package/src/components/Poll/CreatePollContent.tsx b/package/src/components/Poll/CreatePollContent.tsx index bc3a81a356..77314a7350 100644 --- a/package/src/components/Poll/CreatePollContent.tsx +++ b/package/src/components/Poll/CreatePollContent.tsx @@ -16,11 +16,11 @@ import { CreatePollModalState, CreatePollContentContextValue, CreatePollContentProvider, - InputMessageInputContextValue, useCreatePollContentContext, useTheme, useTranslationContext, } from '../../contexts'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useMessageComposer } from '../../contexts/messageInputContext/hooks/useMessageComposer'; import { useStateStore } from '../../hooks/useStateStore'; import { primitives } from '../../theme'; @@ -212,14 +212,13 @@ export const CreatePollContent = () => { export const CreatePoll = ({ closePollCreationDialog, - CreatePollContent: CreatePollContentOverride, createPollOptionGap = 8, sendMessage, }: Pick< CreatePollContentContextValue, 'createPollOptionGap' | 'closePollCreationDialog' | 'sendMessage' -> & - Pick) => { +>) => { + const { CreatePollContent: CreatePollContentOverride } = useComponentsContext(); const messageComposer = useMessageComposer(); const [modalStateStore] = useState( () => new StateStore({ isClosing: false }), diff --git a/package/src/components/Poll/Poll.tsx b/package/src/components/Poll/Poll.tsx index b1ea9fc7f3..1ffa184c96 100644 --- a/package/src/components/Poll/Poll.tsx +++ b/package/src/components/Poll/Poll.tsx @@ -3,28 +3,24 @@ import { StyleSheet, Text, View } from 'react-native'; import { PollOption as PollOptionClass } from 'stream-chat'; -import { PollButtons, PollOption, ShowAllOptionsButton } from './components'; +import { PollOption, ShowAllOptionsButton } from './components'; import { usePollState } from './hooks/usePollState'; import { - MessagesContextValue, PollContextProvider, PollContextValue, useTheme, useTranslationContext, } from '../../contexts'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { primitives } from '../../theme'; import { defaultPollOptionCount } from '../../utils/constants'; -export type PollProps = Pick & - Pick; +export type PollProps = Pick; -export type PollContentProps = { - PollButtons?: React.ComponentType; - PollHeader?: React.ComponentType; -}; +export type PollContentProps = Record; export const PollHeader = () => { const styles = useStyles(); @@ -60,12 +56,11 @@ export const PollHeader = () => { ); }; -export const PollContent = ({ - PollButtons: PollButtonsOverride, - PollHeader: PollHeaderOverride, -}: PollContentProps) => { +export const PollContent = () => { const { options } = usePollState(); const styles = useStyles(); + const { PollButtons: PollButtonsComponent, PollHeader: PollHeaderComponent } = + useComponentsContext(); const { theme: { @@ -77,7 +72,7 @@ export const PollContent = ({ return ( - {PollHeaderOverride ? : } + {options ?.slice(0, defaultPollOptionCount) @@ -86,21 +81,24 @@ export const PollContent = ({ ))} - {PollButtonsOverride ? : } + ); }; -export const Poll = ({ message, poll, PollContent: PollContentOverride }: PollProps) => ( - - {PollContentOverride ? : } - -); +export const Poll = ({ message, poll }: PollProps) => { + const { PollContent: PollContentOverride } = useComponentsContext(); + return ( + + {PollContentOverride ? : } + + ); +}; const useStyles = () => { const { diff --git a/package/src/components/Poll/components/PollAnswersList.tsx b/package/src/components/Poll/components/PollAnswersList.tsx index 17f2b2a303..b522be7281 100644 --- a/package/src/components/Poll/components/PollAnswersList.tsx +++ b/package/src/components/Poll/components/PollAnswersList.tsx @@ -14,6 +14,7 @@ import { useTheme, useTranslationContext, } from '../../../contexts'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { primitives } from '../../../theme'; import { getDateString } from '../../../utils/i18n/getDateString'; import { Button } from '../../ui'; @@ -64,7 +65,6 @@ export const AnswerListAddCommentButton = (props: PollButtonProps) => { export type PollAnswersListProps = PollContextValue & { additionalFlatListProps?: Partial>; - PollAnswersListContent?: React.ComponentType; }; export const PollAnswerListItem = ({ answer }: { answer: PollAnswer }) => { @@ -157,16 +157,14 @@ export const PollAnswersList = ({ additionalFlatListProps, message, poll, - PollAnswersListContent: PollAnswersListOverride, -}: PollAnswersListProps) => ( - - {PollAnswersListOverride ? ( - - ) : ( - - )} - -); +}: PollAnswersListProps) => { + const { PollAnswersListContent: PollAnswersListContentComponent } = useComponentsContext(); + return ( + + + + ); +}; const useStyles = () => { const { diff --git a/package/src/components/Poll/components/PollOption.tsx b/package/src/components/Poll/components/PollOption.tsx index d0a45b5509..4691a07e45 100644 --- a/package/src/components/Poll/components/PollOption.tsx +++ b/package/src/components/Poll/components/PollOption.tsx @@ -16,6 +16,7 @@ import { useTheme, useTranslationContext, } from '../../../contexts'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { Check } from '../../../icons'; import { primitives } from '../../../theme'; @@ -32,7 +33,6 @@ export type PollOptionProps = { export type PollAllOptionsContentProps = PollContextValue & { additionalScrollViewProps?: Partial; - PollAllOptionsContent?: React.ComponentType; }; export const PollAllOptionsContent = ({ @@ -75,16 +75,14 @@ export const PollAllOptions = ({ additionalScrollViewProps, message, poll, - PollAllOptionsContent: PollAllOptionsContentOverride, -}: PollAllOptionsContentProps) => ( - - {PollAllOptionsContentOverride ? ( - - ) : ( - - )} - -); +}: PollAllOptionsContentProps) => { + const { PollAllOptionsContent: PollAllOptionsContentComponent } = useComponentsContext(); + return ( + + + + ); +}; export const PollOption = ({ option, showProgressBar = true, forceIncoming }: PollOptionProps) => { const { latestVotesByOption, voteCountsByOption, voteCount } = usePollState(); diff --git a/package/src/components/Poll/components/PollResults/PollOptionFullResults.tsx b/package/src/components/Poll/components/PollResults/PollOptionFullResults.tsx index 7a38acf87d..9a1d12bf63 100644 --- a/package/src/components/Poll/components/PollResults/PollOptionFullResults.tsx +++ b/package/src/components/Poll/components/PollResults/PollOptionFullResults.tsx @@ -11,6 +11,7 @@ import { useTheme, useTranslationContext, } from '../../../../contexts'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { primitives } from '../../../../theme'; import { usePollOptionVotesPagination } from '../../hooks/usePollOptionVotesPagination'; @@ -19,7 +20,6 @@ import { usePollState } from '../../hooks/usePollState'; export type PollOptionFullResultsProps = PollContextValue & { option: PollOption; additionalFlatListProps?: Partial>; - PollOptionFullResultsContent?: React.ComponentType<{ option: PollOption }>; }; export const renderPollOptionFullResultsItem = ({ item }: { item: PollVoteClass }) => ( @@ -86,19 +86,18 @@ export const PollOptionFullResults = ({ message, option, poll, - PollOptionFullResultsContent: PollOptionFullResultsContentOverride, -}: PollOptionFullResultsProps) => ( - - {PollOptionFullResultsContentOverride ? ( - - ) : ( - { + const { PollOptionFullResultsContent: PollOptionFullResultsContentComponent } = + useComponentsContext(); + return ( + + - )} - -); + + ); +}; const useStyles = () => { const { diff --git a/package/src/components/Poll/components/PollResults/PollResults.tsx b/package/src/components/Poll/components/PollResults/PollResults.tsx index 081dc4f5ea..019d67e945 100644 --- a/package/src/components/Poll/components/PollResults/PollResults.tsx +++ b/package/src/components/Poll/components/PollResults/PollResults.tsx @@ -11,12 +11,12 @@ import { useTheme, useTranslationContext, } from '../../../../contexts'; +import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext'; import { primitives } from '../../../../theme'; import { usePollState } from '../../hooks/usePollState'; export type PollResultsProps = PollContextValue & { additionalScrollViewProps?: Partial; - PollResultsContent?: React.ComponentType; }; export const PollResultsContent = ({ @@ -62,20 +62,14 @@ export const PollResultsContent = ({ ); }; -export const PollResults = ({ - additionalScrollViewProps, - message, - poll, - PollResultsContent: PollResultsContentOverride, -}: PollResultsProps) => ( - - {PollResultsContentOverride ? ( - - ) : ( - - )} - -); +export const PollResults = ({ additionalScrollViewProps, message, poll }: PollResultsProps) => { + const { PollResultsContent: PollResultsContentComponent } = useComponentsContext(); + return ( + + + + ); +}; const useStyles = () => { const { diff --git a/package/src/components/Reply/Reply.tsx b/package/src/components/Reply/Reply.tsx index 687400e309..8a1ed92d26 100644 --- a/package/src/components/Reply/Reply.tsx +++ b/package/src/components/Reply/Reply.tsx @@ -1,5 +1,14 @@ import React, { useMemo } from 'react'; -import { I18nManager, Image, StyleSheet, Text, TextStyle, View, ViewStyle } from 'react-native'; +import { + I18nManager, + Image, + ImageProps, + StyleSheet, + Text, + TextStyle, + View, + ViewStyle, +} from 'react-native'; import { isFileAttachment, @@ -10,7 +19,8 @@ import { import { ReplyMessageView } from './ReplyMessageView'; -import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; +import { useChatContext } from '../../contexts/chatContext/ChatContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, @@ -79,8 +89,10 @@ const RightContent = React.memo( }, ); -export type ReplyPropsWithContext = Pick & - Pick & +export type ReplyPropsWithContext = { ImageComponent: React.ComponentType } & Pick< + MessageContextValue, + 'message' +> & Pick & { isMyMessage: boolean; onDismiss?: () => void; @@ -216,7 +228,8 @@ export type ReplyProps = Partial & export const Reply = (props: ReplyProps) => { const { message: messageFromContext } = useMessageContext(); - const { client, ImageComponent } = useChatContext(); + const { client } = useChatContext(); + const { ImageComponent } = useComponentsContext(); const messageComposer = useMessageComposer(); const { quotedMessage: quotedMessageFromComposer } = useStateStore( diff --git a/package/src/components/Thread/Thread.tsx b/package/src/components/Thread/Thread.tsx index 9643b44a74..147fc03a26 100644 --- a/package/src/components/Thread/Thread.tsx +++ b/package/src/components/Thread/Thread.tsx @@ -4,16 +4,10 @@ import { ThreadFooterComponent } from './components/ThreadFooterComponent'; import { useChannelContext } from '../../contexts/channelContext/ChannelContext'; import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; -import { - MessagesContextValue, - useMessagesContext, -} from '../../contexts/messagesContext/MessagesContext'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { ThreadContextValue, useThreadContext } from '../../contexts/threadContext/ThreadContext'; -import { - MessageComposer as DefaultMessageComposer, - MessageComposerProps, -} from '../MessageInput/MessageComposer'; +import type { MessageComposerProps } from '../MessageInput/MessageComposer'; import { MessageFlashList, MessageFlashListProps } from '../MessageList/MessageFlashList'; import { MessageListProps } from '../MessageList/MessageList'; @@ -26,7 +20,6 @@ try { } type ThreadPropsWithContext = Pick & - Pick & Pick< ThreadContextValue, | 'closeThread' @@ -59,11 +52,6 @@ type ThreadPropsWithContext = Pick & closeThreadOnDismount?: boolean; /** Disables the thread UI. So MessageComposer and MessageList will be disabled. */ disabled?: boolean; - /** - * **Customized MessageComposer component to used within Thread instead of default MessageComposer - * **Available from [MessageComposer](https://getstream.io/chat/docs/sdk/reactnative/ui-components/message-input)** - */ - MessageComposer?: React.ComponentType; /** * Call custom function on closing thread if handling thread state elsewhere */ @@ -81,14 +69,13 @@ const ThreadWithContext = (props: ThreadPropsWithContext) => { closeThreadOnDismount = true, disabled, loadMoreThread, - MessageComposer = DefaultMessageComposer, - MessageList, onThreadDismount, parentMessagePreventPress = true, thread, threadInstance, shouldUseFlashList = false, } = props; + const { MessageList, ThreadMessageComposer: MessageComposer } = useComponentsContext(); useEffect(() => { if (threadInstance?.activate) { @@ -171,7 +158,6 @@ export type ThreadProps = Partial; export const Thread = (props: ThreadProps) => { const { client } = useChatContext(); const { threadList } = useChannelContext(); - const { MessageList } = useMessagesContext(); const { closeThread, loadMoreThread, reloadThread, thread, threadInstance } = useThreadContext(); if (thread?.id && !threadList) { @@ -186,7 +172,6 @@ export const Thread = (props: ThreadProps) => { client, closeThread, loadMoreThread, - MessageList, reloadThread, thread, threadInstance, diff --git a/package/src/components/Thread/components/ThreadFooterComponent.tsx b/package/src/components/Thread/components/ThreadFooterComponent.tsx index 44391da3ec..bc9ac420dd 100644 --- a/package/src/components/Thread/components/ThreadFooterComponent.tsx +++ b/package/src/components/Thread/components/ThreadFooterComponent.tsx @@ -3,10 +3,7 @@ import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'; import type { ThreadState } from 'stream-chat'; -import { - MessagesContextValue, - useMessagesContext, -} from '../../../contexts/messagesContext/MessagesContext'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { ThreadContextValue, @@ -16,8 +13,10 @@ import { useTranslationContext } from '../../../contexts/translationContext/Tran import { useStateStore } from '../../../hooks'; import { primitives } from '../../../theme'; -type ThreadFooterComponentPropsWithContext = Pick & - Pick; +type ThreadFooterComponentPropsWithContext = Pick< + ThreadContextValue, + 'parentMessagePreventPress' | 'thread' | 'threadInstance' +>; export const InlineLoadingMoreThreadIndicator = () => { const { threadLoadingMore } = useThreadContext(); @@ -43,7 +42,8 @@ const selector = (nextValue: ThreadState) => }) as const; const ThreadFooterComponentWithContext = (props: ThreadFooterComponentPropsWithContext) => { - const { Message, parentMessagePreventPress, thread, threadInstance } = props; + const { parentMessagePreventPress, thread, threadInstance } = props; + const { Message } = useComponentsContext(); const { t } = useTranslationContext(); const styles = useStyles(); @@ -131,18 +131,17 @@ const MemoizedThreadFooter = React.memo( areEqual, ) as typeof ThreadFooterComponentWithContext; -export type ThreadFooterComponentProps = Partial> & - Partial>; +export type ThreadFooterComponentProps = Partial< + Pick +>; export const ThreadFooterComponent = (props: ThreadFooterComponentProps) => { - const { Message } = useMessagesContext(); const { parentMessagePreventPress, thread, threadInstance, threadLoadingMore } = useThreadContext(); return ( export type ThreadListProps = Pick< ThreadsContextValue, - | 'additionalFlatListProps' - | 'isFocused' - | 'onThreadSelect' - | 'ThreadListItem' - | 'ThreadListEmptyPlaceholder' - | 'ThreadListLoadingIndicator' - | 'ThreadListUnreadBanner' -> & { ThreadList?: React.ComponentType }; + 'additionalFlatListProps' | 'isFocused' | 'onThreadSelect' +>; export const DefaultThreadListEmptyPlaceholder = () => ; @@ -49,18 +43,15 @@ export const DefaultThreadListLoadingNextIndicator = () => ; -const ThreadListComponent = () => { +export const DefaultThreadListComponent = () => { + const { additionalFlatListProps, isLoading, isLoadingNext, loadMore, threads } = + useThreadsContext(); const { - additionalFlatListProps, - isLoading, - isLoadingNext, - loadMore, - ThreadListEmptyPlaceholder = DefaultThreadListEmptyPlaceholder, - ThreadListLoadingIndicator = DefaultThreadListLoadingIndicator, - ThreadListLoadingMoreIndicator = DefaultThreadListLoadingNextIndicator, - ThreadListUnreadBanner = DefaultThreadListBanner, - threads, - } = useThreadsContext(); + ThreadListEmptyPlaceholder, + ThreadListLoadingIndicator, + ThreadListLoadingMoreIndicator, + ThreadListUnreadBanner, + } = useComponentsContext(); if (isLoading) { return ; @@ -85,7 +76,8 @@ const ThreadListComponent = () => { }; export const ThreadList = (props: ThreadListProps) => { - const { isFocused = true, ThreadList = ThreadListComponent } = props; + const { isFocused = true } = props; + const { ThreadListComponent: ThreadListContent } = useComponentsContext(); const { client } = useChatContext(); useEffect(() => { @@ -120,7 +112,7 @@ export const ThreadList = (props: ThreadListProps) => { - + ); }; diff --git a/package/src/components/ThreadList/ThreadListItem.tsx b/package/src/components/ThreadList/ThreadListItem.tsx index 5a35a1248b..20e6ff5e60 100644 --- a/package/src/components/ThreadList/ThreadListItem.tsx +++ b/package/src/components/ThreadList/ThreadListItem.tsx @@ -10,11 +10,8 @@ import { ThreadState, } from 'stream-chat'; -import { ThreadListItemMessagePreview as ThreadListItemMessagePreviewDefault } from './ThreadListItemMessagePreview'; - -import { ThreadMessagePreviewDeliveryStatus as ThreadMessagePreviewDeliveryStatusDefault } from './ThreadMessagePreviewDeliveryStatus'; - import { useChatContext, useTheme, useTranslationContext } from '../../contexts'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { ThreadListItemProvider, useThreadListItemContext, @@ -62,11 +59,9 @@ export const ThreadListItemComponent = () => { } = useThreadListItemContext(); const online = useChannelPreviewDisplayPresence(channel); const displayName = useChannelPreviewDisplayName(channel); - const { - onThreadSelect, - ThreadListItemMessagePreview = ThreadListItemMessagePreviewDefault, - ThreadMessagePreviewDeliveryStatus = ThreadMessagePreviewDeliveryStatusDefault, - } = useThreadsContext(); + const { onThreadSelect } = useThreadsContext(); + const { ThreadListItemMessagePreview, ThreadMessagePreviewDeliveryStatus } = + useComponentsContext(); const { theme: { semantics }, } = useTheme(); @@ -143,7 +138,7 @@ export const ThreadListItem = (props: ThreadListItemProps) => { const { client } = useChatContext(); const { t, tDateTimeParser } = useTranslationContext(); const { thread, timestampTranslationKey = 'timestamp/ThreadListItem' } = props; - const { ThreadListItem = ThreadListItemComponent } = useThreadsContext(); + const { ThreadListItem: ThreadListItemOverride } = useComponentsContext(); const { text: draftText } = useStateStore( thread.messageComposer.textComposer.state, textComposerStateSelector, @@ -229,7 +224,7 @@ export const ThreadListItem = (props: ThreadListItemProps) => { thread, }} > - + ); }; diff --git a/package/src/components/ui/Avatar/Avatar.tsx b/package/src/components/ui/Avatar/Avatar.tsx index aaff3a3114..fb02eace24 100644 --- a/package/src/components/ui/Avatar/Avatar.tsx +++ b/package/src/components/ui/Avatar/Avatar.tsx @@ -3,7 +3,7 @@ import { ColorValue, StyleProp, StyleSheet, View, ViewStyle } from 'react-native import { avatarSizes } from './constants'; -import { useChatContext } from '../../../contexts'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { primitives } from '../../../theme'; @@ -21,7 +21,7 @@ export const Avatar = (props: AvatarProps) => { const { theme: { semantics }, } = useTheme(); - const { ImageComponent } = useChatContext(); + const { ImageComponent } = useComponentsContext(); const defaultAvatarBg = semantics.avatarPaletteBg1; const { backgroundColor = defaultAvatarBg, diff --git a/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx b/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx index d337f5d221..063d1f6f05 100644 --- a/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx +++ b/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx @@ -2,7 +2,6 @@ import React, { PropsWithChildren, useContext, useMemo, useState } from 'react'; import BottomSheet from '@gorhom/bottom-sheet'; -import { AttachmentPickerContentProps } from '../../components'; import { AttachmentPickerStore, SelectedPickerType, @@ -21,10 +20,6 @@ export type AttachmentPickerContextValue = Pick< MessageInputContextValue, 'attachmentSelectionBarHeight' | 'attachmentPickerBottomSheetHeight' > & { - /** - * Custom UI Component to render select more photos for selected gallery access in iOS. - */ - AttachmentPickerIOSSelectMorePhotos: React.ComponentType; /** * `bottomInset` determine the height of the `AttachmentPicker` and the underlying shift to the `MessageList` when it is opened. * This can also be set via the `setBottomInset` function provided by the `useAttachmentPickerContext` hook. @@ -39,16 +34,6 @@ export type AttachmentPickerContextValue = Pick< topInset: number; disableAttachmentPicker?: boolean; - /** - * Custom UI component to render overlay component, that shows up on top of [selected - * image](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) (with tick mark) - * - * **Default** - * [ImageOverlaySelectedComponent](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AttachmentPicker/components/ImageOverlaySelectedComponent.tsx) - */ - ImageOverlaySelectedComponent: React.ComponentType<{ index: number }>; - AttachmentPickerSelectionBar: React.ComponentType; - AttachmentPickerContent: React.ComponentType; attachmentPickerStore: AttachmentPickerStore; numberOfAttachmentPickerImageColumns?: number; numberOfAttachmentImagesToLoadPerCall?: number; diff --git a/package/src/contexts/channelContext/ChannelContext.tsx b/package/src/contexts/channelContext/ChannelContext.tsx index 36e3c63ed1..6167626f5d 100644 --- a/package/src/contexts/channelContext/ChannelContext.tsx +++ b/package/src/contexts/channelContext/ChannelContext.tsx @@ -3,9 +3,6 @@ import React, { PropsWithChildren, useContext } from 'react'; import type { Channel, ChannelState } from 'stream-chat'; import { MarkReadFunctionOptions } from '../../components/Channel/Channel'; -import type { EmptyStateProps } from '../../components/Indicators/EmptyStateIndicator'; -import type { LoadingProps } from '../../components/Indicators/LoadingIndicator'; -import { StickyHeaderProps } from '../../components/MessageList/StickyHeader'; import { ChannelUnreadStateStore, ChannelUnreadStateStoreType, @@ -38,12 +35,6 @@ export type ChannelContextValue = { * @overrideType Channel */ channel: Channel; - /** - * Custom UI component to display empty state when channel has no messages. - * - * **Default** [EmptyStateIndicator](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Indicators/EmptyStateIndicator.tsx) - */ - EmptyStateIndicator: React.ComponentType; /** * When set to true, reactions will be limited to 1 per user. If user selects another reaction * then his previous reaction will be removed and replaced with new one. @@ -90,10 +81,6 @@ export type ChannelContextValue = { setTargetedMessage?: (messageId: string) => void; }) => Promise; - /** - * Custom loading indicator to override the Stream default - */ - LoadingIndicator: React.ComponentType; markRead: (options?: MarkReadFunctionOptions) => void; /** * @@ -119,10 +106,6 @@ export type ChannelContextValue = { * ``` */ members: ChannelState['members']; - /** - * Custom network down indicator to override the Stream default - */ - NetworkDownIndicator: React.ComponentType; read: ChannelState['read']; reloadChannel: () => Promise; scrollToFirstUnreadThreshold: number; @@ -156,13 +139,6 @@ export type ChannelContextValue = { * currently near them within the viewport. */ maximumMessageLimit?: number; - /** - * Custom UI component for sticky header of channel. - * - * **Default** [DateHeader](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageList/DateHeader.tsx) - */ - StickyHeader?: React.ComponentType; - /** * Id of message, around which Channel/MessageList gets loaded when opened. * You will see a highlighted background for targetted message, when opened. diff --git a/package/src/contexts/channelsContext/ChannelsContext.tsx b/package/src/contexts/channelsContext/ChannelsContext.tsx index 98b834222b..b375a7997c 100644 --- a/package/src/contexts/channelsContext/ChannelsContext.tsx +++ b/package/src/contexts/channelsContext/ChannelsContext.tsx @@ -5,23 +5,8 @@ import type { FlatList } from 'react-native-gesture-handler'; import type { Channel } from 'stream-chat'; -import type { HeaderErrorProps } from '../../components/ChannelList/ChannelListHeaderErrorIndicator'; import type { GetChannelActionItems } from '../../components/ChannelList/hooks/useChannelActionItems'; import type { QueryChannels } from '../../components/ChannelList/hooks/usePaginatedChannels'; -import type { ChannelDetailsBottomSheetProps } from '../../components/ChannelPreview/ChannelDetailsBottomSheet'; -import { ChannelLastMessagePreviewProps } from '../../components/ChannelPreview/ChannelLastMessagePreview'; -import { ChannelMessagePreviewDeliveryStatusProps } from '../../components/ChannelPreview/ChannelMessagePreviewDeliveryStatus'; -import { ChannelPreviewMessageProps } from '../../components/ChannelPreview/ChannelPreviewMessage'; -import type { ChannelPreviewStatusProps } from '../../components/ChannelPreview/ChannelPreviewStatus'; -import type { ChannelPreviewTitleProps } from '../../components/ChannelPreview/ChannelPreviewTitle'; -import { ChannelPreviewTypingIndicatorProps } from '../../components/ChannelPreview/ChannelPreviewTypingIndicator'; -import type { ChannelPreviewUnreadCountProps } from '../../components/ChannelPreview/ChannelPreviewUnreadCount'; -import type { ChannelPreviewViewProps } from '../../components/ChannelPreview/ChannelPreviewView'; -import type { EmptyStateProps } from '../../components/Indicators/EmptyStateIndicator'; -import type { LoadingErrorProps } from '../../components/Indicators/LoadingErrorIndicator'; -import type { LoadingProps } from '../../components/Indicators/LoadingIndicator'; - -import { ChannelAvatarProps } from '../../components/ui/Avatar/ChannelAvatar'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; @@ -53,18 +38,6 @@ export type ChannelsContextValue = { * Channels can be either an array of channels or a promise which resolves to an array of channels */ channels: Channel[] | null; - /** - * Custom indicator to use when channel list is empty - * - * Default: [EmptyStateIndicator](https://getstream.io/chat/docs/sdk/reactnative/core-components/channel/#emptystateindicator) - * */ - EmptyStateIndicator: React.ComponentType; - /** - * Custom loading indicator to display at bottom of the list, while loading further pages - * - * Default: [ChannelListFooterLoadingIndicator](https://getstream.io/chat/docs/sdk/reactnative/contexts/channels-context/#footerloadingindicator) - */ - FooterLoadingIndicator: React.ComponentType; /** * Incremental number change to force update the FlatList */ @@ -73,33 +46,10 @@ export type ChannelsContextValue = { * Whether or not the FlatList has another page to render */ hasNextPage: boolean; - /** - * Custom indicator to display error at top of list, if loading/pagination error occurs - * - * Default: [ChannelListHeaderErrorIndicator](https://getstream.io/chat/docs/sdk/reactnative/contexts/channels-context/#headererrorindicator) - */ - HeaderErrorIndicator: React.ComponentType; - /** - * Custom indicator to display network-down error at top of list, if there is connectivity issue - * - * Default: [ChannelListHeaderNetworkDownIndicator](https://getstream.io/chat/docs/sdk/reactnative/contexts/channels-context/#headernetworkdownindicator) - */ - HeaderNetworkDownIndicator: React.ComponentType; /** * Initial channels query loading state, triggers the LoadingIndicator */ loadingChannels: boolean; - /** - * Custom indicator to use when there is error in fetching channels - * - * Default: [LoadingErrorIndicator](https://getstream.io/chat/docs/sdk/reactnative/contexts/channels-context/#loadingerrorindicator) - * */ - LoadingErrorIndicator: React.ComponentType; - /** - * Custom loading indicator to use on Channel List - * - * */ - LoadingIndicator: React.ComponentType>; /** * Whether or not additional channels are being loaded, triggers the FooterLoadingIndicator */ @@ -121,12 +71,6 @@ export type ChannelsContextValue = { * Number of skeletons that should show when loading. Default: 6 */ numberOfSkeletons: number; - /** - * Custom UI component to display individual channel list items - * - * Default: [ChannelPreviewView](https://getstream.io/chat/docs/sdk/reactnative/ui-components/channel-preview-view/) - */ - Preview: React.ComponentType; /** * Triggered when the channel list is refreshing, displays a loading spinner at the top of the list */ @@ -159,73 +103,16 @@ export type ChannelsContextValue = { * ``` */ setFlatListRef: (ref: FlatList | null) => void; - /** - * Custom UI component to display loading channel skeletons - * - * Default: [Skeleton](https://getstream.io/chat/docs/sdk/reactnative/contexts/channels-context/#skeleton) - */ - Skeleton: React.ComponentType; /** * Error in channels query, if any */ error?: Error; - ListHeaderComponent?: React.ComponentType; /** * Function to set the currently active channel, acts as a bridge between ChannelList and Channel components * * @param channel A channel object */ onSelect?: (channel: Channel) => void; - /** - * Custom UI component to render preview avatar. - * - * **Default** [ChannelAvatar](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelAvatar.tsx) - */ - PreviewAvatar?: React.ComponentType; - /** - * Custom UI component to render preview of latest message on channel. - * - * **Default** [ChannelPreviewMessage](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx) - */ - PreviewMessage?: React.ComponentType; - /** - * Custom UI component to render delivery status of latest message on channel. - * - * **Default** [ChannelMessagePreviewDeliveryStatus](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx) - */ - PreviewMessageDeliveryStatus?: React.ComponentType; - /** - * Custom UI component to render muted status. - * - * **Default** [ChannelMutedStatus](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelPreviewMutedStatus.tsx) - */ - PreviewMutedStatus?: React.ComponentType; - /** - * Custom UI component to render preview avatar. - * - * **Default** [ChannelPreviewStatus](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelPreviewStatus.tsx) - */ - PreviewStatus?: React.ComponentType; - /** - * Custom UI component to render preview avatar. - * - * **Default** [ChannelPreviewTitle](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelPreviewTitle.tsx) - */ - PreviewTitle?: React.ComponentType; - /** - * Custom UI component to render preview avatar. - * - * **Default** [ChannelPreviewUnreadCount](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx) - */ - PreviewUnreadCount?: React.ComponentType; - PreviewTypingIndicator?: React.ComponentType; - ChannelDetailsBottomSheet?: React.ComponentType; - /** - * Custom UI component to render preview of last message on channel. - * - * **Default** [ChannelLastMessagePreview](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelLastMessagePreview.tsx) - */ - PreviewLastMessage?: React.ComponentType; getChannelActionItems?: GetChannelActionItems; swipeActionsEnabled?: boolean; diff --git a/package/src/contexts/chatContext/ChatContext.tsx b/package/src/contexts/chatContext/ChatContext.tsx index 9c26b0cc83..c107c3d8ce 100644 --- a/package/src/contexts/chatContext/ChatContext.tsx +++ b/package/src/contexts/chatContext/ChatContext.tsx @@ -1,5 +1,4 @@ import React, { PropsWithChildren, useContext } from 'react'; -import type { ImageProps } from 'react-native'; import type { AppSettingsAPIResponse, Channel, Mute, StreamChat } from 'stream-chat'; @@ -32,10 +31,6 @@ export type ChatContextValue = { client: StreamChat; connectionRecovering: boolean; enableOfflineSupport: boolean; - /** - * Drop in replacement of all the underlying Image components within SDK. This is useful for the purpose of offline caching of images. Please check the Offline Support Guide for usage. - */ - ImageComponent: React.ComponentType; isOnline: boolean | null; mutedUsers: Mute[]; /** diff --git a/package/src/contexts/componentsContext/ComponentsContext.tsx b/package/src/contexts/componentsContext/ComponentsContext.tsx new file mode 100644 index 0000000000..4c57c082e3 --- /dev/null +++ b/package/src/contexts/componentsContext/ComponentsContext.tsx @@ -0,0 +1,62 @@ +import React, { PropsWithChildren, useContext, useMemo } from 'react'; + +/** + * All overridable UI components in the SDK. + * Derived from the DEFAULT_COMPONENTS map in defaultComponents.ts. + * Adding a new default automatically makes it available as an override. + * + * Every key is optional — only specify the components you want to override. + */ +export type ComponentOverrides = Partial< + (typeof import('./defaultComponents'))['DEFAULT_COMPONENTS'] +>; + +const ComponentsContext = React.createContext({}); + +/** + * Provider to override UI components at any level of the tree. + * Supports nesting — inner overrides merge over outer ones (closest wins). + * + * @example + * ```tsx + * + * + * + * + * + * + * ``` + */ +export const WithComponents = ({ + children, + overrides, +}: PropsWithChildren<{ overrides: ComponentOverrides }>) => { + const parent = useContext(ComponentsContext); + // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally stable: overrides are set once at mount + const merged = useMemo(() => ({ ...parent, ...overrides }), []); + return {children}; +}; + +// Lazy-loaded to break circular dependency: +// defaultComponents.ts → imports components → components import useComponentsContext from this file +let cachedDefaults: ComponentOverrides | undefined; +const getDefaults = (): ComponentOverrides => { + if (!cachedDefaults) { + cachedDefaults = (require('./defaultComponents') as { DEFAULT_COMPONENTS: ComponentOverrides }) + .DEFAULT_COMPONENTS; + } + return cachedDefaults; +}; + +/** + * Hook to access resolved component overrides. + * Returns all components with defaults filled in — user overrides merged over defaults. + */ +export const useComponentsContext = () => { + const overrides = useContext(ComponentsContext); + return useMemo( + () => ({ ...getDefaults(), ...overrides }) as Required, + // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally stable: overrides are set once at mount + [], + ); +}; diff --git a/package/src/contexts/componentsContext/PLAN.md b/package/src/contexts/componentsContext/PLAN.md new file mode 100644 index 0000000000..d19280d2b5 --- /dev/null +++ b/package/src/contexts/componentsContext/PLAN.md @@ -0,0 +1,148 @@ +# WithComponents — Component Override System + +## Design Principle + +**All components are read from `useComponentsContext()`. All other contexts only provide data + APIs — never components.** + +## Current State (Completed) + +### What was done + +1. **Created `ComponentsContext`** — `WithComponents` provider, `useComponentsContext()` hook, `ComponentOverrides` type +2. **Created `defaultComponents.ts`** — centralized map of all ~130 default components +3. **Stripped component keys** from all existing context types: `MessagesContextValue`, `InputMessageInputContextValue`, `ChannelContextValue`, `ChannelsContextValue`, `AttachmentPickerContextValue`, `ThreadsContextValue`, `ImageGalleryContextValue` +4. **Simplified `useCreate*Context` hooks** — no longer receive or forward component params +5. **Simplified `Channel.tsx`** — removed ~90 component imports, prop defaults, forwarding lines +6. **Simplified `ChannelList.tsx`** — removed ~19 component props +7. **Updated ~80 consumer files** — switched from old context hooks to `useComponentsContext()` +8. **Removed component override props** from ALL individual components +9. **Updated all 3 example apps** (SampleApp, ExpoMessaging, TypeScriptMessaging) +10. **Updated ~45 documentation pages** across docs-content repo +11. **Merged with develop** and resolved conflicts + +### Architecture + +``` +User: + ↓ +ComponentsContext (merges parent + overrides, inner wins) + ↓ +useComponentsContext() → { ...DEFAULT_COMPONENTS, ...overrides } + ↓ +Consumer: const { Message } = useComponentsContext() +``` + +### Key Files + +| File | Purpose | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `ComponentsContext.tsx` | ~60 lines. `ComponentOverrides` type (derived from `typeof DEFAULT_COMPONENTS`), `WithComponents` provider, `useComponentsContext()` hook | +| `defaultComponents.ts` | ~300 lines. Single source of truth for all default component mappings. Adding a new component here auto-extends `ComponentOverrides` | + +### Type System + +`ComponentOverrides` is derived automatically: + +```ts +export type ComponentOverrides = Partial<(typeof import('./defaultComponents'))['DEFAULT_COMPONENTS']>; +``` + +No manual type maintenance — add a component to `DEFAULT_COMPONENTS` and the type updates. + +### Circular Dependency Handling + +`defaultComponents.ts` → imports components → components import `useComponentsContext` from `ComponentsContext.tsx`. + +Broken by lazy-loading defaults in the hook: + +```ts +let cachedDefaults: ComponentOverrides | undefined; +const getDefaults = () => { + if (!cachedDefaults) { + cachedDefaults = require('./defaultComponents').DEFAULT_COMPONENTS; + } + return cachedDefaults; +}; +``` + +### Naming Conventions + +Some component keys differ from their default component names to avoid collisions: + +| Override Key | Default Component | Why renamed | +| ----------------------------- | --------------------------------------- | ---------------------------------------------------------- | +| `FileAttachmentIcon` | `FileIcon` | Clarity | +| `ChannelListLoadingIndicator` | `ChannelListLoadingIndicator` | Split from shared `LoadingIndicator` — renders skeleton UI | +| `MessageListLoadingIndicator` | `LoadingIndicator` | Split from shared `LoadingIndicator` — renders text | +| `ChatLoadingIndicator` | `undefined` | Optional, no default | +| `ThreadMessageComposer` | `MessageComposer` | Avoid collision with `MessageComposer` component name | +| `ThreadListComponent` | `DefaultThreadListComponent` | Avoid collision with exported `ThreadList` | +| `StartAudioRecordingButton` | `AudioRecordingButton` | Historical naming | +| `Preview` | `ChannelPreviewView` | ChannelList preview item | +| `PreviewAvatar` | `ChannelAvatar` | ChannelList preview avatar | +| `FooterLoadingIndicator` | `ChannelListFooterLoadingIndicator` | ChannelList footer | +| `HeaderErrorIndicator` | `ChannelListHeaderErrorIndicator` | ChannelList header | +| `HeaderNetworkDownIndicator` | `ChannelListHeaderNetworkDownIndicator` | ChannelList header | + +### Optional Components (no default) + +These exist in `DEFAULT_COMPONENTS` as `undefined` with `React.ComponentType | undefined` type assertions: + +`AttachmentPickerIOSSelectMorePhotos`, `ChatLoadingIndicator`, `CreatePollContent`, `ImageComponent`, `Input`, `ListHeaderComponent`, `MessageContentBottomView`, `MessageContentLeadingView`, `MessageContentTopView`, `MessageContentTrailingView`, `MessageLocation`, `MessageSpacer`, `MessageText`, `PollContent` + +### Shared Component Keys (audited) + +Some keys were used in multiple contexts before the refactor. Audit results: + +| Key | Used By | Same Default? | Resolution | +| ----------------------- | --------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------- | +| `EmptyStateIndicator` | Channel + ChannelList | Yes (differentiates via `listType` prop) | Single key ✅ | +| `LoadingErrorIndicator` | Channel + ChannelList | Yes (differentiates via `listType` prop) | Single key ✅ | +| `LoadingIndicator` | Channel + ChannelList | **No** — Channel used text-based, ChannelList used skeleton | Split into `MessageListLoadingIndicator` + `ChannelListLoadingIndicator` ✅ | + +### API Alignment with stream-chat-react + +| Aspect | React Native | React Web | +| --------- | ----------------------------------- | -------------------------------------- | +| Provider | `WithComponents` | `WithComponents` | +| Prop name | `overrides` | `overrides` | +| Hook | `useComponentsContext()` | `useComponentContext()` | +| Type | `ComponentOverrides` (auto-derived) | `ComponentContextValue` (hand-written) | +| Defaults | Lazy-loaded via `require()` | Set at `Channel` level | +| Merge | `useMemo` | Plain spread (no memo) | + +## Known Issues / Future Work + +### Pre-existing Test Failures (not caused by this work) + +These test suites fail on `develop` too: + +- `offline-support/index.test.ts` — timeout +- `ChannelList.test.js` — filter race condition (`channel.countUnread` mock missing) +- `isAttachmentEqualHandler.test.js`, `MessageContent.test.js`, `MessageTextContainer.test.tsx`, `MessageUserReactions.test.tsx`, `ChannelPreview.test.tsx` — various pre-existing issues + +### Linter Interaction + +`@typescript-eslint/no-unused-vars` (warn, max-warnings 0) aggressively strips unused type keys. When adding new keys to `ComponentOverrides`, the type and its consumer must land in the same edit — otherwise the linter removes the key between saves. + +Since `ComponentOverrides` is now auto-derived from `DEFAULT_COMPONENTS`, this is no longer an issue for the type itself. But be aware when adding optional components (`undefined as React.ComponentType | undefined`). + +### `contexts/index.ts` Barrel Export + +The `export * from './componentsContext/ComponentsContext'` line in `contexts/index.ts` was stripped by the linter multiple times during development. If `WithComponents` becomes unexportable from the package, check this barrel file first. + +### Documentation + +Docs PR: https://github.com/GetStream/docs-content/pull/1169 + +Updated ~45 pages across: + +- Core teaching pages (custom_components, message-customization, etc.) +- Component reference pages (channel-list, message-list, message-composer, etc.) +- Context docs (stripped component keys from 7 context pages) +- Migration guide (upgrading-from-v8.md — comprehensive WithComponents section) +- Advanced guides (audio, AI, image-picker, etc.) + +### SDK PR + +https://github.com/GetStream/stream-chat-react-native/pull/3542 diff --git a/package/src/contexts/componentsContext/__tests__/defaultComponents.test.ts b/package/src/contexts/componentsContext/__tests__/defaultComponents.test.ts new file mode 100644 index 0000000000..4742193501 --- /dev/null +++ b/package/src/contexts/componentsContext/__tests__/defaultComponents.test.ts @@ -0,0 +1,44 @@ +import { DEFAULT_COMPONENTS } from '../defaultComponents'; + +// Optional component keys that are intentionally undefined (no default implementation) +const OPTIONAL_KEYS = new Set([ + 'AttachmentPickerIOSSelectMorePhotos', + 'ChatLoadingIndicator', + 'CreatePollContent', + 'Input', + 'ListHeaderComponent', + 'MessageContentBottomView', + 'MessageContentLeadingView', + 'MessageContentTopView', + 'MessageContentTrailingView', + 'MessageLocation', + 'MessageSpacer', + 'MessageText', + 'PollContent', +]); + +describe('DEFAULT_COMPONENTS', () => { + it('should have all required values defined', () => { + const entries = Object.entries(DEFAULT_COMPONENTS); + expect(entries.length).toBeGreaterThan(50); + + const unexpectedUndefined = entries.filter( + ([key, value]) => value === undefined && !OPTIONAL_KEYS.has(key), + ); + if (unexpectedUndefined.length > 0) { + console.log( + 'Unexpectedly undefined keys:', + unexpectedUndefined.map(([k]) => k), + ); + } + expect(unexpectedUndefined).toEqual([]); + }); + + it('optional keys should be explicitly listed', () => { + const entries = Object.entries(DEFAULT_COMPONENTS); + const actualUndefined = new Set( + entries.filter(([, v]) => v === undefined || v === null).map(([k]) => k), + ); + expect(actualUndefined).toEqual(OPTIONAL_KEYS); + }); +}); diff --git a/package/src/contexts/componentsContext/defaultComponents.ts b/package/src/contexts/componentsContext/defaultComponents.ts new file mode 100644 index 0000000000..12af4049ea --- /dev/null +++ b/package/src/contexts/componentsContext/defaultComponents.ts @@ -0,0 +1,332 @@ +import React from 'react'; +import { Image } from 'react-native'; + +import { Attachment } from '../../components/Attachment/Attachment'; +import { AudioAttachment } from '../../components/Attachment/Audio'; +import { FileAttachment } from '../../components/Attachment/FileAttachment'; +import { FileAttachmentGroup } from '../../components/Attachment/FileAttachmentGroup'; +import { FileIcon } from '../../components/Attachment/FileIcon'; +import { FilePreview } from '../../components/Attachment/FilePreview'; +import { Gallery } from '../../components/Attachment/Gallery'; +import { Giphy } from '../../components/Attachment/Giphy'; +import { ImageLoadingFailedIndicator } from '../../components/Attachment/ImageLoadingFailedIndicator'; +import { ImageLoadingIndicator } from '../../components/Attachment/ImageLoadingIndicator'; +import { UnsupportedAttachment } from '../../components/Attachment/UnsupportedAttachment'; +import { URLPreview } from '../../components/Attachment/UrlPreview'; +import { URLPreviewCompact } from '../../components/Attachment/UrlPreview/URLPreviewCompact'; +import { VideoThumbnail } from '../../components/Attachment/VideoThumbnail'; +import { AttachmentPickerContent } from '../../components/AttachmentPicker/components/AttachmentPickerContent'; +import { AttachmentPickerSelectionBar } from '../../components/AttachmentPicker/components/AttachmentPickerSelectionBar'; +import { ImageOverlaySelectedComponent } from '../../components/AttachmentPicker/components/ImageOverlaySelectedComponent'; +import { AutoCompleteSuggestionHeader } from '../../components/AutoCompleteInput/AutoCompleteSuggestionHeader'; +import { AutoCompleteSuggestionItem } from '../../components/AutoCompleteInput/AutoCompleteSuggestionItem'; +import { AutoCompleteSuggestionList } from '../../components/AutoCompleteInput/AutoCompleteSuggestionList'; +import { InputView } from '../../components/AutoCompleteInput/InputView'; +import { ChannelListFooterLoadingIndicator } from '../../components/ChannelList/ChannelListFooterLoadingIndicator'; +import { ChannelListHeaderErrorIndicator } from '../../components/ChannelList/ChannelListHeaderErrorIndicator'; +import { ChannelListHeaderNetworkDownIndicator } from '../../components/ChannelList/ChannelListHeaderNetworkDownIndicator'; +import { ChannelListLoadingIndicator } from '../../components/ChannelList/ChannelListLoadingIndicator'; +import { Skeleton } from '../../components/ChannelList/Skeleton'; +import { ChannelDetailsBottomSheet } from '../../components/ChannelPreview/ChannelDetailsBottomSheet'; +import { ChannelDetailsHeader } from '../../components/ChannelPreview/ChannelDetailsBottomSheet'; +import { ChannelLastMessagePreview } from '../../components/ChannelPreview/ChannelLastMessagePreview'; +import { ChannelMessagePreviewDeliveryStatus } from '../../components/ChannelPreview/ChannelMessagePreviewDeliveryStatus'; +import { ChannelPreviewMessage } from '../../components/ChannelPreview/ChannelPreviewMessage'; +import { ChannelPreviewMutedStatus } from '../../components/ChannelPreview/ChannelPreviewMutedStatus'; +import { ChannelPreviewStatus } from '../../components/ChannelPreview/ChannelPreviewStatus'; +import { ChannelPreviewTitle } from '../../components/ChannelPreview/ChannelPreviewTitle'; +import { ChannelPreviewTypingIndicator } from '../../components/ChannelPreview/ChannelPreviewTypingIndicator'; +import { ChannelPreviewUnreadCount } from '../../components/ChannelPreview/ChannelPreviewUnreadCount'; +import { ChannelPreviewView } from '../../components/ChannelPreview/ChannelPreviewView'; +import { ImageGalleryFooter } from '../../components/ImageGallery/components/ImageGalleryFooter'; +import { ImageGalleryHeader } from '../../components/ImageGallery/components/ImageGalleryHeader'; +import { ImageGalleryVideoControl } from '../../components/ImageGallery/components/ImageGalleryVideoControl'; +import { ImageGalleryGrid } from '../../components/ImageGallery/components/ImageGrid'; +import { EmptyStateIndicator } from '../../components/Indicators/EmptyStateIndicator'; +import { LoadingErrorIndicator } from '../../components/Indicators/LoadingErrorIndicator'; +import { LoadingIndicator } from '../../components/Indicators/LoadingIndicator'; +import { KeyboardCompatibleView } from '../../components/KeyboardCompatibleView/KeyboardControllerAvoidingView'; +import { Message } from '../../components/Message/Message'; +import { MessagePinnedHeader } from '../../components/Message/MessageItemView/Headers/MessagePinnedHeader'; +import { MessageReminderHeader } from '../../components/Message/MessageItemView/Headers/MessageReminderHeader'; +import { MessageSavedForLaterHeader } from '../../components/Message/MessageItemView/Headers/MessageSavedForLaterHeader'; +import { SentToChannelHeader } from '../../components/Message/MessageItemView/Headers/SentToChannelHeader'; +import { MessageAuthor } from '../../components/Message/MessageItemView/MessageAuthor'; +import { MessageBlocked } from '../../components/Message/MessageItemView/MessageBlocked'; +import { MessageBounce } from '../../components/Message/MessageItemView/MessageBounce'; +import { MessageContent } from '../../components/Message/MessageItemView/MessageContent'; +import { MessageDeleted } from '../../components/Message/MessageItemView/MessageDeleted'; +import { MessageError } from '../../components/Message/MessageItemView/MessageError'; +import { MessageFooter } from '../../components/Message/MessageItemView/MessageFooter'; +import { MessageHeader } from '../../components/Message/MessageItemView/MessageHeader'; +import { MessageItemView } from '../../components/Message/MessageItemView/MessageItemView'; +import { MessageReplies } from '../../components/Message/MessageItemView/MessageReplies'; +import { MessageRepliesAvatars } from '../../components/Message/MessageItemView/MessageRepliesAvatars'; +import { MessageStatus } from '../../components/Message/MessageItemView/MessageStatus'; +import { MessageSwipeContent } from '../../components/Message/MessageItemView/MessageSwipeContent'; +import { MessageTimestamp } from '../../components/Message/MessageItemView/MessageTimestamp'; +import { ReactionListBottom } from '../../components/Message/MessageItemView/ReactionList/ReactionListBottom'; +import { ReactionListClustered } from '../../components/Message/MessageItemView/ReactionList/ReactionListClustered'; +import { + ReactionListCountItem, + ReactionListItem, +} from '../../components/Message/MessageItemView/ReactionList/ReactionListItem'; +import { ReactionListItemWrapper } from '../../components/Message/MessageItemView/ReactionList/ReactionListItemWrapper'; +import { ReactionListTop } from '../../components/Message/MessageItemView/ReactionList/ReactionListTop'; +import { StreamingMessageView } from '../../components/Message/MessageItemView/StreamingMessageView'; +import { AttachmentUploadPreviewList } from '../../components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList'; +import { + FileUploadInProgressIndicator, + FileUploadNotSupportedIndicator, + FileUploadRetryIndicator, + ImageUploadInProgressIndicator, + ImageUploadNotSupportedIndicator, + ImageUploadRetryIndicator, +} from '../../components/MessageInput/components/AttachmentPreview/AttachmentUploadProgressIndicator'; +import { AudioAttachmentUploadPreview } from '../../components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview'; +import { FileAttachmentUploadPreview } from '../../components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview'; +import { ImageAttachmentUploadPreview } from '../../components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview'; +import { VideoAttachmentUploadPreview } from '../../components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview'; +import { AudioRecorder } from '../../components/MessageInput/components/AudioRecorder/AudioRecorder'; +import { AudioRecordingButton } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingButton'; +import { AudioRecordingInProgress } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingInProgress'; +import { AudioRecordingLockIndicator } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator'; +import { AudioRecordingPreview } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingPreview'; +import { AudioRecordingWaveform } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingWaveform'; +import { InputButtons } from '../../components/MessageInput/components/InputButtons'; +import { AttachButton } from '../../components/MessageInput/components/InputButtons/AttachButton'; +import { CooldownTimer } from '../../components/MessageInput/components/OutputButtons/CooldownTimer'; +import { SendButton } from '../../components/MessageInput/components/OutputButtons/SendButton'; +import { MessageComposer } from '../../components/MessageInput/MessageComposer'; +import { MessageComposerLeadingView } from '../../components/MessageInput/MessageComposerLeadingView'; +import { MessageComposerTrailingView } from '../../components/MessageInput/MessageComposerTrailingView'; +import { MessageInputFooterView } from '../../components/MessageInput/MessageInputFooterView'; +import { MessageInputHeaderView } from '../../components/MessageInput/MessageInputHeaderView'; +import { MessageInputLeadingView } from '../../components/MessageInput/MessageInputLeadingView'; +import { MessageInputTrailingView } from '../../components/MessageInput/MessageInputTrailingView'; +import { SendMessageDisallowedIndicator } from '../../components/MessageInput/SendMessageDisallowedIndicator'; +import { ShowThreadMessageInChannelButton } from '../../components/MessageInput/ShowThreadMessageInChannelButton'; +import { StopMessageStreamingButton } from '../../components/MessageInput/StopMessageStreamingButton'; +import { DateHeader } from '../../components/MessageList/DateHeader'; +import { InlineDateSeparator } from '../../components/MessageList/InlineDateSeparator'; +import { InlineUnreadIndicator } from '../../components/MessageList/InlineUnreadIndicator'; +import { MessageList } from '../../components/MessageList/MessageList'; +import { MessageSystem } from '../../components/MessageList/MessageSystem'; +import { NetworkDownIndicator } from '../../components/MessageList/NetworkDownIndicator'; +import { ScrollToBottomButton } from '../../components/MessageList/ScrollToBottomButton'; +import { StickyHeader } from '../../components/MessageList/StickyHeader'; +import { TypingIndicator } from '../../components/MessageList/TypingIndicator'; +import { TypingIndicatorContainer } from '../../components/MessageList/TypingIndicatorContainer'; +import { UnreadMessagesNotification } from '../../components/MessageList/UnreadMessagesNotification'; +import { MessageActionList } from '../../components/MessageMenu/MessageActionList'; +import { MessageActionListItem } from '../../components/MessageMenu/MessageActionListItem'; +import { MessageMenu } from '../../components/MessageMenu/MessageMenu'; +import { MessageReactionPicker } from '../../components/MessageMenu/MessageReactionPicker'; +import { MessageUserReactions } from '../../components/MessageMenu/MessageUserReactions'; +import { MessageUserReactionsAvatar } from '../../components/MessageMenu/MessageUserReactionsAvatar'; +import { MessageUserReactionsItem } from '../../components/MessageMenu/MessageUserReactionsItem'; +import { PollAnswersListContent } from '../../components/Poll/components/PollAnswersList'; +import { PollButtons } from '../../components/Poll/components/PollButtons'; +import { PollAllOptionsContent } from '../../components/Poll/components/PollOption'; +import { PollOptionFullResultsContent } from '../../components/Poll/components/PollResults/PollOptionFullResults'; +import { PollResultsContent } from '../../components/Poll/components/PollResults/PollResults'; +import { PollHeader } from '../../components/Poll/Poll'; +import { Reply } from '../../components/Reply/Reply'; +import { + DefaultThreadListComponent as ThreadListComponent, + DefaultThreadListEmptyPlaceholder, + DefaultThreadListLoadingIndicator, + DefaultThreadListLoadingNextIndicator, +} from '../../components/ThreadList/ThreadList'; +import { ThreadListItemComponent as ThreadListItem } from '../../components/ThreadList/ThreadListItem'; +import { ThreadListItemMessagePreview } from '../../components/ThreadList/ThreadListItemMessagePreview'; +import { ThreadListUnreadBanner } from '../../components/ThreadList/ThreadListUnreadBanner'; +import { ThreadMessagePreviewDeliveryStatus } from '../../components/ThreadList/ThreadMessagePreviewDeliveryStatus'; +import { ChannelAvatar } from '../../components/ui/Avatar/ChannelAvatar'; +import { DefaultMessageOverlayBackground } from '../../contexts/overlayContext/MessageOverlayHostLayer'; + +/** + * All default component implementations used across the SDK. + * These are the components used when no overrides are provided via WithComponents. + */ +export const DEFAULT_COMPONENTS = { + Attachment, + AttachButton, + AttachmentPickerContent, + AttachmentPickerSelectionBar, + AttachmentUploadPreviewList, + AudioAttachment, + AudioAttachmentUploadPreview, + AudioRecorder, + AudioRecordingInProgress, + AudioRecordingLockIndicator, + AudioRecordingPreview, + AudioRecordingWaveform, + AutoCompleteSuggestionHeader, + AutoCompleteSuggestionItem, + AutoCompleteSuggestionList, + ChannelDetailsBottomSheet, + CooldownTimer, + DateHeader, + EmptyStateIndicator, + FileAttachment, + FileAttachmentGroup, + FileAttachmentIcon: FileIcon, + FileAttachmentUploadPreview, + FileUploadInProgressIndicator, + FileUploadNotSupportedIndicator, + FileUploadRetryIndicator, + FilePreview, + FooterLoadingIndicator: ChannelListFooterLoadingIndicator, + Gallery, + Giphy, + HeaderErrorIndicator: ChannelListHeaderErrorIndicator, + HeaderNetworkDownIndicator: ChannelListHeaderNetworkDownIndicator, + ImageAttachmentUploadPreview, + ImageLoadingFailedIndicator, + ImageLoadingIndicator, + ImageOverlaySelectedComponent, + ImageUploadInProgressIndicator, + ImageUploadNotSupportedIndicator, + ImageUploadRetryIndicator, + InlineDateSeparator, + InlineUnreadIndicator, + InputButtons, + InputView, + KeyboardCompatibleView, + LoadingErrorIndicator, + ChannelListLoadingIndicator, + MessageListLoadingIndicator: LoadingIndicator, + Message, + MessageActionList, + MessageActionListItem, + MessageAuthor, + MessageBlocked, + MessageBounce, + MessageComposerLeadingView, + MessageComposerTrailingView, + MessageContent, + MessageDeleted, + MessageError, + MessageFooter, + MessageHeader, + MessageInputFooterView, + MessageInputHeaderView, + MessageInputLeadingView, + MessageInputTrailingView, + MessageItemView, + MessageList, + MessageMenu, + MessagePinnedHeader, + MessageReactionPicker, + MessageReminderHeader, + MessageReplies, + MessageRepliesAvatars, + MessageSavedForLaterHeader, + MessageStatus, + MessageSwipeContent, + MessageSystem, + MessageTimestamp, + MessageUserReactions, + MessageUserReactionsAvatar, + MessageUserReactionsItem, + NetworkDownIndicator, + Preview: ChannelPreviewView, + PreviewAvatar: ChannelAvatar, + PreviewLastMessage: ChannelLastMessagePreview, + PreviewMessage: ChannelPreviewMessage, + PreviewMessageDeliveryStatus: ChannelMessagePreviewDeliveryStatus, + PreviewMutedStatus: ChannelPreviewMutedStatus, + PreviewStatus: ChannelPreviewStatus, + PreviewTitle: ChannelPreviewTitle, + PreviewTypingIndicator: ChannelPreviewTypingIndicator, + PreviewUnreadCount: ChannelPreviewUnreadCount, + ReactionListBottom, + ReactionListClustered, + ReactionListCountItem, + ReactionListItem, + ReactionListItemWrapper, + ReactionListTop, + Reply, + ScrollToBottomButton, + SendButton, + SendMessageDisallowedIndicator, + SentToChannelHeader, + ShowThreadMessageInChannelButton, + Skeleton, + StartAudioRecordingButton: AudioRecordingButton, + StickyHeader, + StopMessageStreamingButton, + StreamingMessageView, + TypingIndicator, + TypingIndicatorContainer, + UnreadMessagesNotification, + UnsupportedAttachment, + UrlPreview: URLPreview, + URLPreviewCompact, + VideoAttachmentUploadPreview, + VideoThumbnail, + + // Channel details + ChannelDetailsHeader, + + // Thread + ThreadMessageComposer: MessageComposer, + ThreadListComponent, + ThreadListEmptyPlaceholder: DefaultThreadListEmptyPlaceholder, + ThreadListItem, + ThreadListItemMessagePreview, + ThreadListLoadingIndicator: DefaultThreadListLoadingIndicator, + ThreadListLoadingMoreIndicator: DefaultThreadListLoadingNextIndicator, + ThreadListUnreadBanner, + ThreadMessagePreviewDeliveryStatus, + + // Poll + PollButtons, + PollHeader, + PollAllOptionsContent, + PollAnswersListContent, + PollResultsContent, + PollOptionFullResultsContent, + + // ImageGallery + ImageGalleryFooter, + ImageGalleryGrid, + ImageGalleryHeader, + ImageGalleryVideoControls: ImageGalleryVideoControl, + + // Overlay + MessageOverlayBackground: DefaultMessageOverlayBackground, + + // Image + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ImageComponent: Image as React.ComponentType, + + // Optional overrides (no defaults — undefined unless user provides via WithComponents) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + AttachmentPickerIOSSelectMorePhotos: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ChatLoadingIndicator: undefined as React.ComponentType | null | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + CreatePollContent: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Input: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ListHeaderComponent: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + MessageContentBottomView: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + MessageContentLeadingView: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + MessageContentTopView: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + MessageContentTrailingView: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + MessageLocation: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + MessageSpacer: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + MessageText: undefined as React.ComponentType | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + PollContent: undefined as React.ComponentType | undefined, +}; diff --git a/package/src/contexts/imageGalleryContext/ImageGalleryContext.tsx b/package/src/contexts/imageGalleryContext/ImageGalleryContext.tsx index 4626097b8b..3b1726d3e5 100644 --- a/package/src/contexts/imageGalleryContext/ImageGalleryContext.tsx +++ b/package/src/contexts/imageGalleryContext/ImageGalleryContext.tsx @@ -7,10 +7,6 @@ import { useImageGalleryContext, } from './ImageGalleryContextBase'; -import { ImageGalleryFooter as ImageGalleryFooterDefault } from '../../components/ImageGallery/components/ImageGalleryFooter'; -import { ImageGalleryHeader as ImageGalleryHeaderDefault } from '../../components/ImageGallery/components/ImageGalleryHeader'; -import { ImageGalleryVideoControl as ImageGalleryVideoControlDefault } from '../../components/ImageGallery/components/ImageGalleryVideoControl'; -import { ImageGalleryGrid as ImageGalleryGridDefault } from '../../components/ImageGallery/components/ImageGrid'; import { ImageGalleryStateStore } from '../../state-store/image-gallery-state-store'; export const ImageGalleryProvider = ({ @@ -30,10 +26,6 @@ export const ImageGalleryProvider = ({ () => ({ autoPlayVideo: value?.autoPlayVideo, imageGalleryStateStore, - ImageGalleryHeader: ImageGalleryHeaderDefault, - ImageGalleryFooter: ImageGalleryFooterDefault, - ImageGalleryVideoControls: ImageGalleryVideoControlDefault, - ImageGalleryGrid: ImageGalleryGridDefault, ...value, }), [imageGalleryStateStore, value], diff --git a/package/src/contexts/imageGalleryContext/ImageGalleryContextBase.tsx b/package/src/contexts/imageGalleryContext/ImageGalleryContextBase.tsx index 2ca8473ac8..eaadec4e79 100644 --- a/package/src/contexts/imageGalleryContext/ImageGalleryContextBase.tsx +++ b/package/src/contexts/imageGalleryContext/ImageGalleryContextBase.tsx @@ -2,12 +2,6 @@ import React, { useContext } from 'react'; import type { Attachment } from 'stream-chat'; -import type { - ImageGalleryFooterProps, - ImageGalleryGridProps, - ImageGalleryHeaderProps, - ImageGalleryVideoControlProps, -} from '../../components/ImageGallery/components/types'; import { ImageGalleryStateStore } from '../../state-store/image-gallery-state-store'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; @@ -17,10 +11,6 @@ export type ImageGalleryProviderProps = { autoPlayVideo?: boolean; giphyVersion?: keyof NonNullable; numberOfImageGalleryGridColumns?: number; - ImageGalleryHeader?: React.ComponentType; - ImageGalleryFooter?: React.ComponentType; - ImageGalleryVideoControls?: React.ComponentType; - ImageGalleryGrid?: React.ComponentType; }; export type ImageGalleryContextValue = ImageGalleryProviderProps & { diff --git a/package/src/contexts/index.ts b/package/src/contexts/index.ts index 5922a2b669..7c50dd7997 100644 --- a/package/src/contexts/index.ts +++ b/package/src/contexts/index.ts @@ -1,4 +1,5 @@ export * from './attachmentPickerContext/AttachmentPickerContext'; +export * from './componentsContext/ComponentsContext'; export * from './bottomSheetContext/BottomSheetContext'; export * from './channelContext/ChannelContext'; export * from './channelsContext/ChannelsContext'; diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index 3dfe213f1b..f48fc32bd5 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -18,40 +18,14 @@ import { Message as StreamMessage, UpdateMessageOptions, UploadRequestFn, - UserResponse, } from 'stream-chat'; import { useCreateMessageInputContext } from './hooks/useCreateMessageInputContext'; import { useMessageComposer } from './hooks/useMessageComposer'; -import { - AutoCompleteSuggestionHeaderProps, - AutoCompleteSuggestionItemProps, - AutoCompleteSuggestionListProps, - FileUploadNotSupportedIndicatorProps, - FileUploadRetryIndicatorProps, - ImageUploadRetryIndicatorProps, - PollContentProps, - StopMessageStreamingButtonProps, -} from '../../components'; -import type { InputViewProps } from '../../components/AutoCompleteInput/InputView'; import { dismissKeyboard } from '../../components/KeyboardCompatibleView/KeyboardControllerAvoidingView'; import { parseLinksFromText } from '../../components/Message/MessageItemView/utils/parseLinks'; -import { AttachmentUploadPreviewListProps } from '../../components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList'; -import { AudioAttachmentUploadPreviewProps } from '../../components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview'; -import { FileAttachmentUploadPreviewProps } from '../../components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview'; -import { ImageAttachmentUploadPreviewProps } from '../../components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview'; -import { VideoAttachmentUploadPreviewProps } from '../../components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview'; -import type { AudioRecorderProps } from '../../components/MessageInput/components/AudioRecorder/AudioRecorder'; -import type { AudioRecordingButtonProps } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingButton'; -import type { AudioRecordingInProgressProps } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingInProgress'; -import type { AudioRecordingLockIndicatorProps } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator'; -import type { AudioRecordingWaveformProps } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingWaveform'; -import type { AttachButtonProps } from '../../components/MessageInput/components/InputButtons/AttachButton'; -import type { InputButtonsProps } from '../../components/MessageInput/components/InputButtons/index'; -import type { SendButtonProps } from '../../components/MessageInput/components/OutputButtons/SendButton'; import { useAudioRecorder } from '../../components/MessageInput/hooks/useAudioRecorder'; -import type { MessageComposerProps } from '../../components/MessageInput/MessageComposer'; import { useStableCallback } from '../../hooks/useStableCallback'; import { createAttachmentsCompositionMiddleware, @@ -125,69 +99,16 @@ export type InputMessageInputContextValue = { * message. */ asyncMessagesSlideToCancelDistance: number; - /** - * Custom UI component for attach button. - * - * Defaults to and accepts same props as: - * [AttachButton](https://getstream.io/chat/docs/sdk/reactnative/ui-components/attach-button/) - */ - AttachButton: React.ComponentType; - /** - * Custom UI component for audio recorder UI. - * - * Defaults to and accepts same props as: - * [AudioRecorder](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/AudioRecorder.tsx) - */ - AudioRecorder: React.ComponentType; /** * Controls whether the async audio feature is enabled. */ audioRecordingEnabled: boolean; - /** - * Custom UI component to render audio recording in progress. - * - * **Default** - * [AudioRecordingInProgress](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx) - */ - AudioRecordingInProgress: React.ComponentType; - /** - * Custom UI component for audio recording lock indicator. - * - * Defaults to and accepts same props as: - * [AudioRecordingLockIndicator](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx) - */ - AudioRecordingLockIndicator: React.ComponentType; - /** - * Custom UI component to render audio recording preview. - * - * **Default** - * [AudioRecordingPreview](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingPreview.tsx) - */ - AudioRecordingPreview: React.ComponentType; - /** - * Custom UI component to render audio recording waveform. - * - * **Default** - * [AudioRecordingWaveform](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingWaveform.tsx) - */ - AudioRecordingWaveform: React.ComponentType; - - AutoCompleteSuggestionHeader: React.ComponentType; - AutoCompleteSuggestionItem: React.ComponentType; - AutoCompleteSuggestionList: React.ComponentType; /** * Height of the image picker bottom sheet when opened. * @type number * @default 40% of window height */ attachmentPickerBottomSheetHeight: number; - /** - * Custom UI component for AttachmentPickerSelectionBar - * - * **Default: ** - * [AttachmentPickerSelectionBar](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/AttachmentPicker/components/AttachmentPickerSelectionBar.tsx) - */ - AttachmentPickerSelectionBar: React.ComponentType; /** * Height of the attachment selection bar displayed on the attachment picker. * @type number @@ -196,28 +117,6 @@ export type InputMessageInputContextValue = { */ attachmentSelectionBarHeight: number; - AttachmentUploadPreviewList: React.ComponentType; - AudioAttachmentUploadPreview: React.ComponentType; - ImageAttachmentUploadPreview: React.ComponentType; - FileAttachmentUploadPreview: React.ComponentType; - VideoAttachmentUploadPreview: React.ComponentType; - - FileUploadInProgressIndicator: React.ComponentType; - FileUploadRetryIndicator: React.ComponentType; - FileUploadNotSupportedIndicator: React.ComponentType; - ImageUploadInProgressIndicator: React.ComponentType; - ImageUploadRetryIndicator: React.ComponentType; - ImageUploadNotSupportedIndicator: React.ComponentType; - - /** - * Custom UI component to display the remaining cooldown a user will have to wait before - * being allowed to send another message. This component is displayed in place of the - * send button for the MessageComposer component. - * - * **default** - * [CooldownTimer](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/CooldownTimer.tsx) - */ - CooldownTimer: React.ComponentType; editMessage: (params: { localMessage: LocalMessage; options?: UpdateMessageOptions; @@ -233,58 +132,11 @@ export type InputMessageInputContextValue = { /** When false, ImageSelectorIcon will be hidden */ hasImagePicker: boolean; - /** - * Custom UI component for send button. - * - * Defaults to and accepts same props as: - * [SendButton](https://getstream.io/chat/docs/sdk/reactnative/ui-components/send-button/) - */ - SendButton: React.ComponentType; sendMessage: (params: { localMessage: LocalMessage; message: StreamMessage; options?: SendMessageOptions; }) => Promise; - /** - * Custom UI component to render checkbox with text ("Also send to channel") in Thread's input box. - * When ticked, message will also be sent in parent channel. - */ - ShowThreadMessageInChannelButton: React.ComponentType<{ - threadList?: boolean; - }>; - /** - * Custom UI component to override leading side of composer container. - */ - MessageComposerLeadingView: React.ComponentType; - /** - * Custom UI component to override trailing side of composer container. - */ - MessageComposerTrailingView: React.ComponentType; - /** - * Custom UI component to override message input header content. - */ - MessageInputHeaderView: React.ComponentType; - /** - * Custom UI component to override message input footer content. - */ - MessageInputFooterView: React.ComponentType; - /** - * Custom UI component to override leading side of input row. - */ - MessageInputLeadingView: React.ComponentType; - /** - * Custom UI component to override trailing side of input row. - */ - MessageInputTrailingView: React.ComponentType; - - /** - * Custom UI component for audio recording mic button. - * - * Defaults to and accepts same props as: - * [AudioRecordingButton](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingButton.tsx) - */ - StartAudioRecordingButton: React.ComponentType; - StopMessageStreamingButton: React.ComponentType | null; /** * Additional props for underlying TextInput component. These props will be forwarded as it is to TextInput component. * @@ -301,10 +153,6 @@ export type InputMessageInputContextValue = { */ compressImageQuality?: number; - /** - * Override the entire content of the CreatePoll component. The component has full access to the useCreatePollContext() hook. - * */ - CreatePollContent?: React.ComponentType; /** * Vertical gap between poll options in poll creation dialog. */ @@ -331,40 +179,7 @@ export type InputMessageInputContextValue = { */ messageInputFloating: boolean; - /** - * Custom UI component for AutoCompleteInput. - * Has access to all of [MessageInputContext](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/contexts/messageInputContext/MessageInputContext.tsx) - */ - Input?: React.ComponentType< - Omit & - InputButtonsProps & { - getUsers: () => UserResponse[]; - } - >; - /** - * Custom UI component to override the combined input body view. - * Defaults to - * [InputView](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/AutoCompleteInput/InputView.tsx) - */ - InputView: React.ComponentType; - /** - * Custom UI component to override buttons on left side of input box - * Defaults to - * [InputButtons](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/InputButtons.tsx), - * which contain following components/buttons: - * - * - AttachButton - * - CommandsButtom - * - * You have access to following prop functions: - * - * - closeAttachmentPicker - * - openAttachmentPicker - * - openCommandsPicker - */ - InputButtons?: React.ComponentType; openPollCreationDialog?: ({ sendMessage }: Pick) => void; - SendMessageDisallowedIndicator?: React.ComponentType; /** * ref for input setter function * diff --git a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts index 54a62a7b7e..5343dad8e0 100644 --- a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +++ b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts @@ -9,75 +9,38 @@ export const useCreateMessageInputContext = ({ asyncMessagesMinimumPressDuration, asyncMessagesSlideToCancelDistance, audioRecordingSendOnComplete, - AttachButton, attachmentPickerBottomSheetHeight, - AttachmentPickerSelectionBar, attachmentSelectionBarHeight, - AttachmentUploadPreviewList, - AudioAttachmentUploadPreview, - AudioRecorder, audioRecordingEnabled, - AudioRecordingInProgress, - AudioRecordingLockIndicator, - AudioRecordingPreview, - AudioRecordingWaveform, - AutoCompleteSuggestionHeader, - AutoCompleteSuggestionItem, - AutoCompleteSuggestionList, closeAttachmentPicker, closePollCreationDialog, compressImageQuality, - CooldownTimer, - CreatePollContent, createPollOptionGap, editMessage, - FileAttachmentUploadPreview, - FileUploadInProgressIndicator, - FileUploadRetryIndicator, - FileUploadNotSupportedIndicator, - ImageUploadInProgressIndicator, - ImageUploadRetryIndicator, - ImageUploadNotSupportedIndicator, handleAttachButtonPress, hasCameraPicker, hasCommands, hasFilePicker, hasImagePicker, - ImageAttachmentUploadPreview, - Input, - InputView, inputBoxRef, - InputButtons, - MessageComposerLeadingView, - MessageComposerTrailingView, messageInputFloating, messageInputHeightStore, - MessageInputFooterView, - MessageInputHeaderView, - MessageInputLeadingView, - MessageInputTrailingView, openAttachmentPicker, openPollCreationDialog, pickAndUploadImageFromNativePicker, pickFile, - SendButton, sendMessage, - SendMessageDisallowedIndicator, setInputBoxRef, setInputRef, showPollCreationDialog, - ShowThreadMessageInChannelButton, - StartAudioRecordingButton, - StopMessageStreamingButton, takeAndUploadImage, - thread, uploadNewFile, - VideoAttachmentUploadPreview, audioRecorderManager, startVoiceRecording, deleteVoiceRecording, uploadVoiceRecording, stopVoiceRecording, + thread, }: MessageInputContextValue & Pick) => { const threadId = thread?.id; @@ -88,69 +51,32 @@ export const useCreateMessageInputContext = ({ asyncMessagesMinimumPressDuration, asyncMessagesSlideToCancelDistance, audioRecordingSendOnComplete, - AttachButton, attachmentPickerBottomSheetHeight, - AttachmentPickerSelectionBar, attachmentSelectionBarHeight, - AttachmentUploadPreviewList, - AudioAttachmentUploadPreview, - AudioRecorder, audioRecordingEnabled, - AudioRecordingInProgress, - AudioRecordingLockIndicator, - AudioRecordingPreview, - AudioRecordingWaveform, - AutoCompleteSuggestionHeader, - AutoCompleteSuggestionItem, - AutoCompleteSuggestionList, closeAttachmentPicker, closePollCreationDialog, compressImageQuality, - CooldownTimer, - CreatePollContent, createPollOptionGap, editMessage, - FileAttachmentUploadPreview, - FileUploadInProgressIndicator, - FileUploadRetryIndicator, - FileUploadNotSupportedIndicator, - ImageUploadInProgressIndicator, - ImageUploadRetryIndicator, - ImageUploadNotSupportedIndicator, handleAttachButtonPress, hasCameraPicker, hasCommands, hasFilePicker, hasImagePicker, - ImageAttachmentUploadPreview, - Input, - InputView, inputBoxRef, - InputButtons, - MessageComposerLeadingView, - MessageComposerTrailingView, messageInputFloating, messageInputHeightStore, - MessageInputFooterView, - MessageInputHeaderView, - MessageInputLeadingView, - MessageInputTrailingView, openAttachmentPicker, openPollCreationDialog, pickAndUploadImageFromNativePicker, pickFile, - SendButton, sendMessage, - SendMessageDisallowedIndicator, setInputBoxRef, setInputRef, showPollCreationDialog, - ShowThreadMessageInChannelButton, - StartAudioRecordingButton, - StopMessageStreamingButton, takeAndUploadImage, uploadNewFile, - VideoAttachmentUploadPreview, audioRecorderManager, startVoiceRecording, deleteVoiceRecording, diff --git a/package/src/contexts/messagesContext/MessagesContext.tsx b/package/src/contexts/messagesContext/MessagesContext.tsx index 5b1b3c9572..210ed7e24e 100644 --- a/package/src/contexts/messagesContext/MessagesContext.tsx +++ b/package/src/contexts/messagesContext/MessagesContext.tsx @@ -1,6 +1,6 @@ import React, { PropsWithChildren, useContext } from 'react'; -import { PressableProps, View, ViewProps } from 'react-native'; +import { PressableProps, ViewProps } from 'react-native'; import type { Attachment, @@ -12,79 +12,14 @@ import type { MessageResponse, } from 'stream-chat'; -import type { - InlineUnreadIndicatorProps, - PollContentProps, - StreamingMessageViewProps, -} from '../../components'; -import type { AttachmentProps } from '../../components/Attachment/Attachment'; -import type { AudioAttachmentProps } from '../../components/Attachment/Audio'; -import type { FileAttachmentProps } from '../../components/Attachment/FileAttachment'; -import type { FileAttachmentGroupProps } from '../../components/Attachment/FileAttachmentGroup'; -import type { FileIconProps } from '../../components/Attachment/FileIcon'; -import { FilePreviewProps } from '../../components/Attachment/FilePreview'; -import type { GalleryProps } from '../../components/Attachment/Gallery'; -import type { GiphyProps } from '../../components/Attachment/Giphy'; -import type { ImageLoadingFailedIndicatorProps } from '../../components/Attachment/ImageLoadingFailedIndicator'; -import { UnsupportedAttachmentProps } from '../../components/Attachment/UnsupportedAttachment'; -import type { - URLPreviewCompactProps, - URLPreviewProps, -} from '../../components/Attachment/UrlPreview'; -import type { VideoThumbnailProps } from '../../components/Attachment/VideoThumbnail'; -import type { - MessagePressableHandlerPayload, - MessageProps, -} from '../../components/Message/Message'; -import type { MessagePinnedHeaderProps } from '../../components/Message/MessageItemView/Headers/MessagePinnedHeader'; -import type { MessageReminderHeaderProps } from '../../components/Message/MessageItemView/Headers/MessageReminderHeader'; -import type { MessageSavedForLaterHeaderProps } from '../../components/Message/MessageItemView/Headers/MessageSavedForLaterHeader'; -import type { SentToChannelHeaderProps } from '../../components/Message/MessageItemView/Headers/SentToChannelHeader'; -import type { MessageAuthorProps } from '../../components/Message/MessageItemView/MessageAuthor'; -import type { MessageBlockedProps } from '../../components/Message/MessageItemView/MessageBlocked'; -import type { MessageBounceProps } from '../../components/Message/MessageItemView/MessageBounce'; -import type { MessageContentProps } from '../../components/Message/MessageItemView/MessageContent'; -import type { MessageDeletedProps } from '../../components/Message/MessageItemView/MessageDeleted'; -import type { MessageFooterProps } from '../../components/Message/MessageItemView/MessageFooter'; -import { MessageHeaderProps } from '../../components/Message/MessageItemView/MessageHeader'; -import type { MessageItemViewProps } from '../../components/Message/MessageItemView/MessageItemView'; -import type { MessageRepliesProps } from '../../components/Message/MessageItemView/MessageReplies'; -import type { MessageRepliesAvatarsProps } from '../../components/Message/MessageItemView/MessageRepliesAvatars'; -import type { MessageStatusProps } from '../../components/Message/MessageItemView/MessageStatus'; -import type { MessageTextProps } from '../../components/Message/MessageItemView/MessageTextContainer'; -import { MessageTimestampProps } from '../../components/Message/MessageItemView/MessageTimestamp'; -import { ReactionListBottomProps } from '../../components/Message/MessageItemView/ReactionList/ReactionListBottom'; -import { ReactionListClusteredProps } from '../../components/Message/MessageItemView/ReactionList/ReactionListClustered'; -import { - ReactionListItemProps, - ReactionListCountItemProps, -} from '../../components/Message/MessageItemView/ReactionList/ReactionListItem'; -import { ReactionListItemWrapperProps } from '../../components/Message/MessageItemView/ReactionList/ReactionListItemWrapper'; -import type { ReactionListTopProps } from '../../components/Message/MessageItemView/ReactionList/ReactionListTop'; +import type { MessagePressableHandlerPayload } from '../../components/Message/Message'; import type { MarkdownRules } from '../../components/Message/MessageItemView/utils/renderText'; import type { MessageActionsParams } from '../../components/Message/utils/messageActions'; -import type { DateHeaderProps } from '../../components/MessageList/DateHeader'; -import type { InlineDateSeparatorProps } from '../../components/MessageList/InlineDateSeparator'; -import type { MessageListProps } from '../../components/MessageList/MessageList'; -import type { MessageSystemProps } from '../../components/MessageList/MessageSystem'; -import type { ScrollToBottomButtonProps } from '../../components/MessageList/ScrollToBottomButton'; -import { TypingIndicatorContainerProps } from '../../components/MessageList/TypingIndicatorContainer'; -import { UnreadMessagesNotificationProps } from '../../components/MessageList/UnreadMessagesNotification'; import type { GroupStyle, MessageGroupStylesParams, } from '../../components/MessageList/utils/getGroupStyles'; -import { MessageActionListProps } from '../../components/MessageMenu/MessageActionList'; -import type { - MessageActionListItemProps, - MessageActionType, -} from '../../components/MessageMenu/MessageActionListItem'; -import { MessageMenuProps } from '../../components/MessageMenu/MessageMenu'; -import type { MessageReactionPickerProps } from '../../components/MessageMenu/MessageReactionPicker'; -import { MessageUserReactionsProps } from '../../components/MessageMenu/MessageUserReactions'; -import { MessageUserReactionsAvatarProps } from '../../components/MessageMenu/MessageUserReactionsAvatar'; -import { MessageUserReactionsItemProps } from '../../components/MessageMenu/MessageUserReactionsItem'; -import type { ReplyProps } from '../../components/Reply/Reply'; +import type { MessageActionType } from '../../components/MessageMenu/MessageActionListItem'; import { NativeHandlers } from '../../native'; import type { ReactionData } from '../../utils/utils'; @@ -111,18 +46,6 @@ export type MessageLocationProps = { }; export type MessagesContextValue = Pick & { - /** - * UI component for Attachment. - * Defaults to: [Attachment](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/Attachment.tsx) - */ - Attachment: React.ComponentType; - /** Custom UI component for AudioAttachment. */ - AudioAttachment: React.ComponentType; - /** - * UI component for DateHeader - * Defaults to: [DateHeader](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageList/DateHeader.tsx) - **/ - DateHeader: React.ComponentType; // FIXME: Remove the signature with optionsOrHardDelete boolean with the next major release deleteMessage: ( message: LocalMessage, @@ -141,249 +64,24 @@ export type MessagesContextValue = Pick; - - /** - * UI component for FilePreview - * Defaults to: [FilePreview](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/FilePreview.tsx) - */ - FilePreview: React.ComponentType; - - /** - * UI component to display File type attachment. - * Defaults to: [FilePickerIcon](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/FileAttachment.tsx) - */ - FileAttachment: React.ComponentType; - /** - * UI component to display group of File type attachments or multiple file attachments (in single message). - * Defaults to: [FileAttachmentGroup](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/FileAttachmentGroup.tsx) - */ - FileAttachmentGroup: React.ComponentType; - /** - * UI component for attachment icon for type 'file' attachment. - * Defaults to: https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/FileIcon.tsx - */ - FileAttachmentIcon: React.ComponentType; FlatList: typeof NativeHandlers.FlatList | undefined; - /** - * UI component to display image attachments - * Defaults to: [Gallery](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/Gallery.tsx) - */ - Gallery: React.ComponentType; - /** - * UI component for Giphy - * Defaults to: [Giphy](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/Giphy.tsx) - */ - Giphy: React.ComponentType; /** * The giphy version to render - check the keys of the [Image Object](https://developers.giphy.com/docs/api/schema#image-object) for possible values. Uses 'fixed_height' by default * */ giphyVersion: keyof NonNullable; - /** - * The indicator rendered when loading an image fails. - */ - ImageLoadingFailedIndicator: React.ComponentType; - - /** - * The indicator rendered when image is loading. By default renders - */ - ImageLoadingIndicator: React.ComponentType; - /** * When true, messageList will be scrolled at first unread message, when opened. */ initialScrollToFirstUnreadMessage: boolean; - /** - * UI component for Message Date Separator Component - * Defaults to: [InlineDateSeparator](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageList/InlineDateSeparator.tsx) - */ - InlineDateSeparator: React.ComponentType; - /** - * UI component for InlineUnreadIndicator - * Defaults to: [InlineUnreadIndicator](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageList/InlineUnreadIndicator.tsx) - **/ - InlineUnreadIndicator: React.ComponentType; - - Message: React.ComponentType; - /** - * Custom UI component for rendering Message actions in message menu. - * - * **Default** [MessageActionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageMenu/MessageActionList.tsx) - */ - MessageActionList: React.ComponentType; - /** - * Custom UI component for rendering Message action item in message menu. - * - * **Default** [MessageActionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageMenu/MessageActionList.tsx) - */ - MessageActionListItem: React.ComponentType; - /** - * UI component for MessageAuthor - * Defaults to: [MessageAuthor](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageItemView/MessageAuthor.tsx) - **/ - MessageAuthor: React.ComponentType; - /** - * UI component for MessageBlocked - * Defaults to: [MessageBlocked](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageItemView/MessageBlocked.tsx) - */ - MessageBlocked: React.ComponentType; - /** - * UI Component for MessageBounce - */ - MessageBounce: React.ComponentType; - /** - * UI component for MessageContent - * Defaults to: [MessageContent](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageItemView/MessageContent.tsx) - */ - MessageContent: React.ComponentType; - /** - * Optional UI component rendered above the message content body. - */ - MessageContentTopView?: React.ComponentType; - /** - * Optional UI component rendered to the left of the message content body. - */ - MessageContentLeadingView?: React.ComponentType; - /** - * Optional UI component rendered to the right of the message content body. - */ - MessageContentTrailingView?: React.ComponentType; - /** - * Optional UI component rendered below the message content body. - */ - MessageContentBottomView?: React.ComponentType; /** Order to render the message content */ messageContentOrder: MessageContentType[]; - /** - * UI component for MessageDeleted - * Defaults to: [MessageDeleted](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageItemView/MessageDeleted.tsx) - */ - MessageDeleted: React.ComponentType; - /** - * UI component for the MessageError. - */ - MessageError: React.ComponentType; - /** - * Custom message footer component - */ - MessageFooter: React.ComponentType; - MessageList: React.ComponentType; - MessageLocation?: React.ComponentType; - /** - * UI component for MessageMenu - */ - MessageMenu: React.ComponentType; - /** - * Custom message pinned component - */ - MessagePinnedHeader: React.ComponentType; - /** - * Custom message reminder component - */ - MessageReminderHeader: React.ComponentType; - /** - * Custom message saved for later component - */ - MessageSavedForLaterHeader: React.ComponentType; - /** - * Custom message sent to channel component - */ - SentToChannelHeader: React.ComponentType; - /** - * UI component for MessageReactionPicker - */ - MessageReactionPicker: React.ComponentType; - /** - * UI component for MessageReplies - * Defaults to: [MessageReplies](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageItemView/MessageReplies.tsx) - */ - MessageReplies: React.ComponentType; - /** - * UI Component for MessageRepliesAvatars - * Defaults to: [MessageRepliesAvatars](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageItemView/MessageRepliesAvatars.tsx) - */ - MessageRepliesAvatars: React.ComponentType; - /** - * Optional UI component for overriding the empty space on a message row. If the message is left aligned, it will be to the right of it - otherwise left. - */ - MessageSpacer?: React.ComponentType; - /** - * UI component for MessageItemView. It encapsulates the entirety of a message row. - * Defaults to: [MessageItemView](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageItemView/MessageItemView.tsx) - */ - MessageItemView: React.ComponentType< - MessageItemViewProps & { ref?: React.RefObject } - >; - /** - * UI component for MessageStatus (delivered/read) - * Defaults to: [MessageStatus](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageItemView/MessageStatus.tsx) - */ - MessageStatus: React.ComponentType; - /** - * UI component for MessageSystem - * Defaults to: [MessageSystem](https://getstream.io/chat/docs/sdk/reactnative/ui-components/message-system/) - */ - MessageSystem: React.ComponentType; - /** - * UI component for MessageTimestamp - * Defaults to: [MessageTimestamp](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/Message/MessageItemView/MessageTimestamp.tsx) - */ - MessageTimestamp: React.ComponentType; - /** - * Custom UI component for rendering user reactions, in message menu. - * - * **Default** [MessageUserReactions](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageMenu/MessageUserReactions.tsx) - */ - MessageUserReactions: React.ComponentType; - /** - * Custom UI component for rendering user reactions avatar under `MessageUserReactionsItem`, in message menu. - * - * **Default** [MessageUserReactionsAvatar](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageMenu/MessageUserReactionsAvatar.tsx) - */ - MessageUserReactionsAvatar: React.ComponentType; - /** - * Custom UI component for rendering individual user reactions item under `MessageUserReactions`, in message menu. - * - * **Default** [MessageUserReactionsItem](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageMenu/MessageUserReactionsItem.tsx) - */ - MessageUserReactionsItem: React.ComponentType; - removeMessage: (message: { id: string; parent_id?: string }) => Promise; - /** - * UI component for Reply - * Defaults to: [Reply](https://getstream.io/chat/docs/sdk/reactnative/ui-components/reply/) - */ - Reply: React.ComponentType; /** * Override the api request for retry message functionality. */ retrySendMessage: (message: LocalMessage) => Promise; - /** - * UI component for ScrollToBottomButton - * Defaults to: [ScrollToBottomButton](https://getstream.io/chat/docs/sdk/reactnative/ui-components/scroll-to-bottom-button/) - */ - ScrollToBottomButton: React.ComponentType; sendReaction: (type: string, messageId: string) => Promise; - /** - * UI component for StreamingMessageView. Displays the text of a message with a typewriter animation. - */ - StreamingMessageView: React.ComponentType; - /** - * UI component for TypingIndicator - * Defaults to: [TypingIndicator](https://getstream.io/chat/docs/sdk/reactnative/ui-components/typing-indicator/) - */ - TypingIndicator: React.ComponentType; - /** - * UI component for TypingIndicatorContainer - * Defaults to: [TypingIndicatorContainer](https://getstream.io/chat/docs/sdk/reactnative/contexts/messages-context/#typingindicatorcontainer) - */ - TypingIndicatorContainer: React.ComponentType; - UnreadMessagesNotification: React.ComponentType; updateMessage: ( updatedMessage: MessageResponse | LocalMessage, extraState?: { @@ -393,17 +91,6 @@ export type MessagesContextValue = Pick void; - /** - * Custom UI component to display enriched url preview. - * Defaults to https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/UrlPreview/URLPreview.tsx - */ - UrlPreview: React.ComponentType; - /** - * Custom UI component to display compact url preview. - * Defaults to https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/UrlPreview/URLPreviewCompact.tsx - */ - URLPreviewCompact: React.ComponentType; - VideoThumbnail: React.ComponentType; /** * Provide any additional props for `Pressable` which wraps inner MessageContent component here. * Please check docs for Pressable for supported props - https://reactnative.dev/docs/pressable#props @@ -537,17 +224,10 @@ export type MessagesContextValue = Pick MessageActionType[]; - /** - * Custom message header component - */ - MessageHeader: React.ComponentType; - MessageSwipeContent?: React.ComponentType; /** * HitSlop for the message swipe to reply gesture */ messageSwipeToReplyHitSlop?: ViewProps['hitSlop']; - /** Custom UI component for message text */ - MessageText?: React.ComponentType; /** * The number of lines of the message text to be displayed */ @@ -637,17 +317,7 @@ export type MessagesContextValue = Pick void; - /** - * Override the entire content of the Poll component. The component has full access to the - * usePollState() and usePollContext() hooks. - * */ - PollContent?: React.ComponentType; quotedMessage?: LocalMessage | null; - /** - * UI component for ReactionListTop - * Defaults to: [ReactionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Reaction/ReactionList.tsx) - */ - ReactionListBottom?: React.ComponentType; /** * The position of the reaction list in the message */ @@ -658,31 +328,6 @@ export type MessagesContextValue = Pick; - - /** - * UI component for ReactionListBottom - * Defaults to: [ReactionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Reaction/ReactionList.tsx) - */ - ReactionListClustered: React.ComponentType; - /** - * UI component for ReactionListSegmented - * Defaults to: [ReactionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Reaction/ReactionList.tsx) - */ - ReactionListItem: React.ComponentType; - - /** - * UI component for ReactionListItemWrapper - * Defaults to: [ReactionListItemWrapper](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Reaction/ReactionListItemWrapper.tsx) - */ - ReactionListItemWrapper: React.ComponentType; - - ReactionListCountItem: React.ComponentType; - /** * Full override of the reaction function on Message and Message Overlay * diff --git a/package/src/contexts/overlayContext/MessageOverlayHostLayer.tsx b/package/src/contexts/overlayContext/MessageOverlayHostLayer.tsx index ffe82957dd..e4844914c1 100644 --- a/package/src/contexts/overlayContext/MessageOverlayHostLayer.tsx +++ b/package/src/contexts/overlayContext/MessageOverlayHostLayer.tsx @@ -28,11 +28,12 @@ import { Rect, useOverlayController, } from '../../state-store'; +import { useComponentsContext } from '../componentsContext/ComponentsContext'; import { useTheme } from '../themeContext/ThemeContext'; const DURATION = 300; -const DefaultMessageOverlayBackground = () => { +export const DefaultMessageOverlayBackground = () => { const { theme: { semantics }, } = useTheme(); @@ -52,11 +53,8 @@ const DefaultMessageOverlayBackground = () => { ); }; -type MessageOverlayHostLayerProps = { - BackgroundComponent?: React.ComponentType; -}; - -export const MessageOverlayHostLayer = ({ BackgroundComponent }: MessageOverlayHostLayerProps) => { +export const MessageOverlayHostLayer = () => { + const { MessageOverlayBackground } = useComponentsContext(); const { id, closing } = useOverlayController(); const insets = useSafeAreaInsets(); const { height: screenH } = useWindowDimensions(); @@ -123,7 +121,7 @@ export const MessageOverlayHostLayer = ({ BackgroundComponent }: MessageOverlayH opacity: backdrop.value, })); - const OverlayBackground = BackgroundComponent ?? DefaultMessageOverlayBackground; + const OverlayBackground = MessageOverlayBackground; const messageShiftY = useDerivedValue(() => { if (!messageH.value || !topH.value || !bottomH.value) return 0; diff --git a/package/src/contexts/overlayContext/OverlayContext.tsx b/package/src/contexts/overlayContext/OverlayContext.tsx index 97b681f30e..0417048c94 100644 --- a/package/src/contexts/overlayContext/OverlayContext.tsx +++ b/package/src/contexts/overlayContext/OverlayContext.tsx @@ -26,10 +26,6 @@ export const OverlayContext = React.createContext( export type OverlayProviderProps = ImageGalleryProviderProps & { /** https://github.com/GetStream/stream-chat-react-native/wiki/Internationalization-(i18n) */ i18nInstance?: Streami18n; - /** - * Custom backdrop component rendered behind overlay content in `MessageOverlayHostLayer`. - */ - MessageOverlayBackground?: React.ComponentType; value?: Partial; }; diff --git a/package/src/contexts/overlayContext/OverlayProvider.tsx b/package/src/contexts/overlayContext/OverlayProvider.tsx index beea9854e6..b809988c93 100644 --- a/package/src/contexts/overlayContext/OverlayProvider.tsx +++ b/package/src/contexts/overlayContext/OverlayProvider.tsx @@ -44,7 +44,6 @@ export const OverlayProvider = (props: PropsWithChildren) const { children, i18nInstance, - MessageOverlayBackground, value, autoPlayVideo, giphyVersion, @@ -107,7 +106,7 @@ export const OverlayProvider = (props: PropsWithChildren) {children} {overlay === 'gallery' && } - + diff --git a/package/src/contexts/overlayContext/__tests__/MessageOverlayHostLayer.test.tsx b/package/src/contexts/overlayContext/__tests__/MessageOverlayHostLayer.test.tsx index 4ec91f1914..7facffe86d 100644 --- a/package/src/contexts/overlayContext/__tests__/MessageOverlayHostLayer.test.tsx +++ b/package/src/contexts/overlayContext/__tests__/MessageOverlayHostLayer.test.tsx @@ -13,6 +13,7 @@ import { setOverlayMessageH, setOverlayTopH, } from '../../../state-store'; +import { WithComponents } from '../../componentsContext/ComponentsContext'; import { MessageOverlayHostLayer } from '../MessageOverlayHostLayer'; jest.mock('react-native', () => { @@ -142,7 +143,11 @@ describe('MessageOverlayHostLayer', () => { it('renders the custom background only while active and pressing the backdrop starts closing', () => { const CustomBackground = () => Background; - render(); + render( + + + , + ); expect(screen.queryByTestId('custom-background')).toBeNull(); expect(screen.queryByTestId('message-overlay-backdrop')).toBeNull(); @@ -161,7 +166,12 @@ describe('MessageOverlayHostLayer', () => { }); it('positions and translates the top, message, and bottom hosts using the registered rects', () => { - const { rerender } = render(); + const renderTree = () => ( + + + + ); + const { rerender } = render(renderTree()); act(() => {}); @@ -172,7 +182,7 @@ describe('MessageOverlayHostLayer', () => { openOverlay('message-1'); }); - rerender(); + rerender(renderTree()); const topSlot = screen.getByTestId('message-overlay-top'); const messageSlot = screen.getByTestId('message-overlay-message'); @@ -205,7 +215,12 @@ describe('MessageOverlayHostLayer', () => { }); it('resets host geometry after finalizeCloseOverlay clears the registered rects', () => { - const { rerender } = render(); + const renderTree = () => ( + + + + ); + const { rerender } = render(renderTree()); act(() => {}); @@ -216,7 +231,7 @@ describe('MessageOverlayHostLayer', () => { openOverlay('message-1'); }); - rerender(); + rerender(renderTree()); expect( StyleSheet.flatten(screen.getByTestId('message-overlay-message').props.style), @@ -229,7 +244,7 @@ describe('MessageOverlayHostLayer', () => { finalizeCloseOverlay(); }); - rerender(); + rerender(renderTree()); expect(StyleSheet.flatten(screen.getByTestId('message-overlay-top').props.style)).toMatchObject( { diff --git a/package/src/contexts/threadsContext/ThreadsContext.tsx b/package/src/contexts/threadsContext/ThreadsContext.tsx index 7116027dcc..d23aeeb1f9 100644 --- a/package/src/contexts/threadsContext/ThreadsContext.tsx +++ b/package/src/contexts/threadsContext/ThreadsContext.tsx @@ -4,8 +4,6 @@ import { FlatListProps } from 'react-native'; import { Channel, Thread } from 'stream-chat'; -import type { ThreadListItemMessagePreviewProps } from '../../components/ThreadList/ThreadListItemMessagePreview'; -import type { ThreadMessagePreviewDeliveryStatusProps } from '../../components/ThreadList/ThreadMessagePreviewDeliveryStatus'; import { ThreadType } from '../threadContext/ThreadContext'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; @@ -19,13 +17,6 @@ export type ThreadsContextValue = { additionalFlatListProps?: Partial>; loadMore?: () => Promise; onThreadSelect?: (thread: ThreadType, channel: Channel) => void; - ThreadListEmptyPlaceholder?: React.ComponentType; - ThreadListItem?: React.ComponentType; - ThreadListItemMessagePreview?: React.ComponentType; - ThreadListLoadingIndicator?: React.ComponentType; - ThreadListLoadingMoreIndicator?: React.ComponentType; - ThreadListUnreadBanner?: React.ComponentType; - ThreadMessagePreviewDeliveryStatus?: React.ComponentType; }; export const ThreadsContext = React.createContext(