Room list: add default sections (#32785)
* feat: add sections to RLSV3 * feat: add sections in vms * feat: add room list section labs flag * fix: wrong margin for room list item when in sections * feat: hide favourites and low priority filters * fix: crash when changing filter * feat: support sticky room in sections * test: update SC snapshot * test: update SC screenshot * test: update RLS tests * test: add tests to RoomListSectionHeaderViewModel * test: fix existing test in RoomListViewModel * test: add sections tests for RoomListViewModel * test: add e2e tests for sections * fix: incorrect selected room when expanding/collasping a section * fix: typo in `roomSkipList` * feat: use one skip list with all filters instead of one list by tag * chore: put back comment about `roomIndexInSection` * chore: add missing `readonly` * chore: add doc about possible undefined value for room item vm
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 39 KiB |
@@ -58,8 +58,11 @@ export interface RoomListViewActions {
|
||||
createChatRoom: () => void;
|
||||
/** Called to create a new room */
|
||||
createRoom: () => void;
|
||||
/** Get view model for a specific room (virtualization API) */
|
||||
getRoomItemViewModel: (roomId: string) => RoomListItemViewModel;
|
||||
/**
|
||||
* Get view model for a specific room (virtualization API)
|
||||
* Allow undefined to be returned if we don't have a view model for the room. In this case the room will not be rendered.
|
||||
*/
|
||||
getRoomItemViewModel: (roomId: string) => RoomListItemViewModel | undefined;
|
||||
/** Called when the visible range changes (virtualization API) */
|
||||
updateVisibleRooms: (startIndex: number, endIndex: number) => void;
|
||||
/** Get view model for a specific section header (virtualization API) */
|
||||
|
||||
@@ -8352,7 +8352,7 @@ exports[`<RoomListView /> > renders LargeSectionList story 1`] = `
|
||||
aria-haspopup="menu"
|
||||
aria-label="Open room General"
|
||||
aria-selected="false"
|
||||
class="flex roomListItem mx_RoomListItemView bold firstItem"
|
||||
class="flex roomListItem mx_RoomListItemView bold"
|
||||
data-state="closed"
|
||||
role="gridcell"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: stretch; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
|
||||
@@ -13652,7 +13652,7 @@ exports[`<RoomListView /> > renders SmallSectionList story 1`] = `
|
||||
aria-haspopup="menu"
|
||||
aria-label="Open room General"
|
||||
aria-selected="false"
|
||||
class="flex roomListItem mx_RoomListItemView bold firstItem"
|
||||
class="flex roomListItem mx_RoomListItemView bold"
|
||||
data-state="closed"
|
||||
role="gridcell"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: stretch; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
|
||||
@@ -13806,7 +13806,7 @@ exports[`<RoomListView /> > renders SmallSectionList story 1`] = `
|
||||
aria-haspopup="menu"
|
||||
aria-label="Open room Random"
|
||||
aria-selected="false"
|
||||
class="flex roomListItem mx_RoomListItemView lastItem"
|
||||
class="flex roomListItem mx_RoomListItemView"
|
||||
data-state="closed"
|
||||
role="gridcell"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: stretch; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
|
||||
|
||||
@@ -139,6 +139,13 @@ export function VirtualizedRoomListView({ vm, renderAvatar, onKeyDown }: Virtual
|
||||
/**
|
||||
* Get the item component for a specific index
|
||||
* Gets the room's view model and passes it to RoomListItemView
|
||||
*
|
||||
* @param index - The index of the item in the list
|
||||
* @param roomId - The ID of the room for this item
|
||||
* @param context - The virtualization context containing list state
|
||||
* @param onFocus - Callback to call when the item is focused
|
||||
* @param isInLastSection - Whether this item is in the last section
|
||||
* @param roomIndexInSection - The index of this room within its section
|
||||
*/
|
||||
const getItemComponent = useCallback(
|
||||
(
|
||||
@@ -146,18 +153,24 @@ export function VirtualizedRoomListView({ vm, renderAvatar, onKeyDown }: Virtual
|
||||
roomId: string,
|
||||
context: VirtualizedListContext<Context>,
|
||||
onFocus: (item: string, e: React.FocusEvent) => void,
|
||||
roomIndexInSection: number,
|
||||
isInLastSection?: boolean,
|
||||
roomIndexInSection?: number,
|
||||
): JSX.Element => {
|
||||
const { activeRoomIndex, roomCount, vm, isFlatList } = context.context;
|
||||
const isSelected = activeRoomIndex === index;
|
||||
const roomItemVM = vm.getRoomItemViewModel(roomId);
|
||||
|
||||
// If we don't have a view model for this room, it means the room has been removed since the list was rendered - return an empty placeholder
|
||||
if (!roomItemVM) {
|
||||
return <React.Fragment key={`stale-${index}`} />;
|
||||
}
|
||||
|
||||
// Item is focused when the list has focus AND this item's key matches tabIndexKey
|
||||
// This matches the old RoomList implementation's roving tabindex pattern
|
||||
const isFocused = context.focused && context.tabIndexKey === roomId;
|
||||
|
||||
const isFirstItem = index === 0;
|
||||
const isLastItem = index === roomCount - 1;
|
||||
const isFirstItem = isFlatList && index === 0;
|
||||
const isLastItem = Boolean((isFlatList || isInLastSection) && index === roomCount - 1);
|
||||
|
||||
return (
|
||||
<RoomListItemAccessibilityWrapper
|
||||
@@ -168,7 +181,8 @@ export function VirtualizedRoomListView({ vm, renderAvatar, onKeyDown }: Virtual
|
||||
isFocused={isFocused}
|
||||
onFocus={onFocus}
|
||||
roomIndex={index}
|
||||
roomIndexInSection={roomIndexInSection}
|
||||
// For a flat list, we don't have sections, so roomIndexInSection is unused and can be set to 0
|
||||
roomIndexInSection={roomIndexInSection || 0}
|
||||
roomCount={roomCount}
|
||||
isFirstItem={isFirstItem}
|
||||
isLastItem={isLastItem}
|
||||
@@ -181,7 +195,6 @@ export function VirtualizedRoomListView({ vm, renderAvatar, onKeyDown }: Virtual
|
||||
|
||||
/**
|
||||
* Get the item component for a specific index in a grouped list
|
||||
* Since we have sections, we can calculate the room's index within its section and pass it to getItemComponent
|
||||
* Gets the room's view model and passes it to RoomListItemView
|
||||
*/
|
||||
const getItemComponentForGroupedList = useCallback(
|
||||
@@ -194,14 +207,14 @@ export function VirtualizedRoomListView({ vm, renderAvatar, onKeyDown }: Virtual
|
||||
): JSX.Element => {
|
||||
const { sections } = context.context;
|
||||
const roomIndexInSection = sections[groupIndex].roomIds.findIndex((id) => id === roomId);
|
||||
return getItemComponent(index, roomId, context, onFocus, roomIndexInSection);
|
||||
const isInLastSection = groupIndex === sections.length - 1;
|
||||
return getItemComponent(index, roomId, context, onFocus, isInLastSection, roomIndexInSection);
|
||||
},
|
||||
[getItemComponent],
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the item component for a specific index in a flat list
|
||||
* Since we don't have sections, we can pass 0 for the room's index within its section to getItemComponent
|
||||
* Gets the room's view model and passes it to RoomListItemView
|
||||
*/
|
||||
const getItemComponentForFlatList = useCallback(
|
||||
@@ -211,8 +224,7 @@ export function VirtualizedRoomListView({ vm, renderAvatar, onKeyDown }: Virtual
|
||||
context: VirtualizedListContext<Context>,
|
||||
onFocus: (item: string, e: React.FocusEvent) => void,
|
||||
): JSX.Element => {
|
||||
// For a flat list, we don't have sections, so roomIndexInSection is unused and can be set to 0
|
||||
return getItemComponent(index, roomId, context, onFocus, 0);
|
||||
return getItemComponent(index, roomId, context, onFocus);
|
||||
},
|
||||
[getItemComponent],
|
||||
);
|
||||
|
||||