Fix React hydration issues (#32958)
* Add more playwright axe tests to settings dialogs * Add utility to jest setupTests to detect React hydration errors * Iterate jest utility * Fix axe issue heading-order * Fix div-in-p issues * Fix setupTests.ts * Fix heading order * Make types happier * Fix hydration issues of thead containing text nodes * Update tests * Fix form-in-form React hydration issues * Fix li-in-li React hydration issues * Fix checked in form without onChange React hydration issue * Fix styling bleeding from _common.pcss * Update snapshots * Fix more remaining issues * Remove _common.pcss h2 rule altogether * Fix test * Update snapshots * Iterate * Iterate * Update snapshots * Simplify diff * Test * Update screenshots * Update screenshot
This commit is contained in:
@@ -184,6 +184,10 @@ const Tile: React.FC<ITileProps> = ({
|
||||
aria-labelledby={checkboxLabelId}
|
||||
checked={!!selected}
|
||||
tabIndex={-1}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleClick();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@@ -311,9 +315,9 @@ const Tile: React.FC<ITileProps> = ({
|
||||
};
|
||||
|
||||
childSection = (
|
||||
<div className="mx_SpaceHierarchy_subspace_children" onKeyDown={onChildrenKeyDown} role="group">
|
||||
<ul className="mx_SpaceHierarchy_subspace_children" onKeyDown={onChildrenKeyDown} role="group">
|
||||
{children}
|
||||
</div>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import React, { type JSX, type ReactNode } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import { LockSolidIcon, CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { Button, Form } from "@vector-im/compound-web";
|
||||
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
@@ -380,7 +380,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
|
||||
<>
|
||||
<LockSolidIcon className="mx_AuthBody_lockIcon" />
|
||||
<h1>{_t("auth|reset_password_title")}</h1>
|
||||
<form onSubmit={this.onSubmitForm}>
|
||||
<Form.Root onSubmit={this.onSubmitForm}>
|
||||
<fieldset disabled={this.state.phase === Phase.ResettingPassword}>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
<PassphraseField
|
||||
@@ -413,6 +413,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
|
||||
<StyledCheckbox
|
||||
onChange={() => this.setState({ logoutDevices: !this.state.logoutDevices })}
|
||||
checked={this.state.logoutDevices}
|
||||
formWrap={false}
|
||||
>
|
||||
{_t("auth|reset_password|sign_out_other_devices")}
|
||||
</StyledCheckbox>
|
||||
@@ -422,7 +423,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
|
||||
{submitButtonChild}
|
||||
</Button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form.Root>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -459,8 +459,8 @@ export class EmailIdentityAuthEntry extends React.Component<
|
||||
{
|
||||
a: (text: string) => (
|
||||
<Fragment>
|
||||
<AccessibleButton kind="link_inline" onClick={null} disabled>
|
||||
{text} <Spinner size={14} />
|
||||
<AccessibleButton element="a" kind="link_inline" onClick={null} disabled>
|
||||
{text} <Spinner as="span" size={14} />
|
||||
</AccessibleButton>
|
||||
</Fragment>
|
||||
),
|
||||
@@ -475,6 +475,7 @@ export class EmailIdentityAuthEntry extends React.Component<
|
||||
{
|
||||
a: (text: string) => (
|
||||
<AccessibleButton
|
||||
element="a"
|
||||
kind="link_inline"
|
||||
title={
|
||||
this.state.requested ? _t("auth|uia|email_resent") : _t("action|resend")
|
||||
|
||||
@@ -104,7 +104,12 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
|
||||
|
||||
return (
|
||||
<div className="mx_WidgetCapabilitiesPromptDialog_cap" key={cap + i}>
|
||||
<StyledCheckbox checked={isChecked} onChange={() => this.onToggle(cap)} description={text.byline}>
|
||||
<StyledCheckbox
|
||||
checked={isChecked}
|
||||
onChange={() => this.onToggle(cap)}
|
||||
description={text.byline}
|
||||
formWrap={false}
|
||||
>
|
||||
{text.primary}
|
||||
</StyledCheckbox>
|
||||
</div>
|
||||
|
||||
@@ -110,7 +110,11 @@ function KeyStorage(): JSX.Element {
|
||||
|
||||
return (
|
||||
<table aria-label={_t("devtools|crypto|key_storage")}>
|
||||
<thead>{_t("devtools|crypto|key_storage")}</thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2}>{_t("devtools|crypto|key_storage")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">{_t("devtools|crypto|key_backup_latest_version")}</th>
|
||||
@@ -212,7 +216,11 @@ function CrossSigning(): JSX.Element {
|
||||
|
||||
return (
|
||||
<table aria-label={_t("devtools|crypto|cross_signing")}>
|
||||
<thead>{_t("devtools|crypto|cross_signing")}</thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2}>{_t("devtools|crypto|cross_signing")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">{_t("devtools|crypto|cross_signing_status")}</th>
|
||||
@@ -303,7 +311,11 @@ function Session(): JSX.Element {
|
||||
|
||||
return (
|
||||
<table aria-label={_t("devtools|crypto|session")}>
|
||||
<thead>{_t("devtools|crypto|session")}</thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2}>{_t("devtools|crypto|session")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">{_t("devtools|crypto|device_id")}</th>
|
||||
|
||||
@@ -152,46 +152,49 @@ const AccessibleButton = function AccessibleButton<T extends ElementType = typeo
|
||||
} else {
|
||||
newProps.onClick = onClick ?? undefined;
|
||||
}
|
||||
// We need to consume enter onKeyDown and space onKeyUp
|
||||
// otherwise we are risking also activating other keyboard focusable elements
|
||||
// that might receive focus as a result of the AccessibleButtonClick action
|
||||
// It's because we are using html buttons at a few places e.g. inside dialogs
|
||||
// And divs which we report as role button to assistive technologies.
|
||||
// Browsers handle space and enter key presses differently and we are only adjusting to the
|
||||
// inconsistencies here
|
||||
newProps.onKeyDown = (e: KeyboardEvent<never>) => {
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick?.(e);
|
||||
case KeyBindingAction.Space:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
default:
|
||||
onKeyDown?.(e);
|
||||
}
|
||||
};
|
||||
newProps.onKeyUp = (e: KeyboardEvent<never>) => {
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
if (element !== "button") {
|
||||
// We need to consume enter onKeyDown and space onKeyUp
|
||||
// otherwise we are risking also activating other keyboard focusable elements
|
||||
// that might receive focus as a result of the AccessibleButtonClick action
|
||||
// It's because we are using html buttons at a few places e.g. inside dialogs
|
||||
// And divs which we report as role button to assistive technologies.
|
||||
// Browsers handle space and enter key presses differently and we are only adjusting to the
|
||||
// inconsistencies here
|
||||
newProps.onKeyDown = (e: KeyboardEvent<never>) => {
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
case KeyBindingAction.Space:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick?.(e);
|
||||
default:
|
||||
onKeyUp?.(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick?.(e);
|
||||
case KeyBindingAction.Space:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
default:
|
||||
onKeyDown?.(e);
|
||||
}
|
||||
};
|
||||
newProps.onKeyUp = (e: KeyboardEvent<never>) => {
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
case KeyBindingAction.Space:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick?.(e);
|
||||
default:
|
||||
onKeyUp?.(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Pass through the ref - used for keyboard shortcut access to some buttons
|
||||
|
||||
@@ -44,6 +44,7 @@ export const CopyTextButton: React.FC<Pick<IProps, "getTextToCopy" | "className"
|
||||
|
||||
return (
|
||||
<AccessibleButton
|
||||
element="button"
|
||||
title={tooltip ?? _t("action|copy")}
|
||||
onClick={onCopyClickInternal}
|
||||
className={className}
|
||||
@@ -62,12 +63,12 @@ const CopyableText: React.FC<IProps> = ({ children, getTextToCopy, border = true
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={combinedClassName} {...props}>
|
||||
<span className={combinedClassName} {...props}>
|
||||
{children}
|
||||
<CopyTextButton getTextToCopy={getTextToCopy} className="mx_CopyableText_copyButton">
|
||||
<CopyIcon />
|
||||
</CopyTextButton>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import Modal from "../../../Modal";
|
||||
import InfoDialog from "../dialogs/InfoDialog";
|
||||
import AccessibleButton, { type ButtonProps } from "./AccessibleButton";
|
||||
|
||||
type Props = Omit<ButtonProps<"div">, "element" | "kind" | "onClick" | "className"> & {
|
||||
type Props = Omit<ButtonProps<"button">, "element" | "kind" | "onClick" | "className"> & {
|
||||
title: string;
|
||||
description: string | React.ReactNode;
|
||||
};
|
||||
@@ -29,7 +29,13 @@ const LearnMore: React.FC<Props> = ({ title, description, ...rest }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<AccessibleButton {...rest} kind="link_inline" onClick={onClick} className="mx_LearnMore_button">
|
||||
<AccessibleButton
|
||||
{...rest}
|
||||
element="button"
|
||||
kind="link_inline"
|
||||
onClick={onClick}
|
||||
className="mx_LearnMore_button"
|
||||
>
|
||||
{_t("action|learn_more")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,11 @@ interface IProps {
|
||||
size?: number;
|
||||
message?: string;
|
||||
onFinished: any; // XXX: Spinner pretends to be a dialog so it must accept an onFinished, but it never calls it
|
||||
/**
|
||||
* Whether to render the content in a div or span.
|
||||
* @default "div"
|
||||
*/
|
||||
as?: "span" | "div";
|
||||
}
|
||||
|
||||
export default class Spinner extends React.PureComponent<IProps> {
|
||||
@@ -23,16 +28,16 @@ export default class Spinner extends React.PureComponent<IProps> {
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const { size, message } = this.props;
|
||||
const { size, message, as: Component = "div" } = this.props;
|
||||
return (
|
||||
<div className="mx_Spinner">
|
||||
<Component className="mx_Spinner">
|
||||
{message && (
|
||||
<React.Fragment>
|
||||
<div className="mx_Spinner_Msg">{message}</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<InlineSpinner size={size} aria-label={_t("common|loading")} role="progressbar" data-testid="spinner" />
|
||||
</div>
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
inputRef?: Ref<HTMLInputElement>;
|
||||
id?: string;
|
||||
description?: ReactNode;
|
||||
formWrap?: boolean;
|
||||
}
|
||||
|
||||
const StyledCheckbox: React.FC<IProps> = ({
|
||||
@@ -22,30 +23,36 @@ const StyledCheckbox: React.FC<IProps> = ({
|
||||
className,
|
||||
inputRef,
|
||||
description,
|
||||
formWrap = true,
|
||||
...otherProps
|
||||
}) => {
|
||||
const id = initialId || "checkbox_" + secureRandomString(10);
|
||||
const name = useId();
|
||||
const descriptionId = useId();
|
||||
return (
|
||||
<Form.Root>
|
||||
<InlineField
|
||||
className={className}
|
||||
name={name}
|
||||
control={
|
||||
<CheckboxInput
|
||||
ref={inputRef}
|
||||
aria-describedby={description ? descriptionId : undefined}
|
||||
id={id}
|
||||
{...otherProps}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{label && <Label htmlFor={id}>{label}</Label>}
|
||||
{description && <HelpMessage id={descriptionId}>{description}</HelpMessage>}
|
||||
</InlineField>
|
||||
</Form.Root>
|
||||
|
||||
const field = (
|
||||
<InlineField
|
||||
className={className}
|
||||
name={name}
|
||||
control={
|
||||
<CheckboxInput
|
||||
ref={inputRef}
|
||||
aria-describedby={description ? descriptionId : undefined}
|
||||
id={id}
|
||||
{...otherProps}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{label && <Label htmlFor={id}>{label}</Label>}
|
||||
{description && <HelpMessage id={descriptionId}>{description}</HelpMessage>}
|
||||
</InlineField>
|
||||
);
|
||||
|
||||
if (formWrap) {
|
||||
return <Form.Root>{field}</Form.Root>;
|
||||
}
|
||||
|
||||
return field;
|
||||
};
|
||||
|
||||
export default StyledCheckbox;
|
||||
|
||||
@@ -255,7 +255,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
|
||||
} else {
|
||||
body = (
|
||||
<p>
|
||||
<Spinner />
|
||||
<Spinner as="span" />
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { type ChangeEventHandler } from "react";
|
||||
import { JoinRule, Visibility } from "matrix-js-sdk/src/matrix";
|
||||
import { SettingsToggleInput } from "@vector-im/compound-web";
|
||||
import { Form, SettingsToggleInput } from "@vector-im/compound-web";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
@@ -16,6 +16,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import DirectoryCustomisations from "../../../customisations/Directory";
|
||||
import Modal from "../../../Modal";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import { onSubmitPreventDefault } from "../../../utils/form.ts";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
@@ -90,16 +91,18 @@ export default class RoomPublishSetting extends React.PureComponent<IProps, ISta
|
||||
const enabled = canSetCanonicalAlias && (isRoomPublishable || this.state.isRoomPublished);
|
||||
|
||||
return (
|
||||
<SettingsToggleInput
|
||||
name="room-publish"
|
||||
checked={this.state.isRoomPublished}
|
||||
onChange={this.onRoomPublishChange}
|
||||
disabled={!enabled || this.state.busy}
|
||||
disabledMessage={disabledMessage}
|
||||
label={_t("room_settings|general|publish_toggle", {
|
||||
domain: client.getDomain(),
|
||||
})}
|
||||
/>
|
||||
<Form.Root onSubmit={onSubmitPreventDefault}>
|
||||
<SettingsToggleInput
|
||||
name="room-publish"
|
||||
checked={this.state.isRoomPublished}
|
||||
onChange={this.onRoomPublishChange}
|
||||
disabled={!enabled || this.state.busy}
|
||||
disabledMessage={disabledMessage}
|
||||
label={_t("room_settings|general|publish_toggle", {
|
||||
domain: client.getDomain(),
|
||||
})}
|
||||
/>
|
||||
</Form.Root>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,12 @@ export default class EventIndexPanel extends React.Component<EmptyObject, IState
|
||||
: _t("error|unknown")}
|
||||
</code>
|
||||
<p>
|
||||
<AccessibleButton key="delete" kind="danger" onClick={this.confirmEventStoreReset}>
|
||||
<AccessibleButton
|
||||
element="button"
|
||||
key="delete"
|
||||
kind="danger"
|
||||
onClick={this.confirmEventStoreReset}
|
||||
>
|
||||
{_t("action|reset")}
|
||||
</AccessibleButton>
|
||||
</p>
|
||||
|
||||
@@ -52,6 +52,7 @@ import { SettingsSubsectionHeading } from "./shared/SettingsSubsectionHeading";
|
||||
import { SettingsSubsection } from "./shared/SettingsSubsection";
|
||||
import { doesRoomHaveUnreadMessages } from "../../../Unread";
|
||||
import SettingsFlag from "../elements/SettingsFlag";
|
||||
import { onSubmitPreventDefault } from "../../../utils/form.ts";
|
||||
|
||||
// TODO: this "view" component still has far too much application logic in it,
|
||||
// which should be factored out to other files.
|
||||
@@ -651,7 +652,7 @@ export default class Notifications extends React.PureComponent<EmptyObject, ISta
|
||||
|
||||
// If all the rules are inhibited, don't show anything.
|
||||
if (this.isInhibited) {
|
||||
return masterSwitch;
|
||||
return <Form.Root onSubmit={onSubmitPreventDefault}>{masterSwitch}</Form.Root>;
|
||||
}
|
||||
|
||||
const emailSwitches = (this.state.threepids || [])
|
||||
@@ -669,19 +670,21 @@ export default class Notifications extends React.PureComponent<EmptyObject, ISta
|
||||
|
||||
return (
|
||||
<SettingsSubsection>
|
||||
{masterSwitch}
|
||||
<Form.Root onSubmit={onSubmitPreventDefault}>
|
||||
{masterSwitch}
|
||||
|
||||
<SettingsFlag name="deviceNotificationsEnabled" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="deviceNotificationsEnabled" level={SettingLevel.DEVICE} />
|
||||
|
||||
{this.state.deviceNotificationsEnabled && (
|
||||
<>
|
||||
<SettingsFlag name="notificationsEnabled" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="notificationBodyEnabled" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="audioNotificationsEnabled" level={SettingLevel.DEVICE} />
|
||||
</>
|
||||
)}
|
||||
{this.state.deviceNotificationsEnabled && (
|
||||
<>
|
||||
<SettingsFlag name="notificationsEnabled" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="notificationBodyEnabled" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="audioNotificationsEnabled" level={SettingLevel.DEVICE} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{emailSwitches}
|
||||
{emailSwitches}
|
||||
</Form.Root>
|
||||
</SettingsSubsection>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ 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 { Form, SettingsToggleInput } from "@vector-im/compound-web";
|
||||
|
||||
import NewAndImprovedIcon from "../../../../../res/img/element-icons/new-and-improved.svg";
|
||||
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
|
||||
@@ -33,6 +33,7 @@ import { SettingsSubsection } from "../shared/SettingsSubsection";
|
||||
import { NotificationPusherSettings } from "./NotificationPusherSettings";
|
||||
import SettingsFlag from "../../elements/SettingsFlag";
|
||||
import { SettingsSubsectionHeading } from "../shared/SettingsSubsectionHeading";
|
||||
import { onSubmitPreventDefault } from "../../../../utils/form.ts";
|
||||
|
||||
enum NotificationDefaultLevels {
|
||||
AllMessages = "all_messages",
|
||||
@@ -111,7 +112,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
</SettingsBanner>
|
||||
)}
|
||||
<SettingsSection>
|
||||
<div className="mx_SettingsSubsection_content mx_NotificationSettings2_flags">
|
||||
<Form.Root className="mx_SettingsSubsection_content" onSubmit={onSubmitPreventDefault}>
|
||||
<SettingsToggleInput
|
||||
name="enable_notifications_account"
|
||||
label={_t("settings|notifications|enable_notifications_account")}
|
||||
@@ -131,7 +132,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
level={SettingLevel.DEVICE}
|
||||
/>
|
||||
<SettingsFlag name="audioNotificationsEnabled" level={SettingLevel.DEVICE} />
|
||||
</div>
|
||||
</Form.Root>
|
||||
<SettingsSubsection
|
||||
heading={
|
||||
<SettingsSubsectionHeading
|
||||
@@ -346,8 +347,10 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
placeholder={_t("notifications|keyword_new")}
|
||||
/>
|
||||
|
||||
<SettingsFlag name="Notifications.showbold" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="Notifications.tac_only_notifications" level={SettingLevel.DEVICE} />
|
||||
<Form.Root onSubmit={onSubmitPreventDefault}>
|
||||
<SettingsFlag name="Notifications.showbold" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="Notifications.tac_only_notifications" level={SettingLevel.DEVICE} />
|
||||
</Form.Root>
|
||||
</SettingsSubsection>
|
||||
<NotificationPusherSettings />
|
||||
<SettingsSubsection heading={_t("settings|notifications|quick_actions_section")}>
|
||||
|
||||
@@ -8,7 +8,6 @@ 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";
|
||||
@@ -72,32 +71,25 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
|
||||
|
||||
return (
|
||||
<SettingsTab data-testid="General">
|
||||
<Form.Root
|
||||
onSubmit={(evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<SettingsSection heading={_t("common|general")}>
|
||||
<RoomProfileSettings roomId={room.roomId} />
|
||||
</SettingsSection>
|
||||
<SettingsSection heading={_t("common|general")}>
|
||||
<RoomProfileSettings roomId={room.roomId} />
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection heading={_t("room_settings|general|aliases_section")}>
|
||||
<AliasSettings
|
||||
roomId={room.roomId}
|
||||
canSetCanonicalAlias={canSetCanonical}
|
||||
canSetAliases={canSetAliases}
|
||||
canonicalAliasEvent={canonicalAliasEv}
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection heading={_t("room_settings|general|aliases_section")}>
|
||||
<AliasSettings
|
||||
roomId={room.roomId}
|
||||
canSetCanonicalAlias={canSetCanonical}
|
||||
canSetAliases={canSetAliases}
|
||||
canonicalAliasEvent={canonicalAliasEv}
|
||||
/>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection heading={_t("room_settings|general|other_section")}>
|
||||
<SettingsSubsection heading={_t("common|moderation_and_safety")} legacy={false}>
|
||||
<MediaPreviewAccountSettings roomId={room.roomId} />
|
||||
</SettingsSubsection>
|
||||
{leaveSection}
|
||||
</SettingsSection>
|
||||
</Form.Root>
|
||||
<SettingsSection heading={_t("room_settings|general|other_section")}>
|
||||
<SettingsSubsection heading={_t("common|moderation_and_safety")} legacy={false}>
|
||||
<MediaPreviewAccountSettings roomId={room.roomId} />
|
||||
</SettingsSubsection>
|
||||
{leaveSection}
|
||||
</SettingsSection>
|
||||
</SettingsTab>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
{
|
||||
a: (sub) => (
|
||||
<AccessibleButton
|
||||
element="a"
|
||||
kind="link_inline"
|
||||
onClick={() => {
|
||||
dialog.close();
|
||||
@@ -334,6 +335,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
{_t("room_settings|security|encrypted_room_public_confirm_description_2", undefined, {
|
||||
a: (sub) => (
|
||||
<AccessibleButton
|
||||
element="a"
|
||||
kind="link_inline"
|
||||
onClick={(): void => {
|
||||
dialog.close();
|
||||
|
||||
@@ -7,7 +7,6 @@ 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";
|
||||
@@ -22,20 +21,13 @@ export default class NotificationUserSettingsTab extends React.Component {
|
||||
|
||||
return (
|
||||
<SettingsTab>
|
||||
<Form.Root
|
||||
onSubmit={(evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{newNotificationSettingsEnabled ? (
|
||||
<NotificationSettings2 />
|
||||
) : (
|
||||
<SettingsSection>
|
||||
<Notifications />
|
||||
</SettingsSection>
|
||||
)}
|
||||
</Form.Root>
|
||||
{newNotificationSettingsEnabled ? (
|
||||
<NotificationSettings2 />
|
||||
) : (
|
||||
<SettingsSection>
|
||||
<Notifications />
|
||||
</SettingsSection>
|
||||
)}
|
||||
</SettingsTab>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -184,14 +184,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
|
||||
</Form.Root>
|
||||
</SettingsFieldset>
|
||||
|
||||
<Form.Root
|
||||
onSubmit={(evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{addressesSection}
|
||||
</Form.Root>
|
||||
{addressesSection}
|
||||
</SettingsSection>
|
||||
</SettingsTab>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type React from "react";
|
||||
|
||||
/**
|
||||
* onSubmit handler which calls preventDefault and stopPropagation on the event
|
||||
* @param e submit event
|
||||
*/
|
||||
export function onSubmitPreventDefault(e: SubmitEvent | React.SubmitEvent): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
Reference in New Issue
Block a user