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

New user profile UI in User Settings #12548

Merged
merged 31 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
06896dc
New user profile UI in User Settings
dbkr May 21, 2024
f16cb80
Show avatar upload error
dbkr May 22, 2024
1b60ea8
Fix avatar upload error
dbkr May 23, 2024
d0624af
Wire up errors & feedback for display name setting
dbkr May 24, 2024
ac46104
Implement avatar upload / remove progress toast
dbkr May 24, 2024
a01d94a
Add 768px breakpoint
dbkr May 24, 2024
236de62
Fix room profile display
dbkr May 28, 2024
6786697
Update to released compund-web with required components / fixes
dbkr May 30, 2024
237570e
Require compound-web 4.4.0
dbkr May 30, 2024
500fb3a
Update snapshots
dbkr May 30, 2024
f0c9d83
Fix duplicate import
dbkr May 30, 2024
513687c
Fix CSS comment
dbkr May 30, 2024
c93ab3d
Update snapshot
dbkr May 30, 2024
be18de1
Run all the tests so the ids stay the same
dbkr May 30, 2024
7330b34
Start of a test for ProfileSettings
dbkr May 30, 2024
b244866
More tests
dbkr May 30, 2024
137df66
Test that a toast appears
dbkr May 30, 2024
48a3d39
Test ToastRack
dbkr May 30, 2024
a99dced
Update snapshots
dbkr Jun 4, 2024
605555a
Add the usernamee control
dbkr Jun 4, 2024
0f56520
Fix playwright tests
dbkr Jun 5, 2024
96802a7
Put ^ back on compound-web version
dbkr Jun 5, 2024
304836d
Split CSS for room & user profile settings
dbkr Jun 5, 2024
d97d8d9
Fix playwright test
dbkr Jun 5, 2024
6a8a981
Update room settings screenshot
dbkr Jun 5, 2024
b2a7f8a
Use original screenshot instead
dbkr Jun 5, 2024
072681d
Merge branch 'develop' into dbkr/new_profilesettings
dbkr Jun 5, 2024
f0c6f94
Fix styling of unrelated buttons
dbkr Jun 5, 2024
ecb6730
Add copyright year
dbkr Jun 5, 2024
672d7cf
Fix copyright year
dbkr Jun 5, 2024
f72d7e7
Merge remote-tracking branch 'origin/develop' into dbkr/new_profilese…
dbkr Jun 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"@sentry/browser": "^7.0.0",
"@testing-library/react-hooks": "^8.0.1",
"@vector-im/compound-design-tokens": "^1.2.0",
"@vector-im/compound-web": "^4.3.1",
"@vector-im/compound-web": "^4.4.1",
"@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4",
"@zxcvbn-ts/language-en": "^3.0.2",
Expand Down
17 changes: 9 additions & 8 deletions playwright/e2e/settings/general-user-settings-tab.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ const USER_NAME_NEW = "Alice";
const IntegrationManager = "scalar.vector.im";

