diff --git a/.changeset/khaki-boxes-suffer.md b/.changeset/khaki-boxes-suffer.md new file mode 100644 index 000000000000..844768235ec0 --- /dev/null +++ b/.changeset/khaki-boxes-suffer.md @@ -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 diff --git a/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx b/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx index 89a4aeacef40..26d41a1307e3 100644 --- a/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx +++ b/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx @@ -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'; @@ -11,6 +9,7 @@ import { GroupedVirtuoso } from 'react-virtuoso'; import { VirtuosoScrollbars } from '../../components/CustomScrollbars'; import { useOpenedRoom } from '../../lib/RoomManager'; import { useAvatarTemplate } from '../hooks/useAvatarTemplate'; +import { useCollapsedGroups } from '../hooks/useCollapsedGroups'; import { usePreventDefault } from '../hooks/usePreventDefault'; import { useRoomList } from '../hooks/useRoomList'; import { useShortcutOpenMenu } from '../hooks/useShortcutOpenMenu'; @@ -19,30 +18,12 @@ 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 { collapsedGroups, handleCollapsedGroups } = useCollapsedGroups(); + const { groupsCount, groupsList, roomList } = useRoomList({ collapsedGroups }); const avatarTemplate = useAvatarTemplate(); const sideBarItemTemplate = useTemplateByViewMode(); const { ref } = useResizeObserver({ debounceDelay: 100 }); @@ -66,14 +47,21 @@ const RoomList = () => { usePreventDefault(ref); useShortcutOpenMenu(ref); - const { groupCounts, groupList, roomList } = getRoomsByGroup(roomsList); - return ( } - itemContent={(index) => } + groupCounts={groupsCount} + groupContent={(index) => ( + handleCollapsedGroups(groupsList[index])} + onKeyDown={() => handleCollapsedGroups(groupsList[index])} + expanded={!collapsedGroups.includes(groupsList[index])} + /> + )} + {...(roomList.length > 0 && { + itemContent: (index) => roomList[index] && , + })} components={{ Item: RoomListRowWrapper, List: RoomListWrapper, Scroller: VirtuosoScrollbars }} /> diff --git a/apps/meteor/client/sidebarv2/hooks/useCollapsedGroups.ts b/apps/meteor/client/sidebarv2/hooks/useCollapsedGroups.ts new file mode 100644 index 000000000000..6aac9f157277 --- /dev/null +++ b/apps/meteor/client/sidebarv2/hooks/useCollapsedGroups.ts @@ -0,0 +1,19 @@ +import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; +import { useCallback } from 'react'; + +export const useCollapsedGroups = () => { + const [collapsedGroups, setCollapsedGroups] = useLocalStorage('sidebarGroups', []); + + const handleCollapsedGroups = useCallback( + (group: string) => { + if (collapsedGroups.includes(group)) { + setCollapsedGroups(collapsedGroups.filter((item) => item !== group)); + } else { + setCollapsedGroups([...collapsedGroups, group]); + } + }, + [collapsedGroups, setCollapsedGroups], + ); + + return { collapsedGroups, handleCollapsedGroups }; +}; diff --git a/apps/meteor/client/sidebarv2/hooks/useRoomList.ts b/apps/meteor/client/sidebarv2/hooks/useRoomList.ts index afdc57086dc4..af6e2029fa9b 100644 --- a/apps/meteor/client/sidebarv2/hooks/useRoomList.ts +++ b/apps/meteor/client/sidebarv2/hooks/useRoomList.ts @@ -1,7 +1,8 @@ import type { ILivechatInquiryRecord, IRoom, ISubscription } from '@rocket.chat/core-typings'; -import { useDebouncedState } from '@rocket.chat/fuselage-hooks'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useUserPreference, useUserSubscriptions, useSetting } from '@rocket.chat/ui-contexts'; -import { useEffect } from 'react'; +import { useMemo } from 'react'; import { useVideoConfIncomingCalls } from '../../contexts/VideoConfContext'; import { useOmnichannelEnabled } from '../../hooks/omnichannel/useOmnichannelEnabled'; @@ -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', @@ -36,11 +25,17 @@ const order: ( 'Channels', 'Direct_Messages', 'Conversations', -]; - -export const useRoomList = (): Array => { - const [roomList, setRoomList] = useDebouncedState<(ISubscription & IRoom)[]>([], 150); - +] as const; + +export const useRoomList = ({ + collapsedGroups, +}: { + collapsedGroups?: string[]; +}): { + roomList: Array; + groupsCount: number[]; + groupsList: TranslationKey[]; +} => { const showOmnichannel = useOmnichannelEnabled(); const sidebarGroupByType = useUserPreference('sidebarGroupByType'); const favoritesEnabled = useUserPreference('sidebarShowFavorites'); @@ -56,13 +51,12 @@ export const useRoomList = (): Array => { const incomingCalls = useVideoConfIncomingCalls(); - let queue = emptyQueue; - if (inquiries.enabled) { - queue = inquiries.queue; - } + const queue = inquiries.enabled ? inquiries.queue : emptyQueue; + + const { groupsCount, groupsList, roomList } = useDebouncedValue( + useMemo(() => { + const isCollapsed = (groupTitle: string) => collapsedGroups?.includes(groupTitle); - useEffect(() => { - setRoomList(() => { const incomingCall = new Set(); const favorite = new Set(); const team = new Set(); @@ -83,7 +77,7 @@ export const useRoomList = (): Array => { return incomingCall.add(room); } - if (sidebarShowUnread && (room.alert || room.unread) && !room.hideUnreadStatus) { + if (sidebarShowUnread && (room.alert || room.unread)) { return unread.add(room); } @@ -118,42 +112,76 @@ export const useRoomList = (): Array => { conversation.add(room); }); - const groups = new Map(); + const groups = new Map>(); incomingCall.size && groups.set('Incoming_Calls', incomingCall); - showOmnichannel && inquiries.enabled && queue.length && groups.set('Incoming_Livechats', queue); + + showOmnichannel && inquiries.enabled && queue.length && groups.set('Incoming_Livechats', new Set(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 - .map((key) => { - const group = groups.get(key); - if (!group) { - return []; + + const { groupsCount, groupsList, roomList } = sidebarOrder.reduce( + (acc, key) => { + const value = groups.get(key); + + if (!value) { + return acc; + } + + acc.groupsList.push(key as TranslationKey); + if (isCollapsed(key)) { + acc.groupsCount.push(0); + return acc; } - return [key, ...group]; - }) - .flat(); - }); - }, [ - rooms, - showOmnichannel, - incomingCalls, - inquiries.enabled, - queue, - sidebarShowUnread, - favoritesEnabled, - sidebarGroupByType, - setRoomList, - isDiscussionEnabled, - sidebarOrder, - ]); - - return roomList; + acc.groupsCount.push(value.size); + acc.roomList.push(...value); + return acc; + }, + { + groupsCount: [], + groupsList: [], + roomList: [], + } as { + groupsCount: number[]; + groupsList: TranslationKey[]; + roomList: Array; + }, + ); + + return { groupsCount, groupsList, roomList }; + }, [ + rooms, + showOmnichannel, + inquiries.enabled, + queue, + sidebarShowUnread, + favoritesEnabled, + sidebarGroupByType, + isDiscussionEnabled, + sidebarOrder, + collapsedGroups, + incomingCalls, + ]), + 50, + ); + + return { + roomList, + groupsCount, + groupsList, + }; }; diff --git a/apps/meteor/package.json b/apps/meteor/package.json index c2f5ffbcc819..ff9061ab8c0a 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -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", diff --git a/apps/uikit-playground/package.json b/apps/uikit-playground/package.json index 0877784db09c..f7e649d8b2b3 100644 --- a/apps/uikit-playground/package.json +++ b/apps/uikit-playground/package.json @@ -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", diff --git a/ee/packages/ui-theming/package.json b/ee/packages/ui-theming/package.json index 4cb281f188c9..0495a92ea034 100644 --- a/ee/packages/ui-theming/package.json +++ b/ee/packages/ui-theming/package.json @@ -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:~", diff --git a/packages/fuselage-ui-kit/package.json b/packages/fuselage-ui-kit/package.json index 13aa830cd6f1..d9b716238c56 100644 --- a/packages/fuselage-ui-kit/package.json +++ b/packages/fuselage-ui-kit/package.json @@ -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", diff --git a/packages/gazzodown/package.json b/packages/gazzodown/package.json index 792695fa890e..c7d11418084c 100644 --- a/packages/gazzodown/package.json +++ b/packages/gazzodown/package.json @@ -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:^", diff --git a/packages/ui-avatar/package.json b/packages/ui-avatar/package.json index 058cd096df7d..15d947c6a819 100644 --- a/packages/ui-avatar/package.json +++ b/packages/ui-avatar/package.json @@ -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", diff --git a/packages/ui-client/package.json b/packages/ui-client/package.json index c40e5e2cc29c..72ea44034243 100644 --- a/packages/ui-client/package.json +++ b/packages/ui-client/package.json @@ -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:~", diff --git a/packages/ui-composer/package.json b/packages/ui-composer/package.json index ed11fd3012cf..35549b7df460 100644 --- a/packages/ui-composer/package.json +++ b/packages/ui-composer/package.json @@ -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", diff --git a/packages/ui-video-conf/package.json b/packages/ui-video-conf/package.json index 6940eb991dec..f26e43571606 100644 --- a/packages/ui-video-conf/package.json +++ b/packages/ui-video-conf/package.json @@ -24,7 +24,7 @@ "@babel/core": "~7.25.8", "@rocket.chat/css-in-js": "~0.31.25", "@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/icons": "~0.38.0", "@rocket.chat/jest-presets": "workspace:~", diff --git a/packages/ui-voip/package.json b/packages/ui-voip/package.json index 3654e21aa468..307da263043a 100644 --- a/packages/ui-voip/package.json +++ b/packages/ui-voip/package.json @@ -28,7 +28,7 @@ "@faker-js/faker": "~8.0.2", "@rocket.chat/css-in-js": "~0.31.25", "@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/icons": "~0.38.0", "@rocket.chat/jest-presets": "workspace:~", diff --git a/yarn.lock b/yarn.lock index c54c20875ca0..8c6ee6df2ea1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8092,7 +8092,7 @@ __metadata: "@rocket.chat/apps-engine": "workspace:^" "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/fuselage": "npm:^0.59.1" + "@rocket.chat/fuselage": "npm:^0.59.3" "@rocket.chat/fuselage-hooks": "npm:^0.33.1" "@rocket.chat/fuselage-polyfills": "npm:~0.31.25" "@rocket.chat/gazzodown": "workspace:^" @@ -8151,9 +8151,9 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/fuselage@npm:^0.59.1": - version: 0.59.1 - resolution: "@rocket.chat/fuselage@npm:0.59.1" +"@rocket.chat/fuselage@npm:^0.59.3": + version: 0.59.3 + resolution: "@rocket.chat/fuselage@npm:0.59.3" dependencies: "@rocket.chat/css-in-js": "npm:^0.31.25" "@rocket.chat/css-supports": "npm:^0.31.25" @@ -8171,7 +8171,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 10/8451ad891a2731d310581e9dae96cee197d34db940c3933d5d875e9710d66e643b19164472af4727470e84bf35442f292d0d9dc11421b34aecdb0ecc83be1ccf + checksum: 10/b3677f4b7b8fc161757ff93d3619ed599ff2474b9243c8955863acbeb9b41a539029e4598be471b03e0be0219e985ddb9d15f42e306bcb060288c35f9752c5af languageName: node linkType: hard @@ -8182,7 +8182,7 @@ __metadata: "@babel/core": "npm:~7.25.8" "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/css-in-js": "npm:~0.31.25" - "@rocket.chat/fuselage": "npm:^0.59.1" + "@rocket.chat/fuselage": "npm:^0.59.3" "@rocket.chat/fuselage-tokens": "npm:^0.33.1" "@rocket.chat/jest-presets": "workspace:~" "@rocket.chat/message-parser": "workspace:^" @@ -8553,7 +8553,7 @@ __metadata: "@rocket.chat/forked-matrix-appservice-bridge": "npm:^4.0.2" "@rocket.chat/forked-matrix-bot-sdk": "npm:^0.6.0-beta.3" "@rocket.chat/freeswitch": "workspace:^" - "@rocket.chat/fuselage": "npm:^0.59.1" + "@rocket.chat/fuselage": "npm:^0.59.3" "@rocket.chat/fuselage-hooks": "npm:^0.33.1" "@rocket.chat/fuselage-polyfills": "npm:~0.31.25" "@rocket.chat/fuselage-toastbar": "npm:^0.33.0" @@ -9445,7 +9445,7 @@ __metadata: resolution: "@rocket.chat/ui-avatar@workspace:packages/ui-avatar" dependencies: "@babel/core": "npm:~7.25.8" - "@rocket.chat/fuselage": "npm:^0.59.1" + "@rocket.chat/fuselage": "npm:^0.59.3" "@rocket.chat/ui-contexts": "workspace:^" "@types/react": "npm:~17.0.80" "@types/react-dom": "npm:~17.0.25" @@ -9470,7 +9470,7 @@ __metadata: "@babel/core": "npm:~7.25.8" "@react-aria/toolbar": "npm:^3.0.0-beta.1" "@rocket.chat/css-in-js": "npm:~0.31.25" - "@rocket.chat/fuselage": "npm:^0.59.1" + "@rocket.chat/fuselage": "npm:^0.59.3" "@rocket.chat/fuselage-hooks": "npm:^0.33.1" "@rocket.chat/icons": "npm:~0.38.0" "@rocket.chat/jest-presets": "workspace:~" @@ -9522,7 +9522,7 @@ __metadata: "@babel/core": "npm:~7.25.8" "@react-aria/toolbar": "npm:^3.0.0-beta.1" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/fuselage": "npm:^0.59.1" + "@rocket.chat/fuselage": "npm:^0.59.3" "@rocket.chat/icons": "npm:~0.38.0" "@storybook/addon-actions": "npm:^8.3.5" "@storybook/addon-docs": "npm:^8.3.5" @@ -9616,7 +9616,7 @@ __metadata: resolution: "@rocket.chat/ui-theming@workspace:ee/packages/ui-theming" dependencies: "@rocket.chat/css-in-js": "npm:~0.31.25" - "@rocket.chat/fuselage": "npm:^0.59.1" + "@rocket.chat/fuselage": "npm:^0.59.3" "@rocket.chat/fuselage-hooks": "npm:^0.33.1" "@rocket.chat/icons": "npm:~0.38.0" "@rocket.chat/ui-contexts": "workspace:~" @@ -9646,7 +9646,7 @@ __metadata: "@rocket.chat/css-in-js": "npm:~0.31.25" "@rocket.chat/emitter": "npm:~0.31.25" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/fuselage": "npm:^0.59.1" + "@rocket.chat/fuselage": "npm:^0.59.3" "@rocket.chat/fuselage-hooks": "npm:^0.33.1" "@rocket.chat/icons": "npm:~0.38.0" "@rocket.chat/jest-presets": "workspace:~" @@ -9695,7 +9695,7 @@ __metadata: "@rocket.chat/css-in-js": "npm:~0.31.25" "@rocket.chat/emitter": "npm:~0.31.25" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/fuselage": "npm:^0.59.1" + "@rocket.chat/fuselage": "npm:^0.59.3" "@rocket.chat/fuselage-hooks": "npm:^0.33.1" "@rocket.chat/icons": "npm:~0.38.0" "@rocket.chat/jest-presets": "workspace:~" @@ -9752,7 +9752,7 @@ __metadata: "@lezer/highlight": "npm:^1.1.6" "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/css-in-js": "npm:~0.31.25" - "@rocket.chat/fuselage": "npm:^0.59.1" + "@rocket.chat/fuselage": "npm:^0.59.3" "@rocket.chat/fuselage-hooks": "npm:^0.33.1" "@rocket.chat/fuselage-polyfills": "npm:~0.31.25" "@rocket.chat/fuselage-toastbar": "npm:^0.33.0"