forked from element-hq/element-web
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRoomListSectionHeaderView.tsx
More file actions
128 lines (120 loc) · 4.71 KB
/
RoomListSectionHeaderView.tsx
File metadata and controls
128 lines (120 loc) · 4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*
* Copyright 2026 Element Creations 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, { memo, type JSX, type FocusEvent, type MouseEventHandler } from "react";
import ChevronRightIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-right";
import classNames from "classnames";
import { useViewModel, type ViewModel } from "../../../core/viewmodel";
import styles from "./RoomListSectionHeaderView.module.css";
import { Flex } from "../../../core/utils/Flex";
import { useI18n } from "../../../core/i18n/i18nContext";
import { getGroupHeaderAccessibleProps } from "../../../core/VirtualizedList";
/**
* The observable state snapshot for a room list section header.
*/
export interface RoomListSectionHeaderViewSnapshot {
/** Unique identifier for the section header (used for list keying) */
id: string;
/** The display title of the section header. */
title: string;
/** Whether the section is currently expanded. */
isExpanded: boolean;
/** Whether the section is unread (has any unread rooms) */
isUnread: boolean;
}
/**
* Actions that can be performed on a room list section header.
*/
export interface RoomListSectionHeaderActions {
/** Handler invoked when the section header is clicked (toggles expand/collapse). */
onClick: MouseEventHandler<HTMLButtonElement>;
}
/**
* The view model type for the room list section header, combining its snapshot and actions.
*/
export type RoomListSectionHeaderViewModel = ViewModel<RoomListSectionHeaderViewSnapshot, RoomListSectionHeaderActions>;
/**
* Props for {@link RoomListSectionHeaderView}.
*/
export interface RoomListSectionHeaderViewProps {
/** The view model driving the section header's state and actions. */
vm: RoomListSectionHeaderViewModel;
/** Whether this header currently has focus within the roving tab index. */
isFocused: boolean;
/** Callback invoked when the header receives focus. */
onFocus: (headerId: string, e: FocusEvent) => void;
/** Index of this section in the list, sections and rooms included */
indexInList: number;
/** Index of this section in the list related to the others sections */
sectionIndex: number;
/** Total number of sections in the list */
sectionCount: number;
/** Number of rooms in this section */
roomCountInSection: number;
}
/**
* A collapsible section header in the room list.
*
* Renders a button that displays the section title alongside a chevron icon
* indicating the current expand/collapse state. Clicking the header toggles
* the section's expanded state via the view model.
*
* @example
* ```tsx
* <RoomListSectionHeaderView
* vm={sectionHeaderViewModel}
* isFocused={isHeaderFocused}
* onFocus={() => setFocusedHeader(sectionId)}
* sectionIndex={index}
* sectionCount={totalSections}
* roomCountInSection={roomCount}
* />
* ```
*/
export const RoomListSectionHeaderView = memo(function RoomListSectionHeaderView({
vm,
isFocused,
onFocus,
indexInList,
sectionIndex,
sectionCount,
roomCountInSection,
}: Readonly<RoomListSectionHeaderViewProps>): JSX.Element {
const { translate: _t } = useI18n();
const { id, title, isExpanded, isUnread } = useViewModel(vm);
const isLastSection = sectionIndex === sectionCount - 1;
return (
<div
aria-expanded={isExpanded}
{...getGroupHeaderAccessibleProps(indexInList, sectionIndex, roomCountInSection)}
>
<button
type="button"
role="gridcell"
className={classNames(styles.header, {
[styles.firstHeader]: sectionIndex === 0,
// If the section is collapsed and it's the last one
[styles.lastHeader]: !isExpanded && isLastSection,
[styles.unread]: isUnread,
})}
onClick={vm.onClick}
aria-expanded={isExpanded}
onFocus={(e) => onFocus(id, e)}
tabIndex={isFocused ? 0 : -1}
aria-label={
isUnread
? _t("room_list|section_header|toggle_unread", { section: title })
: _t("room_list|section_header|toggle", { section: title })
}
>
<Flex className={styles.container} align="center" gap="var(--cpd-space-0-5x)">
<ChevronRightIcon width="24px" height="24px" fill="var(--cpd-color-icon-secondary)" />
<span className={styles.title}>{title}</span>
</Flex>
</button>
</div>
);
});