test.describe("General user settings tab", () => {
let userId: string;

test.use({
displayName: USER_NAME,
config: {
Expand All @@ -34,18 +32,18 @@ test.describe("General user settings tab", () => {
},
});

test("should be rendered properly", async ({ uut }) => {
test("should be rendered properly", async ({ uut, user }) => {
await expect(uut).toMatchScreenshot("general.png");

// Assert that the top heading is rendered
await expect(uut.getByRole("heading", { name: "General" })).toBeVisible();

const profile = uut.locator(".mx_ProfileSettings_profile");
const profile = uut.locator(".mx_UserProfileSettings_profile");
await profile.scrollIntoViewIfNeeded();
await expect(profile.getByRole("textbox", { name: "Display Name" })).toHaveValue(USER_NAME);

// Assert that a userId is rendered
await expect(profile.locator(".mx_ProfileSettings_profile_controls_userId", { hasText: userId })).toBeVisible();
expect(uut.getByLabel("Username")).toHaveText(user.userId);

// Check avatar setting
const avatar = profile.locator(".mx_AvatarSetting_avatar");
Expand Down Expand Up @@ -131,12 +129,15 @@ test.describe("General user settings tab", () => {
});

test("should support adding and removing a profile picture", async ({ uut }) => {
const profileSettings = uut.locator(".mx_ProfileSettings");
const profileSettings = uut.locator(".mx_UserProfileSettings");
// Upload a picture
await profileSettings.getByAltText("Upload").setInputFiles("playwright/sample-files/riot.png");

// Find and click "Remove" link button
await profileSettings.locator(".mx_ProfileSettings_profile").getByRole("button", { name: "Remove" }).click();
await profileSettings
.locator(".mx_UserProfileSettings_profile")
.getByRole("button", { name: "Remove" })
.click();

// Assert that the link button disappeared
await expect(
Expand Down Expand Up @@ -175,7 +176,7 @@ test.describe("General user settings tab", () => {
test("should support changing a display name", async ({ uut, page, app }) => {
// Change the diaplay name to USER_NAME_NEW
const displayNameInput = uut
.locator(".mx_SettingsTab .mx_ProfileSettings")
.locator(".mx_SettingsTab .mx_UserProfileSettings")
.getByRole("textbox", { name: "Display Name" });
await displayNameInput.fill(USER_NAME_NEW);
await displayNameInput.press("Enter");
Expand Down
dbkr marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
dbkr marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion res/css/_common.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,10 @@ legend {
* Elements that should not be styled like a dialog button are mentioned in a :not() pseudo-class.
* For the widest browser support, we use multiple :not pseudo-classes instead of :not(.a, .b).
*/
.mx_Dialog button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton),
.mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_ProfileSettings button
),
.mx_Dialog input[type="submit"],
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton),
.mx_Dialog_buttons input[type="submit"] {
Expand Down
3 changes: 2 additions & 1 deletion res/css/_components.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -337,14 +337,15 @@
@import "./views/settings/_Notifications.pcss";
@import "./views/settings/_PhoneNumbers.pcss";
@import "./views/settings/_PowerLevelSelector.pcss";
@import "./views/settings/_ProfileSettings.pcss";
@import "./views/settings/_RoomProfileSettings.pcss";
@import "./views/settings/_SecureBackupPanel.pcss";
@import "./views/settings/_SetIdServer.pcss";
@import "./views/settings/_SetIntegrationManager.pcss";
@import "./views/settings/_SettingsFieldset.pcss";
@import "./views/settings/_SpellCheckLanguages.pcss";
@import "./views/settings/_ThemeChoicePanel.pcss";
@import "./views/settings/_UpdateCheckButton.pcss";
@import "./views/settings/_UserProfileSettings.pcss";
@import "./views/settings/tabs/_SettingsBanner.pcss";
@import "./views/settings/tabs/_SettingsIndent.pcss";
@import "./views/settings/tabs/_SettingsSection.pcss";
Expand Down
8 changes: 8 additions & 0 deletions res/css/views/dialogs/_UserSettingsDialog.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_SettingsDialog_toastContainer {
position: absolute;
bottom: var(--cpd-space-10x);
width: 100%;
display: flex;
justify-content: center;
}

/* ICONS */
/* ========================================================== */

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_ProfileSettings {
.mx_RoomProfileSettings {
border-bottom: 1px solid $quinary-content;

.mx_ProfileSettings_profile {
.mx_RoomProfileSettings_profile {
display: flex;

.mx_ProfileSettings_profile_controls {
.mx_RoomProfileSettings_profile_controls {
flex-grow: 1;
margin-inline-end: 54px;

.mx_Field {
margin-top: $spacing-8;
}

.mx_ProfileSettings_profile_controls_topic {
.mx_RoomProfileSettings_profile_controls_topic {
margin-top: $spacing-8;

& > textarea {
font-family: inherit;
resize: vertical;
}

&.mx_ProfileSettings_profile_controls_topic--room textarea {
&.mx_RoomProfileSettings_profile_controls_topic--room textarea {
min-height: 4em;
}
}

.mx_ProfileSettings_profile_controls_userId {
.mx_RoomProfileSettings_profile_controls_userId {
margin-inline-end: $spacing-20;
}
}
}

.mx_ProfileSettings_buttons {
.mx_RoomProfileSettings_buttons {
display: flex;
gap: var(--cpd-space-4x);
margin-top: 10px; /* 18px is already accounted for by the <p> above the buttons */
Expand Down
58 changes: 58 additions & 0 deletions res/css/views/settings/_UserProfileSettings.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
t3chguy marked this conversation as resolved.
Show resolved Hide resolved

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_UserProfileSettings {
border-bottom: 1px solid $quinary-content;

.mx_UserProfileSettings_profile {
display: flex;
margin-top: var(--cpd-space-6x);
gap: 16px;
/* This is temporary until the 'Remove' link is replaced by a context menu. */
margin-bottom: 20px;

.mx_UserProfileSettings_profile_displayName {
flex-grow: 1;
width: 100%;
}
}

.mx_UserProfileSettings_profile_controls {
flex-grow: 1;
}

.mx_UserProfileSettings_profile_controls_userId {
width: 100%;
.mx_CopyableText {
margin-top: var(--cpd-space-1x);
width: 100%;
box-sizing: border-box;
}
}

.mx_UserProfileSettings_profile_controls_userId_label {
font-size: 15px;
font-weight: 500;
}
}

@media (max-width: 768px) {
.mx_UserProfileSettings_profile {
flex-direction: column;
align-items: center;
gap: 30px;
}
}
41 changes: 25 additions & 16 deletions src/components/views/dialogs/UserSettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
*/

import React from "react";
import { Toast } from "@vector-im/compound-web";

import TabbedView, { Tab, useActiveTabWithDefault } from "../../structures/TabbedView";
import { _t, _td } from "../../../languageHandler";
Expand All @@ -38,6 +39,7 @@ import { UserTab } from "./UserTab";
import { NonEmptyArray } from "../../../@types/common";
import { SDKContext, SdkContextClass } from "../../../contexts/SDKContext";
import { useSettingValue } from "../../../hooks/useSettings";
import { ToastContext, useActiveToast } from "../../../contexts/ToastContext";

interface IProps {
initialTabId?: UserTab;
Expand Down Expand Up @@ -207,27 +209,34 @@ export default function UserSettingsDialog(props: IProps): JSX.Element {

const [activeTabId, setActiveTabId] = useActiveTabWithDefault(getTabs(), UserTab.General, props.initialTabId);

const [activeToast, toastRack] = useActiveToast();

return (
// XXX: SDKContext is provided within the LoggedInView subtree.
// Modals function outside the MatrixChat React tree, so sdkContext is reprovided here to simulate that.
// The longer term solution is to move our ModalManager into the React tree to inherit contexts properly.
<SDKContext.Provider value={props.sdkContext}>
<BaseDialog
className="mx_UserSettingsDialog"
hasCancel={true}
onFinished={props.onFinished}
title={titleForTabID(activeTabId)}
>
<div className="mx_SettingsDialog_content">
<TabbedView
tabs={getTabs()}
activeTabId={activeTabId}
screenName="UserSettings"
onChange={setActiveTabId}
responsive={true}
/>
</div>
</BaseDialog>
<ToastContext.Provider value={toastRack}>
<BaseDialog
className="mx_UserSettingsDialog"
hasCancel={true}
onFinished={props.onFinished}
title={titleForTabID(activeTabId)}
>
<div className="mx_SettingsDialog_content">
<TabbedView
tabs={getTabs()}
activeTabId={activeTabId}
screenName="UserSettings"
onChange={setActiveTabId}
responsive={true}
/>
</div>
<div className="mx_SettingsDialog_toastContainer">
{activeToast && <Toast>{activeToast}</Toast>}
</div>
</BaseDialog>
</ToastContext.Provider>
</SDKContext.Provider>
);
}
6 changes: 3 additions & 3 deletions src/components/views/elements/CopyableText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import { _t } from "../../../languageHandler";
import { copyPlaintext } from "../../../utils/strings";
import AccessibleButton, { ButtonEvent } from "./AccessibleButton";

interface IProps {
interface IProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
getTextToCopy: () => string | null;
border?: boolean;
className?: string;
}

const CopyableText: React.FC<IProps> = ({ children, getTextToCopy, border = true, className }) => {
const CopyableText: React.FC<IProps> = ({ children, getTextToCopy, border = true, className, ...props }) => {
const [tooltip, setTooltip] = useState<string | undefined>(undefined);

const onCopyClickInternal = async (e: ButtonEvent): Promise<void> => {
Expand All @@ -50,7 +50,7 @@ const CopyableText: React.FC<IProps> = ({ children, getTextToCopy, border = true
});

return (
<div className={combinedClassName}>
<div className={combinedClassName} {...props}>
{children}
<AccessibleButton
title={tooltip ?? _t("action|copy")}
Expand Down
12 changes: 6 additions & 6 deletions src/components/views/room_settings/RoomProfileSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ export default class RoomProfileSettings extends React.Component<IProps, IState>
let profileSettingsButtons;
if (this.state.canSetName || this.state.canSetTopic || this.state.canSetAvatar) {
profileSettingsButtons = (
<div className="mx_ProfileSettings_buttons">
<div className="mx_RoomProfileSettings_buttons">
<AccessibleButton
onClick={this.cancelProfileChanges}
kind="primary_outline"
Expand All @@ -218,9 +218,9 @@ export default class RoomProfileSettings extends React.Component<IProps, IState>
}

return (
<form onSubmit={this.saveProfile} autoComplete="off" noValidate={true} className="mx_ProfileSettings">
<div className="mx_ProfileSettings_profile">
<div className="mx_ProfileSettings_profile_controls">
<form onSubmit={this.saveProfile} autoComplete="off" noValidate={true} className="mx_RoomProfileSettings">
<div className="mx_RoomProfileSettings_profile">
<div className="mx_RoomProfileSettings_profile_controls">
<Field
label={_t("room_settings|general|name_field_label")}
type="text"
Expand All @@ -231,8 +231,8 @@ export default class RoomProfileSettings extends React.Component<IProps, IState>
/>
<Field
className={classNames(
"mx_ProfileSettings_profile_controls_topic",
"mx_ProfileSettings_profile_controls_topic--room",
"mx_RoomProfileSettings_profile_controls_topic",
"mx_RoomProfileSettings_profile_controls_topic--room",
)}
id="profileTopic" // See: NewRoomIntro.tsx
label={_t("room_settings|general|topic_field_label")}
Expand Down
12 changes: 6 additions & 6 deletions src/components/views/settings/AvatarSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { createRef, useCallback, useEffect, useRef, useState } from "react";
import React, { createRef, useCallback, useEffect, useState } from "react";

import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import { mediaFromMxc } from "../../../customisations/Media";
import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
import { useId } from "../../../utils/useId";

interface IProps {
/**
Expand Down Expand Up @@ -75,9 +76,8 @@ const AvatarSetting: React.FC<IProps> = ({ avatar, avatarAltText, onChange, remo
}
}, [avatar]);

// TODO: Use useId() as soon as we're using React 18.
// Prevents ID collisions when this component is used more than once on the same page.
const a11yId = useRef(`hover-text-${Math.random()}`);
const a11yId = useId();

const onFileChanged = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -95,7 +95,7 @@ const AvatarSetting: React.FC<IProps> = ({ avatar, avatarAltText, onChange, remo
element="div"
onClick={uploadAvatar}
className="mx_AvatarSetting_avatarPlaceholder mx_AvatarSetting_avatarDisplay"
aria-labelledby={disabled ? undefined : a11yId.current}
aria-labelledby={disabled ? undefined : a11yId}
// Inhibit tab stop as we have explicit upload/remove buttons
tabIndex={-1}
/>
Expand All @@ -122,7 +122,7 @@ const AvatarSetting: React.FC<IProps> = ({ avatar, avatarAltText, onChange, remo
<AccessibleButton
onClick={uploadAvatar}
className="mx_AvatarSetting_uploadButton"
aria-labelledby={a11yId.current}
aria-labelledby={a11yId}
/>
<input
type="file"
Expand Down Expand Up @@ -151,7 +151,7 @@ const AvatarSetting: React.FC<IProps> = ({ avatar, avatarAltText, onChange, remo
{avatarElement}
<div className="mx_AvatarSetting_hover" aria-hidden="true">
<div className="mx_AvatarSetting_hoverBg" />
{!disabled && <span id={a11yId.current}>{_t("action|upload")}</span>}
{!disabled && <span id={a11yId}>{_t("action|upload")}</span>}
</div>
{uploadAvatarBtn}
{removeAvatarBtn}
Expand Down
Loading
Loading