Use Compound buttons in auth screens (#32562)

* Use Compound buttons in auth screens

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Simplify

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Replace brandClass with testid

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update screenshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Replace OIDC native Continue buttons too

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix contrast issues on legacy sso buttons

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Tidy css

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2026-02-18 16:07:25 +00:00
committed by GitHub
parent 88b9605a9e
commit ba95f7f26b
17 changed files with 87 additions and 115 deletions
@@ -12,6 +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 { _t, _td } from "../../../languageHandler";
import Modal from "../../../Modal";
@@ -417,9 +418,9 @@ export default class ForgotPassword extends React.Component<Props, State> {
</StyledCheckbox>
</div>
{this.state.errorText && <ErrorMessage message={this.state.errorText} />}
<button type="submit" className="mx_Login_submit">
<Button type="submit" className="mx_Login_submit" size="sm">
{submitButtonChild}
</button>
</Button>
</fieldset>
</form>
</>
@@ -432,12 +433,9 @@ export default class ForgotPassword extends React.Component<Props, State> {
<CheckIcon className="mx_Icon mx_Icon_32 mx_Icon_accent" />
<h1>{_t("auth|reset_password|reset_successful")}</h1>
{this.state.logoutDevices ? <p>{_t("auth|reset_password|devices_logout_success")}</p> : null}
<input
className="mx_Login_submit"
type="button"
onClick={this.props.onComplete}
value={_t("auth|reset_password|return_to_login")}
/>
<Button className="mx_Login_submit" size="sm" type="button" onClick={this.props.onComplete}>
{_t("auth|reset_password|return_to_login")}
</Button>
</>
);
}
+4 -2
View File
@@ -10,6 +10,7 @@ import React, { type JSX, type ReactNode } from "react";
import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";
import { type SSOFlow, SSOAction } from "matrix-js-sdk/src/matrix";
import { Button } from "@vector-im/compound-web";
import { _t, UserFriendlyError } from "../../../languageHandler";
import Login, { type ClientLoginFlow, type OidcNativeFlow } from "../../../Login";
@@ -439,9 +440,10 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
private renderOidcNativeStep = (): React.ReactNode => {
const flow = this.state.flows!.find((flow) => flow.type === "oidcNativeFlow")! as OidcNativeFlow;
return (
<AccessibleButton
<Button
className="mx_Login_fullWidthButton"
kind="primary"
size="sm"
onClick={async () => {
await startOidcLogin(
this.props.serverConfig.delegatedAuthentication!,
@@ -452,7 +454,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
}}
>
{_t("action|continue")}
</AccessibleButton>
</Button>
);
};
@@ -23,6 +23,7 @@ import {
import React, { type JSX, Fragment, type ReactNode } from "react";
import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";
import { Button } from "@vector-im/compound-web";
import { _t } from "../../../languageHandler";
import { adminContactStrings, messageForResourceLimitError, resourceLimitStrings } from "../../../utils/ErrorUtils";
@@ -546,9 +547,10 @@ export default class Registration extends React.Component<IProps, IState> {
);
} else if (this.state.matrixClient && this.state.oidcNativeFlow) {
return (
<AccessibleButton
<Button
className="mx_Login_fullWidthButton"
kind="primary"
size="sm"
onClick={async () => {
await startOidcLogin(
this.props.serverConfig.delegatedAuthentication!,
@@ -560,7 +562,7 @@ export default class Registration extends React.Component<IProps, IState> {
}}
>
{_t("action|continue")}
</AccessibleButton>
</Button>
);
} else if (this.state.matrixClient && this.state.flows.length) {
let ssoSection: JSX.Element | undefined;
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { type ReactNode } from "react";
import { Tooltip } from "@vector-im/compound-web";
import { Button, Tooltip } from "@vector-im/compound-web";
import { RestartIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import AccessibleButton from "../../../views/elements/AccessibleButton";
@@ -55,7 +55,9 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
</div>
</div>
{errorText && <ErrorMessage message={errorText} />}
<input onClick={onSubmitForm} type="button" className="mx_Login_submit" value={_t("action|next")} />
<Button onClick={onSubmitForm} type="button" className="mx_Login_submit" size="sm">
{_t("action|next")}
</Button>
<div className="mx_AuthBody_did-not-receive">
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
<Tooltip description={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type ReactNode, useRef } from "react";
import { EmailSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Button } from "@vector-im/compound-web";
import { _t, _td } from "../../../../languageHandler";
import EmailField from "../../../views/auth/EmailField";
@@ -74,9 +75,9 @@ export const EnterEmail: React.FC<EnterEmailProps> = ({
/>
</div>
{errorText && <ErrorMessage message={errorText} />}
<button type="submit" className="mx_Login_submit">
<Button type="submit" className="mx_Login_submit" size="sm">
{submitButtonChild}
</button>
</Button>
<div className="mx_AuthBody_button-container">
<AccessibleButton
className="mx_AuthBody_sign-in-instead-button"
+4 -6
View File
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, type SyntheticEvent } from "react";
import classNames from "classnames";
import { Button } from "@vector-im/compound-web";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
@@ -433,12 +434,9 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
/>
{forgotPasswordJsx}
{!this.props.busy && (
<input
className="mx_Login_submit"
type="submit"
value={_t("action|sign_in")}
disabled={this.props.disableSubmit}
/>
<Button className="mx_Login_submit" size="sm" type="submit" disabled={this.props.disableSubmit}>
{_t("action|sign_in")}
</Button>
)}
</form>
</div>
@@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, type BaseSyntheticEvent, type ComponentProps, type ReactNode } from "react";
import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { Button } from "@vector-im/compound-web";
import * as Email from "../../../email";
import { looksValid as phoneNumberLooksValid, type PhoneNumberCountryDefinition } from "../../../phonenumber";
@@ -548,12 +549,9 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
public render(): ReactNode {
const registerButton = (
<input
className="mx_Login_submit"
type="submit"
value={_t("action|register")}
disabled={!this.props.canSubmit}
/>
<Button className="mx_Login_submit" size="sm" type="submit" disabled={!this.props.canSubmit}>
{_t("action|register")}
</Button>
);
let emailHelperText: JSX.Element | undefined;
+41 -37
View File
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX } from "react";
import React, { type ComponentProps, type JSX } from "react";
import { chunk } from "lodash";
import classNames from "classnames";
import {
@@ -18,12 +18,18 @@ import {
DELEGATED_OIDC_COMPATIBILITY,
} from "matrix-js-sdk/src/matrix";
import { type Signup } from "@matrix-org/analytics-events/types/typescript/Signup";
import { Button, Tooltip } from "@vector-im/compound-web";
import { MacIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import PlatformPeg from "../../../PlatformPeg";
import AccessibleButton from "./AccessibleButton";
import { _t } from "../../../languageHandler";
import { mediaFromMxc } from "../../../customisations/Media";
import { PosthogAnalytics } from "../../../PosthogAnalytics";
import { Icon as FacebookIcon } from "../../../../res/img/element-icons/brands/facebook.svg";
import { Icon as GithubIcon } from "../../../../res/img/element-icons/brands/github.svg";
import { Icon as GitlabIcon } from "../../../../res/img/element-icons/brands/gitlab.svg";
import { Icon as GoogleIcon } from "../../../../res/img/element-icons/brands/google.svg";
import { Icon as TwitterIcon } from "../../../../res/img/element-icons/brands/twitter.svg";
interface ISSOButtonProps extends IProps {
idp?: IIdentityProvider;
@@ -31,24 +37,22 @@ interface ISSOButtonProps extends IProps {
action?: SSOAction;
}
const getIcon = (brand: IdentityProviderBrand | string): string | null => {
const getIcon = (brand: IdentityProviderBrand | string): typeof FacebookIcon | null => {
switch (brand) {
/* eslint-disable @typescript-eslint/no-require-imports */
case IdentityProviderBrand.Apple:
return require("@vector-im/compound-design-tokens/icons/mac.svg").default;
return MacIcon;
case IdentityProviderBrand.Facebook:
return require(`../../../../res/img/element-icons/brands/facebook.svg`).default;
return FacebookIcon;
case IdentityProviderBrand.Github:
return require(`../../../../res/img/element-icons/brands/github.svg`).default;
return GithubIcon;
case IdentityProviderBrand.Gitlab:
return require(`../../../../res/img/element-icons/brands/gitlab.svg`).default;
return GitlabIcon;
case IdentityProviderBrand.Google:
return require(`../../../../res/img/element-icons/brands/google.svg`).default;
return GoogleIcon;
case IdentityProviderBrand.Twitter:
return require(`../../../../res/img/element-icons/brands/twitter.svg`).default;
return TwitterIcon;
default:
return null;
/* eslint-enable @typescript-eslint/no-require-imports */
}
};
@@ -78,10 +82,10 @@ const SSOButton: React.FC<ISSOButtonProps> = ({
fragmentAfterLogin,
idp,
primary,
mini,
mini: iconOnly,
action,
flow,
...props
disabled,
}) => {
let label: string;
if (idp) {
@@ -98,43 +102,43 @@ const SSOButton: React.FC<ISSOButtonProps> = ({
PlatformPeg.get()?.startSingleSignOn(matrixClient, loginType, fragmentAfterLogin, idp?.id, action);
};
const commonProps: Partial<ComponentProps<typeof Button>> & Record<`data-${string}`, string> = {
iconOnly,
className: classNames("mx_SSOButton", {
mx_SSOButton_mini: iconOnly,
}),
onClick,
kind: primary ? "primary" : "secondary",
disabled,
};
let icon: JSX.Element | undefined;
let brandClass: string | undefined;
const brandIcon = idp?.brand ? getIcon(idp.brand) : null;
if (idp?.brand && brandIcon) {
const BrandIcon = idp?.brand ? getIcon(idp.brand) : null;
if (idp?.brand && BrandIcon) {
const brandName = idp.brand.split(".").pop();
brandClass = `mx_SSOButton_brand_${brandName}`;
icon = <img src={brandIcon} height="24" width="24" alt={brandName} />;
icon = <BrandIcon aria-label={brandName} />;
commonProps["data-testid"] = `idp-${idp.id}`;
} else if (typeof idp?.icon === "string" && idp.icon.startsWith("mxc://")) {
const src = mediaFromMxc(idp.icon, matrixClient).getSquareThumbnailHttp(24) ?? undefined;
icon = <img src={src} height="24" width="24" alt={idp.name} />;
icon = <img src={src} alt={idp.name} />;
}
const brandPart = brandClass ? { [brandClass]: brandClass } : undefined;
const classes = classNames(
"mx_SSOButton",
{
mx_SSOButton_mini: mini,
mx_SSOButton_default: !idp,
mx_SSOButton_primary: primary,
},
brandPart,
);
if (mini) {
// TODO fallback icon
// TODO fallback icon
if (iconOnly) {
return (
<AccessibleButton {...props} title={label} className={classes} onClick={onClick}>
{icon}
</AccessibleButton>
<Tooltip label={label}>
<Button {...commonProps} size="lg">
{icon}
</Button>
</Tooltip>
);
}
return (
<AccessibleButton {...props} className={classes} onClick={onClick}>
<Button {...commonProps} size="sm">
{icon}
{label}
</AccessibleButton>
</Button>
);
};