New room list: filter list can be collapsed (#29992)
* feat: add new hook to check if a node is visible * feat: filters in new room list can be collapsed * feat: add animation to filter list * feat: hide chevron when list fit on one line * fix: use correct label for expand button * test: update room list panel snapshots * test: add tests for useIsNodeVisible * chore: update i18n * test: add tests for primary filters * test(e2e): update existing screenshots * test(e2e): update primary filter tests * chore: typo in css file * refactor: replace ternary by if in filter condition * feat: compute filter height instead of hardcoded value * fix: floor floating computation on filter * refactor: move hooks to dedicated files * test: update tests * feat: rework collapse feature * test: remove room list panel snapshot * test: update room list primary filter tests * test(e2e): update screenshots * test(e2e): update screenshots * test(e2e): fix favourite filter in scroll behaviour test * fix: accessibility order when tabbing
@@ -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);
|
||||
|
||||
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<HTMLUListElement>(isExpanded);
|
||||
const filters = useVisibleFilters(vm.primaryFilters, wrappingIndex);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
as="ul"
|
||||
role="listbox"
|
||||
aria-label={_t("room_list|primary_filters")}
|
||||
className="mx_RoomListPrimaryFilters"
|
||||
align="center"
|
||||
gap="var(--cpd-space-2x)"
|
||||
wrap="wrap"
|
||||
data-testid="primary-filters"
|
||||
gap="var(--cpd-space-3x)"
|
||||
direction="row-reverse"
|
||||
>
|
||||
{vm.primaryFilters.map((filter) => (
|
||||
<li role="option" aria-selected={filter.active} key={filter.name}>
|
||||
<ChatFilter selected={filter.active} onClick={filter.toggle}>
|
||||
{filter.name}
|
||||
</ChatFilter>
|
||||
</li>
|
||||
))}
|
||||
{displayChevron && (
|
||||
<IconButton
|
||||
subtleBackground={true}
|
||||
aria-expanded={isExpanded}
|
||||
aria-controls={id}
|
||||
className="mx_RoomListPrimaryFilters_IconButton"
|
||||
aria-label={isExpanded ? _t("room_list|collapse_filters") : _t("room_list|expand_filters")}
|
||||
size="28px"
|
||||
onClick={() => setIsExpanded((_expanded) => !_expanded)}
|
||||
>
|
||||
<ChevronDownIcon color="var(--cpd-color-icon-secondary)" />
|
||||
</IconButton>
|
||||
)}
|
||||
<Flex
|
||||
id={id}
|
||||
as="ul"
|
||||
role="listbox"
|
||||
aria-label={_t("room_list|primary_filters")}
|
||||
align="center"
|
||||
gap="var(--cpd-space-2x)"
|
||||
wrap="wrap"
|
||||
ref={ref}
|
||||
>
|
||||
{filters.map((filter, i) => (
|
||||
<li role="option" aria-selected={filter.active} key={i}>
|
||||
<ChatFilter selected={filter.active} onClick={() => filter.toggle()}>
|
||||
{filter.name}
|
||||
</ChatFilter>
|
||||
</li>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<T extends HTMLElement>(
|
||||
isExpanded: boolean,
|
||||
): { ref: RefObject<T | null>; isWrapping: boolean; wrappingIndex: number } {
|
||||
const ref = useRef<T>(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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -28,16 +28,13 @@ describe("<RoomListPanel />", () => {
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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("<RoomListPrimaryFilters />", () => {
|
||||
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(<RoomListPrimaryFilters vm={vm} />);
|
||||
expect(screen.getByRole("option", { name: "People" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("option", { name: "Rooms" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
// 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(<RoomListPrimaryFilters vm={vm} />);
|
||||
|
||||
// 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(<RoomListPrimaryFilters vm={vm} />);
|
||||
|
||||
// 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(<RoomListPrimaryFilters vm={vm} />);
|
||||
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" }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,459 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<RoomListPanel /> should not render the RoomListSearch component when UIComponent.FilterContainer is at false 1`] = `
|
||||
<DocumentFragment>
|
||||
<section
|
||||
class="mx_Flex mx_RoomListPanel"
|
||||
data-testid="room-list-panel"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<header
|
||||
aria-label="Room options"
|
||||
class="mx_Flex mx_RoomListHeaderView"
|
||||
data-testid="room-list-header"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<div
|
||||
class="mx_Flex mx_RoomListHeaderView_title"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<h1
|
||||
title="Home"
|
||||
>
|
||||
Home
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --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"
|
||||
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
|
||||
color="var(--cpd-color-icon-secondary)"
|
||||
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>
|
||||
<button
|
||||
aria-label="New message"
|
||||
class="_icon-button_m2erp_8"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_zr2a0_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-secondary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M16.937 2.82a2 2 0 0 1 2.828 0l1.415 1.414a2 2 0 0 1 0 2.829l-7.071 7.07c-.195.196-.42.342-.66.44a1 1 0 0 1-.168.072l-3.993 1.331a1 1 0 0 1-1.265-1.265l1.331-3.992q.03-.09.073-.168m10.338-4.903-6.717 6.718-1.414-1.414 6.717-6.718z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M3 5a2 2 0 0 1 2-2h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<ul
|
||||
aria-label="Room list filters"
|
||||
class="mx_Flex mx_RoomListPrimaryFilters"
|
||||
role="listbox"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: wrap;"
|
||||
>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Unreads
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
People
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Rooms
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Mentions
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Invites
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Favourites
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Low priority
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
class="mx_RoomListSkeleton"
|
||||
/>
|
||||
</section>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<RoomListPanel /> should render the RoomListSearch component when UIComponent.FilterContainer is at true 1`] = `
|
||||
<DocumentFragment>
|
||||
<section
|
||||
class="mx_Flex mx_RoomListPanel"
|
||||
data-testid="room-list-panel"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<div
|
||||
class="mx_Flex mx_RoomListSearch"
|
||||
role="search"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<button
|
||||
class="_button_vczzf_8 mx_RoomListSearch_search _has-icon_vczzf_57"
|
||||
data-kind="secondary"
|
||||
data-size="sm"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_Flex"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<span
|
||||
class="mx_RoomListSearch_search_text"
|
||||
>
|
||||
Search
|
||||
</span>
|
||||
<kbd>
|
||||
Ctrl K
|
||||
</kbd>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Explore rooms"
|
||||
class="_button_vczzf_8 mx_RoomListSearch_button _has-icon_vczzf_57 _icon-only_vczzf_50"
|
||||
data-kind="secondary"
|
||||
data-size="sm"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<header
|
||||
aria-label="Room options"
|
||||
class="mx_Flex mx_RoomListHeaderView"
|
||||
data-testid="room-list-header"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<div
|
||||
class="mx_Flex mx_RoomListHeaderView_title"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<h1
|
||||
title="Home"
|
||||
>
|
||||
Home
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --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"
|
||||
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
|
||||
color="var(--cpd-color-icon-secondary)"
|
||||
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>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
aria-label="Add"
|
||||
class="_icon-button_m2erp_8"
|
||||
data-state="closed"
|
||||
id="radix-«r7»"
|
||||
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
|
||||
color="var(--cpd-color-icon-secondary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M16.937 2.82a2 2 0 0 1 2.828 0l1.415 1.414a2 2 0 0 1 0 2.829l-7.071 7.07c-.195.196-.42.342-.66.44a1 1 0 0 1-.168.072l-3.993 1.331a1 1 0 0 1-1.265-1.265l1.331-3.992q.03-.09.073-.168m10.338-4.903-6.717 6.718-1.414-1.414 6.717-6.718z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M3 5a2 2 0 0 1 2-2h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<ul
|
||||
aria-label="Room list filters"
|
||||
class="mx_Flex mx_RoomListPrimaryFilters"
|
||||
role="listbox"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: wrap;"
|
||||
>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Unreads
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
People
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Rooms
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Mentions
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Invites
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Favourites
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Low priority
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
class="mx_RoomListSkeleton"
|
||||
/>
|
||||
</section>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -1,39 +1,62 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<RoomListPrimaryFilters /> should render primary filters 1`] = `
|
||||
exports[`<RoomListPrimaryFilters /> should renders all filters correctly 1`] = `
|
||||
<DocumentFragment>
|
||||
<ul
|
||||
aria-label="Room list filters"
|
||||
<div
|
||||
class="mx_Flex mx_RoomListPrimaryFilters"
|
||||
role="listbox"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: wrap;"
|
||||
data-testid="primary-filters"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<li
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
<ul
|
||||
aria-label="Room list filters"
|
||||
class="mx_Flex"
|
||||
id="«r0»"
|
||||
role="listbox"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: wrap;"
|
||||
>
|
||||
<button
|
||||
<li
|
||||
aria-hidden="false"
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
role="option"
|
||||
>
|
||||
People
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-selected="true"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
People
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-hidden="false"
|
||||
aria-selected="true"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
role="option"
|
||||
>
|
||||
Rooms
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
aria-selected="true"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Rooms
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
aria-hidden="false"
|
||||
aria-selected="false"
|
||||
role="option"
|
||||
>
|
||||
<button
|
||||
aria-selected="false"
|
||||
class="_chat-filter_5qdp0_8"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Unreads
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||