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