diff --git a/playwright/e2e/left-panel/room-list-view/room-list-search.spec.ts b/playwright/e2e/left-panel/room-list-view/room-list-search.spec.ts new file mode 100644 index 0000000000..028503f622 --- /dev/null +++ b/playwright/e2e/left-panel/room-list-view/room-list-search.spec.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2025 New Vector 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 { type Page } from "@playwright/test"; + +import { test, expect } from "../../../element-web-test"; + +test.describe("Search section of the room list", () => { + test.use({ + labsFlags: ["feature_new_room_list"], + }); + + /** + * Get the search section of the room list + * @param page + */ + function getSearchSection(page: Page) { + return page.getByRole("search"); + } + + test.beforeEach(async ({ page, app, user }) => { + // The notification toast is displayed above the search section + await app.closeNotificationToast(); + }); + + test("should render the search section", { tag: "@screenshot" }, async ({ page, app, user }) => { + const searchSection = getSearchSection(page); + // exact=false to ignore the shortcut which is related to the OS + await expect(searchSection.getByRole("button", { name: "Search", exact: false })).toBeVisible(); + await expect(searchSection).toMatchScreenshot("search-section.png"); + }); + + test("should open the spotlight when the search button is clicked", async ({ page, app, user }) => { + const searchSection = getSearchSection(page); + await searchSection.getByRole("button", { name: "Search", exact: false }).click(); + // The spotlight should be displayed + await expect(page.getByRole("dialog", { name: "Search Dialog" })).toBeVisible(); + }); + + test("should open the room directory when the search button is clicked", async ({ page, app, user }) => { + const searchSection = getSearchSection(page); + await searchSection.getByRole("button", { name: "Explore rooms" }).click(); + const dialog = page.getByRole("dialog", { name: "Search Dialog" }); + // The room directory should be displayed + await expect(dialog).toBeVisible(); + // The public room filter should be displayed + await expect(dialog.getByText("Public rooms")).toBeVisible(); + }); +}); diff --git a/playwright/e2e/left-panel/room-list-view/room-list-view.spec.ts b/playwright/e2e/left-panel/room-list-view/room-list-view.spec.ts new file mode 100644 index 0000000000..7cd5122e8a --- /dev/null +++ b/playwright/e2e/left-panel/room-list-view/room-list-view.spec.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2025 New Vector 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 { type Page } from "@playwright/test"; + +import { test, expect } from "../../../element-web-test"; + +test.describe("Search section of the room list", () => { + test.use({ + labsFlags: ["feature_new_room_list"], + }); + + /** + * Get the room list view + * @param page + */ + function getRoomListView(page: Page) { + return page.getByTestId("room-list-view"); + } + + test.beforeEach(async ({ page, app, user }) => { + // The notification toast is displayed above the search section + await app.closeNotificationToast(); + }); + + test("should render the room list view", { tag: "@screenshot" }, async ({ page, app, user }) => { + const roomListView = getRoomListView(page); + await expect(roomListView).toMatchScreenshot("room-list-view.png"); + }); +}); diff --git a/playwright/e2e/settings/security-user-settings-tab.spec.ts b/playwright/e2e/settings/security-user-settings-tab.spec.ts index b723d1398f..9b9439796d 100644 --- a/playwright/e2e/settings/security-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/security-user-settings-tab.spec.ts @@ -25,13 +25,9 @@ test.describe("Security user settings tab", () => { }, }); - test.beforeEach(async ({ page, user }) => { + test.beforeEach(async ({ page, app, user }) => { // Dismiss "Notification" toast - await page - .locator(".mx_Toast_toast", { hasText: "Notifications" }) - .getByRole("button", { name: "Dismiss" }) - .click(); - + await app.closeNotificationToast(); await page.locator(".mx_Toast_buttons").getByRole("button", { name: "Yes" }).click(); // Allow analytics }); diff --git a/playwright/pages/ElementAppPage.ts b/playwright/pages/ElementAppPage.ts index d530c75b54..15b475a5d1 100644 --- a/playwright/pages/ElementAppPage.ts +++ b/playwright/pages/ElementAppPage.ts @@ -202,4 +202,15 @@ export class ElementAppPage { } return this.page.locator(`id=${labelledById ?? describedById}`); } + + /** + * Close the notification toast + */ + public closeNotificationToast(): Promise { + // Dismiss "Notification" toast + return this.page + .locator(".mx_Toast_toast", { hasText: "Notifications" }) + .getByRole("button", { name: "Dismiss" }) + .click(); + } } diff --git a/playwright/snapshots/left-panel/room-list-view/room-list-search.spec.ts/search-section-linux.png b/playwright/snapshots/left-panel/room-list-view/room-list-search.spec.ts/search-section-linux.png new file mode 100644 index 0000000000..6c2684e849 Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-view/room-list-search.spec.ts/search-section-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-view/room-list-view.spec.ts/room-list-view-linux.png b/playwright/snapshots/left-panel/room-list-view/room-list-view.spec.ts/room-list-view-linux.png new file mode 100644 index 0000000000..f114eff64c Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-view/room-list-view.spec.ts/room-list-view-linux.png differ diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 46b8fc932d..e6ca0c7ae9 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -269,6 +269,8 @@ @import "./views/right_panel/_VerificationPanel.pcss"; @import "./views/right_panel/_WidgetCard.pcss"; @import "./views/room_settings/_AliasSettings.pcss"; +@import "./views/rooms/RoomListView/_RoomListSearch.pcss"; +@import "./views/rooms/RoomListView/_RoomListView.pcss"; @import "./views/rooms/_AppsDrawer.pcss"; @import "./views/rooms/_Autocomplete.pcss"; @import "./views/rooms/_AuxPanel.pcss"; diff --git a/res/css/views/rooms/RoomListView/_RoomListSearch.pcss b/res/css/views/rooms/RoomListView/_RoomListSearch.pcss new file mode 100644 index 0000000000..55b1def58a --- /dev/null +++ b/res/css/views/rooms/RoomListView/_RoomListSearch.pcss @@ -0,0 +1,39 @@ +/* + * Copyright 2025 New Vector 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. + */ + +.mx_RoomListSearch { + /* From figma, this should be aligned with the room header */ + height: 64px; + box-sizing: border-box; + border-bottom: 1px solid var(--cpd-color-bg-subtle-primary); + padding: 0 var(--cpd-space-3x); + + svg { + fill: var(--cpd-color-icon-secondary); + } + + .mx_RoomListSearch_search { + /* The search button should take all the remaining space */ + flex: 1; + font: var(--cpd-font-body-md-regular); + color: var(--cpd-color-text-secondary); + + span { + flex: 1; + + kbd { + font-family: inherit; + } + } + } + + .mx_RoomListSearch_explore:hover { + svg { + fill: var(--cpd-color-icon-primary); + } + } +} diff --git a/res/css/views/rooms/RoomListView/_RoomListView.pcss b/res/css/views/rooms/RoomListView/_RoomListView.pcss new file mode 100644 index 0000000000..117810e6b7 --- /dev/null +++ b/res/css/views/rooms/RoomListView/_RoomListView.pcss @@ -0,0 +1,12 @@ +/* + * Copyright 2025 New Vector 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. + */ + +.mx_RoomListView { + background-color: var(--cpd-color-bg-canvas-default); + height: 100%; + border-right: 1px solid var(--cpd-color-bg-subtle-primary); +} diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index c67cea9b5a..82040eefab 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -390,7 +390,7 @@ export default class LeftPanel extends React.Component { return (
- +
); diff --git a/src/components/views/rooms/RoomListView.tsx b/src/components/views/rooms/RoomListView.tsx deleted file mode 100644 index c5f593decf..0000000000 --- a/src/components/views/rooms/RoomListView.tsx +++ /dev/null @@ -1,14 +0,0 @@ -/* -Copyright 2025 New Vector 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"; - -type IProps = unknown; - -export const RoomListView: React.FC = (props: IProps) => { - return
New Room List
; -}; diff --git a/src/components/views/rooms/RoomListView/RoomListSearch.tsx b/src/components/views/rooms/RoomListView/RoomListSearch.tsx new file mode 100644 index 0000000000..415e817ad9 --- /dev/null +++ b/src/components/views/rooms/RoomListView/RoomListSearch.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2025 New Vector 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, { type JSX } from "react"; +import { Button } from "@vector-im/compound-web"; +import ExploreIcon from "@vector-im/compound-design-tokens/assets/web/icons/explore"; +import SearchIcon from "@vector-im/compound-design-tokens/assets/web/icons/search"; + +import { IS_MAC, Key } from "../../../../Keyboard"; +import { _t } from "../../../../languageHandler"; +import { ALTERNATE_KEY_NAME } from "../../../../accessibility/KeyboardShortcuts"; +import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents"; +import { UIComponent } from "../../../../settings/UIFeature"; +import { MetaSpace } from "../../../../stores/spaces"; +import { Action } from "../../../../dispatcher/actions"; +import PosthogTrackers from "../../../../PosthogTrackers"; +import defaultDispatcher from "../../../../dispatcher/dispatcher"; +import { Flex } from "../../../utils/Flex"; + +type RoomListSearchProps = { + /** + * Current active space + * The explore button is only displayed in the Home meta space + */ + activeSpace: string; +}; + +/** + * A search component to be displayed at the top of the room list + * The `Explore` button is displayed only in the Home meta space and when UIComponent.ExploreRooms is enabled. + */ +export function RoomListSearch({ activeSpace }: RoomListSearchProps): JSX.Element { + const displayExploreButton = activeSpace === MetaSpace.Home && shouldShowComponent(UIComponent.ExploreRooms); + + return ( + + + {displayExploreButton && ( + + + + +`; + +exports[` should hide the explore button when UIComponent.ExploreRooms is disabled 1`] = ` + + + +`; + +exports[` should hide the explore button when the active space is not MetaSpace.Home 1`] = ` + + + +`; diff --git a/test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListView-test.tsx.snap new file mode 100644 index 0000000000..4ddc9ac5ec --- /dev/null +++ b/test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListView-test.tsx.snap @@ -0,0 +1,76 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should not render the RoomListSearch component when UIComponent.FilterContainer is at false 1`] = ` + +
+ +`; + +exports[` should render the RoomListSearch component when UIComponent.FilterContainer is at true 1`] = ` + +
+ +
+
+`;