Skip to content

Commit d33fc28

Browse files
committed
Port sections to the new room list
1 parent 4a0e8d6 commit d33fc28

File tree

18 files changed

+343
-71
lines changed

18 files changed

+343
-71
lines changed

res/css/_components.pcss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@
278278
@import "./views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss";
279279
@import "./views/rooms/RoomListPanel/_RoomListSearch.pcss";
280280
@import "./views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss";
281+
@import "./views/rooms/RoomListPanel/_RoomListSectionHeaderView.pcss";
281282
@import "./views/rooms/RoomListPanel/_RoomListSkeleton.pcss";
282283
@import "./views/rooms/_AppsDrawer.pcss";
283284
@import "./views/rooms/_Autocomplete.pcss";
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
.mx_RoomListSectionHeaderView {
9+
height: 48px;
10+
width: 100%;
11+
padding-left: var(--cpd-space-3x);
12+
display: flex;
13+
align-items: end;
14+
15+
> h4 {
16+
margin-top: 0;
17+
margin-bottom: 4px;
18+
font: var(--cpd-font-body-sm-semibold);
19+
color: var(--cpd-color-text-action-accent);
20+
}
21+
}

src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,15 @@ export interface RoomListHeaderViewState {
121121
/**
122122
* Change the sort order of the room-list.
123123
*/
124-
sort: (option: SortOption) => void;
124+
sort: (option: SortOption, useSections: boolean) => void;
125125
/**
126126
* The currently active sort option.
127127
*/
128128
activeSortOption: SortOption;
129+
/**
130+
* Whether to group rooms into sections
131+
*/
132+
useSections: boolean;
129133
}
130134

131135
/**
@@ -147,7 +151,7 @@ export function useRoomListHeaderViewModel(): RoomListHeaderViewState {
147151

148152
/* Actions */
149153

150-
const { activeSortOption, sort } = useSorter();
154+
const { activeSortOption, useSections, sort } = useSorter();
151155

