Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7a0b8a9
feat: implement channel actions hook
isekovanic Feb 27, 2026
f7e3e50
fix: add new translations
isekovanic Feb 27, 2026
0263002
feat: add swipability to channel list item
isekovanic Feb 27, 2026
37fd605
fix: improve swipable animation
isekovanic Feb 27, 2026
95d7361
fix: overshooting
isekovanic Feb 27, 2026
21bf623
fix: resolve defaults properly
isekovanic Feb 27, 2026
3c61a9d
fix: renderitem default
isekovanic Feb 27, 2026
23a179c
feat: implement swipable registry
isekovanic Feb 27, 2026
626b4c4
chore: cleanup
isekovanic Feb 27, 2026
6932c1d
chore: pressable with children
isekovanic Feb 27, 2026
84b8d57
fix: close swipable on aside press
isekovanic Mar 2, 2026
7cd9723
fix: getItemLayout
isekovanic Mar 2, 2026
030637f
feat: introduce action items
isekovanic Mar 2, 2026
7b5dd50
feat: implement missing action hooks
isekovanic Mar 2, 2026
9da27e6
feat: extend swipable wrapper with dynamic items
isekovanic Mar 2, 2026
2f184f8
refactor: move ui logic to items hook
isekovanic Mar 2, 2026
d4241a1
fix: remove redundant arguments
isekovanic Mar 2, 2026
df9cf22
feat: implement existing actions
isekovanic Mar 2, 2026
934f4ba
feat: implement all of the items for channel actions
isekovanic Mar 2, 2026
a7b9236
feat: implement quick swipable actions
isekovanic Mar 2, 2026
84e2b39
feat: implement various channel states
isekovanic Mar 2, 2026
141e3bd
feat: introduce membercount hook
isekovanic Mar 2, 2026
af010fc
fix: remove logs
isekovanic Mar 2, 2026
4e7101a
chore: add override and fix tests
isekovanic Mar 2, 2026
74cd54c
chore: add override for channel actions
isekovanic Mar 2, 2026
8830817
chore: translations
isekovanic Mar 2, 2026
fa36d9c
fix: darkmode
isekovanic Mar 2, 2026
4a0766a
fix: translations
isekovanic Mar 2, 2026
41c923d
fix: lint issues
isekovanic Mar 2, 2026
db698e8
Merge branch 'develop' into feat/channel-details-and-info
isekovanic Mar 3, 2026
8fe488a
fix: merging issues
isekovanic Mar 3, 2026
da5525a
fix: imports
isekovanic Mar 3, 2026
ff69b66
chore: add theming
isekovanic Mar 3, 2026
b55490f
fix: tests
isekovanic Mar 3, 2026
7eef776
feat: add a way to disable swipability
isekovanic Mar 3, 2026
cef14e9
feat: add component overrides
isekovanic Mar 3, 2026
174f4f6
fix: tests
isekovanic Mar 3, 2026
57f75d8
fix: lint issues
isekovanic Mar 3, 2026
22c5db8
fix: remove unnecessary styles
isekovanic Mar 3, 2026
fe5f9cf
fix: sample app failures
isekovanic Mar 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
462 changes: 165 additions & 297 deletions examples/SampleApp/src/components/ChannelInfoOverlay.tsx

Large diffs are not rendered by default.

97 changes: 4 additions & 93 deletions examples/SampleApp/src/components/ChannelPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { RectButton } from 'react-native-gesture-handler';
import Swipeable from 'react-native-gesture-handler/Swipeable';
import {
ChannelPreviewMessenger,
ChannelPreviewMessengerProps,
ChannelPreviewStatus,
ChannelPreviewStatusProps,
Delete,
Pin,
useChannelMembershipState,
useChatContext,
useTheme,
} from 'stream-chat-react-native';

import { useAppOverlayContext } from '../context/AppOverlayContext';
import { useBottomSheetOverlayContext } from '../context/BottomSheetOverlayContext';
import { useChannelInfoOverlayContext } from '../context/ChannelInfoOverlayContext';

import type { NativeStackNavigationProp } from '@react-navigation/native-stack';

import type { StackNavigatorParamList } from '../types';
import { ChannelState } from 'stream-chat';
import { MenuPointHorizontal } from '../icons/MenuPointHorizontal';

