Skip to content

Commit 008f2ec

Browse files
committed
feat: use react-virtuoso in new room list
1 parent 041d6c6 commit 008f2ec

File tree

5 files changed

+121
-113
lines changed

5 files changed

+121
-113
lines changed

res/css/_components.pcss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,6 @@
273273
@import "./views/right_panel/_WidgetCard.pcss";
274274
@import "./views/room_settings/_AliasSettings.pcss";
275275
@import "./views/rooms/RoomListPanel/_EmptyRoomList.pcss";
276-
@import "./views/rooms/RoomListPanel/_RoomList.pcss";
277276
@import "./views/rooms/RoomListPanel/_RoomListHeaderView.pcss";
278277
@import "./views/rooms/RoomListPanel/_RoomListItemMenuView.pcss";
279278
@import "./views/rooms/RoomListPanel/_RoomListItemView.pcss";

res/css/views/rooms/RoomListPanel/_RoomList.pcss

Lines changed: 0 additions & 10 deletions
This file was deleted.

res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,44 @@
1515
* |-------------------------------------------------------|
1616
*/
1717
.mx_RoomListItemView {
18-
all: unset;
18+
/* Remove button default style */
19+
background: unset;
20+
border: none;
21+
padding: 0;
22+
text-align: unset;
23+
1924
cursor: pointer;
25+
height: 48px;
26+
width: 100%;
27+
28+
padding-left: var(--cpd-space-3x);
29+
font: var(--cpd-font-body-md-regular);
2030

21-
.mx_RoomListItemView_container {
22-
padding-left: var(--cpd-space-3x);
23-
font: var(--cpd-font-body-md-regular);
31+
.mx_RoomListItemView_content {
2432
height: 100%;
33+
flex: 1;
34+
/* The border is only under the room name and the future hover menu */
35+
border-bottom: var(--cpd-border-width-0-5) solid var(--cpd-color-bg-subtle-secondary);
36+
box-sizing: border-box;
37+
min-width: 0;
38+
padding-right: var(--cpd-space-5x);
2539

26-
.mx_RoomListItemView_content {
27-
height: 100%;
28-
flex: 1;
29-
/* The border is only under the room name and the future hover menu */
30-
border-bottom: var(--cpd-border-width-0-5) solid var(--cpd-color-bg-subtle-secondary);
31-
box-sizing: border-box;
40+
.mx_RoomListItemView_text {
3241
min-width: 0;
33-
padding-right: var(--cpd-space-5x);
34-
35-
.mx_RoomListItemView_text {
36-
min-width: 0;
37-
}
42+
}
3843

39-
.mx_RoomListItemView_roomName {
40-
white-space: nowrap;
41-
overflow: hidden;
42-
text-overflow: ellipsis;
43-
}
44+
.mx_RoomListItemView_roomName {
45+
white-space: nowrap;
46+
overflow: hidden;
47+
text-overflow: ellipsis;
48+
}
4449

45-
.mx_RoomListItemView_messagePreview {
46-
font: var(--cpd-font-body-sm-regular);
47-
color: var(--cpd-color-text-secondary);
48-
white-space: nowrap;
49-
overflow: hidden;
50-
text-overflow: ellipsis;
51-
}
50+
.mx_RoomListItemView_messagePreview {
51+
font: var(--cpd-font-body-sm-regular);
52+
color: var(--cpd-color-text-secondary);
53+
white-space: nowrap;
54+
overflow: hidden;
55+
text-overflow: ellipsis;
5256
}
5357
}
5458
}
@@ -57,7 +61,7 @@
5761
background-color: var(--cpd-color-bg-action-secondary-hovered);
5862
}
5963

60-
.mx_RoomListItemView_menu_open .mx_RoomListItemView_container .mx_RoomListItemView_content {
64+
.mx_RoomListItemView_menu_open .mx_RoomListItemView_content {
6165
/**
6266
* The figma uses 16px padding (--cpd-space-4x) but due to https://github.com/element-hq/compound-web/issues/331
6367
* the icon size of the menu is 18px instead of 20px with a different internal padding

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

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

8-
import React, { useCallback, type JSX } from "react";
9-
import { AutoSizer, List, type ListRowProps } from "react-virtualized";
8+
import React, { type JSX, useCallback, useEffect, useRef } from "react";
9+
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
10+
import { type Room } from "matrix-js-sdk/src/matrix";
1011

1112
import { type RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
1213
import { _t } from "../../../../languageHandler";
@@ -26,21 +27,51 @@ interface RoomListProps {
2627
/**
2728
* A virtualized list of rooms.
2829
*/
29-
export function RoomList({ vm: { rooms, activeIndex } }: RoomListProps): JSX.Element {
30-
const roomRendererMemoized = useCallback(
31-
({ key, index, style }: ListRowProps) => (
32-
<RoomListItemView room={rooms[index]} key={key} style={style} isSelected={activeIndex === index} />
33-
),
34-
[rooms, activeIndex],
35-
);
30+
export function RoomList({ vm: { rooms, activeIndex: currentRoomIndex } }: RoomListProps): JSX.Element {
31+
// To follow the grid pattern (https://www.w3.org/WAI/ARIA/apg/patterns/grid/), we need to set the first element of the list as a row.
32+
// The virtuoso component is set to role="grid" and the items are set to role="gridcell".
33+
const scrollerRef = useCallback((node: HTMLElement | Window | null) => {
34+
if (node instanceof HTMLElement) {
35+
node.firstElementChild?.setAttribute("role", "row");
36+
}
37+
}, []);
38+
39+
// Scroll to the current room when we switch between spaces
40+
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
41+
useEffect(() => {
42+
if (!virtuosoRef.current) return;
43+
44+
// Without setTimeout, the scrollToIndex does nothing when the space change
45+
setTimeout(() => virtuosoRef.current?.scrollToIndex(currentRoomIndex ?? 0), 0);
46+
}, [currentRoomIndex]);
3647

37-
// The first div is needed to make the virtualized list take all the remaining space and scroll correctly
3848
return (
3949
<RovingTabIndexProvider handleHomeEnd={true} handleUpDown={true}>
4050
{({ onKeyDownHandler }) => (
41-
<div
42-
className="mx_RoomList"
51+
<Virtuoso<Room, { currentRoomIndex?: number }>
4352
data-testid="room-list"
53+
role="grid"
54+
aria-label={_t("room_list|list_title")}
55+
aria-rowcount={1}
56+
aria-colcount={1}
57+
style={{ flex: 1 }}
58+
data={rooms}
59+
fixedItemHeight={48}
60+
context={{ currentRoomIndex }}
61+
itemContent={(index, _room, { currentRoomIndex }) => (
62+
<RoomListItemView
63+
room={rooms[index]}
64+
isSelected={currentRoomIndex === index}
65+
aria-colindex={index}
66+
role="gridcell"
67+
/>
68+
)}
69+
computeItemKey={(index, room) => room.roomId}
70+
/* 240px = 5 rows */
71+
increaseViewportBy={240}
72+
initialTopMostItemIndex={currentRoomIndex ?? 0}
73+
ref={virtuosoRef}
74+
scrollerRef={scrollerRef}
4475
onKeyDown={(ev) => {
4576
const navAction = getKeyBindingsManager().getNavigationAction(ev);
4677
if (
@@ -57,23 +88,7 @@ export function RoomList({ vm: { rooms, activeIndex } }: RoomListProps): JSX.Ele
5788
}
5889
onKeyDownHandler(ev);
5990
}}
60-
>
61-
<AutoSizer>
62-
{({ height, width }) => (
63-
<List
64-
aria-label={_t("room_list|list_title")}
65-
className="mx_RoomList_List"
66-
rowRenderer={roomRendererMemoized}
67-
rowCount={rooms.length}
68-
rowHeight={48}
69-
height={height}
70-
width={width}
71-
scrollToIndex={activeIndex ?? 0}
72-
tabIndex={-1}
73-
/>
74-
)}
75-
</AutoSizer>
76-
</div>
91+
/>
7792
)}
7893
</RovingTabIndexProvider>
7994
);

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

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ export const RoomListItemView = memo(function RoomListItemView({
4848
const showHoverMenu = showHoverDecoration && vm.showHoverMenu;
4949

5050
return (
51-
<button
51+
<Flex
52+
as="button"
5253
ref={ref}
5354
className={classNames("mx_RoomListItemView", {
5455
mx_RoomListItemView_hover: showHoverDecoration,
@@ -71,54 +72,53 @@ export const RoomListItemView = memo(function RoomListItemView({
7172
// We delay the blur event to give time to the focus to move to the menu
7273
onBlur={() => setIsHoverWithDelay(false, 10)}
7374
tabIndex={isActive ? 0 : -1}
75+
gap="var(--cpd-space-3x)"
76+
align="center"
7477
{...props}
7578
>
76-
{/* We need this extra div between the button and the content in order to add a padding which is not messing with the virtualized list */}
77-
<Flex className="mx_RoomListItemView_container" gap="var(--cpd-space-3x)" align="center">
78-
<RoomAvatarView room={room} />
79-
<Flex
80-
className="mx_RoomListItemView_content"
81-
gap="var(--cpd-space-2x)"
82-
align="center"
83-
justify="space-between"
84-
>
85-
{/* We truncate the room name when too long. Title here is to show the full name on hover */}
86-
<div className="mx_RoomListItemView_text">
87-
<div className="mx_RoomListItemView_roomName" title={vm.name}>
88-
{vm.name}
89-
</div>
90-
<div className="mx_RoomListItemView_messagePreview">{vm.messagePreview}</div>
79+
<RoomAvatarView room={room} />
80+
<Flex
81+
className="mx_RoomListItemView_content"
82+
gap="var(--cpd-space-2x)"
83+
align="center"
84+
justify="space-between"
85+
>
86+
{/* We truncate the room name when too long. Title here is to show the full name on hover */}
87+
<div className="mx_RoomListItemView_text">
88+
<div className="mx_RoomListItemView_roomName" title={vm.name}>
89+
{vm.name}
9190
</div>
92-
{showHoverMenu ? (
93-
<RoomListItemMenuView
94-
room={room}
95-
setMenuOpen={(isOpen) => {
96-
if (isOpen) {
97-
setIsMenuOpen(isOpen);
98-
} else {
99-
// To avoid icon blinking when closing the menu, we delay the state update
100-
setTimeout(() => setIsMenuOpen(isOpen), 0);
101-
// After closing the menu, we need to set the focus back to the button
102-
// 10ms because the focus moves to the body and we put back the focus on the button
103-
setTimeout(() => buttonRef.current?.focus(), 10);
104-
}
105-
}}
106-
/>
107-
) : (
108-
<>
109-
{/* aria-hidden because we summarise the unread count/notification status in a11yLabel variable */}
110-
{vm.showNotificationDecoration && (
111-
<NotificationDecoration
112-
notificationState={vm.notificationState}
113-
aria-hidden={true}
114-
hasVideoCall={vm.hasParticipantInCall}
115-
/>
116-
)}
117-
</>
118-
)}
119-
</Flex>
91+
<div className="mx_RoomListItemView_messagePreview">{vm.messagePreview}</div>
92+
</div>
93+
{showHoverMenu ? (
94+
<RoomListItemMenuView
95+
room={room}
96+
setMenuOpen={(isOpen) => {
97+
if (isOpen) {
98+
setIsMenuOpen(isOpen);
99+
} else {
100+
// To avoid icon blinking when closing the menu, we delay the state update
101+
setTimeout(() => setIsMenuOpen(isOpen), 0);
102+
// After closing the menu, we need to set the focus back to the button
103+
// 10ms because the focus moves to the body and we put back the focus on the button
104+
setTimeout(() => buttonRef.current?.focus(), 10);
105+
}
106+
}}
107+
/>
108+
) : (
109+
<>
110+
{/* aria-hidden because we summarise the unread count/notification status in a11yLabel variable */}
111+
{vm.showNotificationDecoration && (
112+
<NotificationDecoration
113+
notificationState={vm.notificationState}
114+
aria-hidden={true}
115+
hasVideoCall={vm.hasParticipantInCall}
116+
/>
117+
)}
118+
</>
119+
)}
120120
</Flex>
121-
</button>
121+
</Flex>
122122
);
123123
});
124124

0 commit comments

Comments
 (0)