diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts index d3aa060a272..7f87f3f43ce 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts @@ -22,13 +22,21 @@ test.describe("Room list filters and sort", () => { }); function getPrimaryFilters(page: Page): Locator { - return page.getByRole("listbox", { name: "Room list filters" }); + return page.getByTestId("primary-filters"); } function getRoomOptionsMenu(page: Page): Locator { return page.getByRole("button", { name: "Room Options" }); } + function getFilterExpandButton(page: Page): Locator { + return getPrimaryFilters(page).getByRole("button", { name: "Expand filter list" }); + } + + function getFilterCollapseButton(page: Page): Locator { + return getPrimaryFilters(page).getByRole("button", { name: "Collapse filter list" }); + } + /** * Get the room list * @param page @@ -136,6 +144,7 @@ test.describe("Room list filters and sort", () => { await tile.click(); // Enable Favourite filter + await getFilterExpandButton(page).click(); const primaryFilters = getPrimaryFilters(page); await primaryFilters.getByRole("option", { name: "Favourite" }).click(); await expect(tile).not.toBeVisible(); @@ -223,10 +232,6 @@ test.describe("Room list filters and sort", () => { expect(await roomList.locator("role=gridcell").count()).toBe(4); await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png"); - await primaryFilters.getByRole("option", { name: "Favourite" }).click(); - await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible(); - expect(await roomList.locator("role=gridcell").count()).toBe(1); - await primaryFilters.getByRole("option", { name: "People" }).click(); await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible(); await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible(); @@ -240,6 +245,12 @@ test.describe("Room list filters and sort", () => { await expect(roomList.getByRole("gridcell", { name: "Low prio room" })).toBeVisible(); expect(await roomList.locator("role=gridcell").count()).toBe(5); + await getFilterExpandButton(page).click(); + + await primaryFilters.getByRole("option", { name: "Favourite" }).click(); + await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible(); + expect(await roomList.locator("role=gridcell").count()).toBe(1); + await primaryFilters.getByRole("option", { name: "Mentions" }).click(); await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible(); expect(await roomList.locator("role=gridcell").count()).toBe(1); @@ -247,6 +258,9 @@ test.describe("Room list filters and sort", () => { await primaryFilters.getByRole("option", { name: "Invites" }).click(); await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible(); expect(await roomList.locator("role=gridcell").count()).toBe(1); + + await getFilterCollapseButton(page).click(); + await expect(primaryFilters.locator("role=option").first()).toHaveText("Invites"); }); test( @@ -326,6 +340,8 @@ test.describe("Room list filters and sort", () => { { tag: "@screenshot" }, async ({ page, app, user }) => { const primaryFilters = getPrimaryFilters(page); + await getFilterExpandButton(page).click(); + await primaryFilters.getByRole("option", { name: filter }).click(); const emptyRoomList = getEmptyRoomList(page); @@ -343,6 +359,8 @@ test.describe("Room list filters and sort", () => { { tag: "@screenshot" }, async ({ page, app, user }) => { const primaryFilters = getPrimaryFilters(page); + await getFilterExpandButton(page).click(); + await primaryFilters.getByRole("option", { name: filter }).click(); const emptyRoomList = getEmptyRoomList(page); diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png index 6c71c11b1d4..8cf4a3c97b3 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png index c7396da41d9..c770da3377a 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png index 15620d36126..44e27d96189 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png index 7b73e0b819f..3f700037cbc 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png index eb3add57336..e01564eeb40 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png index 70ed4bb782d..94b09ac14f5 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png index 6781c1d3641..45d2a775ea0 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png index 4c82dc21e73..fc391d56cfb 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png index 2f12ee4e415..13577e0a1bc 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png index 5458395e673..ac8abd60adb 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png index 878818f820a..0b879c18fe1 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png index c706e71b0fa..43d8781239a 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png index bada16f154b..75cea916c91 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png index b0de5b5253a..e023a0d34d5 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png index aa73d79988b..bc1aa9f4f11 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png index 55f4af3972d..1315363e7ed 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png index 36b7304a010..b400beac7c4 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png index 1f2b691b4ac..44d90bac341 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png index cc2adcc5989..fe5ef29ecf2 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png index 310912e50da..86032973a3b 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png index 9fa531f5b15..b134c90d3aa 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png index dac349eb2db..8970c20cb5f 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png index 144604ffebf..0c99720d01a 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png index 126f5d718de..e9f1db7361f 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png index a8fbeb0b032..b638b2880e2 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png index 724218249ba..43077507877 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png index d6ecbc1c40f..c75c1d08cc3 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png index 1bd0eaab1bd..cf8bb5a058f 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png index b654a1f592d..e6d3d50e93e 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png index 1a53f6151e7..8fab88fc7f7 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png index 05f824a2c89..85a30b02f28 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png differ diff --git a/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss b/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss index ac85782bbd0..f8fc31ae124 100644 --- a/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss +++ b/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss @@ -6,7 +6,32 @@ */ .mx_RoomListPrimaryFilters { - margin: unset; - list-style-type: none; - padding: var(--cpd-space-2x) var(--cpd-space-3x); + padding: var(--cpd-space-2x) var(--cpd-space-4x) var(--cpd-space-2x) var(--cpd-space-3x); + + .mx_RoomListPrimaryFilters_wrapping { + display: none; + } + + ul { + margin: unset; + padding: unset; + list-style-type: none; + /** + * The InteractionObserver needs the height to be set to work properly. + */ + height: 100%; + flex: 1; + } + + .mx_RoomListPrimaryFilters_IconButton { + svg { + transition: transform 0.1s linear; + } + } + + .mx_RoomListPrimaryFilters_IconButton[aria-expanded="true"] { + svg { + transform: rotate(180deg); + } + } } diff --git a/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx b/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx index ebf972d3613..892f2b56b74 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx @@ -5,8 +5,9 @@ * Please see LICENSE files in the repository root for full details. */ -import React, { type JSX } from "react"; -import { ChatFilter } from "@vector-im/compound-web"; +import React, { type JSX, useEffect, useId, useRef, useState, type RefObject } from "react"; +import { ChatFilter, IconButton } from "@vector-im/compound-web"; +import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down"; import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel"; import { Flex } from "../../../utils/Flex"; @@ -23,23 +24,146 @@ interface RoomListPrimaryFiltersProps { * The primary filters for the room list */ export function RoomListPrimaryFilters({ vm }: RoomListPrimaryFiltersProps): JSX.Element { + const id = useId(); + const [isExpanded, setIsExpanded] = useState(false); + + const { ref, isWrapping: displayChevron, wrappingIndex } = useCollapseFilters(isExpanded); + const filters = useVisibleFilters(vm.primaryFilters, wrappingIndex); + return ( - {vm.primaryFilters.map((filter) => ( -
  • - - {filter.name} - -
  • - ))} + {displayChevron && ( + setIsExpanded((_expanded) => !_expanded)} + > + + + )} + + {filters.map((filter, i) => ( +
  • + filter.toggle()}> + {filter.name} + +
  • + ))} +
    ); } + +/** + * A hook to manage the wrapping of filters in the room list. + * It observes the filter list and hides filters that are wrapping when the list is not expanded. + * @param isExpanded + * @returns an object containing: + * - `ref`: a ref to put on the filter list element + * - `isWrapping`: a boolean indicating if the filters are wrapping + * - `wrappingIndex`: the index of the first filter that is wrapping + */ +function useCollapseFilters( + isExpanded: boolean, +): { ref: RefObject; isWrapping: boolean; wrappingIndex: number } { + const ref = useRef(null); + const [isWrapping, setIsWrapping] = useState(false); + const [wrappingIndex, setWrappingIndex] = useState(-1); + + useEffect(() => { + if (!ref.current) return; + + const hideFilters = (list: Element): void => { + let isWrapping = false; + Array.from(list.children).forEach((node, i): void => { + const child = node as HTMLElement; + const wrappingClass = "mx_RoomListPrimaryFilters_wrapping"; + child.setAttribute("aria-hidden", "false"); + child.classList.remove(wrappingClass); + + // If the filter list is expanded, all filters are visible + if (isExpanded) return; + + // If the previous element is on the left element of the current one, it means that the filter is wrapping + const previousSibling = child.previousElementSibling as HTMLElement | null; + if (previousSibling && child.offsetLeft < previousSibling.offsetLeft) { + if (!isWrapping) setWrappingIndex(i); + isWrapping = true; + } + + // If the filter is wrapping, we hide it + child.classList.toggle(wrappingClass, isWrapping); + child.setAttribute("aria-hidden", isWrapping.toString()); + }); + + if (!isWrapping) setWrappingIndex(-1); + setIsWrapping(isExpanded || isWrapping); + }; + + hideFilters(ref.current); + const observer = new ResizeObserver((entries) => entries.forEach((entry) => hideFilters(entry.target))); + + observer.observe(ref.current); + return () => { + observer.disconnect(); + }; + }, [isExpanded]); + + return { ref, isWrapping, wrappingIndex }; +} + +/** + * A hook to sort the filters by active state. + * The list is sorted if the current filter index is greater than or equal to the wrapping index. + * If the wrapping index is -1, the filters are not sorted. + * + * @param filters - the list of filters to sort. + * @param wrappingIndex - the index of the first filter that is wrapping. + */ +export function useVisibleFilters( + filters: RoomListViewState["primaryFilters"], + wrappingIndex: number, +): RoomListViewState["primaryFilters"] { + // By default, the filters are not sorted + const [sortedFilters, setSortedFilters] = useState(filters); + + useEffect(() => { + const isActiveFilterWrapping = filters.findIndex((f) => f.active) >= wrappingIndex; + // If the active filter is not wrapping, we don't need to sort the filters + if (!isActiveFilterWrapping || wrappingIndex === -1) { + setSortedFilters(filters); + return; + } + + // Sort the filters with the current filter at first position + setSortedFilters( + filters.slice().sort((filterA, filterB) => { + // If the filter is active, it should be at the top of the list + if (filterA.active && !filterB.active) return -1; + if (!filterA.active && filterB.active) return 1; + // If both filters are active or not, keep their original order + return 0; + }), + ); + }, [filters, wrappingIndex]); + + return sortedFilters; +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1d18ec8c730..5763e0a7811 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2115,6 +2115,7 @@ "add_space_label": "Add space", "breadcrumbs_empty": "No recently visited rooms", "breadcrumbs_label": "Recently visited rooms", + "collapse_filters": "Collapse filter list", "empty": { "no_chats": "No chats yet", "no_chats_description": "Get started by messaging someone or by creating a room", @@ -2132,6 +2133,7 @@ "show_activity": "See all activity", "show_chats": "Show all chats" }, + "expand_filters": "Expand filter list", "failed_add_tag": "Failed to add tag %(tagName)s to room", "failed_remove_tag": "Failed to remove tag %(tagName)s from room", "failed_set_dm_tag": "Failed to set direct message tag", diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPanel-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPanel-test.tsx index 525a17e1ac8..2b2cb341858 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPanel-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPanel-test.tsx @@ -28,16 +28,13 @@ describe("", () => { }); it("should render the RoomListSearch component when UIComponent.FilterContainer is at true", () => { - const { asFragment } = renderComponent(); + renderComponent(); expect(screen.getByRole("button", { name: "Search Ctrl K" })).toBeInTheDocument(); - expect(asFragment()).toMatchSnapshot(); }); it("should not render the RoomListSearch component when UIComponent.FilterContainer is at false", () => { mocked(shouldShowComponent).mockReturnValue(false); - const { asFragment } = renderComponent(); - + renderComponent(); expect(screen.queryByRole("button", { name: "Search Ctrl K" })).toBeNull(); - expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx index fe605ce70fa..7128ecd1b8e 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -import React from "react"; +import React, { act } from "react"; import { render, screen } from "jest-matrix-react"; import userEvent from "@testing-library/user-event"; @@ -15,31 +15,111 @@ import { FilterKey } from "../../../../../../src/stores/room-list-v3/skip-list/f describe("", () => { let vm: RoomListViewState; + const filterToggleMocks = [jest.fn(), jest.fn(), jest.fn()]; + + let resizeCallback: ResizeObserverCallback; beforeEach(() => { + // Reset mocks between tests + filterToggleMocks.forEach((mock) => mock.mockClear()); + + // Mock ResizeObserver + global.ResizeObserver = jest.fn().mockImplementation((callback) => { + resizeCallback = callback; + return { + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + }; + }); + vm = { - isLoadingRooms: false, - rooms: [], - canCreateRoom: true, - createRoom: jest.fn(), - createChatRoom: jest.fn(), primaryFilters: [ - { name: "People", active: false, toggle: jest.fn(), key: FilterKey.PeopleFilter }, - { name: "Rooms", active: true, toggle: jest.fn(), key: FilterKey.RoomsFilter }, + { name: "People", active: false, toggle: filterToggleMocks[0], key: FilterKey.PeopleFilter }, + { name: "Rooms", active: true, toggle: filterToggleMocks[1], key: FilterKey.RoomsFilter }, + { name: "Unreads", active: false, toggle: filterToggleMocks[2], key: FilterKey.UnreadFilter }, ], - activeIndex: undefined, - }; + } as unknown as RoomListViewState; }); - it("should render primary filters", async () => { - const user = userEvent.setup(); - + it("should renders all filters correctly", () => { const { asFragment } = render(); + + // Check that all filters are rendered expect(screen.getByRole("option", { name: "People" })).toBeInTheDocument(); + expect(screen.getByRole("option", { name: "Rooms" })).toBeInTheDocument(); + expect(screen.getByRole("option", { name: "Unreads" })).toBeInTheDocument(); + + // Check that the active filter is marked as selected expect(screen.getByRole("option", { name: "Rooms" })).toHaveAttribute("aria-selected", "true"); + expect(screen.getByRole("option", { name: "People" })).toHaveAttribute("aria-selected", "false"); + expect(screen.getByRole("option", { name: "Unreads" })).toHaveAttribute("aria-selected", "false"); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should call toggle function when a filter is clicked", async () => { + const user = userEvent.setup(); + render(); + // Click on an inactive filter await user.click(screen.getByRole("button", { name: "People" })); - expect(vm.primaryFilters[0].toggle).toHaveBeenCalled(); + + // Check that the toggle function was called + expect(filterToggleMocks[0]).toHaveBeenCalledTimes(1); + }); + + function mockFiltersOffsetLeft() { + jest.spyOn(screen.getByRole("option", { name: "People" }), "offsetLeft", "get").mockReturnValue(0); + jest.spyOn(screen.getByRole("option", { name: "Rooms" }), "offsetLeft", "get").mockReturnValue(30); + // Unreads is wrapping + jest.spyOn(screen.getByRole("option", { name: "Unreads" }), "offsetLeft", "get").mockReturnValue(0); + } + + it("should hide or display filters if they are wrapping", async () => { + const user = userEvent.setup(); + render(); + + // No filter is wrapping, so chevron shouldn't be visible + expect(screen.queryByRole("button", { name: "Expand filter list" })).toBeNull(); + expect(screen.queryByRole("option", { name: "Unreads" })).toBeVisible(); + + mockFiltersOffsetLeft(); + // @ts-ignore + act(() => resizeCallback([{ target: screen.getByRole("listbox", { name: "Room list filters" }) }])); + + // The Unreads filter is wrapping, it should not be visible + expect(screen.queryByRole("option", { name: "Unreads" })).toBeNull(); + // Now filters are wrapping, so chevron should be visible + await user.click(screen.getByRole("button", { name: "Expand filter list" })); + // The list is expanded, so Unreads should be visible + expect(screen.getByRole("option", { name: "Unreads" })).toBeVisible(); + }); + + it("should move the active filter if the list is collapsed and the filter is wrapping", async () => { + vm = { + primaryFilters: [ + { name: "People", active: false, toggle: filterToggleMocks[0], key: FilterKey.PeopleFilter }, + { name: "Rooms", active: false, toggle: filterToggleMocks[1], key: FilterKey.RoomsFilter }, + { name: "Unreads", active: true, toggle: filterToggleMocks[2], key: FilterKey.UnreadFilter }, + ], + } as unknown as RoomListViewState; + + const user = userEvent.setup(); + render(); + mockFiltersOffsetLeft(); + // @ts-ignore + act(() => resizeCallback([{ target: screen.getByRole("listbox", { name: "Room list filters" }) }])); + + // Unread filter should be moved to the first position + expect(screen.getByRole("listbox", { name: "Room list filters" }).children[0]).toBe( + screen.getByRole("option", { name: "Unreads" }), + ); + + // When the list is expanded, the Unreads filter should move to its original position + await user.click(screen.getByRole("button", { name: "Expand filter list" })); + expect(screen.getByRole("listbox", { name: "Room list filters" }).children[0]).not.toEqual( + screen.getByRole("option", { name: "Unreads" }), + ); }); }); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap deleted file mode 100644 index 7708ab1adbe..00000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap +++ /dev/null @@ -1,459 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should not render the RoomListSearch component when UIComponent.FilterContainer is at false 1`] = ` - -
    -
    -
    -

    - Home -

    -
    -
    - - -
    -
    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    -
    -
    -
    -`; - -exports[` should render the RoomListSearch component when UIComponent.FilterContainer is at true 1`] = ` - -
    - -
    -
    -

    - Home -

    -
    -
    - - -
    -
    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    -
    -
    -
    -`; diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap index 6c705944b86..ee68963b2f8 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap @@ -1,39 +1,62 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should render primary filters 1`] = ` +exports[` should renders all filters correctly 1`] = ` -
      -
    • - -
    • -
    • - +
    • +
    • + +
    • +
    • - Rooms - -
    • -
    + + + +
    `;