Skip to content

Commit 21b542e

Browse files
committed
feat: filters in new room list can be collapsed
1 parent d74138d commit 21b542e

File tree

2 files changed

+133
-21
lines changed

2 files changed

+133
-21
lines changed

res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,35 @@
66
*/
77

88
.mx_RoomListPrimaryFilters {
9-
margin: unset;
10-
list-style-type: none;
11-
padding: var(--cpd-space-2x) var(--cpd-space-3x);
9+
padding: var(--cpd-space-2x) var(--cpd-space-4x) var(--cpd-space-2x) var(--cpd-space-3x);
10+
max-height: 44px;
11+
overflow: hidden;
12+
box-sizing: border-box;
13+
14+
&[data-expanded="true"] {
15+
max-height: unset;
16+
overflow: unset;
17+
}
18+
19+
ul {
20+
margin: unset;
21+
padding: unset;
22+
list-style-type: none;
23+
width: 100%;
24+
height: 100%;
25+
}
26+
27+
.mx_RoomListPrimaryFilters_IconButton {
28+
background-color: var(--cpd-color-bg-subtle-secondary);
29+
30+
svg {
31+
transition: transform 0.1s linear;
32+
}
33+
}
34+
35+
.mx_RoomListPrimaryFilters_IconButton[aria-expanded="true"] {
36+
svg {
37+
transform: rotate(180deg);
38+
}
39+
}
1240
}

src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
* Please see LICENSE files in the repository root for full details.
66
*/
77

8-
import React, { type JSX } from "react";
9-
import { ChatFilter } from "@vector-im/compound-web";
8+
import React, { type JSX, useEffect, useId, useState } from "react";
9+
import { ChatFilter, IconButton } from "@vector-im/compound-web";
10+
import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down";
1011

1112
import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
1213
import { Flex } from "../../../utils/Flex";
1314
import { _t } from "../../../../languageHandler";
15+
import { useIsNodeVisible } from "../../../../hooks/useIsNodeVisible";
1416

1517
interface RoomListPrimaryFiltersProps {
1618
/**
@@ -23,23 +25,105 @@ interface RoomListPrimaryFiltersProps {
2325
* The primary filters for the room list
2426
*/
2527
export function RoomListPrimaryFilters({ vm }: RoomListPrimaryFiltersProps): JSX.Element {
28+
const id = useId();
29+
const [isExpanded, setIsExpanded] = useState(false);
30+
31+
// threshold: 0.5 means that the filter is considered visible if at least 50% of it is visible
32+
// this value is arbitrary, we want we to have a bit of flexibility
33+
const { isVisible, rootRef, nodeRef } = useIsNodeVisible<HTMLLIElement, HTMLUListElement>({ threshold: 0.5 });
34+
const { filters, onFilterChange } = useFilters(vm.primaryFilters, isExpanded, isVisible);
35+
2636
return (
27-
<Flex
28-
as="ul"
29-
role="listbox"
30-
aria-label={_t("room_list|primary_filters")}
31-
className="mx_RoomListPrimaryFilters"
32-
align="center"
33-
gap="var(--cpd-space-2x)"
34-
wrap="wrap"
35-
>
36-
{vm.primaryFilters.map((filter) => (
37-
<li role="option" aria-selected={filter.active} key={filter.name}>
38-
<ChatFilter selected={filter.active} onClick={filter.toggle}>
39-
{filter.name}
40-
</ChatFilter>
41-
</li>
42-
))}
37+
<Flex id={id} className="mx_RoomListPrimaryFilters" gap="var(--cpd-space-3x)" data-expanded={isExpanded}>
38+
<Flex
39+
as="ul"
40+
role="listbox"
41+
aria-label={_t("room_list|primary_filters")}
42+
align="center"
43+
gap="var(--cpd-space-2x)"
44+
wrap="wrap"
45+
ref={rootRef}
46+
>
47+
{filters.map((filter) => (
48+
<li
49+
ref={filter.active ? nodeRef : undefined}
50+
role="option"
51+
aria-selected={filter.active}
52+
key={filter.name}
53+
>
54+
<ChatFilter
55+
selected={filter.active}
56+
onClick={() => {
57+
onFilterChange();
58+
filter.toggle();
59+
}}
60+
>
61+
{filter.name}
62+
</ChatFilter>
63+
</li>
64+
))}
65+
</Flex>
66+
<IconButton
67+
aria-expanded={isExpanded}
68+
aria-controls={id}
69+
className="mx_RoomListPrimaryFilters_IconButton"
70+
aria-label={_t("room_list|room_options")}
71+
size="28px"
72+
onClick={() => setIsExpanded((_expanded) => !_expanded)}
73+
>
74+
<ChevronDownIcon color="var(--cpd-color-icon-secondary)" />
75+
</IconButton>
4376
</Flex>
4477
);
4578
}
79+
80+
/**
81+
* A hook to sort the filters by active state.
82+
* The list is sorted if the current filter is not visible when the list is unexpanded.
83+
*
84+
* @param filters - the list of filters to sort.
85+
* @param isExpanded - the filter is expanded or not (fully visible).
86+
* @param isVisible - `null` if there is not selected filter. `true` or `false` if the filter is visible or not.
87+
*/
88+
function useFilters(
89+
filters: RoomListViewState["primaryFilters"],
90+
isExpanded: boolean,
91+
isVisible: boolean | null,
92+
): {
93+
/**
94+
* The new list of filters.
95+
*/
96+
filters: RoomListViewState["primaryFilters"];
97+
/**
98+
* Reset the filter sorting when called.
99+
*/
100+
onFilterChange: () => void;
101+
} {
102+
// By default, the filters are not sorted
103+
const [filterState, setFilterState] = useState({ filters, isSorted: false });
104+
105+
useEffect(() => {
106+
// If there is no current filter (isVisible is null)
107+
// or if the filter list is fully visible (isExpanded is true)
108+
// or if the current filter is visible and the list isn't sorted
109+
// then we don't need to sort the filters
110+
if (isVisible === null || isExpanded || (isVisible && !filterState.isSorted)) {
111+
setFilterState({ filters, isSorted: false });
112+
return;
113+
}
114+
115+
// Sort the filters with the current filter at first position
116+
setFilterState({
117+
filters: filters
118+
.slice()
119+
.sort((filterA, filterB) => (filterA.active === filterB.active ? 0 : filterA.active ? -1 : 1)),
120+
isSorted: true,
121+
});
122+
}, [filters, isVisible, filterState.isSorted, isExpanded]);
123+
124+
const onFilterChange = (): void => {
125+
// Reset the filter sorting
126+
setFilterState({ filters, isSorted: false });
127+
};
128+
return { filters: filterState.filters, onFilterChange };
129+
}

0 commit comments

Comments
 (0)