const styles = StyleSheet.create({
leftSwipeableButton: {
paddingLeft: 16,
Expand All @@ -49,15 +34,10 @@ const styles = StyleSheet.create({
},
});

type ChannelListScreenNavigationProp = NativeStackNavigationProp<
StackNavigatorParamList,
'ChannelListScreen'
>;
const CustomChannelPreviewStatus = (props: ChannelPreviewStatusProps) => {
const { channel } = props;

const CustomChannelPreviewStatus = (
props: ChannelPreviewStatusProps & { membership: ChannelState['membership'] },
) => {
const { membership } = props;
const membership = useChannelMembershipState(channel);
const {
theme: { semantics },
} = useTheme();
Expand All @@ -75,74 +55,5 @@ const CustomChannelPreviewStatus = (
};

export const ChannelPreview: React.FC<ChannelPreviewMessengerProps> = (props) => {
const { channel } = props;

const { setOverlay } = useAppOverlayContext();

const { setData: setDataBottomSheet } = useBottomSheetOverlayContext();

const { data, setData } = useChannelInfoOverlayContext();

const { client } = useChatContext();

const navigation = useNavigation<ChannelListScreenNavigationProp>();

const membership = useChannelMembershipState(channel);

const {
theme: {
colors: { accent_red, white_smoke },
},
} = useTheme();

const otherMembers = channel
? Object.values(channel.state.members).filter((member) => member.user?.id !== data?.clientId)
: [];

return (
<Swipeable
overshootLeft={false}
overshootRight={false}
renderRightActions={() => (
<View style={[styles.swipeableContainer, { backgroundColor: white_smoke }]}>
<RectButton
onPress={() => {
setData({ channel, clientId: client.userID, membership, navigation });
setOverlay('channelInfo');
}}
style={[styles.leftSwipeableButton]}
>
<MenuPointHorizontal />
</RectButton>
<RectButton
onPress={() => {
setDataBottomSheet({
confirmText: 'DELETE',
onConfirm: () => {
channel.delete();
setOverlay('none');
},
subtext: `Are you sure you want to delete this ${
otherMembers.length === 1 ? 'conversation' : 'group'
}?`,
title: `Delete ${otherMembers.length === 1 ? 'Conversation' : 'Group'}`,
});
setOverlay('confirmation');
}}
style={[styles.rightSwipeableButton]}
>
<Delete height={20} width={20} stroke={accent_red} />
</RectButton>
</View>
)}
>
<ChannelPreviewMessenger
{...props}
// eslint-disable-next-line react/no-unstable-nested-components
PreviewStatus={(statusProps) => (
<CustomChannelPreviewStatus {...statusProps} membership={membership} />
)}
/>
</Swipeable>
);
return <ChannelPreviewMessenger {...props} PreviewStatus={CustomChannelPreviewStatus} />;
};
26 changes: 26 additions & 0 deletions examples/SampleApp/src/icons/ChannelInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { useTheme, IconProps } from 'stream-chat-react-native';

export const ChannelInfo = (props: IconProps) => {
const {
theme: {
semantics,
},
} = useTheme();

return (
<Svg
width={20}
height={20}
viewBox='0 0 20 20'
fill='none'
{...props}
>
<Path
d='M8.95841 8.41663C8.5442 8.41663 8.20841 8.75241 8.20841 9.16663C8.20841 9.58084 8.5442 9.91663 8.95841 9.91663V9.16663V8.41663ZM10.0001 9.16663H10.7501C10.7501 8.75241 10.4143 8.41663 10.0001 8.41663V9.16663ZM9.25008 13.5416C9.25008 13.9558 9.58587 14.2916 10.0001 14.2916C10.4143 14.2916 10.7501 13.9558 10.7501 13.5416H10.0001H9.25008ZM8.95841 9.16663V9.91663H10.0001V9.16663V8.41663H8.95841V9.16663ZM10.0001 9.16663H9.25008V13.5416H10.0001H10.7501V9.16663H10.0001ZM17.7084 9.99996H16.9584C16.9584 13.8429 13.843 16.9583 10.0001 16.9583V17.7083V18.4583C14.6715 18.4583 18.4584 14.6713 18.4584 9.99996H17.7084ZM10.0001 17.7083V16.9583C6.1571 16.9583 3.04175 13.8429 3.04175 9.99996H2.29175H1.54175C1.54175 14.6713 5.32867 18.4583 10.0001 18.4583V17.7083ZM2.29175 9.99996H3.04175C3.04175 6.15698 6.1571 3.04163 10.0001 3.04163V2.29163V1.54163C5.32868 1.54163 1.54175 5.32855 1.54175 9.99996H2.29175ZM10.0001 2.29163V3.04163C13.843 3.04163 16.9584 6.15698 16.9584 9.99996H17.7084H18.4584C18.4584 5.32855 14.6715 1.54163 10.0001 1.54163V2.29163ZM9.89592 6.1458V5.3958C9.19402 5.3958 8.62508 5.96478 8.62508 6.66663H9.37508H10.1251C10.1251 6.79318 10.0225 6.8958 9.89592 6.8958V6.1458ZM9.37508 6.66663H8.62508C8.62508 7.36848 9.19402 7.93747 9.89592 7.93747V7.18747V6.43747C10.0225 6.43747 10.1251 6.54008 10.1251 6.66663H9.37508ZM9.89592 7.18747V7.93747C10.5978 7.93747 11.1667 7.36848 11.1667 6.66663H10.4167H9.66675C9.66675 6.54008 9.76935 6.43747 9.89592 6.43747V7.18747ZM10.4167 6.66663H11.1667C11.1667 5.96478 10.5978 5.3958 9.89592 5.3958V6.1458V6.8958C9.76935 6.8958 9.66675 6.79318 9.66675 6.66663H10.4167Z'
fill={semantics.textTertiary}
/>
</Svg>
);
};
56 changes: 51 additions & 5 deletions examples/SampleApp/src/icons/MenuPointHorizontal.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,58 @@
import React from 'react';

import { IconProps, RootPath, RootSvg } from 'stream-chat-react-native';
import Svg, { Path } from 'react-native-svg';

import { IconProps } from './utils/base';

export const MenuPointHorizontal = (props: IconProps) => (
<RootSvg {...props}>
<RootPath
d='M8 12a2 2 0 11-4 0 2 2 0 014 0zM14 12a2 2 0 11-4 0 2 2 0 014 0zM18 14a2 2 0 100-4 2 2 0 000 4z'
<Svg viewBox='0 0 17 4' fill='none' {...props} width={20} height={20}>
<Path
d='M8.45833 2.41667C8.91858 2.41667 9.29167 2.04358 9.29167 1.58333C9.29167 1.12308 8.91858 0.75 8.45833 0.75C7.99808 0.75 7.625 1.12308 7.625 1.58333C7.625 2.04358 7.99808 2.41667 8.45833 2.41667Z'
fill='black'
/>
<Path
d='M15.3333 2.41667C15.7936 2.41667 16.1667 2.04358 16.1667 1.58333C16.1667 1.12308 15.7936 0.75 15.3333 0.75C14.8731 0.75 14.5 1.12308 14.5 1.58333C14.5 2.04358 14.8731 2.41667 15.3333 2.41667Z'
fill='black'
/>
<Path
d='M1.58333 2.41667C2.04357 2.41667 2.41667 2.04358 2.41667 1.58333C2.41667 1.12308 2.04357 0.75 1.58333 0.75C1.1231 0.75 0.75 1.12308 0.75 1.58333C0.75 2.04358 1.1231 2.41667 1.58333 2.41667Z'
fill='black'
/>
<Path
d='M8.45833 2.41667C8.91858 2.41667 9.29167 2.04358 9.29167 1.58333C9.29167 1.12308 8.91858 0.75 8.45833 0.75C7.99808 0.75 7.625 1.12308 7.625 1.58333C7.625 2.04358 7.99808 2.41667 8.45833 2.41667Z'
strokeWidth={1.5}
strokeLinecap='round'
{...props}
/>
<Path
d='M15.3333 2.41667C15.7936 2.41667 16.1667 2.04358 16.1667 1.58333C16.1667 1.12308 15.7936 0.75 15.3333 0.75C14.8731 0.75 14.5 1.12308 14.5 1.58333C14.5 2.04358 14.8731 2.41667 15.3333 2.41667Z'
strokeWidth={1.5}
strokeLinecap='round'
{...props}
/>
<Path
d='M1.58333 2.41667C2.04357 2.41667 2.41667 2.04358 2.41667 1.58333C2.41667 1.12308 2.04357 0.75 1.58333 0.75C1.1231 0.75 0.75 1.12308 0.75 1.58333C0.75 2.04358 1.1231 2.41667 1.58333 2.41667Z'
strokeWidth={1.5}
strokeLinecap='round'
{...props}
/>
<Path
d='M8.45833 2.41667C8.91858 2.41667 9.29167 2.04358 9.29167 1.58333C9.29167 1.12308 8.91858 0.75 8.45833 0.75C7.99808 0.75 7.625 1.12308 7.625 1.58333C7.625 2.04358 7.99808 2.41667 8.45833 2.41667Z'
strokeWidth={1.5}
strokeLinecap='round'
{...props}
/>
<Path
d='M15.3333 2.41667C15.7936 2.41667 16.1667 2.04358 16.1667 1.58333C16.1667 1.12308 15.7936 0.75 15.3333 0.75C14.8731 0.75 14.5 1.12308 14.5 1.58333C14.5 2.04358 14.8731 2.41667 15.3333 2.41667Z'
strokeWidth={1.5}
strokeLinecap='round'
{...props}
/>
<Path
d='M1.58333 2.41667C2.04357 2.41667 2.41667 2.04358 2.41667 1.58333C2.41667 1.12308 2.04357 0.75 1.58333 0.75C1.1231 0.75 0.75 1.12308 0.75 1.58333C0.75 2.04358 1.1231 2.41667 1.58333 2.41667Z'
strokeWidth={1.5}
strokeLinecap='round'
{...props}
/>
</RootSvg>
</Svg>
);
39 changes: 35 additions & 4 deletions examples/SampleApp/src/screens/ChannelListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
View,
} from 'react-native';
import { useNavigation, useScrollToTop } from '@react-navigation/native';
import { ChannelList, useTheme } from 'stream-chat-react-native';
import { ChannelList, useTheme, useStableCallback, ChannelActionItem } from 'stream-chat-react-native';
import { Channel } from 'stream-chat';
import { ChannelPreview } from '../components/ChannelPreview';
import { ChatScreenHeader } from '../components/ChatScreenHeader';
Expand All @@ -20,7 +20,8 @@ import { usePaginatedSearchedMessages } from '../hooks/usePaginatedSearchedMessa
import type { ChannelSort } from 'stream-chat';
import { useStreamChatContext } from '../context/StreamChatContext';
import { Search } from '../icons/Search';
import { CircleClose } from '../icons/CircleClose';
import { ChannelInfo } from '../icons/ChannelInfo.tsx';
import { CircleClose } from '../icons/CircleClose.tsx';

const styles = StyleSheet.create({
channelListContainer: {
Expand Down Expand Up @@ -119,8 +120,8 @@ export const ChannelListScreen: React.FC = () => {
() => ({
getItemLayout: (_: unknown, index: number) => ({
index,
length: 65,
offset: 65 * index,
length: 80,
offset: 80 * index,
}),
keyboardDismissMode: 'on-drag',
}),
Expand All @@ -144,6 +145,35 @@ export const ChannelListScreen: React.FC = () => {
[],
);

const getChannelActionItems = useStableCallback(({ context: { isDirectChat, channel }, defaultItems }) => {
const viewInfo = () => {
if (!channel) {
return;
}
if (navigation) {
if (isDirectChat) {
navigation.navigate('OneOnOneChannelDetailScreen', {
channel,
});
} else {
navigation.navigate('GroupChannelDetailsScreen', {
channel,
});
}
}
};

const viewInfoItem: ChannelActionItem = {
action: viewInfo,
Icon: ChannelInfo,
id: 'info',
label: 'View Info',
placement: 'sheet',
type: 'standard',
}
return [viewInfoItem, ...defaultItems]
})

if (!chatClient) {
return null;
}
Expand Down Expand Up @@ -226,6 +256,7 @@ export const ChannelListScreen: React.FC = () => {
options={options}
Preview={ChannelPreview}
setFlatListRef={setScrollRef}
getChannelActionItems={getChannelActionItems}
sort={sort}
/>
</View>
Expand Down
14 changes: 13 additions & 1 deletion package/src/components/ChannelList/ChannelList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
ChannelsProvider,
} from '../../contexts/channelsContext/ChannelsContext';
import { useChatContext } from '../../contexts/chatContext/ChatContext';
import { SwipeRegistryProvider } from '../../contexts/swipeableContext/SwipeRegistryContext';
import type { ChannelListEventListenerOptions } from '../../types/types';
import { ChannelPreview } from '../ChannelPreview/ChannelPreview';
import { EmptyStateIndicator as EmptyStateIndicatorDefault } from '../Indicators/EmptyStateIndicator';
Expand All @@ -51,6 +52,9 @@ export type ChannelListProps = Partial<
| 'PreviewStatus'
| 'PreviewTitle'
| 'PreviewUnreadCount'
| 'ChannelDetailsBottomSheet'
| 'getChannelActionItems'
| 'swipeActionsEnabled'
| 'loadMoreThreshold'
| 'Skeleton'
| 'maxUnreadCount'
Expand Down Expand Up @@ -276,17 +280,20 @@ export const ChannelList = (props: ChannelListProps) => {
onSelect,
options = DEFAULT_OPTIONS,
Preview = ChannelPreview,
getChannelActionItems,
PreviewAvatar,
PreviewMessage,
PreviewMutedStatus,
PreviewStatus,
PreviewTitle,
PreviewUnreadCount,
ChannelDetailsBottomSheet,
setFlatListRef,
Skeleton = SkeletonDefault,
sort = DEFAULT_SORT,
queryChannelsOverride,
mutedStatusPosition = 'inlineTitle',
swipeActionsEnabled = true,
} = props;

const [forceUpdate, setForceUpdate] = useState(0);
Expand Down Expand Up @@ -403,12 +410,15 @@ export const ChannelList = (props: ChannelListProps) => {
numberOfSkeletons,
onSelect,
Preview,
getChannelActionItems,
PreviewAvatar,
PreviewMessage,
PreviewMutedStatus,
PreviewStatus,
PreviewTitle,
PreviewUnreadCount,
ChannelDetailsBottomSheet,
swipeActionsEnabled,
refreshing,
refreshList,
reloadList,
Expand All @@ -423,7 +433,9 @@ export const ChannelList = (props: ChannelListProps) => {

return (
<ChannelsProvider value={channelsContext}>
<List />
<SwipeRegistryProvider>
<List />
</SwipeRegistryProvider>
</ChannelsProvider>
);
};
15 changes: 8 additions & 7 deletions package/src/components/ChannelList/ChannelListMessenger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useChatContext } from '../../contexts/chatContext/ChatContext';
import { useDebugContext } from '../../contexts/debugContext/DebugContext';
import { useTheme } from '../../contexts/themeContext/ThemeContext';

import { useStableCallback } from '../../hooks';
import { ChannelPreview } from '../ChannelPreview/ChannelPreview';

const styles = StyleSheet.create({
Expand Down Expand Up @@ -127,6 +128,13 @@ const ChannelListMessengerWithContext = (props: ChannelListMessengerPropsWithCon
}
}

const onEndReached = useStableCallback(() => {
if (!onEndReachedCalledDuringCurrentScrollRef.current && hasNextPage) {
loadNextPage();
onEndReachedCalledDuringCurrentScrollRef.current = true;
}
});

if (error && !refreshing && !loadingChannels && (channels === null || !channelListInitialized)) {
return (
<LoadingErrorIndicator
Expand All @@ -138,13 +146,6 @@ const ChannelListMessengerWithContext = (props: ChannelListMessengerPropsWithCon
);
}

const onEndReached = () => {
if (!onEndReachedCalledDuringCurrentScrollRef.current && hasNextPage) {
loadNextPage();
onEndReachedCalledDuringCurrentScrollRef.current = true;
}
};

return (
<>
<FlatList
Expand Down
Loading
Loading