Merge branch 'develop' into hs/enable-profile-updates
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
@ -834,18 +834,6 @@ legend {
|
||||
}
|
||||
}
|
||||
|
||||
@define-mixin ButtonResetDefault {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
line-height: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@define-mixin LegacyCallButton {
|
||||
box-sizing: border-box;
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
|
||||
@ -13,8 +13,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.mx_ShareDialogButtons_button {
|
||||
@mixin ButtonResetDefault;
|
||||
button.mx_ShareDialogButtons_button {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
border-radius: 50%;
|
||||
|
||||
@ -9,6 +9,19 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_AccessibleButton {
|
||||
cursor: pointer;
|
||||
|
||||
&:where(button) {
|
||||
/* Clear default button styling */
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
line-height: inherit;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
&.mx_AccessibleButton_disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
|
||||
@ -28,7 +28,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
/* using em here to adapt to the local font size */
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
cursor: pointer;
|
||||
padding-left: 12px;
|
||||
padding-right: 10px;
|
||||
display: block;
|
||||
|
||||
@ -12,12 +12,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
gap: 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> form {
|
||||
gap: 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SettingsSubsection_description {
|
||||
|
||||
@ -14,12 +14,13 @@ Please see LICENSE files in the repository root for full details.
|
||||
color: $links;
|
||||
}
|
||||
|
||||
form:not(.mx_EncryptionUserSettingsTab form) {
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-8;
|
||||
gap: var(--cpd-space-3x);
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
// never want full width buttons
|
||||
// event when other content is 100% width
|
||||
.mx_AccessibleButton {
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
17
apps/web/src/utils/form.ts
Normal file
@ -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();
|
||||
}
|
||||
@ -199,8 +199,11 @@ export class RoomListHeaderViewModel
|
||||
SettingsStore.setValue("RoomList.showMessagePreview", null, SettingLevel.DEVICE, isMessagePreviewEnabled);
|
||||
this.snapshot.merge({ isMessagePreviewEnabled });
|
||||
};
|
||||
}
|
||||
|
||||
public createSection = (): void => {
|
||||
// To be implemented when custom section creation is added in vms
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get the initial snapshot for the RoomListHeaderViewModel.
|
||||
* @param spaceStore - The space store instance.
|
||||
@ -280,5 +283,8 @@ function computeHeaderSpaceState(
|
||||
displaySpaceMenu,
|
||||
canInviteInSpace,
|
||||
canAccessSpaceSettings,
|
||||
// To be implemented when custom section creation is added in vms
|
||||
canCreateSection: false,
|
||||
useComposeIcon: true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -303,6 +303,8 @@ export class RoomListItemViewModel
|
||||
canMarkAsRead,
|
||||
canMarkAsUnread,
|
||||
roomNotifState,
|
||||
// To be implemented when custom section creation is added in vms
|
||||
canMoveToSection: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -381,4 +383,8 @@ export class RoomListItemViewModel
|
||||
const echoChamber = EchoChamber.forRoom(this.props.room);
|
||||
echoChamber.notificationVolume = elementNotifState;
|
||||
};
|
||||
|
||||
public onCreateSection = (): void => {
|
||||
// To be implemented when custom section creation is added in vms
|
||||
};
|
||||
}
|
||||
|
||||
@ -572,6 +572,12 @@ export class RoomListViewModel
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public closeToast: () => void = () => {
|
||||
this.snapshot.merge({
|
||||
toast: undefined,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -66,3 +66,28 @@ if (env["GITHUB_ACTIONS"] !== undefined) {
|
||||
require("./setup/setupManualMocks"); // must be first
|
||||
require("./setup/setupLanguage");
|
||||
require("./setup/setupConfig");
|
||||
|
||||
// Utility to check for React errors during the tests
|
||||
// Fails tests on errors like the following:
|
||||
// In HTML, <div> cannot be a descendant of <p>.
|
||||
// In HTML, <form> cannot be a descendant of <form>.
|
||||
// In HTML, text nodes cannot be a child of <thead>.
|
||||
// This will cause a hydration error.
|
||||
// You provided a `checked` prop to a form field without an `onChange` handler.
|
||||
let errors: any[] = [];
|
||||
beforeEach(() => {
|
||||
errors = [];
|
||||
const originalError = console.error;
|
||||
jest.spyOn(console, "error").mockImplementation((...args) => {
|
||||
if (/validateDOMNesting|Hydration failed|hydration error|prop to a form field without an/i.test(args[0])) {
|
||||
errors.push(args[0]);
|
||||
}
|
||||
originalError.call(console, ...args);
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
mocked(console.error).mockRestore?.();
|
||||
if (errors.length > 0) {
|
||||
throw new Error("Test failed due to React hydration errors in the console.");
|
||||
}
|
||||
});
|
||||
|
||||
@ -378,114 +378,115 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
<ul
|
||||
class="mx_SpaceHierarchy_subspace_children"
|
||||
role="group"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
aria-labelledby="_r_k_"
|
||||
class="mx_SpaceHierarchy_roomTileWrapper"
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_SpaceHierarchy_roomTile_item"
|
||||
<li
|
||||
aria-labelledby="_r_k_"
|
||||
class="mx_SpaceHierarchy_roomTileWrapper"
|
||||
role="treeitem"
|
||||
>
|
||||
<div
|
||||
class="mx_SpaceHierarchy_roomTile_avatar"
|
||||
>
|
||||
<span
|
||||
class="_avatar_7h2br_8 mx_BaseAvatar _avatar-imageless_7h2br_55"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 20px;"
|
||||
>
|
||||
N
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpaceHierarchy_roomTile_name"
|
||||
>
|
||||
<span
|
||||
id="_r_k_"
|
||||
>
|
||||
Nested room
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpaceHierarchy_roomTile_info"
|
||||
>
|
||||
3 members
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpaceHierarchy_actions"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
Join
|
||||
</div>
|
||||
<span
|
||||
aria-labelledby="_r_l_"
|
||||
tabindex="0"
|
||||
>
|
||||
<form
|
||||
class="_root_19upo_16"
|
||||
<div
|
||||
class="mx_SpaceHierarchy_roomTile_item"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_19upo_32"
|
||||
class="mx_SpaceHierarchy_roomTile_avatar"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_19upo_44"
|
||||
<span
|
||||
class="_avatar_7h2br_8 mx_BaseAvatar _avatar-imageless_7h2br_55"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 20px;"
|
||||
>
|
||||
N
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpaceHierarchy_roomTile_name"
|
||||
>
|
||||
<span
|
||||
id="_r_k_"
|
||||
>
|
||||
Nested room
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpaceHierarchy_roomTile_info"
|
||||
>
|
||||
3 members
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpaceHierarchy_actions"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
Join
|
||||
</div>
|
||||
<span
|
||||
aria-labelledby="_r_l_"
|
||||
tabindex="0"
|
||||
>
|
||||
<form
|
||||
class="_root_19upo_16"
|
||||
>
|
||||
<div
|
||||
class="_container_153f2_10"
|
||||
class="_inline-field_19upo_32"
|
||||
>
|
||||
<input
|
||||
aria-labelledby="_r_k_"
|
||||
class="_input_153f2_18"
|
||||
disabled=""
|
||||
id="checkbox_RD7nyrA2oh"
|
||||
role="presentation"
|
||||
tabindex="-1"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
class="_ui_153f2_19"
|
||||
class="_inline-field-control_19upo_44"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<div
|
||||
class="_container_153f2_10"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
<input
|
||||
aria-labelledby="_r_k_"
|
||||
class="_input_153f2_18"
|
||||
disabled=""
|
||||
id="checkbox_RD7nyrA2oh"
|
||||
role="presentation"
|
||||
tabindex="-1"
|
||||
type="checkbox"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="_ui_153f2_19"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_19upo_38"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_19upo_38"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li
|
||||
aria-labelledby="_r_t_"
|
||||
@ -967,7 +968,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> should not render cycles 1`] = `
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
<ul
|
||||
class="mx_SpaceHierarchy_subspace_children"
|
||||
role="group"
|
||||
/>
|
||||
|
||||
@ -19,14 +19,14 @@ exports[`<EmailIdentityAuthEntry/> should render 1`] = `
|
||||
>
|
||||
<span>
|
||||
Did not receive it?
|
||||
<div
|
||||
<a
|
||||
aria-label="Resend"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Resend it
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -54,10 +54,10 @@ exports[`<BeaconListItem /> when a beacon is live and has locations renders beac
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText mx_ShareLatestLocation_copy"
|
||||
>
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -77,8 +77,8 @@ exports[`<BeaconListItem /> when a beacon is live and has locations renders beac
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
|
||||
@ -94,10 +94,10 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText mx_ShareLatestLocation_copy"
|
||||
>
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -117,8 +117,8 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
|
||||
@ -25,10 +25,10 @@ exports[`<ShareLatestLocation /> renders share buttons when there is a location
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText mx_ShareLatestLocation_copy"
|
||||
>
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -48,7 +48,7 @@ exports[`<ShareLatestLocation /> renders share buttons when there is a location
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
@ -29,11 +29,11 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
|
||||
>
|
||||
Toolbox
|
||||
</div>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText mx_DevTools_label_right"
|
||||
>
|
||||
Room ID: !id
|
||||
<div
|
||||
<button
|
||||
aria-describedby="_r_2_"
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
@ -54,8 +54,8 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
<div
|
||||
class="mx_DevTools_label_bottom"
|
||||
/>
|
||||
|
||||
@ -5,7 +5,13 @@ exports[`<Crypto /> <CrossSigning /> should display when the cross-signing data
|
||||
aria-label="Cross-signing"
|
||||
>
|
||||
<thead>
|
||||
Cross-signing
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
>
|
||||
Cross-signing
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
@ -77,7 +83,13 @@ exports[`<Crypto /> <CrossSigning /> should display when the cross-signing data
|
||||
aria-label="Cross-signing"
|
||||
>
|
||||
<thead>
|
||||
Cross-signing
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
>
|
||||
Cross-signing
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
@ -149,7 +161,13 @@ exports[`<Crypto /> <KeyStorage /> should display when the key storage data are
|
||||
aria-label="Key Storage"
|
||||
>
|
||||
<thead>
|
||||
Key Storage
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
>
|
||||
Key Storage
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
@ -221,7 +239,13 @@ exports[`<Crypto /> <KeyStorage /> should display when the key storage data are
|
||||
aria-label="Key Storage"
|
||||
>
|
||||
<thead>
|
||||
Key Storage
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
>
|
||||
Key Storage
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
|
||||
@ -7,11 +7,11 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
User ID: @alice:example.com
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -31,15 +31,15 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
Device ID: SIGNED
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -59,8 +59,8 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
Displayname:
|
||||
@ -95,11 +95,11 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
|
||||
Device keys
|
||||
<ul>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
ed25519: an_ed25519_public_key
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -119,15 +119,15 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
curve25519: a_curve25519_public_key
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -147,8 +147,8 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@ -171,11 +171,11 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
User ID: @alice:example.com
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -195,15 +195,15 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
Device ID: UNSIGNED
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -223,8 +223,8 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
Displayname:
|
||||
@ -259,11 +259,11 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
|
||||
Device keys
|
||||
<ul>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
ed25519: an_ed25519_public_key
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -283,15 +283,15 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
curve25519: a_curve25519_public_key
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -311,8 +311,8 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@ -335,11 +335,11 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
User ID: @alice:example.com
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -359,15 +359,15 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
Device ID: VERIFIED
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -387,8 +387,8 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
Displayname:
|
||||
@ -425,11 +425,11 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
|
||||
Device keys
|
||||
<ul>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
ed25519: an_ed25519_public_key
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -449,15 +449,15 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
curve25519: a_curve25519_public_key
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -477,8 +477,8 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@ -501,11 +501,11 @@ exports[`<Users /> should render a single user 1`] = `
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
User ID: @alice:example.com
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -525,8 +525,8 @@ exports[`<Users /> should render a single user 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
Membership: join
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
|
||||
exports[`<LearnMore /> renders button 1`] = `
|
||||
<div>
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
data-testid="testid"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -59,7 +59,7 @@ exports[`<TextualBody /> renders formatted m.text correctly linkification is not
|
||||
</code>
|
||||
</div>
|
||||
</pre>
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_EventTile_button mx_EventTile_copyButton"
|
||||
role="button"
|
||||
@ -79,7 +79,7 @@ exports[`<TextualBody /> renders formatted m.text correctly linkification is not
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -278,7 +278,7 @@ exports[`<TextualBody /> renders formatted m.text correctly pills do not appear
|
||||
</code>
|
||||
</div>
|
||||
</pre>
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_EventTile_button mx_EventTile_copyButton"
|
||||
role="button"
|
||||
@ -298,7 +298,7 @@ exports[`<TextualBody /> renders formatted m.text correctly pills do not appear
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -482,7 +482,7 @@ num_sqrt = num **
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_EventTile_button mx_EventTile_copyButton mx_EventTile_buttonBottom"
|
||||
role="button"
|
||||
@ -502,7 +502,7 @@ num_sqrt = num **
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@ -99,11 +99,11 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
|
||||
>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
customUserIdentifier
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -123,8 +123,8 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
@ -401,11 +401,11 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
|
||||
>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
customUserIdentifier
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -425,8 +425,8 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@ -48,11 +48,11 @@ exports[`<UserInfoHeaderView /> renders custom user identifiers in the header 1`
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
|
||||
>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
customUserIdentifier
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -72,8 +72,8 @@ exports[`<UserInfoHeaderView /> renders custom user identifiers in the header 1`
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@ -37,7 +37,6 @@ import {
|
||||
import { mocked } from "jest-mock";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
import { Form } from "@vector-im/compound-web";
|
||||
|
||||
import Notifications from "../../../../../src/components/views/settings/Notifications";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
@ -249,12 +248,7 @@ const pushRules: IPushRules = {
|
||||
const flushPromises = async () => await new Promise((resolve) => window.setTimeout(resolve));
|
||||
|
||||
describe("<Notifications />", () => {
|
||||
const getComponent = () =>
|
||||
render(
|
||||
<Form.Root>
|
||||
<Notifications />
|
||||
</Form.Root>,
|
||||
);
|
||||
const getComponent = () => render(<Notifications />);
|
||||
|
||||
// get component, wait for async data and force a render
|
||||
const getComponentAndWait = async () => {
|
||||
|
||||
@ -42,74 +42,74 @@ exports[`<Notifications /> main notification switches renders only enable notifi
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
class="_root_19upo_16"
|
||||
</form>
|
||||
<form
|
||||
class="_root_19upo_16"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_19upo_32"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_19upo_32"
|
||||
class="_inline-field-control_19upo_44"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_19upo_44"
|
||||
class="_container_udcm8_10"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="_input_udcm8_24"
|
||||
id="mx_SettingsFlag_testid_0"
|
||||
role="switch"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
class="_container_udcm8_10"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="_input_udcm8_24"
|
||||
id="mx_SettingsFlag_testid_0"
|
||||
role="switch"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
class="_ui_udcm8_34"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_19upo_38"
|
||||
>
|
||||
<label
|
||||
class="_label_19upo_59"
|
||||
for="mx_SettingsFlag_testid_0"
|
||||
>
|
||||
Show all activity in the room list (dots or number of unread messages)
|
||||
</label>
|
||||
class="_ui_udcm8_34"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_19upo_32"
|
||||
class="_inline-field-body_19upo_38"
|
||||
>
|
||||
<label
|
||||
class="_label_19upo_59"
|
||||
for="mx_SettingsFlag_testid_0"
|
||||
>
|
||||
Show all activity in the room list (dots or number of unread messages)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_19upo_32"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_19upo_44"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_19upo_44"
|
||||
class="_container_udcm8_10"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="_input_udcm8_24"
|
||||
id="mx_SettingsFlag_testid_1"
|
||||
role="switch"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
class="_container_udcm8_10"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="_input_udcm8_24"
|
||||
id="mx_SettingsFlag_testid_1"
|
||||
role="switch"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
class="_ui_udcm8_34"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_19upo_38"
|
||||
>
|
||||
<label
|
||||
class="_label_19upo_59"
|
||||
for="mx_SettingsFlag_testid_1"
|
||||
>
|
||||
Only show notifications in the thread activity centre
|
||||
</label>
|
||||
class="_ui_udcm8_34"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
class="_inline-field-body_19upo_38"
|
||||
>
|
||||
<label
|
||||
class="_label_19upo_59"
|
||||
for="mx_SettingsFlag_testid_1"
|
||||
>
|
||||
Only show notifications in the thread activity centre
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -57,13 +57,13 @@ HTMLCollection [
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify your current session for enhanced secure messaging.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
@ -334,13 +334,13 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify your current session for enhanced secure messaging.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
@ -521,13 +521,13 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify your current session for enhanced secure messaging.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
|
||||
@ -37,13 +37,13 @@ exports[`<DeviceDetailHeading /> displays name edit form on rename button click
|
||||
id="device-rename-description-123"
|
||||
>
|
||||
Please be aware that session names are also visible to people you communicate with.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@ -59,13 +59,13 @@ exports[`<DeviceDetails /> renders a verified device 1`] = `
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
This session is ready for secure messaging.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -175,13 +175,13 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify or remove this session for best security and reliability.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -391,13 +391,13 @@ exports[`<DeviceDetails /> renders device without metadata 1`] = `
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify or remove this session for best security and reliability.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -34,13 +34,13 @@ exports[`<DeviceVerificationStatusCard /> renders a verified device 1`] = `
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
This session is ready for secure messaging.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -79,13 +79,13 @@ exports[`<DeviceVerificationStatusCard /> renders an unverifiable device 1`] = `
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
This session doesn't support encryption and thus can't be verified.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -124,13 +124,13 @@ exports[`<DeviceVerificationStatusCard /> renders an unverified device 1`] = `
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify or remove this session for best security and reliability.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
|
||||
@ -39,13 +39,13 @@ HTMLCollection [
|
||||
>
|
||||
<span>
|
||||
Consider removing old sessions (90 days or older) you don't use anymore.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
@ -90,13 +90,13 @@ HTMLCollection [
|
||||
>
|
||||
<span>
|
||||
Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
@ -143,13 +143,13 @@ HTMLCollection [
|
||||
>
|
||||
<span>
|
||||
For best security, remove any session that you don't recognize or use anymore.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -57,13 +57,13 @@ exports[`<SecurityRecommendations /> renders both cards when user has both unver
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
@ -105,13 +105,13 @@ exports[`<SecurityRecommendations /> renders both cards when user has both unver
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Consider removing old sessions (90 days or older) you don't use anymore.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
@ -189,13 +189,13 @@ exports[`<SecurityRecommendations /> renders inactive devices section when user
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
@ -237,13 +237,13 @@ exports[`<SecurityRecommendations /> renders inactive devices section when user
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Consider removing old sessions (90 days or older) you don't use anymore.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
@ -321,13 +321,13 @@ exports[`<SecurityRecommendations /> renders unverified devices section when use
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
@ -369,13 +369,13 @@ exports[`<SecurityRecommendations /> renders unverified devices section when use
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Consider removing old sessions (90 days or older) you don't use anymore.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
|
||||
@ -18,7 +18,6 @@ import {
|
||||
RuleId,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import React from "react";
|
||||
import { Form } from "@vector-im/compound-web";
|
||||
|
||||
import NotificationSettings2 from "../../../../../../src/components/views/settings/notifications/NotificationSettings2";
|
||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||
@ -94,9 +93,7 @@ describe("<Notifications />", () => {
|
||||
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -109,9 +106,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(async () => {
|
||||
@ -172,9 +167,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -190,9 +183,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -230,9 +221,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -267,9 +256,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -298,9 +285,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -325,9 +310,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -349,9 +332,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -379,9 +360,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -405,9 +384,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -429,9 +406,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -459,9 +434,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -485,9 +458,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -514,9 +485,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -535,9 +504,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -648,9 +615,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -709,9 +674,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const screen = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await act(waitForUpdate);
|
||||
@ -731,9 +694,7 @@ describe("<Notifications />", () => {
|
||||
|
||||
const { container } = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await waitForUpdate();
|
||||
@ -760,9 +721,7 @@ describe("<Notifications />", () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</Form.Root>
|
||||
<NotificationSettings2 />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await waitForUpdate();
|
||||
|
||||
@ -38,11 +38,11 @@ exports[`AdvancedRoomSettingsTab should render as expected 1`] = `
|
||||
<span>
|
||||
Internal room ID
|
||||
</span>
|
||||
<div
|
||||
<span
|
||||
class="mx_CopyableText mx_CopyableText_border"
|
||||
>
|
||||
!room:example.com
|
||||
<div
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
@ -62,8 +62,8 @@ exports[`AdvancedRoomSettingsTab should render as expected 1`] = `
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -186,7 +186,7 @@ exports[`<SecurityRoomSettingsTab /> join rule warns when trying to make an encr
|
||||
|
||||
<span>
|
||||
To avoid these issues, create a
|
||||
<div
|
||||
<a
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@ -194,7 +194,7 @@ exports[`<SecurityRoomSettingsTab /> join rule warns when trying to make an encr
|
||||
|
||||
new public room
|
||||
|
||||
</div>
|
||||
</a>
|
||||
for the conversation you plan to have.
|
||||
</span>
|
||||
|
||||
|
||||
@ -32,13 +32,13 @@ HTMLCollection [
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
This session doesn't support encryption and thus can't be verified.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>,
|
||||
@ -238,13 +238,13 @@ exports[`<SessionManagerTab /> current session section renders current session s
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Your current session is ready for secure messaging.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -411,13 +411,13 @@ exports[`<SessionManagerTab /> current session section renders current session s
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
Verify your current session for enhanced secure messaging.
|
||||
<div
|
||||
<button
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</button>
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_actions"
|
||||
|
||||
@ -167,75 +167,75 @@ exports[`<SpaceSettingsVisibilityTab /> for a public space renders addresses sec
|
||||
</form>
|
||||
</div>
|
||||
</fieldset>
|
||||
<form
|
||||
class="_root_19upo_16"
|
||||
<div
|
||||
class="mx_SettingsSection"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSection"
|
||||
<h3
|
||||
class="mx_Heading_h4"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h4"
|
||||
Address
|
||||
</h3>
|
||||
<div
|
||||
class="mx_SettingsSection_subSections"
|
||||
>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
data-testid="published-address-fieldset"
|
||||
>
|
||||
Address
|
||||
</h3>
|
||||
<div
|
||||
class="mx_SettingsSection_subSections"
|
||||
>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
data-testid="published-address-fieldset"
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
Published Addresses
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_description"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
Published Addresses
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_description"
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
Published addresses can be used by anyone on any server to join your space. To publish an address, it needs to be set as a local address first.
|
||||
</div>
|
||||
Published addresses can be used by anyone on any server to join your space. To publish an address, it needs to be set as a local address first.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
class="mx_Field mx_Field_select"
|
||||
>
|
||||
<div
|
||||
class="mx_Field mx_Field_select"
|
||||
<select
|
||||
disabled=""
|
||||
id="canonicalAlias"
|
||||
label="Main address"
|
||||
placeholder="Main address"
|
||||
type="text"
|
||||
>
|
||||
<select
|
||||
disabled=""
|
||||
id="canonicalAlias"
|
||||
label="Main address"
|
||||
placeholder="Main address"
|
||||
type="text"
|
||||
<option
|
||||
value=""
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
>
|
||||
not specified
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
for="canonicalAlias"
|
||||
>
|
||||
Main address
|
||||
</label>
|
||||
<svg
|
||||
class="mx_Field_select_chevron"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
not specified
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
for="canonicalAlias"
|
||||
>
|
||||
Main address
|
||||
</label>
|
||||
<svg
|
||||
class="mx_Field_select_chevron"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<form
|
||||
class="_root_19upo_16"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_19upo_32"
|
||||
>
|
||||
@ -274,59 +274,65 @@ exports[`<SpaceSettingsVisibilityTab /> for a public space renders addresses sec
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<datalist
|
||||
id="mx_AliasSettings_altRecommendations"
|
||||
/>
|
||||
</form>
|
||||
<datalist
|
||||
id="mx_AliasSettings_altRecommendations"
|
||||
/>
|
||||
<div
|
||||
class="mx_EditableItemList"
|
||||
id="roomAltAliases"
|
||||
>
|
||||
<div
|
||||
class="mx_EditableItemList_label"
|
||||
>
|
||||
No other published addresses yet, add one below
|
||||
</div>
|
||||
<ul />
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
data-testid="local-address-fieldset"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
Local Addresses
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_description"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
Set addresses for this space so users can find this space through your homeserver (matrix.org)
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<details>
|
||||
<summary
|
||||
class="mx_AliasSettings_localAddresses"
|
||||
>
|
||||
Show more
|
||||
</summary>
|
||||
<div
|
||||
class="mx_EditableItemList"
|
||||
id="roomAltAliases"
|
||||
id="roomAliases"
|
||||
>
|
||||
<div
|
||||
class="mx_EditableItemList_label"
|
||||
>
|
||||
No other published addresses yet, add one below
|
||||
This space has no local addresses
|
||||
</div>
|
||||
<ul />
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
data-testid="local-address-fieldset"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
Local Addresses
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_description"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
Set addresses for this space so users can find this space through your homeserver (matrix.org)
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<details>
|
||||
<summary
|
||||
class="mx_AliasSettings_localAddresses"
|
||||
<form
|
||||
autocomplete="off"
|
||||
class="mx_EditableItemList_newItem"
|
||||
novalidate=""
|
||||
>
|
||||
Show more
|
||||
</summary>
|
||||
<div
|
||||
class="mx_EditableItemList"
|
||||
id="roomAliases"
|
||||
>
|
||||
<div
|
||||
class="mx_EditableItemList_label"
|
||||
>
|
||||
This space has no local addresses
|
||||
</div>
|
||||
<div
|
||||
class="mx_Field mx_Field_input mx_RoomAliasField mx_Field_labelAlwaysTopLeft"
|
||||
>
|
||||
@ -368,13 +374,13 @@ exports[`<SpaceSettingsVisibilityTab /> for a public space renders addresses sec
|
||||
>
|
||||
Add
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -528,9 +534,6 @@ exports[`<SpaceSettingsVisibilityTab /> renders container 1`] = `
|
||||
</form>
|
||||
</div>
|
||||
</fieldset>
|
||||
<form
|
||||
class="_root_19upo_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
18
apps/web/test/unit-tests/utils/form-test.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
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 { onSubmitPreventDefault } from "../../../src/utils/form.ts";
|
||||
|
||||
describe("onSubmitPreventDefault", () => {
|
||||
it("should preventDefault", () => {
|
||||
const event = new SubmitEvent("submit");
|
||||
const spy = jest.spyOn(event, "preventDefault");
|
||||
|
||||
onSubmitPreventDefault(event);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 61 KiB |
@ -5,6 +5,7 @@
|
||||
"action": {
|
||||
"back": "Back",
|
||||
"click": "Click",
|
||||
"close": "Close",
|
||||
"collapse": "Collapse",
|
||||
"delete": "Delete",
|
||||
"dismiss": "Dismiss",
|
||||
@ -16,6 +17,7 @@
|
||||
"invite": "Invite",
|
||||
"new_conversation": "New conversation",
|
||||
"new_room": "New room",
|
||||
"new_section": "New section",
|
||||
"new_video_room": "New video room",
|
||||
"open_menu": "Open menu",
|
||||
"pause": "Pause",
|
||||
@ -132,7 +134,8 @@
|
||||
"leave_room": "Leave room",
|
||||
"low_priority": "Low priority",
|
||||
"mark_read": "Mark as read",
|
||||
"mark_unread": "Mark as unread"
|
||||
"mark_unread": "Mark as unread",
|
||||
"move_to_section": "Move to"
|
||||
},
|
||||
"notification_options": "Notification options",
|
||||
"open_space_menu": "Open space menu",
|
||||
@ -141,6 +144,7 @@
|
||||
"more_options": "More Options"
|
||||
},
|
||||
"room_options": "Room Options",
|
||||
"section_created": "Section created",
|
||||
"section_header": {
|
||||
"toggle": "Toggle %(section)s section",
|
||||
"toggle_unread": "Toggle %(section)s section with unread room(s)"
|
||||
|
||||
@ -30,6 +30,7 @@ const RoomListHeaderViewWrapperImpl = ({
|
||||
openSpacePreferences,
|
||||
sort,
|
||||
toggleMessagePreview,
|
||||
createSection,
|
||||
...rest
|
||||
}: RoomListHeaderProps): JSX.Element => {
|
||||
const vm = useMockedViewModel(rest, {
|
||||
@ -42,6 +43,7 @@ const RoomListHeaderViewWrapperImpl = ({
|
||||
sort,
|
||||
openSpacePreferences,
|
||||
toggleMessagePreview,
|
||||
createSection,
|
||||
});
|
||||
return <RoomListHeaderView vm={vm} />;
|
||||
};
|
||||
@ -62,6 +64,7 @@ const meta = {
|
||||
sort: fn(),
|
||||
openSpacePreferences: fn(),
|
||||
toggleMessagePreview: fn(),
|
||||
createSection: fn(),
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
@ -100,3 +103,9 @@ export const LongTitle: Story = {
|
||||
title: "Loooooooooooooooooooooooooooooooooooooong title",
|
||||
},
|
||||
};
|
||||
|
||||
export const PlusIcon: Story = {
|
||||
args: {
|
||||
useComposeIcon: false,
|
||||
},
|
||||
};
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
import React, { type JSX } from "react";
|
||||
import { IconButton, H1 } from "@vector-im/compound-web";
|
||||
import ComposeIcon from "@vector-im/compound-design-tokens/assets/web/icons/compose";
|
||||
import PlusIcon from "@vector-im/compound-design-tokens/assets/web/icons/plus";
|
||||
|
||||
import { type ViewModel, useViewModel } from "../../core/viewmodel";
|
||||
import { Flex } from "../../core/utils/Flex";
|
||||
@ -59,6 +60,14 @@ export interface RoomListHeaderViewSnapshot {
|
||||
* Whether message previews are enabled in the room list.
|
||||
*/
|
||||
isMessagePreviewEnabled: boolean;
|
||||
/**
|
||||
* Whether the user can create sections in the room list.
|
||||
*/
|
||||
canCreateSection: boolean;
|
||||
/**
|
||||
* Whether to use the compose icon instead of the create icon.
|
||||
*/
|
||||
useComposeIcon: boolean;
|
||||
}
|
||||
|
||||
export interface RoomListHeaderViewActions {
|
||||
@ -98,6 +107,10 @@ export interface RoomListHeaderViewActions {
|
||||
* Toggle message preview display in the room list.
|
||||
*/
|
||||
toggleMessagePreview: () => void;
|
||||
/**
|
||||
* Create a new section in the room list.
|
||||
*/
|
||||
createSection: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,7 +136,7 @@ interface RoomListHeaderViewProps {
|
||||
*/
|
||||
export function RoomListHeaderView({ vm }: Readonly<RoomListHeaderViewProps>): JSX.Element {
|
||||
const { translate: _t } = useI18n();
|
||||
const { title, displaySpaceMenu, displayComposeMenu } = useViewModel(vm);
|
||||
const { title, displaySpaceMenu, displayComposeMenu, useComposeIcon } = useViewModel(vm);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@ -153,7 +166,11 @@ export function RoomListHeaderView({ vm }: Readonly<RoomListHeaderViewProps>): J
|
||||
onClick={(e) => vm.createChatRoom(e.nativeEvent)}
|
||||
tooltip={_t("action|new_conversation")}
|
||||
>
|
||||
<ComposeIcon color="var(--cpd-color-icon-secondary)" aria-hidden />
|
||||
{useComposeIcon ? (
|
||||
<ComposeIcon color="var(--cpd-color-icon-secondary)" aria-hidden />
|
||||
) : (
|
||||
<PlusIcon color="var(--cpd-color-icon-secondary)" aria-hidden />
|
||||
)}
|
||||
</IconButton>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@ -110,6 +110,7 @@ exports[`RoomListHeaderView > renders the default state 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
color="var(--cpd-color-icon-secondary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@ -340,6 +341,7 @@ exports[`RoomListHeaderView > renders without space menu 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
color="var(--cpd-color-icon-secondary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
@ -17,4 +17,6 @@ export const defaultSnapshot: RoomListHeaderViewSnapshot = {
|
||||
canAccessSpaceSettings: true,
|
||||
activeSortOption: "recent",
|
||||
isMessagePreviewEnabled: true,
|
||||
useComposeIcon: true,
|
||||
canCreateSection: true,
|
||||
};
|
||||
|
||||
@ -101,4 +101,15 @@ describe("<ComposeMenuView />", () => {
|
||||
|
||||
expect(vm.createVideoRoom).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should create a new section", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const vm = new MockedViewModel({ ...defaultSnapshot, isMessagePreviewEnabled: true });
|
||||
render(<ComposeMenuView vm={vm} />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "New conversation" }));
|
||||
await user.click(screen.getByRole("menuitem", { name: "New section" }));
|
||||
expect(vm.createSection).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -11,6 +11,8 @@ import ComposeIcon from "@vector-im/compound-design-tokens/assets/web/icons/comp
|
||||
import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call";
|
||||
import ChatIcon from "@vector-im/compound-design-tokens/assets/web/icons/chat";
|
||||
import RoomIcon from "@vector-im/compound-design-tokens/assets/web/icons/room";
|
||||
import SectionIcon from "@vector-im/compound-design-tokens/assets/web/icons/section";
|
||||
import PlusIcon from "@vector-im/compound-design-tokens/assets/web/icons/plus";
|
||||
|
||||
import { type RoomListHeaderViewModel } from "../RoomListHeaderView";
|
||||
import { useI18n } from "../../../core/i18n/i18nContext";
|
||||
@ -35,7 +37,7 @@ interface ComposeMenuViewProps {
|
||||
export function ComposeMenuView({ vm }: ComposeMenuViewProps): JSX.Element {
|
||||
const { translate: _t } = useI18n();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { canCreateRoom, canCreateVideoRoom } = useViewModel(vm);
|
||||
const { canCreateRoom, canCreateVideoRoom, canCreateSection, useComposeIcon } = useViewModel(vm);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
@ -47,7 +49,11 @@ export function ComposeMenuView({ vm }: ComposeMenuViewProps): JSX.Element {
|
||||
trigger={
|
||||
// 28px button with a 20px icon
|
||||
<IconButton size="28px" style={{ padding: "4px" }} tooltip={_t("action|new_conversation")}>
|
||||
<ComposeIcon aria-hidden />
|
||||
{useComposeIcon ? (
|
||||
<ComposeIcon color="var(--cpd-color-icon-secondary)" aria-hidden />
|
||||
) : (
|
||||
<PlusIcon color="var(--cpd-color-icon-secondary)" aria-hidden />
|
||||
)}
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
@ -63,6 +69,9 @@ export function ComposeMenuView({ vm }: ComposeMenuViewProps): JSX.Element {
|
||||
hideChevron
|
||||
/>
|
||||
)}
|
||||
{canCreateSection && (
|
||||
<MenuItem Icon={SectionIcon} label={_t("action|new_section")} onSelect={vm.createSection} hideChevron />
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ exports[`<ComposeMenuView /> > should match snapshot 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
color="var(--cpd-color-icon-secondary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
@ -23,6 +23,7 @@ export class MockedViewModel extends MockViewModel<RoomListHeaderViewSnapshot> i
|
||||
public sort = vi.fn<() => void>();
|
||||
public openSpacePreferences = vi.fn<() => void>();
|
||||
public toggleMessagePreview = vi.fn<() => void>();
|
||||
public createSection = vi.fn<() => void>();
|
||||
}
|
||||
|
||||
export { defaultSnapshot } from "./default-snapshot";
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.toast {
|
||||
position: absolute;
|
||||
bottom: var(--cpd-space-4x);
|
||||
left: var(--cpd-space-3x);
|
||||
right: var(--cpd-space-3x);
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 React from "react";
|
||||
import { fn } from "storybook/test";
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { RoomListToast } from "./RoomListToast";
|
||||
|
||||
const meta = {
|
||||
title: "Room List/RoomListView/RoomListToast",
|
||||
component: RoomListToast,
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
type: "section_created",
|
||||
onClose: fn(),
|
||||
},
|
||||
argTypes: {
|
||||
type: {
|
||||
control: "select",
|
||||
options: ["section_created"],
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ position: "relative", width: "320px", height: "100px", backgroundColor: "grey" }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof RoomListToast>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const SectionCreated: Story = {};
|
||||
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 React from "react";
|
||||
import { render, screen } from "@test-utils";
|
||||
import { composeStories } from "@storybook/react-vite";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import * as stories from "./RoomListToast.stories";
|
||||
|
||||
const { SectionCreated } = composeStories(stories);
|
||||
|
||||
describe("<RoomListToast />", () => {
|
||||
it("renders SectionCreated story", () => {
|
||||
const { container } = render(<SectionCreated />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calls onClose when the close button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<SectionCreated />);
|
||||
const closeButton = screen.getByRole("button", { name: "Close" });
|
||||
await user.click(closeButton);
|
||||
expect(SectionCreated.args.onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 React, { type ComponentType, type JSX, type MouseEventHandler } from "react";
|
||||
import { Toast } from "@vector-im/compound-web";
|
||||
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
|
||||
|
||||
import styles from "./RoomListToast.module.css";
|
||||
import { useI18n } from "../../../core/i18n/i18nContext";
|
||||
|
||||
export type ToastType = "section_created";
|
||||
|
||||
interface RoomListToastProps {
|
||||
/** The type of toast to display */
|
||||
type: ToastType;
|
||||
/** Callback when the close button is clicked */
|
||||
onClose: MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A toast component used for displaying temporary messages in the room list view.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <RoomListToast type="section_created" onClose={onCloseHandler} />
|
||||
* ```
|
||||
*/
|
||||
export function RoomListToast({ type, onClose }: Readonly<RoomListToastProps>): JSX.Element {
|
||||
const { translate: _t } = useI18n();
|
||||
|
||||
let content: { text: string; icon: ComponentType<React.SVGAttributes<SVGElement>> };
|
||||
switch (type) {
|
||||
case "section_created":
|
||||
content = { text: _t("room_list|section_created"), icon: CheckIcon };
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<Toast className={styles.toast} Icon={content.icon} onClose={onClose} tooltip={_t("action|close")}>
|
||||
{content.text}
|
||||
</Toast>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<RoomListToast /> > renders SectionCreated story 1`] = `
|
||||
<div>
|
||||
<div
|
||||
style="position: relative; width: 320px; height: 100px; background-color: grey;"
|
||||
>
|
||||
<div
|
||||
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _toast-container_1ysb3_8 RoomListToast-module_toast _has-close_1ysb3_30"
|
||||
>
|
||||
<div
|
||||
class="_content_1ysb3_34"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_1ysb3_26"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
Section created
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby="_r_0_"
|
||||
class="_icon-button_1215g_8 _close_1ysb3_41 _no-background_1215g_42"
|
||||
data-kind="secondary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 24px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export type { ToastType } from "./RoomListToast";
|
||||
export { RoomListToast } from "./RoomListToast";
|
||||
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.list {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
@ -39,6 +39,7 @@ const RoomListViewWrapperImpl = ({
|
||||
getSectionHeaderViewModel,
|
||||
updateVisibleRooms,
|
||||
renderAvatar: renderAvatarProp,
|
||||
closeToast,
|
||||
...rest
|
||||
}: RoomListViewProps): JSX.Element => {
|
||||
const vm = useMockedViewModel(rest, {
|
||||
@ -48,6 +49,7 @@ const RoomListViewWrapperImpl = ({
|
||||
getRoomItemViewModel,
|
||||
getSectionHeaderViewModel,
|
||||
updateVisibleRooms,
|
||||
closeToast,
|
||||
});
|
||||
return <RoomListView vm={vm} renderAvatar={renderAvatarProp} />;
|
||||
};
|
||||
@ -98,6 +100,8 @@ const meta = {
|
||||
updateVisibleRooms: fn(),
|
||||
renderAvatar,
|
||||
isFlatList: true,
|
||||
toast: undefined,
|
||||
closeToast: fn(),
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
@ -245,3 +249,9 @@ export const LargeSectionList: Story = {
|
||||
getSectionHeaderViewModel: createGetSectionHeaderViewModel(mockLargeListSections.map((section) => section.id)),
|
||||
},
|
||||
};
|
||||
|
||||
export const Toast: Story = {
|
||||
args: {
|
||||
toast: "section_created",
|
||||
},
|
||||
};
|
||||
|
||||
@ -31,6 +31,7 @@ const {
|
||||
EmptyInvitesFilter,
|
||||
EmptyMentionsFilter,
|
||||
EmptyLowPriorityFilter,
|
||||
Toast,
|
||||
} = composeStories(stories);
|
||||
|
||||
const renderWithMockContext = (component: React.ReactElement): ReturnType<typeof render> => {
|
||||
@ -124,6 +125,11 @@ describe("<RoomListView />", () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders Toast story", () => {
|
||||
const { container } = renderWithMockContext(<Toast />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call onToggleFilter when filter is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithMockContext(<Default />);
|
||||
@ -186,4 +192,13 @@ describe("<RoomListView />", () => {
|
||||
|
||||
expect(EmptyLowPriorityFilter.args.onToggleFilter).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call closeToast when close button is clicked on toast", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithMockContext(<Toast />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Close" }));
|
||||
|
||||
expect(EmptyLowPriorityFilter.args.closeToast).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -17,6 +17,9 @@ import {
|
||||
type RoomListItemViewModel,
|
||||
} from "../VirtualizedRoomListView/RoomListItemAccessibilityWrapper/RoomListItemView";
|
||||
import { type RoomListSectionHeaderViewModel } from "../VirtualizedRoomListView/RoomListSectionHeaderView";
|
||||
import { type ToastType, RoomListToast } from "./RoomListToast";
|
||||
import styles from "./RoomListView.module.css";
|
||||
import { Flex } from "../../core/utils/Flex";
|
||||
|
||||
export type RoomListSection = {
|
||||
/** Unique identifier for the section */
|
||||
@ -49,6 +52,8 @@ export type RoomListViewSnapshot = {
|
||||
canCreateRoom?: boolean;
|
||||
/** Whether the room list is displayed as a flat list */
|
||||
isFlatList: boolean;
|
||||
/** Optional toast to display */
|
||||
toast?: ToastType;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -70,6 +75,8 @@ export interface RoomListViewActions {
|
||||
updateVisibleRooms: (startIndex: number, endIndex: number) => void;
|
||||
/** Get view model for a specific section header (virtualization API) */
|
||||
getSectionHeaderViewModel: (sectionId: string) => RoomListSectionHeaderViewModel;
|
||||
/** Called to close the toast message */
|
||||
closeToast: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,7 +120,10 @@ export const RoomListView: React.FC<RoomListViewProps> = ({ vm, renderAvatar, on
|
||||
onToggleFilter={vm.onToggleFilter}
|
||||
/>
|
||||
</div>
|
||||
{listBody}
|
||||
<Flex direction="column" className={styles.list}>
|
||||
{listBody}
|
||||
{snapshot.toast && <RoomListToast type={snapshot.toast} onClose={vm.closeToast} />}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -10,7 +10,7 @@ import { render, screen } from "@test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
|
||||
import { RoomListItemMoreOptionsMenu } from "./RoomListItemMoreOptionsMenu";
|
||||
import { RoomListItemMoreOptionsMenu, MoreOptionContent } from "./RoomListItemMoreOptionsMenu";
|
||||
import { useMockedViewModel } from "../../../../core/viewmodel";
|
||||
import type { RoomListItemViewSnapshot } from "./RoomListItemView";
|
||||
import { defaultSnapshot } from "./default-snapshot";
|
||||
@ -26,6 +26,7 @@ describe("<RoomListItemMoreOptionsMenu />", () => {
|
||||
onCopyRoomLink: vi.fn(),
|
||||
onLeaveRoom: vi.fn(),
|
||||
onSetRoomNotifState: vi.fn(),
|
||||
onCreateSection: vi.fn(),
|
||||
};
|
||||
|
||||
const renderMenu = (overrides: Partial<RoomListItemViewSnapshot> = {}): ReturnType<typeof render> => {
|
||||
@ -224,4 +225,19 @@ describe("<RoomListItemMoreOptionsMenu />", () => {
|
||||
|
||||
expect(mockCallbacks.onLeaveRoom).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call onCreateSection when new section is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
// We need to render the MoreOptionContent directly here as radix is kind of messing in the test env
|
||||
const TestComponent = (): JSX.Element => {
|
||||
const vm = useMockedViewModel(defaultSnapshot, mockCallbacks);
|
||||
return <MoreOptionContent vm={vm} />;
|
||||
};
|
||||
render(<TestComponent />);
|
||||
|
||||
const newSection = screen.getByRole("menuitem", { name: "New section" });
|
||||
await user.click(newSection);
|
||||
|
||||
expect(mockCallbacks.onCreateSection).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import React, { useState, type JSX } from "react";
|
||||
import { IconButton, Menu, MenuItem, Separator, ToggleMenuItem } from "@vector-im/compound-web";
|
||||
import { IconButton, Menu, MenuItem, Separator, SubMenu, ToggleMenuItem } from "@vector-im/compound-web";
|
||||
import {
|
||||
MarkAsReadIcon,
|
||||
MarkAsUnreadIcon,
|
||||
@ -16,6 +16,7 @@ import {
|
||||
LinkIcon,
|
||||
LeaveIcon,
|
||||
OverflowHorizontalIcon,
|
||||
ArrowRightIcon,
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../../core/i18n/i18n";
|
||||
@ -106,6 +107,7 @@ export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element {
|
||||
onSelect={vm.onToggleLowPriority}
|
||||
onClick={(evt) => evt.stopPropagation()}
|
||||
/>
|
||||
<Separator />
|
||||
{snapshot.canInvite && (
|
||||
<MenuItem
|
||||
Icon={UserAddIcon}
|
||||
@ -124,6 +126,19 @@ export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element {
|
||||
hideChevron={true}
|
||||
/>
|
||||
)}
|
||||
{snapshot.canMoveToSection && (
|
||||
<SubMenu
|
||||
trigger={
|
||||
<MenuItem
|
||||
Icon={ArrowRightIcon}
|
||||
label={_t("room_list|more_options|move_to_section")}
|
||||
onSelect={null}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<MenuItem label={_t("action|new_section")} onSelect={vm.onCreateSection} hideChevron={true} />
|
||||
</SubMenu>
|
||||
)}
|
||||
<Separator />
|
||||
<MenuItem
|
||||
kind="critical"
|
||||
|
||||
@ -27,6 +27,7 @@ describe("<RoomListItemNotificationMenu />", () => {
|
||||
onCopyRoomLink: vi.fn(),
|
||||
onLeaveRoom: vi.fn(),
|
||||
onSetRoomNotifState: vi.fn(),
|
||||
onCreateSection: vi.fn(),
|
||||
};
|
||||
|
||||
const renderMenu = (roomNotifState: RoomNotifState = RoomNotifState.AllMessages): ReturnType<typeof render> => {
|
||||
|
||||
@ -38,6 +38,7 @@ const RoomListItemWrapperImpl = ({
|
||||
onCopyRoomLink,
|
||||
onLeaveRoom,
|
||||
onSetRoomNotifState,
|
||||
onCreateSection,
|
||||
isSelected,
|
||||
isFocused,
|
||||
onFocus,
|
||||
@ -56,6 +57,7 @@ const RoomListItemWrapperImpl = ({
|
||||
onCopyRoomLink,
|
||||
onLeaveRoom,
|
||||
onSetRoomNotifState,
|
||||
onCreateSection,
|
||||
});
|
||||
return (
|
||||
<RoomListItemView
|
||||
|
||||
@ -79,6 +79,8 @@ export interface RoomListItemViewSnapshot {
|
||||
canMarkAsUnread: boolean;
|
||||
/** The room's notification state */
|
||||
roomNotifState: RoomNotifState;
|
||||
/** Whether the room can be moved to a section */
|
||||
canMoveToSection: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,6 +106,8 @@ export interface RoomListItemViewActions {
|
||||
onLeaveRoom: () => void;
|
||||
/** Called when setting the room notification state */
|
||||
onSetRoomNotifState: (state: RoomNotifState) => void;
|
||||
/** Called when creating a new section */
|
||||
onCreateSection: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -36,4 +36,5 @@ export const defaultSnapshot: RoomListItemViewSnapshot = {
|
||||
canMarkAsRead: false,
|
||||
canMarkAsUnread: true,
|
||||
roomNotifState: RoomNotifState.AllMessages,
|
||||
canMoveToSection: true,
|
||||
};
|
||||
|
||||
@ -19,4 +19,5 @@ export const mockedActions: RoomListItemViewActions = {
|
||||
onCopyRoomLink: fn(),
|
||||
onLeaveRoom: fn(),
|
||||
onSetRoomNotifState: fn(),
|
||||
onCreateSection: fn(),
|
||||
};
|
||||
|
||||
@ -9,6 +9,5 @@
|
||||
* Room list container styles
|
||||
*/
|
||||
.roomList {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ const RoomListWrapperImpl = ({
|
||||
getRoomItemViewModel,
|
||||
getSectionHeaderViewModel,
|
||||
updateVisibleRooms,
|
||||
closeToast,
|
||||
renderAvatar: renderAvatarProp,
|
||||
...rest
|
||||
}: RoomListStoryProps): JSX.Element => {
|
||||
@ -44,6 +45,7 @@ const RoomListWrapperImpl = ({
|
||||
getRoomItemViewModel,
|
||||
getSectionHeaderViewModel,
|
||||
updateVisibleRooms,
|
||||
closeToast,
|
||||
});
|
||||
|
||||
return (
|
||||
@ -82,6 +84,7 @@ const meta = {
|
||||
updateVisibleRooms: fn(),
|
||||
renderAvatar,
|
||||
isFlatList: true,
|
||||
closeToast: fn(),
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
|
||||
@ -21,6 +21,7 @@ import type { RoomListViewSnapshot, RoomListViewModel } from "../RoomListView";
|
||||
import { GroupedVirtualizedList } from "../../core/VirtualizedList";
|
||||
import { RoomListSectionHeaderView } from "./RoomListSectionHeaderView";
|
||||
import { RoomListItemAccessibilityWrapper } from "./RoomListItemAccessibilityWrapper";
|
||||
import styles from "./VirtualizedRoomListView.module.css";
|
||||
|
||||
/**
|
||||
* Filter key type - opaque string type for filter identifiers
|
||||
@ -350,6 +351,7 @@ export function VirtualizedRoomListView({ vm, renderAvatar, onKeyDown }: Virtual
|
||||
rangeChanged,
|
||||
onKeyDown,
|
||||
increaseViewportBy,
|
||||
className: styles.roomList,
|
||||
};
|
||||
|
||||
if (isFlatList) {
|
||||
|
||||
@ -10,6 +10,7 @@ exports[`<VirtualizedRoomListView /> > renders Default story 1`] = `
|
||||
>
|
||||
<div
|
||||
aria-label="Room list"
|
||||
class="VirtualizedRoomListView-module_roomList"
|
||||
data-testid="room-list"
|
||||
data-virtuoso-scroller="true"
|
||||
role="listbox"
|
||||
|
||||