diff --git a/package.json b/package.json index 34cb581025..e6cd0b132c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/playwright/e2e/settings/general-user-settings-tab.spec.ts b/playwright/e2e/settings/general-user-settings-tab.spec.ts index ae3718aba9..ff2d7c2207 100644 --- a/playwright/e2e/settings/general-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/general-user-settings-tab.spec.ts @@ -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: { @@ -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"); @@ -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( @@ -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"); diff --git a/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-linux.png b/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-linux.png index e11ef9c410..f6463ffd22 100644 Binary files a/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-linux.png and b/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-linux.png differ diff --git a/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-smallscreen-linux.png b/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-smallscreen-linux.png index 75febc97d7..7f22193662 100644 Binary files a/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-smallscreen-linux.png and b/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-smallscreen-linux.png differ diff --git a/res/css/_common.pcss b/res/css/_common.pcss index 20ed9dfa39..7b5fa8e994 100644 --- a/res/css/_common.pcss +++ b/res/css/_common.pcss @@ -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"] { @@ -614,11 +617,17 @@ legend { font-family: inherit; } -.mx_Dialog button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):last-child { +.mx_Dialog + button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( + .mx_ProfileSettings button + ):last-child { margin-right: 0px; } -.mx_Dialog button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):focus, +.mx_Dialog + button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( + .mx_ProfileSettings button + ):focus, .mx_Dialog input[type="submit"]:focus, .mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus, .mx_Dialog_buttons input[type="submit"]:focus { @@ -627,7 +636,8 @@ legend { .mx_Dialog button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]), .mx_Dialog input[type="submit"].mx_Dialog_primary, -.mx_Dialog_buttons button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton), +.mx_Dialog_buttons + button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_ProfileSettings button), .mx_Dialog_buttons input[type="submit"].mx_Dialog_primary { color: var(--cpd-color-text-on-solid-primary); background-color: var(--cpd-color-bg-action-primary-rest); @@ -637,7 +647,8 @@ legend { .mx_Dialog button.danger:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]), .mx_Dialog input[type="submit"].danger, -.mx_Dialog_buttons button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton), +.mx_Dialog_buttons + button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_ProfileSettings button), .mx_Dialog_buttons input[type="submit"].danger { background-color: var(--cpd-color-bg-critical-primary); border: solid 1px var(--cpd-color-bg-critical-primary); @@ -650,7 +661,10 @@ legend { color: var(--cpd-color-text-critical-primary); } -.mx_Dialog button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):disabled, +.mx_Dialog + button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( + .mx_ProfileSettings button + ):disabled, .mx_Dialog input[type="submit"]:disabled, .mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled, .mx_Dialog_buttons input[type="submit"]:disabled { diff --git a/res/css/_components.pcss b/res/css/_components.pcss index cc7e41bc99..a7c79bfbf2 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -337,7 +337,7 @@ @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"; @@ -345,6 +345,7 @@ @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"; diff --git a/res/css/views/dialogs/_UserSettingsDialog.pcss b/res/css/views/dialogs/_UserSettingsDialog.pcss index 41d39f8b79..1e27bb4b6a 100644 --- a/res/css/views/dialogs/_UserSettingsDialog.pcss +++ b/res/css/views/dialogs/_UserSettingsDialog.pcss @@ -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 */ /* ========================================================== */ diff --git a/res/css/views/settings/_ProfileSettings.pcss b/res/css/views/settings/_RoomProfileSettings.pcss similarity index 75% rename from res/css/views/settings/_ProfileSettings.pcss rename to res/css/views/settings/_RoomProfileSettings.pcss index 73cdcd75c8..8af0249ab4 100644 --- a/res/css/views/settings/_ProfileSettings.pcss +++ b/res/css/views/settings/_RoomProfileSettings.pcss @@ -1,5 +1,5 @@ /* -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020, 2024 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,13 +14,13 @@ 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; @@ -28,7 +28,7 @@ limitations under the License. margin-top: $spacing-8; } - .mx_ProfileSettings_profile_controls_topic { + .mx_RoomProfileSettings_profile_controls_topic { margin-top: $spacing-8; & > textarea { @@ -36,18 +36,18 @@ limitations under the License. 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
above the buttons */
diff --git a/res/css/views/settings/_UserProfileSettings.pcss b/res/css/views/settings/_UserProfileSettings.pcss
new file mode 100644
index 0000000000..3a9dc7dcc7
--- /dev/null
+++ b/res/css/views/settings/_UserProfileSettings.pcss
@@ -0,0 +1,58 @@
+/*
+Copyright 2019, 2020, 2024 The Matrix.org Foundation C.I.C.
+
+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;
+ }
+}
diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx
index afcbc182d0..9aafeca2fd 100644
--- a/src/components/views/dialogs/UserSettingsDialog.tsx
+++ b/src/components/views/dialogs/UserSettingsDialog.tsx
@@ -15,6 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { Toast } from "@vector-im/compound-web";
import React, { useState } from "react";
import TabbedView, { Tab, useActiveTabWithDefault } from "../../structures/TabbedView";
@@ -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;
@@ -215,27 +217,34 @@ export default function UserSettingsDialog(props: IProps): JSX.Element {
setShowMsc4108QrCode(false);
};
+ 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.