152156
const createChatRoom = useCallback((e: Event) => {
153157
defaultDispatcher.fire(Action.CreateChat);
@@ -219,6 +223,7 @@ export function useRoomListHeaderViewModel(): RoomListHeaderViewState {
219223
openSpacePreferences,
220224
openSpaceSettings,
221225
activeSortOption,
226+
useSections,
222227
sort,
223228
};
224229
}

src/components/viewmodels/roomlist/useRoomListNavigation.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,28 @@
55
* Please see LICENSE files in the repository root for full details.
66
*/
77

8-
import { type Room } from "matrix-js-sdk/src/matrix";
9-
108
import dispatcher from "../../../dispatcher/dispatcher";
119
import { useDispatcher } from "../../../hooks/useDispatcher";
1210
import { Action } from "../../../dispatcher/actions";
1311
import { type ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload";
1412
import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
1513
import { SdkContextClass } from "../../../contexts/SDKContext";
1614
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
15+
import { isRoomListRoom, type RoomListEntry } from "../../../stores/room-list-v3/RoomListStoreV3.ts";
1716

1817
/**
1918
* Hook to navigate the room list using keyboard shortcuts.
2019
* It listens to the ViewRoomDelta action and updates the room list accordingly.
21-
* @param rooms
20+
* @param entries
2221
*/
23-
export function useRoomListNavigation(rooms: Room[]): void {
22+
export function useRoomListNavigation(entries: RoomListEntry[]): void {
2423
useDispatcher(dispatcher, (payload) => {
2524
if (payload.action !== Action.ViewRoomDelta) return;
2625
const roomId = SdkContextClass.instance.roomViewStore.getRoomId();
2726
if (!roomId) return;
2827

28+
const rooms = entries.filter(isRoomListRoom);
29+
2930
const { delta, unread } = payload as ViewRoomDeltaPayload;
3031
const filteredRooms = unread
3132
? // Filter the rooms to only include unread ones and the active room

src/components/viewmodels/roomlist/useSorter.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ const sortOptionToSortingAlgorithm = {
3535
};
3636

3737
interface SortState {
38-
sort: (option: SortOption) => void;
38+
sort: (option: SortOption, useSections: boolean) => void;
3939
activeSortOption: SortOption;
40+
useSections: boolean;
4041
}
4142

4243
/**
@@ -48,15 +49,18 @@ export function useSorter(): SortState {
4849
const [activeSortingAlgorithm, setActiveSortingAlgorithm] = useState(() =>
4950
SettingsStore.getValue("RoomList.preferredSorting"),
5051
);
52+
const [useSections, setUseSections] = useState(() => SettingsStore.getValue("RoomList.useSections"));
5153

52-
const sort = (option: SortOption): void => {
54+
const sort = (option: SortOption, useSections: boolean): void => {
5355
const sortingAlgorithm = sortOptionToSortingAlgorithm[option];
54-
RoomListStoreV3.instance.resort(sortingAlgorithm);
56+
RoomListStoreV3.instance.resort(sortingAlgorithm, useSections);
5557
setActiveSortingAlgorithm(sortingAlgorithm);
58+
setUseSections(useSections);
5659
};
5760

5861
return {
5962
sort,
6063
activeSortOption: sortingAlgorithmToSortingOption[activeSortingAlgorithm!],
64+
useSections,
6165
};
6266
}

src/components/viewmodels/roomlist/useStickyRoomList.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,21 @@ import { SdkContextClass } from "../../../contexts/SDKContext";
1111
import { useDispatcher } from "../../../hooks/useDispatcher";
1212
import dispatcher from "../../../dispatcher/dispatcher";
1313
import { Action } from "../../../dispatcher/actions";
14-
import type { Room } from "matrix-js-sdk/src/matrix";
1514
import type { Optional } from "matrix-events-sdk";
1615
import SpaceStore from "../../../stores/spaces/SpaceStore";
17-
import { type RoomsResult } from "../../../stores/room-list-v3/RoomListStoreV3";
16+
import { isRoomListRoom, type RoomListEntry, type RoomsResult } from "../../../stores/room-list-v3/RoomListStoreV3";
1817

19-
function getIndexByRoomId(rooms: Room[], roomId: Optional<string>): number | undefined {
20-
const index = rooms.findIndex((room) => room.roomId === roomId);
18+
function getIndexByRoomId(rooms: RoomListEntry[], roomId: Optional<string>): number | undefined {
19+
const index = rooms.findIndex((room) => isRoomListRoom(room) && room.roomId === roomId);
2120
return index === -1 ? undefined : index;
2221
}
2322

2423
function getRoomsWithStickyRoom(
25-
rooms: Room[],
24+
rooms: RoomListEntry[],
2625
oldIndex: number | undefined,
2726
newIndex: number | undefined,
2827
isRoomChange: boolean,
29-
): { newRooms: Room[]; newIndex: number | undefined } {
28+
): { newRooms: RoomListEntry[]; newIndex: number | undefined } {
3029
const updated = { newIndex, newRooms: rooms };
3130
if (isRoomChange) {
3231
/*
@@ -83,8 +82,8 @@ export interface StickyRoomListResult {
8382
* - Provides a list of rooms such that the active room is sticky i.e the active room is kept
8483
* in the same index even when the order of rooms in the list changes.
8584
* - Provides the index of the active room.
86-
* @param rooms list of rooms
87-
* @see {@link StickyRoomListResult} details what this hook returns..
85+
* @param roomsResult list of rooms
86+
* @see {@link StickyRoomListResult} details what this hook returns.
8887
*/
8988
export function useStickyRoomList(roomsResult: RoomsResult): StickyRoomListResult {
9089
const [listState, setListState] = useState<StickyRoomListResult>({

src/components/views/rooms/RoomListPanel/RoomList.tsx

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
* Please see LICENSE files in the repository root for full details.
66
*/
77

8-
import React, { useCallback, useRef, useState, type JSX } from "react";
9-
import { type Room } from "matrix-js-sdk/src/matrix";
8+
import React, { type JSX, useCallback, useRef, useState } from "react";
109
import { type ScrollIntoViewLocation } from "react-virtuoso";
1110
import { isEqual } from "lodash";
1211

@@ -18,6 +17,8 @@ import { type FilterKey } from "../../../../stores/room-list-v3/skip-list/filter
1817
import { getKeyBindingsManager } from "../../../../KeyBindingsManager";
1918
import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
2019
import { Landmark, LandmarkNavigation } from "../../../../accessibility/LandmarkNavigation";
20+
import { isRoomListSectionHeader, type RoomListEntry } from "../../../../stores/room-list-v3/RoomListStoreV3.ts";
21+
import { RoomListSectionHeaderView } from "./RoomListSectionHeaderView.tsx";
2122

2223
interface RoomListProps {
2324
/**
@@ -37,6 +38,11 @@ const ROOM_LIST_ITEM_HEIGHT = 48;
3738
* We would likely need to simplify the item content to improve this case.
3839
*/
3940
const EXTENDED_VIEWPORT_HEIGHT = 25 * ROOM_LIST_ITEM_HEIGHT;
41+
42+
const getItemKey = (item: RoomListEntry): string => {
43+
return isRoomListSectionHeader(item) ? item.key : item.roomId;
44+
};
45+
4046
/**
4147
* A virtualized list of rooms.
4248
*/
@@ -48,38 +54,38 @@ export function RoomList({ vm: { roomsResult, activeIndex } }: RoomListProps): J
4854
const getItemComponent = useCallback(
4955
(
5056
index: number,
51-
item: Room,
57+
item: RoomListEntry,
5258
context: ListContext<{
5359
spaceId: string;
5460
filterKeys: FilterKey[] | undefined;
5561
}>,
5662
onFocus: (e: React.FocusEvent) => void,
5763
): JSX.Element => {
58-
const itemKey = item.roomId;
64+
const itemKey = getItemKey(item);
5965
const isRovingItem = itemKey === context.tabIndexKey;
6066
const isFocused = isRovingItem && context.focused;
6167
const isSelected = activeIndex === index;
62-
return (
63-
<RoomListItemView
64-
room={item}
65-
key={itemKey}
66-
isSelected={isSelected}
67-
isFocused={isFocused}
68-
tabIndex={isRovingItem ? 0 : -1}
69-
roomIndex={index}
70-
roomCount={roomCount}
71-
onFocus={onFocus}
72-
listIsScrolling={isScrolling}
73-
/>
74-
);
68+
if (isRoomListSectionHeader(item)) {
69+
return <RoomListSectionHeaderView section={item} />;
70+
} else {
71+
return (
72+
<RoomListItemView
73+
room={item}
74+
key={itemKey}
75+
isSelected={isSelected}
76+
isFocused={isFocused}
77+
tabIndex={isRovingItem ? 0 : -1}
78+
roomIndex={index}
79+
roomCount={roomCount}
80+
onFocus={onFocus}
81+
listIsScrolling={isScrolling}
82+
/>
83+
);
84+
}
7585
},
7686
[activeIndex, roomCount, isScrolling],
7787
);
7888

79-
const getItemKey = useCallback((item: Room): string => {
80-
return item.roomId;
81-
}, []);
82-
8389
const scrollIntoViewOnChange = useCallback(
8490
(params: {
8591
context: ListContext<{ spaceId: string; filterKeys: FilterKey[] | undefined }>;

src/components/views/rooms/RoomListPanel/RoomListOptionsMenu.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Please see LICENSE files in the repository root for full details.
66
*/
77

8-
import { IconButton, Menu, MenuTitle, Tooltip, RadioMenuItem } from "@vector-im/compound-web";
8+
import { IconButton, Menu, MenuTitle, Tooltip, RadioMenuItem, CheckboxMenuItem } from "@vector-im/compound-web";
99
import React, { type Ref, type JSX, useState, useCallback } from "react";
1010
import OverflowHorizontalIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal";
1111

@@ -36,11 +36,15 @@ export function RoomListOptionsMenu({ vm }: Props): JSX.Element {
3636
const [open, setOpen] = useState(false);
3737

3838
const onActivitySelected = useCallback(() => {
39-
vm.sort(SortOption.Activity);
39+
vm.sort(SortOption.Activity, vm.useSections);
4040
}, [vm]);
4141

4242
const onAtoZSelected = useCallback(() => {
43-
vm.sort(SortOption.AToZ);
43+
vm.sort(SortOption.AToZ, vm.useSections);
44+
}, [vm]);
45+
46+
const onUseSectionsSelected = useCallback(() => {
47+
vm.sort(vm.activeSortOption, !vm.useSections);
4448
}, [vm]);
4549

4650
return (
@@ -63,6 +67,11 @@ export function RoomListOptionsMenu({ vm }: Props): JSX.Element {
6367
checked={vm.activeSortOption === SortOption.AToZ}
6468
onSelect={onAtoZSelected}
6569
/>
70+
<CheckboxMenuItem
71+
label={_t("room_list|sort_sections")}
72+
checked={vm.useSections}
73+
onSelect={onUseSectionsSelected}
74+
/>
6675
</Menu>
6776
);
6877
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import React, { type JSX, type ReactNode } from "react";
9+
10+
import {
11+
type RoomListSectionHeader,
12+
RoomListSectionKey,
13+
} from "../../../../stores/room-list-v3/skip-list/SectionProcessor.ts";
14+
import { _t } from "../../../../shared-components/utils/i18n.tsx";
15+
16+
interface RoomListSectionHeaderViewProps extends React.HTMLAttributes<HTMLDivElement> {
17+
section: RoomListSectionHeader;
18+
}
19+
20+
function sectionTitle(section: RoomListSectionKey): ReactNode {
21+
switch (section) {
22+
case RoomListSectionKey.Favourite:
23+
return _t("room_list|sections|favourite");
24+
case RoomListSectionKey.Unread:
25+
return _t("room_list|sections|unread");
26+
case RoomListSectionKey.Chat:
27+
return _t("room_list|sections|chat");
28+
case RoomListSectionKey.LowPriority:
29+
return _t("room_list|sections|low_priority");
30+
}
31+
}
32+
33+
/**
34+
* An item in the room list
35+
*/
36+
export function RoomListSectionHeaderView({ section, ...props }: RoomListSectionHeaderViewProps): JSX.Element {
37+
return (
38+
<div className="mx_RoomListSectionHeaderView" {...props}>
39+
<h4>{sectionTitle(section.key)}</h4>
40+
</div>
41+
);
42+
}

src/i18n/strings/en_EN.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,6 +2200,12 @@
22002200
"open_room": "Open room %(roomName)s"
22012201
},
22022202
"room_options": "Room Options",
2203+
"sections": {
2204+
"chat": "Chats",
2205+
"favourite": "Favourites",
2206+
"low_priority": "Low priority",
2207+
"unread": "Unreads"
2208+
},
22032209
"show_less": "Show less",
22042210
"show_n_more": {
22052211
"one": "Show %(count)s more",
@@ -2210,6 +2216,7 @@
22102216
"sort_by": "Sort by",
22112217
"sort_by_activity": "Activity",
22122218
"sort_by_alphabet": "A-Z",
2219+
"sort_sections": "Group rooms by type",
22132220
"sort_type": {
22142221
"activity": "Activity",
22152222
"atoz": "A-Z"

0 commit comments

Comments
 (0)