diff --git a/apps/web/src/viewmodels/room-list/RoomListViewViewModel.ts b/apps/web/src/viewmodels/room-list/RoomListViewViewModel.ts index ed1c305796b..2eaea3d246b 100644 --- a/apps/web/src/viewmodels/room-list/RoomListViewViewModel.ts +++ b/apps/web/src/viewmodels/room-list/RoomListViewViewModel.ts @@ -61,6 +61,8 @@ export class RoomListViewViewModel const roomsResult = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(undefined); const canCreateRoom = hasCreateRoomRights(props.client, activeSpace); const filterIds = [...filterKeyToIdMap.values()]; + const roomIds = roomsResult.rooms.map((room) => room.roomId); + const sections = [{ id: "all", roomIds }]; super(props, { // Initial view state - start with empty, will populate in async init @@ -73,7 +75,9 @@ export class RoomListViewViewModel spaceId: roomsResult.spaceId, filterKeys: undefined, }, - roomIds: roomsResult.rooms.map((room) => room.roomId), + // Until we implement sections, this view model only supports the flat list mode + isFlatList: true, + sections, canCreateRoom, }); @@ -195,6 +199,15 @@ export class RoomListViewViewModel return viewModel; } + /** + * Not implemented - this view model does not support sections. + * Flat list mode is forced so this method is never be called. + * @throw Error if called + */ + public getSectionHeaderViewModel(): never { + throw new Error("Sections are not supported in this room list"); + } + /** * Update which rooms are currently visible. * Called by the view when scroll position changes. @@ -408,6 +421,7 @@ export class RoomListViewViewModel // Build the complete state atomically to ensure consistency // roomIds and roomListState must always be in sync const roomIds = this.roomIds; + const sections = [{ id: "all", roomIds }]; // Update filter keys - only update if they have actually changed to prevent unnecessary re-renders of the room list const previousFilterKeys = this.snapshot.current.roomListState.filterKeys; @@ -428,7 +442,7 @@ export class RoomListViewViewModel isRoomListEmpty, activeFilterId, roomListState, - roomIds, + sections, }); } diff --git a/apps/web/test/viewmodels/room-list/RoomListViewViewModel-test.tsx b/apps/web/test/viewmodels/room-list/RoomListViewViewModel-test.tsx index abdd6d10f7f..ceeb940669d 100644 --- a/apps/web/test/viewmodels/room-list/RoomListViewViewModel-test.tsx +++ b/apps/web/test/viewmodels/room-list/RoomListViewViewModel-test.tsx @@ -66,7 +66,7 @@ describe("RoomListViewViewModel", () => { viewModel = new RoomListViewViewModel({ client: matrixClient }); const snapshot = viewModel.getSnapshot(); - expect(snapshot.roomIds).toEqual(["!room1:server", "!room2:server", "!room3:server"]); + expect(snapshot.sections[0].roomIds).toEqual(["!room1:server", "!room2:server", "!room3:server"]); expect(snapshot.isRoomListEmpty).toBe(false); expect(snapshot.isLoadingRooms).toBe(false); expect(snapshot.roomListState.spaceId).toBe("home"); @@ -82,7 +82,7 @@ describe("RoomListViewViewModel", () => { viewModel = new RoomListViewViewModel({ client: matrixClient }); - expect(viewModel.getSnapshot().roomIds).toEqual([]); + expect(viewModel.getSnapshot().sections[0].roomIds).toEqual([]); expect(viewModel.getSnapshot().isRoomListEmpty).toBe(true); }); @@ -106,7 +106,7 @@ describe("RoomListViewViewModel", () => { RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate); - expect(viewModel.getSnapshot().roomIds).toEqual([ + expect(viewModel.getSnapshot().sections[0].roomIds).toEqual([ "!room1:server", "!room2:server", "!room3:server", @@ -156,7 +156,7 @@ describe("RoomListViewViewModel", () => { RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate); expect(viewModel.getSnapshot().roomListState.spaceId).toBe("!space:server"); - expect(viewModel.getSnapshot().roomIds).toEqual(["!room1:server", "!room2:server"]); + expect(viewModel.getSnapshot().sections[0].roomIds).toEqual(["!room1:server", "!room2:server"]); }); it("should clear view models when space changes", () => { @@ -240,7 +240,7 @@ describe("RoomListViewViewModel", () => { // Active room should still be at index 1 (sticky behavior) expect(viewModel.getSnapshot().roomListState.activeRoomIndex).toBe(1); - expect(viewModel.getSnapshot().roomIds[1]).toBe("!room2:server"); + expect(viewModel.getSnapshot().sections[0].roomIds[1]).toBe("!room2:server"); }); it("should not apply sticky behavior when user changes rooms", async () => { @@ -283,7 +283,7 @@ describe("RoomListViewViewModel", () => { viewModel.onToggleFilter("unread"); expect(viewModel.getSnapshot().activeFilterId).toBe("unread"); - expect(viewModel.getSnapshot().roomIds).toEqual(["!room1:server"]); + expect(viewModel.getSnapshot().sections[0].roomIds).toEqual(["!room1:server"]); }); it("should toggle filter off", () => { @@ -307,7 +307,11 @@ describe("RoomListViewViewModel", () => { viewModel.onToggleFilter("unread"); expect(viewModel.getSnapshot().activeFilterId).toBeUndefined(); - expect(viewModel.getSnapshot().roomIds).toEqual(["!room1:server", "!room2:server", "!room3:server"]); + expect(viewModel.getSnapshot().sections[0].roomIds).toEqual([ + "!room1:server", + "!room2:server", + "!room3:server", + ]); }); }); diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItemAccessibilityWrapper/RoomListItemAccessibilityWrapper.stories.tsx/flat-list-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItemAccessibilityWrapper/RoomListItemAccessibilityWrapper.stories.tsx/flat-list-auto.png new file mode 100644 index 00000000000..65d2b7e41de Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItemAccessibilityWrapper/RoomListItemAccessibilityWrapper.stories.tsx/flat-list-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItemAccessibilityWrapper/RoomListItemAccessibilityWrapper.stories.tsx/sections-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItemAccessibilityWrapper/RoomListItemAccessibilityWrapper.stories.tsx/sections-auto.png new file mode 100644 index 00000000000..65d2b7e41de Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItemAccessibilityWrapper/RoomListItemAccessibilityWrapper.stories.tsx/sections-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/collapsed-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/collapsed-auto.png new file mode 100644 index 00000000000..1a0616cbca9 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/collapsed-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/default-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/default-auto.png new file mode 100644 index 00000000000..0f8bb587a60 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/default-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/first-header-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/first-header-auto.png new file mode 100644 index 00000000000..63b3b3bea61 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/first-header-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/last-header-collapsed-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/last-header-collapsed-auto.png new file mode 100644 index 00000000000..1a0616cbca9 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/last-header-collapsed-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/long-title-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/long-title-auto.png new file mode 100644 index 00000000000..db8ade7e594 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListSectionHeaderView/RoomListSectionHeaderView.stories.tsx/long-title-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-flat-list-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-flat-list-auto.png new file mode 100644 index 00000000000..667be0e5c6c Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-flat-list-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-list-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-list-auto.png deleted file mode 100644 index eb90df57d38..00000000000 Binary files a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-list-auto.png and /dev/null differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-section-list-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-section-list-auto.png new file mode 100644 index 00000000000..9d43af46193 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-section-list-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/section-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/section-auto.png new file mode 100644 index 00000000000..d8992ad6e64 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/section-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/small-flat-list-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/small-flat-list-auto.png new file mode 100644 index 00000000000..df403e69184 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/small-flat-list-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/small-list-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/small-list-auto.png deleted file mode 100644 index d23566e44ae..00000000000 Binary files a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/small-list-auto.png and /dev/null differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/small-section-list-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/small-section-list-auto.png new file mode 100644 index 00000000000..97dcad563f7 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/small-section-list-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/VirtualizedRoomListView/VirtualizedRoomListView.stories.tsx/sections-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/VirtualizedRoomListView/VirtualizedRoomListView.stories.tsx/sections-auto.png new file mode 100644 index 00000000000..36b07b87ba5 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/VirtualizedRoomListView/VirtualizedRoomListView.stories.tsx/sections-auto.png differ diff --git a/packages/shared-components/src/i18n/strings/en_EN.json b/packages/shared-components/src/i18n/strings/en_EN.json index 1a1dc79ccdd..1f6f01bdc25 100644 --- a/packages/shared-components/src/i18n/strings/en_EN.json +++ b/packages/shared-components/src/i18n/strings/en_EN.json @@ -122,6 +122,9 @@ "more_options": "More Options" }, "room_options": "Room Options", + "section_header": { + "toggle": "Toggle %(section)s section" + }, "show_message_previews": "Show message previews", "sort": "Sort", "sort_type": { diff --git a/packages/shared-components/src/index.ts b/packages/shared-components/src/index.ts index b29287fdd82..72409f9ebcd 100644 --- a/packages/shared-components/src/index.ts +++ b/packages/shared-components/src/index.ts @@ -35,8 +35,10 @@ export * from "./rich-list/RichItem"; export * from "./rich-list/RichList"; export * from "./room-list/RoomListHeaderView"; export * from "./room-list/RoomListSearchView"; +export * from "./room-list/RoomListSectionHeaderView"; export * from "./room-list/RoomListView"; export * from "./room-list/RoomListItemView"; +export * from "./room-list/RoomListItemAccessibilityWrapper"; export * from "./room-list/RoomListPrimaryFilters"; export * from "./room-list/VirtualizedRoomListView"; export * from "./timeline/DateSeparatorView/"; diff --git a/packages/shared-components/src/room-list/RoomListItemAccessibilityWrapper/RoomListItemAccessibilityWrapper.stories.tsx b/packages/shared-components/src/room-list/RoomListItemAccessibilityWrapper/RoomListItemAccessibilityWrapper.stories.tsx new file mode 100644 index 00000000000..d5e5c18b0a0 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItemAccessibilityWrapper/RoomListItemAccessibilityWrapper.stories.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import React from "react"; +import { fn } from "storybook/test"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { RoomListItemAccessibilityWrapper } from "./RoomListItemAccessibilityWrapper"; +import { createMockRoomItemViewModel, renderAvatar } from "../story-mocks"; + +const meta = { + title: "Room List/RoomListItemAccessibiltyWrapper", + component: RoomListItemAccessibilityWrapper, + tags: ["autodocs"], + args: { + roomIndex: 0, + roomIndexInSection: 0, + roomCount: 10, + onFocus: fn(), + isFirstItem: false, + isLastItem: false, + renderAvatar, + isSelected: false, + isFocused: false, + vm: createMockRoomItemViewModel("!room:server", "Room name", 0), + }, + decorators: [ + (Story) => ( +