Add message preview support to the new room list (#29784)

* Add message preview support to the new room list

 * Support showing message previews in the room list items
 * Add the secondary filters bar with the '...' menu, containing
   just the option for message previews for now
 * Change message preview toggle hook to update when setting is updated

* Use new compund release

* Unused i18n keys

* Unused imports

* Fix test & update snapshot

* Fix more snapshots

* Fix test

Split into two tests that test setting & updating

* Type import

* Snapshots

* Remove unnecessary Flex container

and update screenshots as the room list has got shorter from the added bar

* More snapshots & screenshots

* More snapshots

* Add test and remove active filter that's not done yet

* Update snapshots & screenshots again

* Other screenshot

* Add more tests

* Fix syntax

* Fix tests

* Use setter directly

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix CSS

* Remopve filter button css for now

* Update to remove forwardRef

* Add comment on why lack of TypedEventEmitter

* snapshots again

* Screenshots again

* Use original screenshots, maybe they'll work now

* Add comment

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
David Baker
2025-04-24 16:03:39 +01:00
committed by GitHub
parent 22d5c00174
commit 714f8f40dd
30 changed files with 674 additions and 92 deletions

View File

@@ -5,7 +5,7 @@
* Please see LICENSE files in the repository root for full details.
*/
import { renderHook } from "jest-matrix-react";
import { renderHook, waitFor } from "jest-matrix-react";
import { type Room } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
@@ -20,22 +20,40 @@ import {
import { RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState";
import { RoomNotificationStateStore } from "../../../../../src/stores/notifications/RoomNotificationStateStore";
import * as UseCallModule from "../../../../../src/hooks/useCall";
import { type MessagePreview, MessagePreviewStore } from "../../../../../src/stores/room-list/MessagePreviewStore";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { useMessagePreviewToggle } from "../../../../../src/components/viewmodels/roomlist/useMessagePreviewToggle";
jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({
hasAccessToOptionsMenu: jest.fn().mockReturnValue(false),
hasAccessToNotificationMenu: jest.fn().mockReturnValue(false),
}));
jest.mock("../../../../../src/components/viewmodels/roomlist/useMessagePreviewToggle", () => ({
useMessagePreviewToggle: jest.fn().mockReturnValue({ shouldShowMessagePreview: true }),
}));
describe("RoomListItemViewModel", () => {
let room: Room;
beforeEach(() => {
const matrixClient = createTestClient();
room = mkStubRoom("roomId", "roomName", matrixClient);
const dmRoomMap = {
getUserIdForRoomId: jest.fn(),
getDMRoomsForUserId: jest.fn(),
} as unknown as DMRoomMap;
DMRoomMap.setShared(dmRoomMap);
mocked(useMessagePreviewToggle).mockReturnValue({
shouldShowMessagePreview: false,
toggleMessagePreview: jest.fn(),
});
});
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});
it("should dispatch view room action on openRoom", async () => {
@@ -87,6 +105,39 @@ describe("RoomListItemViewModel", () => {
expect(vm.current.showHoverMenu).toBe(true);
});
it("should return a message preview if one is available and they are enabled", async () => {
jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({
text: "Message look like this",
} as MessagePreview);
mocked(useMessagePreviewToggle).mockReturnValue({
shouldShowMessagePreview: true,
toggleMessagePreview: jest.fn(),
});
const { result: vm } = renderHook(
() => useRoomListItemViewModel(room),
withClientContextRenderOptions(room.client),
);
await waitFor(() => expect(vm.current.messagePreview).toBe("Message look like this"));
});
it("should hide message previews when disabled", async () => {
jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({
text: "Message look like this",
} as MessagePreview);
const { result: vm, rerender } = renderHook(
() => useRoomListItemViewModel(room),
withClientContextRenderOptions(room.client),
);
// This doesn't seem to test that the hook actually triggers an update,
// but I can't see how to test that.
rerender();
expect(vm.current.messagePreview).toBe(undefined);
});
describe("notification", () => {
let notificationState: RoomNotificationState;
beforeEach(() => {

View File

@@ -17,13 +17,14 @@ import { FilterKey } from "../../../../../src/stores/room-list-v3/skip-list/filt
import { SecondaryFilters } from "../../../../../src/components/viewmodels/roomlist/useFilteredRooms";
import { SortingAlgorithm } from "../../../../../src/stores/room-list-v3/skip-list/sorters";
import { SortOption } from "../../../../../src/components/viewmodels/roomlist/useSorter";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import SettingsStore, { type CallbackFn } from "../../../../../src/settings/SettingsStore";
import { hasCreateRoomRights, createRoom } from "../../../../../src/components/viewmodels/roomlist/utils";
import dispatcher from "../../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../../src/dispatcher/actions";
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
import { UPDATE_SELECTED_SPACE } from "../../../../../src/stores/spaces";
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({
hasCreateRoomRights: jest.fn().mockReturnValue(false),
@@ -308,6 +309,25 @@ describe("RoomListViewModel", () => {
expect(vm.current.shouldShowMessagePreview).toEqual(true);
});
it("should update when setting changes", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(() => true);
let watchFn: CallbackFn;
jest.spyOn(SettingsStore, "watchSetting").mockImplementation((_settingname, _roomId, fn) => {
watchFn = fn;
return "";
});
mockAndCreateRooms();
const { result: vm } = renderHook(() => useRoomListViewModel());
expect(vm.current.shouldShowMessagePreview).toEqual(true);
jest.spyOn(SettingsStore, "getValue").mockImplementation(() => false);
act(() => {
watchFn("RoomList.showMessagePreview", "", SettingLevel.DEVICE, false, false);
});
expect(vm.current.shouldShowMessagePreview).toEqual(false);
});
it("should change setting on toggle", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(() => true);
const fn = jest.spyOn(SettingsStore, "setValue").mockImplementation(async () => {});
@@ -317,8 +337,7 @@ describe("RoomListViewModel", () => {
act(() => {
vm.current.toggleMessagePreview();
});
expect(vm.current.shouldShowMessagePreview).toEqual(false);
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledWith("RoomList.showMessagePreview", null, "device", false);
});
});

View File

@@ -51,6 +51,7 @@ describe("<RoomListItemView />", () => {
hasParticipantInCall: false,
name: room.name,
showNotificationDecoration: false,
messagePreview: undefined,
};
mocked(useRoomListItemViewModel).mockReturnValue(defaultValue);
@@ -62,6 +63,14 @@ describe("<RoomListItemView />", () => {
expect(asFragment()).toMatchSnapshot();
});
test("should render a room item with a message preview", () => {
defaultValue.messagePreview = "The message looks list this";
const onClick = jest.fn();
const { asFragment } = render(<RoomListItemView room={room} onClick={onClick} isSelected={false} />);
expect(asFragment()).toMatchSnapshot();
});
test("should call openRoom when clicked", async () => {
const user = userEvent.setup();
render(<RoomListItemView room={room} isSelected={false} />);

View File

@@ -0,0 +1,41 @@
/*
* 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";
import { render, screen } from "jest-matrix-react";
import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel";
import { SecondaryFilters } from "../../../../../../src/components/viewmodels/roomlist/useFilteredRooms";
import { SortOption } from "../../../../../../src/components/viewmodels/roomlist/useSorter";
import { RoomListSecondaryFilters } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListSecondaryFilters";
describe("<RoomListSecondaryFilters />", () => {
let vm: RoomListViewState;
beforeEach(() => {
vm = {
rooms: [],
canCreateRoom: true,
createRoom: jest.fn(),
createChatRoom: jest.fn(),
primaryFilters: [],
activateSecondaryFilter: () => {},
activeSecondaryFilter: SecondaryFilters.AllActivity,
sort: jest.fn(),
activeSortOption: SortOption.Activity,
shouldShowMessagePreview: false,
toggleMessagePreview: jest.fn(),
activeIndex: undefined,
};
});
it("should render 'room options' button", async () => {
const { asFragment } = render(<RoomListSecondaryFilters vm={vm} />);
expect(screen.getByRole("button", { name: "Room Options" })).toBeInTheDocument();
expect(asFragment()).toMatchSnapshot();
});
});

View File

@@ -57,12 +57,19 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room0"
<div
class="mx_RoomListItemView_text"
>
room0
</span>
<div
class="mx_RoomListItemView_roomName"
title="room0"
>
room0
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
@@ -101,12 +108,19 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room1"
<div
class="mx_RoomListItemView_text"
>
room1
</span>
<div
class="mx_RoomListItemView_roomName"
title="room1"
>
room1
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
@@ -145,12 +159,19 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room2"
<div
class="mx_RoomListItemView_text"
>
room2
</span>
<div
class="mx_RoomListItemView_roomName"
title="room2"
>
room2
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
@@ -189,12 +210,19 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room3"
<div
class="mx_RoomListItemView_text"
>
room3
</span>
<div
class="mx_RoomListItemView_roomName"
title="room3"
>
room3
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
@@ -233,12 +261,19 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room4"
<div
class="mx_RoomListItemView_text"
>
room4
</span>
<div
class="mx_RoomListItemView_roomName"
title="room4"
>
room4
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
@@ -277,12 +312,19 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room5"
<div
class="mx_RoomListItemView_text"
>
room5
</span>
<div
class="mx_RoomListItemView_roomName"
title="room5"
>
room5
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
@@ -321,12 +363,19 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room6"
<div
class="mx_RoomListItemView_text"
>
room6
</span>
<div
class="mx_RoomListItemView_roomName"
title="room6"
>
room6
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
@@ -365,12 +414,19 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room7"
<div
class="mx_RoomListItemView_text"
>
room7
</span>
<div
class="mx_RoomListItemView_roomName"
title="room7"
>
room7
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
@@ -409,12 +465,19 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room8"
<div
class="mx_RoomListItemView_text"
>
room8
</span>
<div
class="mx_RoomListItemView_roomName"
title="room8"
>
room8
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
@@ -453,12 +516,19 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room9"
<div
class="mx_RoomListItemView_text"
>
room9
</span>
<div
class="mx_RoomListItemView_roomName"
title="room9"
>
room9
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>

View File

@@ -35,12 +35,19 @@ exports[`<RoomListItemView /> should be selected if isSelected=true 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room1"
<div
class="mx_RoomListItemView_text"
>
room1
</span>
<div
class="mx_RoomListItemView_roomName"
title="room1"
>
room1
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
@@ -82,12 +89,19 @@ exports[`<RoomListItemView /> should display notification decoration 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room1"
<div
class="mx_RoomListItemView_text"
>
room1
</span>
<div
class="mx_RoomListItemView_roomName"
title="room1"
>
room1
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
<div
aria-hidden="true"
class="mx_Flex"
@@ -141,12 +155,75 @@ exports[`<RoomListItemView /> should render a room item 1`] = `
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
class="mx_RoomListItemView_roomName"
title="room1"
<div
class="mx_RoomListItemView_text"
>
room1
</span>
<div
class="mx_RoomListItemView_roomName"
title="room1"
>
room1
</div>
<div
class="mx_RoomListItemView_messagePreview"
/>
</div>
</div>
</div>
</button>
</DocumentFragment>
`;
exports[`<RoomListItemView /> should render a room item with a message preview 1`] = `
<DocumentFragment>
<button
aria-label="Open room room1"
aria-selected="false"
class="mx_RoomListItemView mx_RoomListItemView_empty"
type="button"
>
<div
class="mx_Flex mx_RoomListItemView_container"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<span
aria-label="Avatar"
class="_avatar_1qbcf_8 mx_BaseAvatar"
data-color="3"
data-testid="avatar-img"
data-type="round"
style="--cpd-avatar-size: 32px;"
>
<img
alt=""
class="_image_1qbcf_41"
data-type="round"
height="32px"
loading="lazy"
referrerpolicy="no-referrer"
src="http://this.is.a.url/avatar.url/room.png"
width="32px"
/>
</span>
<div
class="mx_Flex mx_RoomListItemView_content"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
>
<div
class="mx_RoomListItemView_text"
>
<div
class="mx_RoomListItemView_roomName"
title="room1"
>
room1
</div>
<div
class="mx_RoomListItemView_messagePreview"
>
The message looks list this
</div>
</div>
</div>
</div>
</button>

View File

@@ -113,6 +113,43 @@ exports[`<RoomListPanel /> should not render the RoomListSearch component when U
</button>
</li>
</ul>
<div
aria-label="Secondary filters"
class="mx_Flex mx_RoomListSecondaryFilters"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 8px; --mx-flex-wrap: nowrap;"
>
<button
aria-disabled="false"
aria-expanded="false"
aria-haspopup="menu"
aria-label="Room Options"
aria-labelledby="«rc»"
class="_icon-button_m2erp_8 mx_RoomListSecondaryFilters_roomOptionsButton"
data-state="closed"
id="radix-«ra»"
role="button"
style="--cpd-icon-button-size: 32px;"
tabindex="0"
type="button"
>
<div
class="_indicator-icon_zr2a0_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
/>
</svg>
</div>
</button>
</div>
<div
class="mx_Flex mx_EmptyRoomList_GenericPlaceholder"
data-testid="empty-room-list"
@@ -334,6 +371,43 @@ exports[`<RoomListPanel /> should render the RoomListSearch component when UICom
</button>
</li>
</ul>
<div
aria-label="Secondary filters"
class="mx_Flex mx_RoomListSecondaryFilters"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 8px; --mx-flex-wrap: nowrap;"
>
<button
aria-disabled="false"
aria-expanded="false"
aria-haspopup="menu"
aria-label="Room Options"
aria-labelledby="«r4»"
class="_icon-button_m2erp_8 mx_RoomListSecondaryFilters_roomOptionsButton"
data-state="closed"
id="radix-«r2»"
role="button"
style="--cpd-icon-button-size: 32px;"
tabindex="0"
type="button"
>
<div
class="_indicator-icon_zr2a0_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
/>
</svg>
</div>
</button>
</div>
<div
class="mx_Flex mx_EmptyRoomList_GenericPlaceholder"
data-testid="empty-room-list"

View File

@@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<RoomListSecondaryFilters /> should render 'room options' button 1`] = `
<DocumentFragment>
<div
aria-label="Secondary filters"
class="mx_Flex mx_RoomListSecondaryFilters"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 8px; --mx-flex-wrap: nowrap;"
>
<button
aria-disabled="false"
aria-expanded="false"
aria-haspopup="menu"
aria-label="Room Options"
aria-labelledby="«r2»"
class="_icon-button_m2erp_8 mx_RoomListSecondaryFilters_roomOptionsButton"
data-state="closed"
id="radix-«r0»"
role="button"
style="--cpd-icon-button-size: 32px;"
tabindex="0"
type="button"
>
<div
class="_indicator-icon_zr2a0_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
/>
</svg>
</div>
</button>
</div>
</DocumentFragment>
`;