Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit cf8b87f

Browse files
Add tabs to the right panel (#12672)
* Create new method for header button behaviour With the introduction of tabs, the behaviour of the header buttons is changed as follows: - Close any right panel if open - Open the correct right panel if no panel was open before The old method (and behaviour) is retained as showOrHidePhase. * Implement tabs in the right panel There are three tabs: Info, People and Threads * Remove unwanted code from RoomSummaryCard - Remove the menu item for opening the memberlist since that is now taken of by the tabs. - Remove the close button * Remove code for focusing close button from tac item See #12410 There's no longer a close button to focus so we instead focus the thread tab. This is done in RightPaneltabs.tsx so we just need to remove this code. * Introduce a room info icon to the header This was previously present in the legacy room header but not in the new header. * BaseCard changes - Adds id, ariaLabelledBy and role props to implement tab accessibility. - Adds hideHeaderButtons prop to hide header buttons (think back and close buttons). - Change confusing header rendering code: header is not rendered ONLY when no header is passed AND hideHeaderButtons is true. * Refactor repeated code into function Created a new function createSpaceScopeHeader which returns the component if the room is a space room. Previously this code was duplicated in every component that uses SpaceScopeHeader component. * Pass BaseCard attributes and use helper function Actually using the code from the last two commits * Add, update and remove tests/screenshots/snapshots * Fix distance between search bar and tabs * Update compound * Update screenshots/snapshots
1 parent cd39d91 commit cf8b87f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+501
-294
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
"@sentry/browser": "^8.0.0",
7979
"@testing-library/react-hooks": "^8.0.1",
8080
"@vector-im/compound-design-tokens": "^1.2.0",
81-
"@vector-im/compound-web": "^5.1.2",
81+
"@vector-im/compound-web": "^5.2.3",
8282
"@zxcvbn-ts/core": "^3.0.4",
8383
"@zxcvbn-ts/language-common": "^3.0.4",
8484
"@zxcvbn-ts/language-en": "^3.0.2",

playwright/e2e/crypto/crypto.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const verify = async (page: Page, bob: Bot) => {
103103
const bobsVerificationRequestPromise = waitForVerificationRequest(bob);
104104

105105
const roomInfo = await openRoomInfo(page);
106-
await roomInfo.getByRole("menuitem", { name: "People" }).click();
106+
await page.locator(".mx_RightPanelTabs").getByText("People").click();
107107
await roomInfo.getByText("Bob").click();
108108
await roomInfo.getByRole("button", { name: "Verify" }).click();
109109
await roomInfo.getByRole("button", { name: "Start Verification" }).click();
@@ -279,7 +279,7 @@ test.describe("Cryptography", function () {
279279

280280
// Assert that verified icon is rendered
281281
await page.getByRole("button", { name: "Room members" }).click();
282-
await page.getByRole("button", { name: "Room information" }).click();
282+
await page.locator(".mx_RightPanelTabs").getByText("Info").click();
283283
await expect(page.locator('.mx_RoomSummaryCard_badges [data-kind="success"]')).toContainText("Encrypted");
284284

285285
// Take a snapshot of RoomSummaryCard with a verified E2EE icon

playwright/e2e/crypto/dehydration.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ test.describe("Dehydration", () => {
102102

103103
await viewRoomSummaryByName(page, app, ROOM_NAME);
104104

105-
await page.getByRole("menuitem", { name: "People" }).click();
105+
await page.locator(".mx_RightPanelTabs").getByText("People").click();
106106
await expect(page.locator(".mx_MemberList")).toBeVisible();
107107

108108
await getMemberTileByName(page, NAME).click();

playwright/e2e/lazy-loading/lazy-loading.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ test.describe("Lazy Loading", () => {
8080

8181
async function openMemberlist(page: Page): Promise<void> {
8282
await page.locator(".mx_LegacyRoomHeader").getByRole("button", { name: "Room info" }).click();
83-
await page.locator(".mx_RoomSummaryCard").getByRole("menuitem", { name: "People" }).click(); // \d represents the number of the room members
83+
await page.locator(".mx_RightPanelTabs").getByText("People").click();
8484
}
8585

8686
function getMemberInMemberlist(page: Page, name: string): Locator {

playwright/e2e/read-receipts/index.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -399,19 +399,18 @@ class Helpers {
399399
}
400400

401401
/**
402-
* Close the threads panel. (Actually, close any right panel, but for these
403-
* tests we only open the threads panel.)
402+
* Close the threads panel.
404403
*/
405404
async closeThreadsPanel() {
406-
await this.page.locator(".mx_RightPanel").getByLabel("Close").click();
405+
await this.page.locator(".mx_LegacyRoomHeader").getByLabel("Threads").click();
407406
await expect(this.page.locator(".mx_RightPanel")).not.toBeVisible();
408407
}
409408

410409
/**
411410
* Return to the list of threads, given we are viewing a single thread.
412411
*/
413412
async backToThreadsList() {
414-
await this.page.locator(".mx_RightPanel").getByLabel("Threads").click();
413+
await this.page.locator(".mx_LegacyRoomHeader").getByLabel("Threads").click();
415414
}
416415

417416
/**

playwright/e2e/right-panel/right-panel.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ test.describe("RightPanel", () => {
113113
test("should handle viewing room member", async ({ page, app }) => {
114114
await viewRoomSummaryByName(page, app, ROOM_NAME);
115115

116-
await page.getByRole("menuitem", { name: "People" }).click();
116+
await page.locator(".mx_RightPanelTabs").getByText("People").click();
117117
await expect(page.locator(".mx_MemberList")).toBeVisible();
118118

119119
await getMemberTileByName(page, NAME).click();
@@ -123,7 +123,7 @@ test.describe("RightPanel", () => {
123123
await page.getByRole("button", { name: "Room members" }).click();
124124
await expect(page.locator(".mx_MemberList")).toBeVisible();
125125

126-
await page.getByRole("button", { name: "Room information" }).click();
126+
await page.locator(".mx_RightPanelTabs").getByText("Info").click();
127127
await checkRoomSummaryCard(page, ROOM_NAME);
128128
});
129129
});

playwright/e2e/spaces/threads-activity-centre/index.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,10 @@ export class Helpers {
337337
}
338338

339339
/**
340-
* Assert that the thread panel is focused (actually the 'close' button, specifically)
340+
* Assert that the thread tab is focused
341341
*/
342-
assertThreadPanelFocused() {
343-
return expect(
344-
this.page.locator(".mx_ThreadPanel").locator(".mx_BaseCard_header").getByLabel("Close"),
345-
).toBeFocused();
342+
assertThreadTabFocused() {
343+
return expect(this.page.locator("#thread-panel-tab")).toBeFocused();
346344
}
347345

348346
/**

playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,12 @@ test.describe("Threads Activity Centre", () => {
161161
await util.assertNoTacIndicator();
162162
});
163163

164-
test("should focus the thread panel close button when clicking an item in the TAC", async ({
165-
room1,
166-
room2,
167-
util,
168-
msg,
169-
}) => {
164+
test("should focus the thread tab when clicking an item in the TAC", async ({ room1, room2, util, msg }) => {
170165
await util.receiveMessages(room1, ["Msg1", msg.threadedOff("Msg1", "Resp1")]);
171166

172167
await util.openTac();
173168
await util.clickRoomInTac(room1.name);
174169

175-
await util.assertThreadPanelFocused();
170+
await util.assertThreadTabFocused();
176171
});
177172
});
Loading
Loading
Loading
Loading

res/css/_components.pcss

+1
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@
261261
@import "./views/right_panel/_BaseCard.pcss";
262262
@import "./views/right_panel/_EncryptionInfo.pcss";
263263
@import "./views/right_panel/_PinnedMessagesCard.pcss";
264+
@import "./views/right_panel/_RightPanelTabs.pcss";
264265
@import "./views/right_panel/_RoomSummaryCard.pcss";
265266
@import "./views/right_panel/_ThreadPanel.pcss";
266267
@import "./views/right_panel/_TimelineCard.pcss";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
Copyright 2024 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
.mx_RightPanelTabs {
18+
margin: 0;
19+
height: 64px;
20+
box-sizing: border-box;
21+
22+
ul {
23+
margin-left: 16px;
24+
}
25+
}

res/css/views/right_panel/_RoomSummaryCard.pcss

+1-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ limitations under the License.
235235
}
236236

237237
.mx_RoomSummaryCard_header {
238-
padding: 15px 12px;
238+
padding: 24px 12px 15px;
239239
}
240240

241241
.mx_RoomSummaryCard_search {

res/css/views/rooms/_MemberList.pcss

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ limitations under the License.
1919
display: flex;
2020
flex-direction: column;
2121
min-height: 0;
22+
margin-top: 24px;
2223

2324
.mx_Spinner {
2425
flex: 1 0 auto;

src/components/structures/RightPanel.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore";
4242
import { IRightPanelCard, IRightPanelCardState } from "../../stores/right-panel/RightPanelStoreIPanelState";
4343
import { Action } from "../../dispatcher/actions";
4444
import { XOR } from "../../@types/common";
45+
import { RightPanelTabs } from "../views/right_panel/RightPanelTabs";
4546

4647
interface BaseProps {
4748
overwriteCard?: IRightPanelCard; // used to display a custom card and ignoring the RightPanelStore (used for UserView)
@@ -171,6 +172,7 @@ export default class RightPanel extends React.Component<Props, IState> {
171172
<MemberList
172173
roomId={roomId}
173174
key={roomId}
175+
hideHeaderButtons
174176
onClose={this.onClose}
175177
searchQuery={this.state.searchQuery}
176178
onSearchQueryChanged={this.onSearchQueryChanged}
@@ -294,7 +296,6 @@ export default class RightPanel extends React.Component<Props, IState> {
294296
card = (
295297
<RoomSummaryCard
296298
room={this.props.room}
297-
onClose={this.onClose}
298299
// whenever RightPanel is passed a room it is passed a permalinkcreator
299300
permalinkCreator={this.props.permalinkCreator!}
300301
onSearchChange={this.props.onSearchChange}
@@ -314,6 +315,7 @@ export default class RightPanel extends React.Component<Props, IState> {
314315

315316
return (
316317
<aside className="mx_RightPanel" id="mx_RightPanel">
318+
{phase && <RightPanelTabs phase={phase} />}
317319
{card}
318320
</aside>
319321
);

src/components/structures/RoomView.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
12871287
]);
12881288
}
12891289
} else {
1290-
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.RoomMemberList);
1290+
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomMemberList);
12911291
}
12921292
break;
12931293
case Action.View3pidInvite:

src/components/structures/ThreadPanel.tsx

+4-11
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ import { ButtonEvent } from "../views/elements/AccessibleButton";
3737
import Spinner from "../views/elements/Spinner";
3838
import Heading from "../views/typography/Heading";
3939
import { clearRoomNotification } from "../../utils/notifications";
40-
import { useDispatcher } from "../../hooks/useDispatcher";
41-
import dis from "../../dispatcher/dispatcher";
42-
import { Action } from "../../dispatcher/actions";
4340

4441
interface IProps {
4542
roomId: string;
@@ -259,14 +256,6 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
259256
}
260257
}, [timelineSet, timelinePanel]);
261258

262-
useDispatcher(dis, (payload) => {
263-
// This actually foucses the close button on the threads panel, as its the only interactive element,
264-
// but at least it puts the user in the right area of the app.
265-
if (payload.action === Action.FocusThreadsPanel) {
266-
closeButonRef.current?.focus();
267-
}
268-
});
269-
270259
return (
271260
<RoomContext.Provider
272261
value={{
@@ -277,14 +266,18 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
277266
}}
278267
>
279268
<BaseCard
269+
hideHeaderButtons
280270
header={
281271
<ThreadPanelHeader
282272
filterOption={filterOption}
283273
setFilterOption={setFilterOption}
284274
empty={!hasThreads}
285275
/>
286276
}
277+
id="thread-panel"
287278
className="mx_ThreadPanel"
279+
ariaLabelledBy="thread-panel-tab"
280+
role="tabpanel"
288281
onClose={onClose}
289282
withoutScrollContainer={true}
290283
ref={card}

src/components/views/right_panel/BaseCard.tsx

+30-4
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,12 @@ import { CardContext } from "./context";
2626

2727
interface IProps {
2828
header?: ReactNode | null;
29+
hideHeaderButtons?: boolean;
2930
footer?: ReactNode;
3031
className?: string;
32+
id?: string;
33+
role?: "tabpanel";
34+
ariaLabelledBy?: string;
3135
withoutScrollContainer?: boolean;
3236
closeLabel?: string;
3337
onClose?(ev: ButtonEvent): void;
@@ -62,6 +66,10 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
6266
onClose,
6367
onBack,
6468
className,
69+
id,
70+
ariaLabelledBy,
71+
role,
72+
hideHeaderButtons,
6573
header,
6674
footer,
6775
withoutScrollContainer,
@@ -100,13 +108,31 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
100108
children = <AutoHideScrollbar>{children}</AutoHideScrollbar>;
101109
}
102110

111+
let headerButtons: React.ReactElement | undefined;
112+
if (!hideHeaderButtons) {
113+
headerButtons = (
114+
<>
115+
{backButton}
116+
{closeButton}
117+
</>
118+
);
119+
}
120+
121+
const shouldRenderHeader = header || !hideHeaderButtons;
122+
103123
return (
104124
<CardContext.Provider value={{ isCard: true }}>
105-
<div className={classNames("mx_BaseCard", className)} ref={ref} onKeyDown={onKeyDown}>
106-
{header !== null && (
125+
<div
126+
id={id}
127+
aria-labelledby={ariaLabelledBy}
128+
role={role}
129+
className={classNames("mx_BaseCard", className)}
130+
ref={ref}
131+
onKeyDown={onKeyDown}
132+
>
133+
{shouldRenderHeader && (
107134
<div className="mx_BaseCard_header">
108-
{backButton}
109-
{closeButton}
135+
{headerButtons}
110136
<div className="mx_BaseCard_headerProp">{header}</div>
111137
</div>
112138
)}

src/components/views/right_panel/LegacyRoomHeaderButtons.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -214,27 +214,27 @@ export default class LegacyRoomHeaderButtons extends HeaderButtons<IProps> {
214214
const currentPhase = RightPanelStore.instance.currentCard.phase;
215215
if (currentPhase && ROOM_INFO_PHASES.includes(currentPhase)) {
216216
if (this.state.phase === currentPhase) {
217-
RightPanelStore.instance.showOrHidePanel(currentPhase);
217+
RightPanelStore.instance.showOrHidePhase(currentPhase);
218218
} else {
219-
RightPanelStore.instance.showOrHidePanel(currentPhase, RightPanelStore.instance.currentCard.state);
219+
RightPanelStore.instance.showOrHidePhase(currentPhase, RightPanelStore.instance.currentCard.state);
220220
}
221221
} else {
222222
// This toggles for us, if needed
223-
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.RoomSummary);
223+
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomSummary);
224224
}
225225
};
226226

227227
private onNotificationsClicked = (): void => {
228228
// This toggles for us, if needed
229-
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.NotificationPanel);
229+
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.NotificationPanel);
230230
};
231231

232232
private onPinnedMessagesClicked = (): void => {
233233
// This toggles for us, if needed
234-
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.PinnedMessages);
234+
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.PinnedMessages);
235235
};
236236
private onTimelineCardClicked = (): void => {
237-
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.Timeline);
237+
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.Timeline);
238238
};
239239

240240
private onThreadsPanelClicked = (ev: ButtonEvent): void => {

0 commit comments

Comments
 (0)