Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: collapse sidebar groups #33592

Merged
merged 7 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
14 changes: 14 additions & 0 deletions .changeset/khaki-boxes-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@rocket.chat/fuselage-ui-kit': minor
'@rocket.chat/ui-theming': minor
'@rocket.chat/ui-video-conf': minor
'@rocket.chat/uikit-playground': minor
'@rocket.chat/ui-composer': minor
'@rocket.chat/gazzodown': minor
'@rocket.chat/ui-avatar': minor
'@rocket.chat/ui-client': minor
'@rocket.chat/ui-voip': minor
'@rocket.chat/meteor': minor
---

Adds ability to collapse/expand sidebar groups
47 changes: 19 additions & 28 deletions apps/meteor/client/sidebarv2/RoomList/RoomList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/* eslint-disable react/no-multi-comp */
import type { ISubscription, IRoom } from '@rocket.chat/core-typings';
import { Box, SidebarV2GroupTitle } from '@rocket.chat/fuselage';
import { Box, SidebarV2CollapseGroup } from '@rocket.chat/fuselage';
import { useResizeObserver } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useUserPreference, useUserId } from '@rocket.chat/ui-contexts';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -19,30 +17,10 @@ import RoomListRow from './RoomListRow';
import RoomListRowWrapper from './RoomListRowWrapper';
import RoomListWrapper from './RoomListWrapper';

const getRoomsByGroup = (rooms: (ISubscription & IRoom)[]) => {
const groupCounts = rooms
.reduce((acc, item, index) => {
if (typeof item === 'string') {
acc.push(index);
}
return acc;
}, [] as number[])
.map((item, index, arr) => (arr[index + 1] ? arr[index + 1] : rooms.length) - item - 1);

const groupList = rooms.filter((item) => typeof item === 'string') as unknown as TranslationKey[];
const roomList = rooms.filter((item) => typeof item !== 'string');

return {
groupCounts,
groupList,
roomList,
};
};

