diff --git a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts index 4d09f228d0..5cc78eca69 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts @@ -428,7 +428,9 @@ test.describe("Room list", () => { await app.settings.closeDialog(); await app.settings.openUserSettings("Notifications"); - await page.getByText("Show all activity in the room list (dots or number of unread messages)").click(); + await page + .getByRole("switch", { name: "Show all activity in the room list (dots or number of unread messages)" }) + .check(); await app.settings.closeDialog(); // Switch to the other room to avoid the notification to be cleared diff --git a/playwright/e2e/room-directory/room-directory.spec.ts b/playwright/e2e/room-directory/room-directory.spec.ts index 8f90ef4b7e..741fde3505 100644 --- a/playwright/e2e/room-directory/room-directory.spec.ts +++ b/playwright/e2e/room-directory/room-directory.spec.ts @@ -38,12 +38,11 @@ test.describe("Room Directory", () => { // Publish into the public rooms directory const publishedAddresses = page.locator(".mx_SettingsFieldset", { hasText: "Published Addresses" }); await expect(publishedAddresses.locator("#canonicalAlias")).toHaveValue(`#gaming:${user.homeServer}`); - const checkbox = publishedAddresses - .locator(".mx_SettingsFlag", { - hasText: `Publish this room to the public in ${user.homeServer}'s room directory?`, - }) - .getByRole("switch"); - await checkbox.check(); + const checkbox = publishedAddresses.getByRole("switch", { + name: `Publish this room to the public in ${user.homeServer}'s room directory?`, + }); + // .click() rather than .check() as checking happens after publish + await checkbox.click(); await expect(checkbox).toBeChecked(); await app.closeDialog(); diff --git a/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts index 53f9c37dd0..df41ef4b70 100644 --- a/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts @@ -50,8 +50,8 @@ test.describe("Appearance user settings tab", () => { // Click "Show advanced" link button await tab.getByRole("button", { name: "Show advanced" }).click(); - await tab.getByLabel("Use bundled emoji font").click(); - await tab.getByLabel("Use a system font").click(); + await tab.getByRole("switch", { name: "Use bundled emoji font" }).click(); + await tab.getByRole("switch", { name: "Use a system font" }).click(); // Assert that the font-family value was removed await expect(page.locator("body")).toHaveCSS("font-family", '""'); diff --git a/playwright/e2e/settings/appearance-user-settings-tab/index.ts b/playwright/e2e/settings/appearance-user-settings-tab/index.ts index e6eccac15a..15f2f47888 100644 --- a/playwright/e2e/settings/appearance-user-settings-tab/index.ts +++ b/playwright/e2e/settings/appearance-user-settings-tab/index.ts @@ -84,7 +84,7 @@ class Helpers { /** * Return the system theme toggle */ - getMatchSystemThemeCheckbox() { + getMatchSystemThemeSwitch() { return this.getThemePanel().getByRole("switch", { name: "Match system theme" }); } @@ -216,9 +216,9 @@ class Helpers { } /** - * Return the compact layout checkbox + * Return the compact layout switch */ - getCompactLayoutCheckbox() { + getCompactLayoutSwitch() { return this.getMessageLayoutPanel().getByRole("switch", { name: "Show compact text and messages" }); } diff --git a/playwright/e2e/settings/appearance-user-settings-tab/message-layout-panel.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab/message-layout-panel.spec.ts index ce0d614d90..3c05487723 100644 --- a/playwright/e2e/settings/appearance-user-settings-tab/message-layout-panel.spec.ts +++ b/playwright/e2e/settings/appearance-user-settings-tab/message-layout-panel.spec.ts @@ -40,9 +40,9 @@ test.describe("Appearance user settings tab", () => { ); test("should enable compact layout when the modern layout is selected", async ({ page, app, user, util }) => { - await expect(util.getCompactLayoutCheckbox()).not.toBeChecked(); + await expect(util.getCompactLayoutSwitch()).not.toBeChecked(); - await util.getCompactLayoutCheckbox().click(); + await util.getCompactLayoutSwitch().click(); await util.assertCompactLayout(); }); @@ -52,11 +52,11 @@ test.describe("Appearance user settings tab", () => { user, util, }) => { - await expect(util.getCompactLayoutCheckbox()).not.toBeDisabled(); + await expect(util.getCompactLayoutSwitch()).not.toBeDisabled(); // Select the bubble layout, which should disable the compact layout checkbox await util.getBubbleLayout().click(); - await expect(util.getCompactLayoutCheckbox()).toBeDisabled(); + await expect(util.getCompactLayoutSwitch()).toBeDisabled(); }); }); }); diff --git a/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts index 1fad16948d..7198e1fd82 100644 --- a/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts +++ b/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts @@ -25,7 +25,7 @@ test.describe("Appearance user settings tab", () => { { tag: "@screenshot" }, async ({ page, app, util }) => { // Assert that 'Match system theme' is not checked - await expect(util.getMatchSystemThemeCheckbox()).not.toBeChecked(); + await expect(util.getMatchSystemThemeSwitch()).not.toBeChecked(); // Assert that the light theme is selected await expect(util.getLightTheme()).toBeChecked(); @@ -41,7 +41,7 @@ test.describe("Appearance user settings tab", () => { "should disable the themes when the system theme is clicked", { tag: "@screenshot" }, async ({ page, app, util }) => { - await util.getMatchSystemThemeCheckbox().click(); + await util.getMatchSystemThemeSwitch().click(); // Assert that the themes are disabled await expect(util.getLightTheme()).toBeDisabled(); diff --git a/playwright/e2e/sliding-sync/sliding-sync.spec.ts b/playwright/e2e/sliding-sync/sliding-sync.spec.ts index e2669e0883..b31deadace 100644 --- a/playwright/e2e/sliding-sync/sliding-sync.spec.ts +++ b/playwright/e2e/sliding-sync/sliding-sync.spec.ts @@ -184,11 +184,9 @@ test.describe("Sliding Sync", () => { test("should update user settings promptly", async ({ page, app }) => { await app.settings.openUserSettings("Preferences"); - const locator = page.locator(".mx_SettingsFlag").filter({ hasText: "Show timestamps in 12 hour format" }); + const locator = page.getByRole("switch", { name: "Show timestamps in 12 hour format" }); await expect(locator).toBeVisible(); - await expect(locator.locator(".mx_ToggleSwitch_on")).not.toBeAttached(); - await locator.locator(".mx_ToggleSwitch_ball").click(); - await expect(locator.locator(".mx_ToggleSwitch_on")).toBeAttached(); + await locator.check(); }); test("should send subscribe_rooms on room switch if room not already subscribed", async ({ page, app }) => { diff --git a/playwright/snapshots/devtools/devtools.spec.ts/devtools-dialog-linux.png b/playwright/snapshots/devtools/devtools.spec.ts/devtools-dialog-linux.png index d412500841..7f802aca0e 100644 Binary files a/playwright/snapshots/devtools/devtools.spec.ts/devtools-dialog-linux.png and b/playwright/snapshots/devtools/devtools.spec.ts/devtools-dialog-linux.png differ diff --git a/playwright/snapshots/devtools/upgraderoom.spec.ts/upgrade-room-linux.png b/playwright/snapshots/devtools/upgraderoom.spec.ts/upgrade-room-linux.png index 4a27b52da0..e4f9bf4b13 100644 Binary files a/playwright/snapshots/devtools/upgraderoom.spec.ts/upgrade-room-linux.png and b/playwright/snapshots/devtools/upgraderoom.spec.ts/upgrade-room-linux.png differ diff --git a/playwright/snapshots/invite/decline-and-block-invite-dialog.spec.ts/decline-and-block-invite-empty-linux.png b/playwright/snapshots/invite/decline-and-block-invite-dialog.spec.ts/decline-and-block-invite-empty-linux.png index 4726c446d3..cb3f9f8618 100644 Binary files a/playwright/snapshots/invite/decline-and-block-invite-dialog.spec.ts/decline-and-block-invite-empty-linux.png and b/playwright/snapshots/invite/decline-and-block-invite-dialog.spec.ts/decline-and-block-invite-empty-linux.png differ diff --git a/playwright/snapshots/location/location.spec.ts/location-live-share-dialog-linux.png b/playwright/snapshots/location/location.spec.ts/location-live-share-dialog-linux.png index a2dce30b15..13ba504919 100644 Binary files a/playwright/snapshots/location/location.spec.ts/location-live-share-dialog-linux.png and b/playwright/snapshots/location/location.spec.ts/location-live-share-dialog-linux.png differ diff --git a/playwright/snapshots/right-panel/right-panel.spec.ts/room-report-dialog-linux.png b/playwright/snapshots/right-panel/right-panel.spec.ts/room-report-dialog-linux.png index 627071591c..573d63416d 100644 Binary files a/playwright/snapshots/right-panel/right-panel.spec.ts/room-report-dialog-linux.png and b/playwright/snapshots/right-panel/right-panel.spec.ts/room-report-dialog-linux.png differ diff --git a/playwright/snapshots/room/create-room.spec.ts/create-room-linux.png b/playwright/snapshots/room/create-room.spec.ts/create-room-linux.png index a16373c8dc..85ad3a4b7b 100644 Binary files a/playwright/snapshots/room/create-room.spec.ts/create-room-linux.png and b/playwright/snapshots/room/create-room.spec.ts/create-room-linux.png differ diff --git a/playwright/snapshots/room/create-room.spec.ts/create-room-no-public-linux.png b/playwright/snapshots/room/create-room.spec.ts/create-room-no-public-linux.png index 84494f46bf..ac9f1fbd00 100644 Binary files a/playwright/snapshots/room/create-room.spec.ts/create-room-no-public-linux.png and b/playwright/snapshots/room/create-room.spec.ts/create-room-no-public-linux.png differ diff --git a/playwright/snapshots/room/create-room.spec.ts/create-video-room-linux.png b/playwright/snapshots/room/create-room.spec.ts/create-video-room-linux.png index 628ae1159c..31236db103 100644 Binary files a/playwright/snapshots/room/create-room.spec.ts/create-video-room-linux.png and b/playwright/snapshots/room/create-room.spec.ts/create-video-room-linux.png differ diff --git a/playwright/snapshots/room/invites.spec.ts/Invites-reject-dialog-linux.png b/playwright/snapshots/room/invites.spec.ts/Invites-reject-dialog-linux.png index 22ef895d1a..ce50be25a2 100644 Binary files a/playwright/snapshots/room/invites.spec.ts/Invites-reject-dialog-linux.png and b/playwright/snapshots/room/invites.spec.ts/Invites-reject-dialog-linux.png differ diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png index 67e047eb50..5a9190aa6b 100644 Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png differ diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png index 528aaee7c4..c86f95ee29 100644 Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png differ diff --git a/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png index a4f6d3f13d..9671f97e42 100644 Binary files a/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png and b/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png differ diff --git a/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png b/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png index bf9ed51729..5bd96ef009 100644 Binary files a/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png and b/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png differ diff --git a/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png b/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png index 6182c73b75..9452120bfb 100644 Binary files a/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png and b/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png differ diff --git a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png index b3ecf2c281..ac54127655 100644 Binary files a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png and b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png differ diff --git a/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-linux.png b/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-linux.png index c04653fc70..c3eef375df 100644 Binary files a/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-linux.png and b/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-linux.png differ diff --git a/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-world-readable-linux.png b/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-world-readable-linux.png index 6c192bdb53..d1aa699c18 100644 Binary files a/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-world-readable-linux.png and b/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-world-readable-linux.png differ diff --git a/playwright/snapshots/settings/room-settings/room-video-tab.spec.ts/room-video-settings-linux.png b/playwright/snapshots/settings/room-settings/room-video-tab.spec.ts/room-video-settings-linux.png index 8f78d02350..664febf8da 100644 Binary files a/playwright/snapshots/settings/room-settings/room-video-tab.spec.ts/room-video-settings-linux.png and b/playwright/snapshots/settings/room-settings/room-video-tab.spec.ts/room-video-settings-linux.png differ diff --git a/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png b/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png index 9280d578f2..5ae88a2b1c 100644 Binary files a/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png and b/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png differ diff --git a/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png b/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png index 80f051c2d5..2408c046bd 100644 Binary files a/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png and b/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png differ diff --git a/playwright/snapshots/widgets/permissions-dialog.spec.ts/widget-capabilites-prompt-linux.png b/playwright/snapshots/widgets/permissions-dialog.spec.ts/widget-capabilites-prompt-linux.png index ec021ebd75..747ffb15ff 100644 Binary files a/playwright/snapshots/widgets/permissions-dialog.spec.ts/widget-capabilites-prompt-linux.png and b/playwright/snapshots/widgets/permissions-dialog.spec.ts/widget-capabilites-prompt-linux.png differ diff --git a/res/css/components/views/settings/shared/_SettingsSubsection.pcss b/res/css/components/views/settings/shared/_SettingsSubsection.pcss index 3b22c679c5..690da59d90 100644 --- a/res/css/components/views/settings/shared/_SettingsSubsection.pcss +++ b/res/css/components/views/settings/shared/_SettingsSubsection.pcss @@ -39,7 +39,7 @@ Please see LICENSE files in the repository root for full details. .mx_SettingsSubsection_content { width: 100%; display: grid; - gap: $spacing-8; + gap: var(--cpd-space-4x); /* setting minwidth 0 makes columns definitely sized fixing horizontal overflow */ grid-template-columns: minmax(0, 1fr); justify-items: flex-start; diff --git a/res/css/views/dialogs/_CreateRoomDialog.pcss b/res/css/views/dialogs/_CreateRoomDialog.pcss index a96b9ba472..1dff0896e9 100644 --- a/res/css/views/dialogs/_CreateRoomDialog.pcss +++ b/res/css/views/dialogs/_CreateRoomDialog.pcss @@ -57,16 +57,9 @@ Please see LICENSE files in the repository root for full details. width: 100%; } -/* needed to make the alias field only grow as wide as needed */ -/* as opposed to full width */ .mx_CreateRoomDialog_aliasContainer { + /* needed to make the alias field only grow as wide as needed as opposed to full width */ display: flex; - /* put margin on container so it can collapse with siblings */ - margin: 24px 0 10px; - - .mx_RoomAliasField { - margin: 0; - } } .mx_CreateRoomDialog { @@ -102,6 +95,14 @@ Please see LICENSE files in the repository root for full details. margin-top: 24px; } + .mx_Field { + margin: 0; + } + + form { + gap: var(--cpd-space-4x); + } + p { margin: 0 85px 0 0; font-size: $font-12px; diff --git a/res/css/views/dialogs/_DevtoolsDialog.pcss b/res/css/views/dialogs/_DevtoolsDialog.pcss index 557345ff0e..efcf4db372 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.pcss +++ b/res/css/views/dialogs/_DevtoolsDialog.pcss @@ -22,6 +22,10 @@ Please see LICENSE files in the repository root for full details. margin-bottom: 0; } } + + .mx_DevTools_toggleForm { + gap: var(--cpd-space-2x); + } } .mx_DevTools_toolHeading { diff --git a/res/css/views/settings/_NotificationSettings2.pcss b/res/css/views/settings/_NotificationSettings2.pcss index 285282c89c..db439f0706 100644 --- a/res/css/views/settings/_NotificationSettings2.pcss +++ b/res/css/views/settings/_NotificationSettings2.pcss @@ -6,20 +6,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -.mx_NotificationSettings2 { +.mx_SettingsTab .mx_NotificationSettings2 { .mx_SettingsSection_subSections { color: $primary-content; gap: 32px; display: flex; flex-direction: column; + + > form { + gap: 32px; + display: flex; + flex-direction: column; + } } .mx_SettingsSubsection_description { margin-bottom: 20px; .mx_SettingsSubsection_text { - font-size: 1.2rem; - .mx_NotificationBadge { vertical-align: baseline; display: inline-flex; @@ -35,14 +39,6 @@ Please see LICENSE files in the repository root for full details. justify-content: stretch; } - .mx_SettingsBanner { - margin-bottom: 32px; - } - - .mx_NotificationSettings2_flags { - gap: 4px; - } - .mx_StyledRadioButton_content { margin-left: 10px; margin-right: 10px; diff --git a/res/css/views/settings/tabs/_SettingsTab.pcss b/res/css/views/settings/tabs/_SettingsTab.pcss index 99690d5657..92a392950f 100644 --- a/res/css/views/settings/tabs/_SettingsTab.pcss +++ b/res/css/views/settings/tabs/_SettingsTab.pcss @@ -77,10 +77,6 @@ Please see LICENSE files in the repository root for full details. } } -.mx_SettingsTab_toggleWithDescription { - margin-top: $spacing-24; -} - .mx_SettingsTab_sections { display: grid; grid-template-columns: 1fr; diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.pcss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.pcss index dd899389ef..adc55b58f6 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.pcss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.pcss @@ -8,6 +8,6 @@ Please see LICENSE files in the repository root for full details. .mx_Field.mx_AppearanceUserSettingsTab_checkboxControlledField { width: 256px; - /* matches checkbox box + padding to align with checkbox label */ - margin-inline-start: calc($font-16px + 10px); + /* Line up with Settings field toggle button */ + margin-inline-start: 0; } diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss index d3a5f6178f..90a4fdd5b1 100644 --- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss @@ -35,3 +35,8 @@ Please see LICENSE files in the repository root for full details. color: $alert; } } + +form.mx_SecurityUserSettingsTab_posthogSection { + /* Inhibit compound spacing here as it clashes with pre-compound UI */ + display: contents !important; +} diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 821182aff0..7bf34aff0a 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -7,8 +7,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, type ChangeEvent, createRef, type KeyboardEvent, type SyntheticEvent } from "react"; +import React, { + type JSX, + type ChangeEvent, + createRef, + type KeyboardEvent, + type SyntheticEvent, + type ChangeEventHandler, +} from "react"; import { type Room, RoomType, JoinRule, Preset, Visibility } from "matrix-js-sdk/src/matrix"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import SdkConfig from "../../../SdkConfig"; import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation"; @@ -17,7 +25,6 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { checkUserIsAllowedToChangeEncryption, type IOpts } from "../../../createRoom"; import Field from "../elements/Field"; import RoomAliasField from "../elements/RoomAliasField"; -import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import DialogButtons from "../elements/DialogButtons"; import BaseDialog from "../dialogs/BaseDialog"; import JoinRuleDropdown from "../elements/JoinRuleDropdown"; @@ -25,7 +32,6 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { privateShouldBeEncrypted } from "../../../utils/rooms"; import SettingsStore from "../../../settings/SettingsStore"; -import LabelledCheckbox from "../elements/LabelledCheckbox"; import { UIFeature } from "../../../settings/UIFeature"; interface IProps { @@ -226,8 +232,8 @@ export default class CreateRoomDialog extends React.Component { this.setState({ joinRule }); }; - private onEncryptedChange = (isEncrypted: boolean): void => { - this.setState({ isEncrypted }); + private onEncryptedChange: ChangeEventHandler = (evt): void => { + this.setState({ isEncrypted: evt.target.checked }); }; private onAliasChange = (alias: string): void => { @@ -238,8 +244,8 @@ export default class CreateRoomDialog extends React.Component { this.setState({ detailsOpen: (ev.target as HTMLDetailsElement).open }); }; - private onNoFederateChange = (noFederate: boolean): void => { - this.setState({ noFederate }); + private onNoFederateChange: ChangeEventHandler = (evt): void => { + this.setState({ noFederate: evt.target.checked }); }; private onNameValidate = async (fieldState: IFieldState): Promise => { @@ -248,8 +254,8 @@ export default class CreateRoomDialog extends React.Component { return result; }; - private onIsPublicKnockRoomChange = (isPublicKnockRoom: boolean): void => { - this.setState({ isPublicKnockRoom }); + private onIsPublicKnockRoomChange: ChangeEventHandler = (evt): void => { + this.setState({ isPublicKnockRoom: evt.target.checked }); }; private static validateRoomName = withValidation({ @@ -336,11 +342,12 @@ export default class CreateRoomDialog extends React.Component { let visibilitySection: JSX.Element | undefined; if (this.state.joinRule === JoinRule.Knock) { visibilitySection = ( - ); } @@ -360,16 +367,14 @@ export default class CreateRoomDialog extends React.Component { microcopy = _t("settings|security|e2ee_default_disabled_warning"); } e2eeSection = ( - - -

{microcopy}

-
+ ); } @@ -399,8 +404,8 @@ export default class CreateRoomDialog extends React.Component { title={title} screenName="CreateRoom" > -
-
+
+ { className="mx_CreateRoomDialog_topic" /> - +
+ + + {publicPrivateLabel} +
- {publicPrivateLabel} {visibilitySection} {e2eeSection} {aliasField} @@ -439,18 +447,20 @@ export default class CreateRoomDialog extends React.Component { {this.state.detailsOpen ? _t("action|hide_advanced") : _t("action|show_advanced")} - -

{federateLabel}

+ z

{federateLabel}

)} -
- + +
void; @@ -22,6 +21,14 @@ export const DeclineAndBlockInviteDialog: React.FunctionComponent = ({ o const [shouldReport, setShouldReport] = useState(false); const [ignoreUser, setIgnoreUser] = useState(false); + const onShouldReportChanged = useCallback>( + (e) => setShouldReport(e.target.checked), + [setShouldReport], + ); + const onIgnoreUserChanged = useCallback>( + (e) => setIgnoreUser(e.target.checked), + [setIgnoreUser], + ); const [reportReason, setReportReason] = useState(""); const reportReasonChanged = useCallback>( (e) => setReportReason(e.target.value), @@ -43,17 +50,19 @@ export const DeclineAndBlockInviteDialog: React.FunctionComponent = ({ o >

{_t("decline_invitation_dialog|confirm", { roomName })}

- - {adminMessage} {busy ? : null} - { - this.setState({ inviteUsersToNewRoom }); + private onInviteUsersToggle: ChangeEventHandler = (evt): void => { + this.setState({ inviteUsersToNewRoom: evt.target.checked }); }; private openBugReportDialog = (e: SyntheticEvent): void => { @@ -104,11 +104,19 @@ export default class RoomUpgradeWarningDialog extends React.Component + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + + ); } diff --git a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx index 3cc54b4499..33ca9c510b 100644 --- a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx +++ b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React from "react"; +import React, { type ChangeEventHandler } from "react"; import { type Capability, isTimelineCapability, @@ -15,13 +15,13 @@ import { type WidgetKind, } from "matrix-widget-api"; import { lexicographicCompare } from "matrix-js-sdk/src/utils"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import BaseDialog from "./BaseDialog"; import { _t } from "../../../languageHandler"; import { objectShallowClone } from "../../../utils/objects"; import StyledCheckbox from "../elements/StyledCheckbox"; import DialogButtons from "../elements/DialogButtons"; -import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import { CapabilityText } from "../../../widgets/CapabilityText"; interface IProps { @@ -64,8 +64,8 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< this.setState({ booleanStates: newStates }); }; - private onRememberSelectionChange = (newVal: boolean): void => { - this.setState({ rememberSelection: newVal }); + private onRememberSelectionChange: ChangeEventHandler = (evt): void => { + this.setState({ rememberSelection: evt.target.checked }); }; private onSubmit = async (): Promise => { @@ -117,7 +117,7 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< onFinished={this.props.onFinished} title={_t("widget|capabilities_dialog|title")} > -
+
{_t("widget|capabilities_dialog|content_starting_text")}
{checkboxRows} @@ -127,16 +127,16 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< onPrimaryButtonClick={this.onSubmit} onCancel={this.onReject} additive={ - } />
- +
); } diff --git a/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx b/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx index f9729a695e..d0d06d12d5 100644 --- a/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx +++ b/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx @@ -7,12 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React from "react"; +import React, { type ChangeEventHandler } from "react"; import { type Widget, type WidgetKind } from "matrix-widget-api"; import { logger } from "matrix-js-sdk/src/logger"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import { _t } from "../../../languageHandler"; -import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import { OIDCState } from "../../../stores/widgets/WidgetPermissionStore"; import BaseDialog from "./BaseDialog"; import DialogButtons from "../elements/DialogButtons"; @@ -61,8 +61,8 @@ export default class WidgetOpenIDPermissionsDialog extends React.PureComponent { - this.setState({ rememberSelection: newVal }); + private onRememberSelectionChange: ChangeEventHandler = (evt): void => { + this.setState({ rememberSelection: evt.target.checked }); }; public render(): React.ReactNode { @@ -85,12 +85,19 @@ export default class WidgetOpenIDPermissionsDialog extends React.PureComponent + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + + } /> diff --git a/src/components/views/dialogs/devtools/RoomState.tsx b/src/components/views/dialogs/devtools/RoomState.tsx index 1f33fc5eea..b9863b69ff 100644 --- a/src/components/views/dialogs/devtools/RoomState.tsx +++ b/src/components/views/dialogs/devtools/RoomState.tsx @@ -7,9 +7,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { useContext, useEffect, useMemo, useState } from "react"; +import React, { type ChangeEventHandler, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { type IContent, type MatrixEvent } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import { _t, _td } from "../../../../languageHandler"; import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool"; @@ -19,7 +20,6 @@ import FilteredList from "./FilteredList"; import Spinner from "../../elements/Spinner"; import SyntaxHighlight from "../../elements/SyntaxHighlight"; import { useAsyncMemo } from "../../../../hooks/useAsyncMemo"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; export const StateEventEditor: React.FC = ({ mxEvent, onBack }) => { const context = useContext(DevtoolsContext); @@ -116,6 +116,10 @@ const RoomStateExplorerEventType: React.FC = ({ eventType, onBa const [event, setEvent] = useState(null); const [history, setHistory] = useState(false); const [showEmptyState, setShowEmptyState] = useState(true); + const onEmptyStateToggled = useCallback>( + (e) => setShowEmptyState(e.target.checked), + [setShowEmptyState], + ); const events = context.room.currentState.events.get(eventType)!; @@ -157,11 +161,19 @@ const RoomStateExplorerEventType: React.FC = ({ eventType, onBa setEvent(ev)} /> ))} - + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + + ); }; diff --git a/src/components/views/dialogs/devtools/Users.tsx b/src/components/views/dialogs/devtools/Users.tsx index 1d98117c99..889d437355 100644 --- a/src/components/views/dialogs/devtools/Users.tsx +++ b/src/components/views/dialogs/devtools/Users.tsx @@ -12,11 +12,11 @@ import React, { type JSX, useContext, useState } from "react"; import { type Device, type RoomMember } from "matrix-js-sdk/src/matrix"; import { type CryptoApi } from "matrix-js-sdk/src/crypto-api"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import { _t } from "../../../../languageHandler"; import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool"; import FilteredList from "./FilteredList"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; import { useAsyncMemo } from "../../../../hooks/useAsyncMemo"; import CopyableText from "../../elements/CopyableText"; import E2EIcon from "../../rooms/E2EIcon"; @@ -57,11 +57,21 @@ export const UserList: React.FC> = ({ onBack }) = setMember(member)} /> ))} - + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + { + setShowOnlyJoined(e.target.checked); + }} + checked={showOnlyJoined} + /> + ); }; diff --git a/src/components/views/elements/LabelledCheckbox.tsx b/src/components/views/elements/LabelledCheckbox.tsx index ba07ed9201..45cef43da9 100644 --- a/src/components/views/elements/LabelledCheckbox.tsx +++ b/src/components/views/elements/LabelledCheckbox.tsx @@ -24,11 +24,13 @@ interface IProps { onChange(checked: boolean): void; // Optional additional CSS class to apply to the label className?: string; + // The id for the checkbox + id?: string; } -const LabelledCheckbox: React.FC = ({ value, label, byline, disabled, onChange, className }) => { +const LabelledCheckbox: React.FC = ({ value, label, byline, disabled, onChange, className, id }) => { return ( -
+
= ({ - label, - caption, - value, - disabled, - onChange, - tooltip, - toggleInFront, - className, - "data-testid": testId, -}) => { - // This is a minimal version of a SettingsFlag - const generatedId = useId(); - const id = `mx_LabelledToggleSwitch_${generatedId}`; - let firstPart = ( - -
{label}
- {caption && {caption}} -
- ); - let secondPart = ( - - ); - - if (toggleInFront) { - [firstPart, secondPart] = [secondPart, firstPart]; - } - - const classes = classNames("mx_SettingsFlag", className, { - mx_SettingsFlag_toggleInFront: toggleInFront, - }); - return ( -
- {firstPart} - {secondPart} -
- ); -}; - -export default LabelledToggleSwitch; diff --git a/src/components/views/elements/SettingsFlag.tsx b/src/components/views/elements/SettingsFlag.tsx index d7fac91875..eb05301270 100644 --- a/src/components/views/elements/SettingsFlag.tsx +++ b/src/components/views/elements/SettingsFlag.tsx @@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React from "react"; +import React, { type ChangeEvent } from "react"; import { secureRandomString } from "matrix-js-sdk/src/randomstring"; +import { SettingsToggleInput } from "@vector-im/compound-web"; +import { logger } from "matrix-js-sdk/src/logger"; import SettingsStore from "../../../settings/SettingsStore"; import { _t } from "../../../languageHandler"; -import ToggleSwitch from "./ToggleSwitch"; -import StyledCheckbox from "./StyledCheckbox"; import { type SettingLevel } from "../../../settings/SettingLevel"; import { type BooleanSettingKey, defaultWatchManager } from "../../../settings/Settings"; @@ -24,8 +24,6 @@ interface IProps { roomId?: string; // for per-room settings label?: string; isExplicit?: boolean; - // XXX: once design replaces all toggles make this the default - useCheckbox?: boolean; hideIfCannotSet?: boolean; onChange?(checked: boolean): void; } @@ -74,14 +72,16 @@ export default class SettingsFlag extends React.Component { }); }; - private onChange = async (checked: boolean): Promise => { - await this.save(checked); - this.setState({ value: checked }); - this.props.onChange?.(checked); - }; - - private checkBoxOnChange = (e: React.ChangeEvent): void => { - this.onChange(e.target.checked); + private onChange = async (evt: ChangeEvent): Promise => { + const newValue = evt.target.checked; + try { + await this.save(newValue); + } catch (ex) { + logger.info(`Failed to save setting ${this.props.name}`, ex); + return; + } + this.setState({ value: newValue }); + this.props.onChange?.(newValue); }; private save = async (val?: boolean): Promise => { @@ -101,45 +101,28 @@ export default class SettingsFlag extends React.Component { const label = this.props.label ?? SettingsStore.getDisplayName(this.props.name, this.props.level); const description = SettingsStore.getDescription(this.props.name); const shouldWarn = SettingsStore.shouldHaveWarning(this.props.name); - - if (this.props.useCheckbox) { - return ( - - {label} - - ); - } else { - return ( -
- - -
- ); - } + const helpMessage = shouldWarn + ? _t( + "settings|warning", + {}, + { + w: (sub) => {sub}, + description, + }, + ) + : description; + const disabledMessage = SettingsStore.disabledMessage(this.props.name); + return ( + + ); } } diff --git a/src/components/views/location/EnableLiveShare.tsx b/src/components/views/location/EnableLiveShare.tsx index 18fc6025d3..baa42ae6f9 100644 --- a/src/components/views/location/EnableLiveShare.tsx +++ b/src/components/views/location/EnableLiveShare.tsx @@ -6,12 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { useState } from "react"; +import React, { type ChangeEventHandler, type FormEventHandler, useCallback, useState } from "react"; +import { SettingsToggleInput, Form, Button } from "@vector-im/compound-web"; import { _t } from "../../../languageHandler"; import StyledLiveBeaconIcon from "../beacon/StyledLiveBeaconIcon"; -import AccessibleButton from "../elements/AccessibleButton"; -import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import Heading from "../typography/Heading"; interface Props { @@ -20,6 +19,23 @@ interface Props { export const EnableLiveShare: React.FC = ({ onSubmit }) => { const [isEnabled, setEnabled] = useState(false); + + const onEnabledChanged = useCallback>( + (e) => setEnabled(e.target.checked), + [setEnabled], + ); + + const onSubmitForm = useCallback( + (evt) => { + evt.preventDefault(); + evt.stopPropagation(); + if (isEnabled) { + onSubmit(); + } + }, + [isEnabled, onSubmit], + ); + return (
@@ -27,22 +43,17 @@ export const EnableLiveShare: React.FC = ({ onSubmit }) => { {_t("location_sharing|live_enable_heading")}

{_t("location_sharing|live_enable_description")}

- - - {_t("action|ok")} - + + + +
); }; diff --git a/src/components/views/room_settings/RoomPublishSetting.tsx b/src/components/views/room_settings/RoomPublishSetting.tsx index 5147945c36..cac8a55e24 100644 --- a/src/components/views/room_settings/RoomPublishSetting.tsx +++ b/src/components/views/room_settings/RoomPublishSetting.tsx @@ -6,10 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React from "react"; +import React, { type ChangeEventHandler } from "react"; import { JoinRule, Visibility } from "matrix-js-sdk/src/matrix"; +import { SettingsToggleInput } from "@vector-im/compound-web"; +import { logger } from "matrix-js-sdk/src/logger"; -import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import DirectoryCustomisations from "../../../customisations/Directory"; @@ -24,6 +25,7 @@ interface IProps { interface IState { isRoomPublished: boolean; + busy: boolean; } export default class RoomPublishSetting extends React.PureComponent { @@ -32,6 +34,7 @@ export default class RoomPublishSetting extends React.PureComponent { - const valueBefore = this.state.isRoomPublished; - const newValue = !valueBefore; - this.setState({ isRoomPublished: newValue }); + private onRoomPublishChange: ChangeEventHandler = async (evt): Promise => { + const newValue = evt.target.checked; + this.setState({ busy: true }); const client = MatrixClientPeg.safeGet(); - client - .setRoomDirectoryVisibility(this.props.roomId, newValue ? Visibility.Public : Visibility.Private) - .catch(() => { - this.showError(); - // Roll back the local echo on the change - this.setState({ isRoomPublished: valueBefore }); - }); + try { + await client.setRoomDirectoryVisibility( + this.props.roomId, + newValue ? Visibility.Public : Visibility.Private, + ); + this.setState({ isRoomPublished: newValue }); + } catch (ex) { + logger.error("Error while setting room directory visibility", ex); + this.showError(); + } finally { + this.setState({ busy: false }); + } }; public componentDidMount(): void { @@ -69,17 +76,26 @@ export default class RoomPublishSetting extends React.PureComponent { return ( -
+ { + evt.preventDefault(); + evt.stopPropagation(); + }} + > -
+ ); }; @@ -213,9 +215,6 @@ export default class Notifications extends React.PureComponent - this.setState({ desktopNotifications: value as boolean }), - ), SettingsStore.watchSetting("deviceNotificationsEnabled", null, (...[, , , , value]) => { this.setState({ deviceNotificationsEnabled: value as boolean }); }), - SettingsStore.watchSetting("notificationBodyEnabled", null, (...[, , , , value]) => - this.setState({ desktopShowBody: value as boolean }), - ), - SettingsStore.watchSetting("audioNotificationsEnabled", null, (...[, , , , value]) => - this.setState({ audioNotifications: value as boolean }), - ), ]; // noinspection JSIgnoredPromiseFromCall @@ -286,7 +276,7 @@ export default class Notifications extends React.PureComponent => { + private onMasterRuleChanged: ChangeEventHandler = async (evt): Promise => { + const { checked } = evt.target; this.setState({ phase: Phase.Persisting }); const masterRule = this.state.masterPushRule!; @@ -431,11 +422,8 @@ export default class Notifications extends React.PureComponent => { - await SettingsStore.setValue("deviceNotificationsEnabled", null, SettingLevel.DEVICE, checked); - }; - - private onEmailNotificationsChanged = async (email: string, checked: boolean): Promise => { + private onEmailNotificationsChanged = async (email: string, evt: ChangeEvent): Promise => { + const { checked } = evt.target; this.setState({ phase: Phase.Persisting }); try { @@ -470,18 +458,6 @@ export default class Notifications extends React.PureComponent => { - await SettingsStore.setValue("notificationsEnabled", null, SettingLevel.DEVICE, checked); - }; - - private onDesktopShowBodyChanged = async (checked: boolean): Promise => { - await SettingsStore.setValue("notificationBodyEnabled", null, SettingLevel.DEVICE, checked); - }; - - private onAudioNotificationsChanged = async (checked: boolean): Promise => { - await SettingsStore.setValue("audioNotificationsEnabled", null, SettingLevel.DEVICE, checked); - }; - private onRadioChecked = async (rule: IVectorPushRule, checkedState: VectorState): Promise => { this.setState(({ ruleIdsWithError }) => ({ phase: Phase.Persisting, @@ -663,11 +639,11 @@ export default class Notifications extends React.PureComponent @@ -681,10 +657,10 @@ export default class Notifications extends React.PureComponent t.medium === ThreepidMedium.Email) .map((e) => ( - p.kind === "email" && p.pushkey === e.address)} + checked={!!this.state.pushers?.some((p) => p.kind === "email" && p.pushkey === e.address)} label={_t("settings|notifications|enable_email_notifications", { email: e.address })} onChange={this.onEmailNotificationsChanged.bind(this, e.address)} disabled={this.state.phase === Phase.Persisting} @@ -695,37 +671,13 @@ export default class Notifications extends React.PureComponent {masterSwitch} - this.updateDeviceNotifications(checked)} - disabled={this.state.phase === Phase.Persisting} - /> + {this.state.deviceNotificationsEnabled && ( <> - - - + + + )} diff --git a/src/components/views/settings/SetIntegrationManager.tsx b/src/components/views/settings/SetIntegrationManager.tsx index 98066bc0a4..44507067ad 100644 --- a/src/components/views/settings/SetIntegrationManager.tsx +++ b/src/components/views/settings/SetIntegrationManager.tsx @@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details. import React from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { type EmptyObject } from "matrix-js-sdk/src/matrix"; -import { Root, InlineField, Label, ToggleInput } from "@vector-im/compound-web"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import { _t } from "../../../languageHandler"; import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; @@ -66,33 +66,31 @@ export default class SetIntegrationManager extends React.Component + { + evt.preventDefault(); + evt.stopPropagation(); + }} + className="mx_SetIntegrationManager" + data-testid="mx_SetIntegrationManager" + >
{_t("integration_manager|manage_title")} - {managerName} + + {managerName} +
- {bodyText} + {bodyText} {_t("integration_manager|explainer")} - - - } - > - - - -
+ + ); } } diff --git a/src/components/views/settings/notifications/NotificationPusherSettings.tsx b/src/components/views/settings/notifications/NotificationPusherSettings.tsx index a0560dc9ca..f7b21efbd6 100644 --- a/src/components/views/settings/notifications/NotificationPusherSettings.tsx +++ b/src/components/views/settings/notifications/NotificationPusherSettings.tsx @@ -86,6 +86,7 @@ export function NotificationPusherSettings(): JSX.Element { {_t("settings|notifications|email_description")} @@ -109,7 +110,7 @@ export function NotificationPusherSettings(): JSX.Element { {notificationTargets.length > 0 && ( - +
    {pushers .filter((it) => it.kind !== "email") diff --git a/src/components/views/settings/notifications/NotificationSettings2.tsx b/src/components/views/settings/notifications/NotificationSettings2.tsx index ae5aa46d31..c316d46129 100644 --- a/src/components/views/settings/notifications/NotificationSettings2.tsx +++ b/src/components/views/settings/notifications/NotificationSettings2.tsx @@ -7,11 +7,11 @@ Please see LICENSE files in the repository root for full details. */ import React, { type JSX, useState } from "react"; +import { SettingsToggleInput } from "@vector-im/compound-web"; import NewAndImprovedIcon from "../../../../../res/img/element-icons/new-and-improved.svg"; import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; import { useNotificationSettings } from "../../../../hooks/useNotificationSettings"; -import { useSettingValue } from "../../../../hooks/useSettings"; import { _t } from "../../../../languageHandler"; import { DefaultNotificationSettings, @@ -19,13 +19,11 @@ import { } from "../../../../models/notificationsettings/NotificationSettings"; import { RoomNotifState } from "../../../../RoomNotifs"; import { SettingLevel } from "../../../../settings/SettingLevel"; -import SettingsStore from "../../../../settings/SettingsStore"; import { NotificationLevel } from "../../../../stores/notifications/NotificationLevel"; import { clearAllNotifications } from "../../../../utils/notifications"; import AccessibleButton from "../../elements/AccessibleButton"; import ExternalLink from "../../elements/ExternalLink"; import LabelledCheckbox from "../../elements/LabelledCheckbox"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; import StyledRadioGroup from "../../elements/StyledRadioGroup"; import TagComposer from "../../elements/TagComposer"; import { StatelessNotificationBadge } from "../../rooms/NotificationBadge/StatelessNotificationBadge"; @@ -71,10 +69,6 @@ function useHasUnreadNotifications(): boolean { export default function NotificationSettings2(): JSX.Element { const cli = useMatrixClientContext(); - const desktopNotifications = useSettingValue("notificationsEnabled"); - const desktopShowBody = useSettingValue("notificationBodyEnabled"); - const audioNotifications = useSettingValue("audioNotificationsEnabled"); - const { model, hasPendingChanges, reconcile } = useNotificationSettings(cli); const disabled = model === null || hasPendingChanges; @@ -118,38 +112,25 @@ export default function NotificationSettings2(): JSX.Element { )}
    - { + onChange={(evt) => { reconcile({ ...model!, - globalMute: !value, + globalMute: !evt.target.checked, }); }} /> - - SettingsStore.setValue("notificationsEnabled", null, SettingLevel.DEVICE, value) - } - /> - + - SettingsStore.setValue("notificationBodyEnabled", null, SettingLevel.DEVICE, value) - } - /> - - SettingsStore.setValue("audioNotificationsEnabled", null, SettingLevel.DEVICE, value) - } + level={SettingLevel.DEVICE} /> +
    { diff --git a/src/components/views/settings/shared/SettingsSubsection.tsx b/src/components/views/settings/shared/SettingsSubsection.tsx index 6f7fdde1f0..ac4872f598 100644 --- a/src/components/views/settings/shared/SettingsSubsection.tsx +++ b/src/components/views/settings/shared/SettingsSubsection.tsx @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import classNames from "classnames"; import React, { type HTMLAttributes } from "react"; -import { Separator } from "@vector-im/compound-web"; +import { Form, Separator } from "@vector-im/compound-web"; import { SettingsSubsectionHeading } from "./SettingsSubsectionHeading"; @@ -23,6 +23,11 @@ export interface SettingsSubsectionProps extends HTMLAttributes * @default true */ legacy?: boolean; + + /** + * Wrap in a Form Root component, for compatibility with compound components. + */ + formWrap?: boolean; } export const SettingsSubsectionText: React.FC> = ({ children, ...rest }) => ( @@ -37,31 +42,48 @@ export const SettingsSubsection: React.FC = ({ children, stretchContent, legacy = true, + formWrap, ...rest -}) => ( -
    - {typeof heading === "string" ? : <>{heading}} - {!!description && ( -
    - {description} -
    - )} - {!!children && ( -
    { + const content = ( +
    + {typeof heading === "string" ? : <>{heading}} + {!!description && ( +
    + {description} +
    + )} + {!!children && ( +
    + {children} +
    + )} + {!legacy && } +
    + ); + + if (formWrap) { + return ( + { + evt.preventDefault(); + evt.stopPropagation(); + }} > - {children} -
    - )} - {!legacy && } -
    -); + {content} + + ); + } + return content; +}; diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx index b50eb22a7c..4b04e4bcf3 100644 --- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { type ContextType } from "react"; import { type Room } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; +import { Form } from "@vector-im/compound-web"; import { _t } from "../../../../../languageHandler"; import RoomProfileSettings from "../../../room_settings/RoomProfileSettings"; @@ -78,26 +79,33 @@ export default class GeneralRoomSettingsTab extends React.Component - - - + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + + + - - - + + + - - {urlPreviewSettings} - - - - {leaveSection} - + + {urlPreviewSettings} + + + + {leaveSection} + + ); } diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index 37faaea5d8..ed72b6963c 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, type ReactNode } from "react"; +import React, { type ChangeEventHandler, type JSX, type ReactNode } from "react"; import { GuestAccess, HistoryVisibility, @@ -17,11 +17,10 @@ import { EventType, } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; -import { InlineSpinner } from "@vector-im/compound-web"; +import { Form, InlineSpinner, SettingsToggleInput } from "@vector-im/compound-web"; import { Icon as WarningIcon } from "../../../../../../res/img/warning.svg"; import { _t } from "../../../../../languageHandler"; -import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import Modal from "../../../../../Modal"; import QuestionDialog from "../../../dialogs/QuestionDialog"; import StyledRadioGroup from "../../../elements/StyledRadioGroup"; @@ -184,7 +183,8 @@ export default class SecurityRoomSettingsTab extends React.Component { + private onGuestAccessChange: ChangeEventHandler = (evt): void => { + const allowed = evt.target.checked; const guestAccess = allowed ? GuestAccess.CanJoin : GuestAccess.Forbidden; const beforeGuestAccess = this.state.guestAccess; if (beforeGuestAccess === guestAccess) return; @@ -464,13 +464,14 @@ export default class SecurityRoomSettingsTab extends React.Component - -

    {_t("room_settings|security|guest_access_warning")}

); } @@ -503,35 +504,43 @@ export default class SecurityRoomSettingsTab extends React.Component - - - {isEncryptionLoading ? ( - - ) : ( - <> - - {isEncryptionForceDisabled && !isEncrypted && ( - {_t("room_settings|security|encryption_forced")} - )} - {encryptionSettings} - - )} - - {this.renderJoinRule()} - {historySection} - + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + + + {isEncryptionLoading ? ( + + ) : ( + <> + + {isEncryptionForceDisabled && !isEncrypted && ( + {_t("room_settings|security|encryption_forced")} + )} + {encryptionSettings} + + )} + + {this.renderJoinRule()} + {historySection} + + ); } diff --git a/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx index 177d495366..6c28105060 100644 --- a/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx @@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { useCallback, useMemo, useState } from "react"; +import React, { type ChangeEventHandler, useCallback, useMemo, useState } from "react"; import { JoinRule, EventType, type RoomState, type Room } from "matrix-js-sdk/src/matrix"; import { type RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import { _t } from "../../../../../languageHandler"; -import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import { SettingsSubsection } from "../../shared/SettingsSubsection"; import SettingsTab from "../SettingsTab"; import { useRoomState } from "../../../../../hooks/useRoomState"; @@ -45,8 +45,9 @@ const ElementCallSwitch: React.FC = ({ room }) => { return content.events?.[ElementCallMemberEventType.name] === 0; }); - const onChange = useCallback( - (enabled: boolean): void => { + const onChange = useCallback>( + (evt): void => { + const enabled = evt.target.checked; setElementCallEnabled(enabled); // Take a copy to avoid mutating the original @@ -73,16 +74,17 @@ const ElementCallSwitch: React.FC = ({ room }) => { const brand = SdkConfig.get("element_call").brand ?? DEFAULTS.element_call.brand; return ( - ); }; @@ -95,9 +97,16 @@ export const VoipRoomSettingsTab: React.FC = ({ room }) => { return ( - - - + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + + + + ); diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 7d46cf72da..4a8cecc204 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -9,9 +9,9 @@ Please see LICENSE files in the repository root for full details. import React, { type ChangeEvent, type ReactNode } from "react"; import { type EmptyObject } from "matrix-js-sdk/src/matrix"; +import { Form } from "@vector-im/compound-web"; import { _t } from "../../../../../languageHandler"; -import SdkConfig from "../../../../../SdkConfig"; import SettingsStore from "../../../../../settings/SettingsStore"; import SettingsFlag from "../../../elements/SettingsFlag"; import Field from "../../../elements/Field"; @@ -48,7 +48,6 @@ export default class AppearanceUserSettingsTab extends React.Component - + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + this.setState({ useBundledEmojiFont: checked })} /> this.setState({ useSystemFont: checked })} /> - + ); } return ( diff --git a/src/components/views/settings/tabs/user/InviteRulesAccountSettings.tsx b/src/components/views/settings/tabs/user/InviteRulesAccountSettings.tsx index 7c66e88046..715da57346 100644 --- a/src/components/views/settings/tabs/user/InviteRulesAccountSettings.tsx +++ b/src/components/views/settings/tabs/user/InviteRulesAccountSettings.tsx @@ -5,15 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type FC, useCallback, useState } from "react"; -import { Root } from "@vector-im/compound-web"; +import React, { type ChangeEvent, type FC, useCallback, useState } from "react"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../../../languageHandler"; import { useSettingValue } from "../../../../../hooks/useSettings"; import SettingsStore from "../../../../../settings/SettingsStore"; import { SettingLevel } from "../../../../../settings/SettingLevel"; -import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; /** * A settings component which allows the user to enable/disable invite blocking. @@ -31,10 +30,10 @@ export const InviteRulesAccountSetting: FC = () => { const [busy, setBusy] = useState(false); const onChange = useCallback( - async (allowInvites: boolean) => { + async (e: ChangeEvent) => { try { setBusy(true); - if (allowInvites) { + if (e.target.checked) { // When allowing invites, clear the block setting on both bits of account data. await SettingsStore.setValue("blockInvites", null, SettingLevel.ACCOUNT, false); await SettingsStore.setValue("inviteRules", null, SettingLevel.ACCOUNT, { allBlocked: false }); @@ -58,15 +57,22 @@ export const InviteRulesAccountSetting: FC = () => { const disabledMessage = msc4155Disabled && msc4380Disabled; const invitesBlocked = (!msc4155Disabled && msc4155Rules.allBlocked) || (!msc4380Disabled && msc4380BlockInvites); return ( - - { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + - + ); }; diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx b/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx index 622343b287..85d41cbf0d 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { type JSX } from "react"; import { sortBy } from "lodash"; import { type EmptyObject } from "matrix-js-sdk/src/matrix"; +import { Form } from "@vector-im/compound-web"; import { _t } from "../../../../../languageHandler"; import SettingsStore from "../../../../../settings/SettingsStore"; @@ -106,37 +107,44 @@ export default class LabsUserSettingsTab extends React.Component { return ( - - - {_t("labs|beta_description", { brand: SdkConfig.get("brand") })} - - {betaSection} - - - {labsSections && ( - + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + - {_t( - "labs|experimental_description", - {}, - { - a: (sub) => { - return ( - - {sub} - - ); - }, - }, - )} + {_t("labs|beta_description", { brand: SdkConfig.get("brand") })} - {labsSections} + {betaSection} - )} + + {labsSections && ( + + + {_t( + "labs|experimental_description", + {}, + { + a: (sub) => { + return ( + + {sub} + + ); + }, + }, + )} + + {labsSections} + + )} + ); } diff --git a/src/components/views/settings/tabs/user/MediaPreviewAccountSettings.tsx b/src/components/views/settings/tabs/user/MediaPreviewAccountSettings.tsx index f6a2b7cf38..1194f2fd02 100644 --- a/src/components/views/settings/tabs/user/MediaPreviewAccountSettings.tsx +++ b/src/components/views/settings/tabs/user/MediaPreviewAccountSettings.tsx @@ -6,9 +6,8 @@ Please see LICENSE files in the repository root for full details. */ import React, { type ChangeEventHandler, useCallback } from "react"; -import { Field, HelpMessage, InlineField, Label, RadioInput, Root } from "@vector-im/compound-web"; +import { Field, HelpMessage, InlineField, Label, RadioInput, Root, SettingsToggleInput } from "@vector-im/compound-web"; -import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import { type MediaPreviewConfig, MediaPreviewValue } from "../../../../../@types/media_preview"; import { _t } from "../../../../../languageHandler"; import { useSettingValue } from "../../../../../hooks/useSettings"; @@ -30,12 +29,12 @@ export const MediaPreviewAccountSettings: React.FC<{ roomId?: string }> = ({ roo [roomId], ); - const avatarOnChange = useCallback( - (c: boolean) => { + const avatarOnChange = useCallback>( + (evt) => { changeSetting({ ...currentMediaPreview, // Switch is inverted. "Hide avatars..." - invite_avatars: c ? MediaPreviewValue.Off : MediaPreviewValue.On, + invite_avatars: evt.target.checked ? MediaPreviewValue.Off : MediaPreviewValue.On, }); }, [changeSetting, currentMediaPreview], @@ -83,10 +82,10 @@ export const MediaPreviewAccountSettings: React.FC<{ roomId?: string }> = ({ roo return ( {!roomId && ( - )} diff --git a/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx b/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx index b63e790736..6d6c25e122 100644 --- a/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx @@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import React from "react"; +import { Form } from "@vector-im/compound-web"; import { Features } from "../../../../../settings/Settings"; import SettingsStore from "../../../../../settings/SettingsStore"; @@ -21,13 +22,20 @@ export default class NotificationUserSettingsTab extends React.Component { return ( - {newNotificationSettingsEnabled ? ( - - ) : ( - - - - )} + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + {newNotificationSettingsEnabled ? ( + + ) : ( + + + + )} + ); } diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index d373aee60e..cab58ee085 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -7,7 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, type ReactElement, useCallback, useEffect, useState } from "react"; +import React, { type ChangeEvent, type JSX, type ReactElement, useCallback, useEffect, useState } from "react"; +import { SettingsToggleInput } from "@vector-im/compound-web"; import { type NonEmptyArray } from "../../../../../@types/common"; import { _t, getCurrentLanguage } from "../../../../../languageHandler"; @@ -29,7 +30,6 @@ import LanguageDropdown from "../../../elements/LanguageDropdown"; import PlatformPeg from "../../../../../PlatformPeg"; import { IS_MAC } from "../../../../../Keyboard"; import SpellCheckSettings from "../../SpellCheckSettings"; -import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import * as TimezoneHandler from "../../../../../TimezoneHandler"; import { type BooleanSettingKey } from "../../../../../settings/Settings.tsx"; import { MediaPreviewAccountSettings } from "./MediaPreviewAccountSettings.tsx"; @@ -92,7 +92,8 @@ const SpellCheckSection: React.FC = () => { })(); }, []); - const onSpellCheckEnabledChange = useCallback((enabled: boolean) => { + const onSpellCheckEnabledChange = useCallback((e: ChangeEvent) => { + const enabled = e.target.checked; setSpellCheckEnabled(enabled); PlatformPeg.get()?.setSpellCheckEnabled(enabled); }, []); @@ -106,10 +107,11 @@ const SpellCheckSection: React.FC = () => { return ( <> - {spellCheckEnabled && spellCheckLanguages !== undefined && !IS_MAC && ( @@ -261,13 +263,16 @@ export default class PreferencesUserSettingsTab extends React.Component {/* The heading string is still 'general' from where it was moved, but this section should become 'general' */} - + {SettingsStore.canSetValue("Electron.autoLaunch", null, SettingLevel.PLATFORM) && ( - + )} - + {!newRoomListEnabled && this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)} {/* The settings is on device level where the other room list settings are on account level */} {newRoomListEnabled && ( @@ -285,7 +290,7 @@ export default class PreferencesUserSettingsTab extends React.Component - + {this.renderGroup(PreferencesUserSettingsTab.SPACES_SETTINGS, SettingLevel.ACCOUNT)} @@ -302,11 +307,12 @@ export default class PreferencesUserSettingsTab extends React.Component {this.renderGroup(PreferencesUserSettingsTab.KEYBINDINGS_SETTINGS)} - +
{_t("settings|preferences|user_timezone")} {this.renderGroup(PreferencesUserSettingsTab.PRESENCE_SETTINGS)} - + {this.renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)} - + {this.renderGroup(PreferencesUserSettingsTab.CODE_BLOCKS_SETTINGS)} - + {this.renderGroup(PreferencesUserSettingsTab.IMAGES_AND_VIDEOS_SETTINGS)} - + {this.renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)} @@ -356,11 +363,11 @@ export default class PreferencesUserSettingsTab extends React.Component - + {this.renderGroup(PreferencesUserSettingsTab.ROOM_DIRECTORY_SETTINGS)} - + {this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)} diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx index c0e63f6f04..d301f9daab 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx @@ -12,6 +12,7 @@ import { type Room, RoomEvent, type IServerVersions } from "matrix-js-sdk/src/ma import { KnownMembership, type Membership } from "matrix-js-sdk/src/types"; import { logger } from "matrix-js-sdk/src/logger"; import { WarningIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; +import { Form } from "@vector-im/compound-web"; import { _t } from "../../../../../languageHandler"; import { MatrixClientPeg } from "../../../../../MatrixClientPeg"; @@ -321,7 +322,13 @@ export default class SecurityUserSettingsTab extends React.Component + { + evt.preventDefault(); + evt.stopPropagation(); + }} + className="mx_SecurityUserSettingsTab_posthogSection" + > - + ); } diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx index 84b4ec778f..b1b47788a5 100644 --- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx @@ -7,10 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, type ReactNode } from "react"; +import React, { type ChangeEventHandler, type JSX, type ReactNode } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { FALLBACK_ICE_SERVER } from "matrix-js-sdk/src/webrtc/call"; import { type EmptyObject } from "matrix-js-sdk/src/matrix"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import { _t } from "../../../../../languageHandler"; import MediaDeviceHandler, { type IMediaDevices, MediaDeviceKindEnum } from "../../../../../MediaDeviceHandler"; @@ -18,7 +19,6 @@ import Field from "../../../elements/Field"; import AccessibleButton from "../../../elements/AccessibleButton"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import SettingsFlag from "../../../elements/SettingsFlag"; -import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import { requestMediaPermissions } from "../../../../../utils/media/requestMediaPermissions"; import SettingsTab from "../SettingsTab"; import { SettingsSection } from "../../shared/SettingsSection"; @@ -140,6 +140,24 @@ export default class VoiceUserSettingsTab extends React.Component = async (event) => { + const enable = event.target.checked; + await MediaDeviceHandler.setAudioAutoGainControl(enable); + this.setState({ audioAutoGainControl: MediaDeviceHandler.getAudioAutoGainControl() }); + }; + + private onNoiseSuppressionChanged: ChangeEventHandler = async (event) => { + const enable = event.target.checked; + await MediaDeviceHandler.setAudioNoiseSuppression(enable); + this.setState({ audioNoiseSuppression: MediaDeviceHandler.getAudioNoiseSuppression() }); + }; + + private onEchoCancellationChanged: ChangeEventHandler = async (event) => { + const enable = event.target.checked; + await MediaDeviceHandler.setAudioEchoCancellation(enable); + this.setState({ audioEchoCancellation: MediaDeviceHandler.getAudioEchoCancellation() }); + }; + public render(): ReactNode { let requestButton: ReactNode | undefined; let speakerDropdown: ReactNode | undefined; @@ -169,64 +187,62 @@ export default class VoiceUserSettingsTab extends React.Component - - {requestButton} - - {speakerDropdown} - {microphoneDropdown} - => { - await MediaDeviceHandler.setAudioAutoGainControl(v); - this.setState({ audioAutoGainControl: MediaDeviceHandler.getAudioAutoGainControl() }); - }} - label={_t("settings|voip|voice_agc")} - data-testid="voice-auto-gain" - /> - - - {webcamDropdown} - - - + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + + {requestButton} + + {speakerDropdown} + {microphoneDropdown} + + + + {webcamDropdown} + + + - - - => { - await MediaDeviceHandler.setAudioNoiseSuppression(v); - this.setState({ audioNoiseSuppression: MediaDeviceHandler.getAudioNoiseSuppression() }); - }} - label={_t("settings|voip|noise_suppression")} - data-testid="voice-noise-suppression" - /> - => { - await MediaDeviceHandler.setAudioEchoCancellation(v); - this.setState({ audioEchoCancellation: MediaDeviceHandler.getAudioEchoCancellation() }); - }} - label={_t("settings|voip|echo_cancellation")} - data-testid="voice-echo-cancellation" - /> - - - - - - + + + + + + + + + + + ); } diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx index e456070cce..a148608790 100644 --- a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx +++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, useState } from "react"; +import React, { type ChangeEventHandler, type JSX, useCallback, useState } from "react"; import { type Room, EventType, @@ -15,12 +15,12 @@ import { JoinRule, type MatrixClient, } from "matrix-js-sdk/src/matrix"; +import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; import AliasSettings from "../room_settings/AliasSettings"; import { useStateToggle } from "../../../hooks/useStateToggle"; -import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import { useLocalEcho } from "../../../hooks/useLocalEcho"; import JoinRuleSettings from "../settings/JoinRuleSettings"; import { useRoomState } from "../../../hooks/useRoomState"; @@ -50,6 +50,7 @@ const SpaceSettingsVisibilityTab: React.FC = ({ matrixClient: cli, space const userId = cli.getUserId()!; const joinRule = useRoomState(space, (state) => state.getJoinRule()); + const [guestAccessEnabled, setGuestAccessEnabled] = useLocalEcho( () => space.currentState.getStateEvents(EventType.RoomGuestAccess, "")?.getContent()?.guest_access === @@ -65,6 +66,10 @@ const SpaceSettingsVisibilityTab: React.FC = ({ matrixClient: cli, space ), () => setError(_t("room_settings|visibility|error_update_guest_access")), ); + const onGuestAccessEnabledChanged = useCallback>( + (e) => setGuestAccessEnabled(e.target.checked), + [setGuestAccessEnabled], + ); const [historyVisibility, setHistoryVisibility] = useLocalEcho( () => space.currentState.getStateEvents(EventType.RoomHistoryVisibility, "")?.getContent()?.history_visibility || @@ -103,19 +108,15 @@ const SpaceSettingsVisibilityTab: React.FC = ({ matrixClient: cli, space {showAdvancedSection && ( -
- -

- {_t("room_settings|visibility|guest_access_explainer")} -
- {_t("room_settings|visibility|guest_access_explainer_public_space")} -

-
+ )}
); @@ -155,26 +156,32 @@ const SpaceSettingsVisibilityTab: React.FC = ({ matrixClient: cli, space onError={(): void => setError(_t("room_settings|visibility|error_failed_save"))} closeSettingsFn={closeSettingsFn} /> - {advancedSection} -
- { + { + evt.preventDefault(); + evt.stopPropagation(); + }} + > + {advancedSection} + { setHistoryVisibility( - checked ? HistoryVisibility.WorldReadable : HistoryVisibility.Shared, + evt.target.checked ? HistoryVisibility.WorldReadable : HistoryVisibility.Shared, ); }} + helpMessage={_t("room_settings|visibility|history_visibility_anyone_space_description")} disabled={!canSetHistoryVisibility} + disabledMessage={_t("room_settings|visibility|history_visibility_anyone_space_disabled")} label={_t("room_settings|visibility|history_visibility_anyone_space")} />

- {_t("room_settings|visibility|history_visibility_anyone_space_description")} -
{_t("room_settings|visibility|history_visibility_anyone_space_recommendation")}

-
+ {addressesSection} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 968fefae10..3d2fd7f6d9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2333,6 +2333,8 @@ "no_aliases_space": "This space has no local addresses", "other_section": "Other", "publish_toggle": "Publish this room to the public in %(domain)s's room directory?", + "publish_warn_invite_only": "You cannot publish a room that is invite-only.", + "publish_warn_no_canonical_permission": "You must have permission to set the main address to publish this room.", "published_aliases_description": "To publish an address, it needs to be set as a local address first.", "published_aliases_explainer_room": "Published addresses can be used by anyone on any server to join your room.", "published_aliases_explainer_space": "Published addresses can be used by anyone on any server to join your space.", @@ -2488,11 +2490,12 @@ "error_failed_save": "Failed to update the visibility of this space", "error_update_guest_access": "Failed to update the guest access of this space", "error_update_history_visibility": "Failed to update the history visibility of this space", - "guest_access_explainer": "Guests can join a space without having an account.", - "guest_access_explainer_public_space": "This may be useful for public spaces.", + "guest_access_disabled": "You do not have permission to change guest access.", + "guest_access_explainer": "Guests can join a space without having an account. This may be useful for public spaces.", "guest_access_label": "Enable guest access", "history_visibility_anyone_space": "Preview Space", "history_visibility_anyone_space_description": "Allow people to preview your space before they join.", + "history_visibility_anyone_space_disabled": "You do not have permission to change history visibilty.", "history_visibility_anyone_space_recommendation": "Recommended for public spaces.", "title": "Visibility" }, diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index d9a8ec1ebf..2ceb1b807f 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -974,6 +974,10 @@ export const SETTINGS: Settings = { default: false, displayName: _td("settings|appearance|custom_font"), controller: new SystemFontController(), + description: () => + _t("settings|appearance|custom_font_description", { + brand: SdkConfig.get().brand, + }), }, "systemFont": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, @@ -1135,10 +1139,12 @@ export const SETTINGS: Settings = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, default: false, controller: new NotificationsEnabledController(), + displayName: _td("settings|notifications|enable_desktop_notifications_session"), }, "deviceNotificationsEnabled": { supportedLevels: [SettingLevel.DEVICE], default: true, + displayName: _td("settings|notifications|enable_notifications_device"), }, "notificationSound": { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, @@ -1150,10 +1156,12 @@ export const SETTINGS: Settings = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, default: true, controller: new NotificationBodyEnabledController(), + displayName: _td("settings|notifications|show_message_desktop_notification"), }, "audioNotificationsEnabled": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, default: true, + displayName: _td("settings|notifications|enable_audible_notifications_session"), }, "enableWidgetScreenshots": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, diff --git a/test/unit-tests/components/views/dialogs/CreateRoomDialog-test.tsx b/test/unit-tests/components/views/dialogs/CreateRoomDialog-test.tsx index 6ac423570f..f9ebaacbd9 100644 --- a/test/unit-tests/components/views/dialogs/CreateRoomDialog-test.tsx +++ b/test/unit-tests/components/views/dialogs/CreateRoomDialog-test.tsx @@ -19,9 +19,6 @@ describe("", () => { const userId = "@alice:server.org"; const getE2eeEnableToggleInputElement = () => screen.getByLabelText("Enable end-to-end encryption"); - // labelled toggle switch doesn't set the disabled attribute, only aria-disabled - const getE2eeEnableToggleIsDisabled = () => - getE2eeEnableToggleInputElement().getAttribute("aria-disabled") === "true"; let mockClient: ReturnType; beforeEach(() => { @@ -121,7 +118,7 @@ describe("", () => { await flushPromises(); expect(getE2eeEnableToggleInputElement()).not.toBeChecked(); - expect(getE2eeEnableToggleIsDisabled()).toBeFalsy(); + expect(getE2eeEnableToggleInputElement()).not.toBeDisabled(); expect( screen.getByText( "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.", @@ -141,7 +138,7 @@ describe("", () => { await flushPromises(); expect(getE2eeEnableToggleInputElement()).not.toBeChecked(); - expect(getE2eeEnableToggleIsDisabled()).toBeTruthy(); + expect(getE2eeEnableToggleInputElement()).toBeDisabled(); expect( screen.getByText( "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.", @@ -161,7 +158,7 @@ describe("", () => { await flushPromises(); // encryption enabled expect(getE2eeEnableToggleInputElement()).toBeChecked(); - expect(getE2eeEnableToggleIsDisabled()).toBeFalsy(); + expect(getE2eeEnableToggleInputElement()).not.toBeDisabled(); }); it("should use defaultEncrypted prop when it is false", async () => { @@ -177,7 +174,7 @@ describe("", () => { // encryption disabled expect(getE2eeEnableToggleInputElement()).not.toBeChecked(); // not forced to off - expect(getE2eeEnableToggleIsDisabled()).toBeFalsy(); + expect(getE2eeEnableToggleInputElement()).not.toBeDisabled(); }); it("should override defaultEncrypted when server .well-known forces disabled encryption", async () => { @@ -192,7 +189,7 @@ describe("", () => { // server forces encryption to disabled, even though defaultEncrypted is false expect(getE2eeEnableToggleInputElement()).not.toBeChecked(); - expect(getE2eeEnableToggleIsDisabled()).toBeTruthy(); + expect(getE2eeEnableToggleInputElement()).toBeDisabled(); expect( screen.getByText( "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.", @@ -207,7 +204,7 @@ describe("", () => { // server forces encryption to enabled, even though defaultEncrypted is true expect(getE2eeEnableToggleInputElement()).toBeChecked(); - expect(getE2eeEnableToggleIsDisabled()).toBeTruthy(); + expect(getE2eeEnableToggleInputElement()).toBeDisabled(); expect(screen.getByText("Your server requires encryption to be enabled in private rooms.")).toBeDefined(); }); @@ -217,7 +214,7 @@ describe("", () => { await flushPromises(); expect(getE2eeEnableToggleInputElement()).toBeChecked(); - expect(getE2eeEnableToggleIsDisabled()).toBeTruthy(); + expect(getE2eeEnableToggleInputElement()).toBeDisabled(); expect(screen.getByText("Your server requires encryption to be enabled in private rooms.")).toBeDefined(); }); @@ -319,7 +316,7 @@ describe("", () => { it("should create a knock room with public visibility", async () => { fireEvent.click( - screen.getByRole("checkbox", { name: "Make this room visible in the public room directory." }), + screen.getByRole("switch", { name: "Make this room visible in the public room directory." }), ); fireEvent.click(screen.getByText("Create room")); await flushPromises(); diff --git a/test/unit-tests/components/views/dialogs/WidgetOpenIDPermissionsDialog-test.tsx b/test/unit-tests/components/views/dialogs/WidgetOpenIDPermissionsDialog-test.tsx new file mode 100644 index 0000000000..2f1ddae972 --- /dev/null +++ b/test/unit-tests/components/views/dialogs/WidgetOpenIDPermissionsDialog-test.tsx @@ -0,0 +1,37 @@ +/* +Copyright 2025 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 from "react"; +import { render } from "jest-matrix-react"; +import { WidgetKind } from "matrix-widget-api"; + +import { stubClient } from "../../../../test-utils"; +import WidgetOpenIDPermissionsDialog from "../../../../../src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx"; + +describe("WidgetOpenIDPermissionsDialog", () => { + const mockWidget = { + id: "test-widget", + name: "Test Widget", + templateUrl: "https://imawidget", + } as any; + + const onFinished = jest.fn(); + + beforeEach(() => { + stubClient(); + onFinished.mockClear(); + }); + + it("should render", () => { + const dialog = render( + , + ); + + expect(dialog.getByText("Allow this widget to verify your identity")).toBeInTheDocument(); + expect(dialog.asFragment()).toMatchSnapshot(); + }); +}); diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmRejectInviteDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmRejectInviteDialog-test.tsx.snap index f67b0aa1be..74b5bf31eb 100644 --- a/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmRejectInviteDialog-test.tsx.snap +++ b/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmRejectInviteDialog-test.tsx.snap @@ -32,67 +32,77 @@ exports[`ConfirmRejectInviteDialog can reject with options selected 1`] = ` Are you sure you want to decline the invitation to join "foo"?

-
+ +
+
+
+
+
+ You will not see any messages or room invites from this user. -
-
-
-
+ +
+
+
+
+
+ Report this room to your account provider. -
-
-
for a private room should create a private room 1` Create a private room
-
-
+
for a private room should create a private room 1` Topic (optional)
-
+
+

+ Only people invited will be able to find and join this room. You can change this at any time from room settings. +

-

- Only people invited will be able to find and join this room. You can change this at any time from room settings. -

-
+ +
+
+
+
+
-
-
-
+ + + You can't disable this later. Bridges & most bots won't work yet. +
-

- You can't disable this later. Bridges & most bots won't work yet. -

@@ -137,36 +151,49 @@ exports[` for a private room should create a private room 1` Show advanced
-
+ +
+
+
+
+
-
-
-
+ + + You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later. +
+ z

You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.

-
- + +
@@ -227,9 +254,11 @@ exports[` for a private room should render not the advanced Create a private room
-
-
+
for a private room should render not the advanced Topic (optional)
-
+
+

+ Only people invited will be able to find and join this room. You can change this at any time from room settings. +

-

- Only people invited will be able to find and join this room. You can change this at any time from room settings. -

-
+ +
+
+
+
+
-
-
-
+ + + You can't disable this later. Bridges & most bots won't work yet. +
-

- You can't disable this later. Bridges & most bots won't work yet. -

-
- + +
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap index 45b6afe664..7db3667746 100644 --- a/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap +++ b/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap @@ -116,119 +116,129 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = ` End-to-end encryption
-
+

Options

- -
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
+
- -
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+
+
Report this room to your account provider. If the messages are encrypted, your admin will not be able to read them. @@ -66,28 +66,34 @@ exports[`ReportRoomDialog displays admin message 1`] = `

-
+ +
+
+
+
+
-
-
-
+
+
+