const RoomList = () => {
const { t } = useTranslation();
const isAnonymous = !useUserId();
const roomsList = useRoomList();
const { groupsCount, groupsList, roomList, handleCollapsedGroups, collapsedGroups } = useRoomList();
const avatarTemplate = useAvatarTemplate();
const sideBarItemTemplate = useTemplateByViewMode();
const { ref } = useResizeObserver<HTMLElement>({ debounceDelay: 100 });
Expand All @@ -66,14 +44,27 @@ const RoomList = () => {
usePreventDefault(ref);
useShortcutOpenMenu(ref);

const { groupCounts, groupList, roomList } = getRoomsByGroup(roomsList);
const handleCollapse = (groupTitle: string) => {
if (groupTitle) {
handleCollapsedGroups(groupTitle);
}
};

return (
<Box position='relative' display='flex' overflow='hidden' height='full' flexGrow={1} flexShrink={1} flexBasis='auto' ref={ref}>
<GroupedVirtuoso
groupCounts={groupCounts}
groupContent={(index) => <SidebarV2GroupTitle title={t(groupList[index])} />}
itemContent={(index) => <RoomListRow data={itemData} item={roomList[index]} />}
groupCounts={groupsCount}
groupContent={(index) => (
<SidebarV2CollapseGroup
aleksandernsilva marked this conversation as resolved.
Show resolved Hide resolved
aleksandernsilva marked this conversation as resolved.
Show resolved Hide resolved
title={t(groupsList[index])}
onClick={() => handleCollapse(groupsList[index])}
onKeyDown={() => handleCollapse(groupsList[index])}
expanded={!collapsedGroups.includes(groupsList[index])}
/>
)}
{...(roomList.length > 0 && {
itemContent: (index) => roomList[index] && <RoomListRow data={itemData} item={roomList[index]} />,
})}
components={{ Item: RoomListRowWrapper, List: RoomListWrapper, Scroller: VirtuosoScrollbars }}
/>
</Box>
Expand Down
158 changes: 112 additions & 46 deletions apps/meteor/client/sidebarv2/hooks/useRoomList.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { ILivechatInquiryRecord, IRoom, ISubscription } from '@rocket.chat/core-typings';
import { useDebouncedState } from '@rocket.chat/fuselage-hooks';
import { useDebouncedValue, useLocalStorage } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey, SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import { useUserPreference, useUserSubscriptions, useSetting } from '@rocket.chat/ui-contexts';
import { useEffect } from 'react';
import { useCallback, useMemo } from 'react';

import { useVideoConfIncomingCalls } from '../../contexts/VideoConfContext';
import { useOmnichannelEnabled } from '../../hooks/omnichannel/useOmnichannelEnabled';
Expand All @@ -12,19 +13,7 @@ const query = { open: { $ne: false } };

const emptyQueue: ILivechatInquiryRecord[] = [];

const order: (
| 'Incoming_Calls'
| 'Incoming_Livechats'
| 'Open_Livechats'
| 'On_Hold_Chats'
| 'Unread'
| 'Favorites'
| 'Teams'
| 'Discussions'
| 'Channels'
| 'Direct_Messages'
| 'Conversations'
)[] = [
const order = [
'Incoming_Calls',
'Incoming_Livechats',
'Open_Livechats',
Expand All @@ -36,10 +25,42 @@ const order: (
'Channels',
'Direct_Messages',
'Conversations',
];
] as const;

const CATEGORIES = {
Incoming_Calls: 'Incoming_Calls',
Incoming_Livechats: 'Incoming_Livechats',
Open_Livechats: 'Open_Livechats',
On_Hold_Chats: 'On_Hold_Chats',
Unread: 'Unread',
Favorites: 'Favorites',
Teams: 'Teams',
Discussions: 'Discussions',
Channels: 'Channels',
Direct_Messages: 'Direct_Messages',
Conversations: 'Conversations',
};

const getGroupsCount = (rooms: Array<ISubscription & IRoom>) => {
return rooms
.reduce((acc, item, index) => {
if (typeof item === 'string') {
acc.push(index);
}
return acc;
}, [] as number[])
.map((item, index, arr) => (arr[index + 1] ? arr[index + 1] : rooms.length) - item - 1);
};

export const useRoomList = (): Array<ISubscription & IRoom> => {
const [roomList, setRoomList] = useDebouncedState<(ISubscription & IRoom)[]>([], 150);
export const useRoomList = (): {
flatList: Array<ISubscription & IRoom>;
roomList: Array<ISubscription & IRoom>;
groupsCount: number[];
groupsList: TranslationKey[];
handleCollapsedGroups: (groupTitle: string) => void;
collapsedGroups: string[];
} => {
const [collapsedGroups, setCollapsedGroups] = useLocalStorage<string[]>('sidebarGroups', []);

const showOmnichannel = useOmnichannelEnabled();
const sidebarGroupByType = useUserPreference('sidebarGroupByType');
Expand All @@ -61,8 +82,12 @@ export const useRoomList = (): Array<ISubscription & IRoom> => {
queue = inquiries.queue;
}

useEffect(() => {
setRoomList(() => {
const { flatRoomList, groupsCount, groupsList, roomList } = useDebouncedValue(
useMemo(() => {
const isCollapsed = (groupTitle: string) => collapsedGroups.includes(groupTitle);
const shouldAddUnread = (room: SubscriptionWithRoom) =>
!(sidebarShowUnread && isCollapsed(CATEGORIES.Unread) && (room.alert || room.unread));

const incomingCall = new Set();
const favorite = new Set();
const team = new Set();
Expand All @@ -83,77 +108,118 @@ export const useRoomList = (): Array<ISubscription & IRoom> => {
return incomingCall.add(room);
}

if (sidebarShowUnread && (room.alert || room.unread) && !room.hideUnreadStatus) {
if (sidebarShowUnread && (room.alert || room.unread)) {
return unread.add(room);
}

if (favoritesEnabled && room.f) {
return favorite.add(room);
return shouldAddUnread(room) && favorite.add(room);
}

if (sidebarGroupByType && room.teamMain) {
return team.add(room);
return shouldAddUnread(room) && team.add(room);
}

if (sidebarGroupByType && isDiscussionEnabled && room.prid) {
return discussion.add(room);
return shouldAddUnread(room) && discussion.add(room);
}

if (room.t === 'c' || room.t === 'p') {
channels.add(room);
shouldAddUnread(room) && channels.add(room);
}

if (room.t === 'l' && room.onHold) {
return showOmnichannel && onHold.add(room);
return showOmnichannel && shouldAddUnread(room) && onHold.add(room);
}

if (room.t === 'l') {
return showOmnichannel && omnichannel.add(room);
return showOmnichannel && shouldAddUnread(room) && omnichannel.add(room);
}

if (room.t === 'd') {
direct.add(room);
shouldAddUnread(room) && direct.add(room);
}

conversation.add(room);
if (shouldAddUnread(room)) {
conversation.add(room);
}
});

const groups = new Map();
incomingCall.size && groups.set('Incoming_Calls', incomingCall);

showOmnichannel && inquiries.enabled && queue.length && groups.set('Incoming_Livechats', queue);
showOmnichannel && omnichannel.size && groups.set('Open_Livechats', omnichannel);
showOmnichannel && onHold.size && groups.set('On_Hold_Chats', onHold);

sidebarShowUnread && unread.size && groups.set('Unread', unread);

favoritesEnabled && favorite.size && groups.set('Favorites', favorite);

sidebarGroupByType && team.size && groups.set('Teams', team);

sidebarGroupByType && isDiscussionEnabled && discussion.size && groups.set('Discussions', discussion);

sidebarGroupByType && channels.size && groups.set('Channels', channels);

sidebarGroupByType && direct.size && groups.set('Direct_Messages', direct);

!sidebarGroupByType && groups.set('Conversations', conversation);
return sidebarOrder

const groupsList: TranslationKey[] = [];
const roomList: Array<ISubscription & IRoom> = [];

const flatRoomList = sidebarOrder
.map((key) => {
const group = groups.get(key);
if (!group) {
return [];
}
groupsList.push(key);
if (isCollapsed(key)) {
return [key];
}

roomList.push(...group);

return [key, ...group];
})
.flat();
});
}, [
rooms,
showOmnichannel,
incomingCalls,
inquiries.enabled,
queue,
sidebarShowUnread,
favoritesEnabled,
sidebarGroupByType,
setRoomList,
isDiscussionEnabled,
sidebarOrder,
]);

return roomList;

return { flatRoomList, groupsCount: getGroupsCount([...flatRoomList]), groupsList, roomList };
}, [
rooms,
showOmnichannel,
incomingCalls,
inquiries.enabled,
queue,
sidebarShowUnread,
favoritesEnabled,
sidebarGroupByType,
isDiscussionEnabled,
sidebarOrder,
collapsedGroups,
]),
50,
);

const handleCollapsedGroups = useCallback(
(group: string) => {
if (collapsedGroups.includes(group)) {
setCollapsedGroups(collapsedGroups.filter((item) => item !== group));
} else {
setCollapsedGroups([...collapsedGroups, group]);
}
},
[collapsedGroups, setCollapsedGroups],
);

return {
flatList: flatRoomList,
roomList,
groupsCount,
groupsList,
handleCollapsedGroups,
collapsedGroups,
};
};
2 changes: 1 addition & 1 deletion apps/meteor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@
"@rocket.chat/forked-matrix-appservice-bridge": "^4.0.2",
"@rocket.chat/forked-matrix-bot-sdk": "^0.6.0-beta.3",
"@rocket.chat/freeswitch": "workspace:^",
"@rocket.chat/fuselage": "^0.59.1",
"@rocket.chat/fuselage": "^0.59.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/fuselage-polyfills": "~0.31.25",
"@rocket.chat/fuselage-toastbar": "^0.33.0",
Expand Down
2 changes: 1 addition & 1 deletion apps/uikit-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"@lezer/highlight": "^1.1.6",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.59.1",
"@rocket.chat/fuselage": "^0.59.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/fuselage-polyfills": "~0.31.25",
"@rocket.chat/fuselage-toastbar": "^0.33.0",
Expand Down
2 changes: 1 addition & 1 deletion ee/packages/ui-theming/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"devDependencies": {
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.59.1",
"@rocket.chat/fuselage": "^0.59.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/icons": "~0.38.0",
"@rocket.chat/ui-contexts": "workspace:~",
Expand Down
2 changes: 1 addition & 1 deletion packages/fuselage-ui-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@rocket.chat/apps-engine": "workspace:^",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/fuselage": "^0.59.1",
"@rocket.chat/fuselage": "^0.59.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/fuselage-polyfills": "~0.31.25",
"@rocket.chat/icons": "~0.38.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/gazzodown/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@babel/core": "~7.25.8",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.59.1",
"@rocket.chat/fuselage": "^0.59.3",
"@rocket.chat/fuselage-tokens": "^0.33.1",
"@rocket.chat/jest-presets": "workspace:~",
"@rocket.chat/message-parser": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-avatar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"devDependencies": {
"@babel/core": "~7.25.8",
"@rocket.chat/fuselage": "^0.59.1",
"@rocket.chat/fuselage": "^0.59.3",
"@rocket.chat/ui-contexts": "workspace:^",
"@types/react": "~17.0.80",
"@types/react-dom": "~17.0.25",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@babel/core": "~7.25.8",
"@react-aria/toolbar": "^3.0.0-beta.1",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.59.1",
"@rocket.chat/fuselage": "^0.59.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/icons": "~0.38.0",
"@rocket.chat/jest-presets": "workspace:~",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-composer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@babel/core": "~7.25.8",
"@react-aria/toolbar": "^3.0.0-beta.1",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/fuselage": "^0.59.1",
"@rocket.chat/fuselage": "^0.59.3",
"@rocket.chat/icons": "~0.38.0",
"@storybook/addon-actions": "^8.3.5",
"@storybook/addon-docs": "^8.3.5",
Expand Down
Loading
Loading