Merge pull request #2624 from robintown/compound-typography

Replace typography components with Compound components
This commit is contained in:
Robin 2024-09-12 13:04:09 -04:00 committed by GitHub
commit e699cb6411
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 126 additions and 423 deletions

View File

@ -8,14 +8,20 @@ Please see LICENSE in the repository root for full details.
import { FC } from "react"; import { FC } from "react";
import { Trans } from "react-i18next"; import { Trans } from "react-i18next";
import { Link } from "../typography/Typography"; import { ExternalLink } from "../button/Link";
export const AnalyticsNotice: FC = () => ( export const AnalyticsNotice: FC = () => (
<Trans i18nKey="analytics_notice"> <Trans i18nKey="analytics_notice">
By participating in this beta, you consent to the collection of anonymous By participating in this beta, you consent to the collection of anonymous
data, which we use to improve the product. You can find more information data, which we use to improve the product. You can find more information
about which data we track in our{" "} about which data we track in our{" "}
<Link href="https://element.io/privacy">Privacy Policy</Link> and our{" "} <ExternalLink href="https://element.io/privacy">
<Link href="https://element.io/cookie-policy">Cookie Policy</Link>. Privacy Policy
</ExternalLink>{" "}
and our{" "}
<ExternalLink href="https://element.io/cookie-policy">
Cookie Policy
</ExternalLink>
.
</Trans> </Trans>
); );

View File

@ -19,7 +19,7 @@ import { captureException } from "@sentry/react";
import { sleep } from "matrix-js-sdk/src/utils"; import { sleep } from "matrix-js-sdk/src/utils";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Button } from "@vector-im/compound-web"; import { Button, Text } from "@vector-im/compound-web";
import { FieldRow, InputField, ErrorMessage } from "../input/Input"; import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { useClientLegacy } from "../ClientContext"; import { useClientLegacy } from "../ClientContext";
@ -28,10 +28,10 @@ import styles from "./LoginPage.module.css";
import Logo from "../icons/LogoLarge.svg?react"; import Logo from "../icons/LogoLarge.svg?react";
import { LoadingView } from "../FullScreenView"; import { LoadingView } from "../FullScreenView";
import { useRecaptcha } from "./useRecaptcha"; import { useRecaptcha } from "./useRecaptcha";
import { Caption, Link } from "../typography/Typography";
import { usePageTitle } from "../usePageTitle"; import { usePageTitle } from "../usePageTitle";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { Config } from "../config/Config"; import { Config } from "../config/Config";
import { ExternalLink, Link } from "../button/Link";
export const RegisterPage: FC = () => { export const RegisterPage: FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -201,24 +201,24 @@ export const RegisterPage: FC = () => {
data-testid="register_confirm_password" data-testid="register_confirm_password"
/> />
</FieldRow> </FieldRow>
<Caption> <Text size="sm">
<Trans i18nKey="recaptcha_caption"> <Trans i18nKey="recaptcha_caption">
This site is protected by ReCAPTCHA and the Google{" "} This site is protected by ReCAPTCHA and the Google{" "}
<Link href="https://www.google.com/policies/privacy/"> <ExternalLink href="https://www.google.com/policies/privacy/">
Privacy Policy Privacy Policy
</Link>{" "} </ExternalLink>{" "}
and{" "} and{" "}
<Link href="https://policies.google.com/terms"> <ExternalLink href="https://policies.google.com/terms">
Terms of Service Terms of Service
</Link>{" "} </ExternalLink>{" "}
apply. apply.
<br /> <br />
By clicking "Register", you agree to our{" "} By clicking "Register", you agree to our{" "}
<Link href={Config.get().eula}> <ExternalLink href={Config.get().eula}>
End User Licensing Agreement (EULA) End User Licensing Agreement (EULA)
</Link> </ExternalLink>
</Trans> </Trans>
</Caption> </Text>
{error && ( {error && (
<FieldRow> <FieldRow>
<ErrorMessage error={error} /> <ErrorMessage error={error} />

View File

@ -0,0 +1,13 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
.external {
/* By default links will be blue/purple (or whatever the user agent does), but
in our designs we generally want external links to be the same color as the
surrounding text */
color: inherit;
}

View File

@ -15,10 +15,16 @@ import {
import { Link as CpdLink } from "@vector-im/compound-web"; import { Link as CpdLink } from "@vector-im/compound-web";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { createPath, LocationDescriptor, Path } from "history"; import { createPath, LocationDescriptor, Path } from "history";
import classNames from "classnames";
import { useLatest } from "../useLatest";
import styles from "./Link.module.css";
export function useLink( export function useLink(
to: LocationDescriptor, to: LocationDescriptor,
state?: unknown,
): [Path, (e: MouseEvent) => void] { ): [Path, (e: MouseEvent) => void] {
const latestState = useLatest(state);
const history = useHistory(); const history = useHistory();
const path = useMemo( const path = useMemo(
() => (typeof to === "string" ? to : createPath(to)), () => (typeof to === "string" ? to : createPath(to)),
@ -27,9 +33,9 @@ export function useLink(
const onClick = useCallback( const onClick = useCallback(
(e: MouseEvent) => { (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
history.push(to); history.push(to, latestState.current);
}, },
[history, to], [history, to, latestState],
); );
return [path, onClick]; return [path, onClick];
@ -38,15 +44,37 @@ export function useLink(
type Props = Omit< type Props = Omit<
ComponentPropsWithoutRef<typeof CpdLink>, ComponentPropsWithoutRef<typeof CpdLink>,
"href" | "onClick" "href" | "onClick"
> & { to: LocationDescriptor }; > & { to: LocationDescriptor; state?: unknown };
/** /**
* A version of Compound's link component that integrates with our router setup. * A version of Compound's link component that integrates with our router setup.
* This is only for app-internal links.
*/ */
export const Link = forwardRef<HTMLAnchorElement, Props>(function Link( export const Link = forwardRef<HTMLAnchorElement, Props>(function Link(
{ to, ...props }, { to, state, ...props },
ref, ref,
) { ) {
const [path, onClick] = useLink(to); const [path, onClick] = useLink(to, state);
return <CpdLink ref={ref} {...props} href={path} onClick={onClick} />; return <CpdLink ref={ref} {...props} href={path} onClick={onClick} />;
}); });
/**
* A link to an external web page, made to fit into blocks of text more subtly
* than the normal Compound link component.
*/
export const ExternalLink = forwardRef<
HTMLAnchorElement,
ComponentPropsWithoutRef<"a">
>(function ExternalLink({ className, children, ...props }, ref) {
return (
<a
ref={ref}
className={classNames(className, styles.external)}
target="_blank"
rel="noreferrer noopener"
{...props}
>
{children}
</a>
);
});

View File

@ -50,6 +50,12 @@ Please see LICENSE in the repository root for full details.
margin-bottom: 0; margin-bottom: 0;
} }
.callName {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.facePile { .facePile {
margin-top: 8px; margin-top: 8px;
} }

View File

@ -10,11 +10,11 @@ import { MatrixClient } from "matrix-js-sdk/src/client";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { FC } from "react"; import { FC } from "react";
import { Text } from "@vector-im/compound-web";
import { Avatar, Size } from "../Avatar"; import { Avatar, Size } from "../Avatar";
import styles from "./CallList.module.css"; import styles from "./CallList.module.css";
import { getRelativeRoomUrl } from "../utils/matrix"; import { getRelativeRoomUrl } from "../utils/matrix";
import { Body } from "../typography/Typography";
import { GroupCallRoom } from "./useGroupCallRooms"; import { GroupCallRoom } from "./useGroupCallRooms";
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement"; import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
@ -65,9 +65,9 @@ const CallTile: FC<CallTileProps> = ({ name, avatarUrl, room }) => {
> >
<Avatar id={room.roomId} name={name} size={Size.LG} src={avatarUrl} /> <Avatar id={room.roomId} name={name} size={Size.LG} src={avatarUrl} />
<div className={styles.callInfo}> <div className={styles.callInfo}>
<Body overflowEllipsis fontWeight="semiBold"> <Text weight="semibold" className={styles.callName}>
{name} {name}
</Body> </Text>
</div> </div>
<div className={styles.copyButtonSpacer} /> <div className={styles.copyButtonSpacer} />
</Link> </Link>

View File

@ -9,7 +9,7 @@ import { useState, useCallback, FormEvent, FormEventHandler, FC } from "react";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Heading } from "@vector-im/compound-web"; import { Heading, Text } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Button } from "@vector-im/compound-web"; import { Button } from "@vector-im/compound-web";
@ -27,7 +27,6 @@ import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { CallList } from "./CallList"; import { CallList } from "./CallList";
import { UserMenuContainer } from "../UserMenuContainer"; import { UserMenuContainer } from "../UserMenuContainer";
import { JoinExistingCallModal } from "./JoinExistingCallModal"; import { JoinExistingCallModal } from "./JoinExistingCallModal";
import { Caption } from "../typography/Typography";
import { Form } from "../form/Form"; import { Form } from "../form/Form";
import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { E2eeType } from "../e2ee/e2eeType"; import { E2eeType } from "../e2ee/e2eeType";
@ -144,9 +143,9 @@ export const RegisteredView: FC<Props> = ({ client }) => {
</Button> </Button>
</FieldRow> </FieldRow>
{optInAnalytics === null && ( {optInAnalytics === null && (
<Caption className={styles.notice}> <Text size="sm" className={styles.notice}>
<AnalyticsNotice /> <AnalyticsNotice />
</Caption> </Text>
)} )}
{error && ( {error && (
<FieldRow className={styles.fieldRow}> <FieldRow className={styles.fieldRow}>

View File

@ -9,7 +9,7 @@ import { FC, useCallback, useState, FormEventHandler } from "react";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { randomString } from "matrix-js-sdk/src/randomstring"; import { randomString } from "matrix-js-sdk/src/randomstring";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { Button, Heading } from "@vector-im/compound-web"; import { Button, Heading, Text } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { useClient } from "../ClientContext"; import { useClient } from "../ClientContext";
@ -25,7 +25,6 @@ import {
import { useInteractiveRegistration } from "../auth/useInteractiveRegistration"; import { useInteractiveRegistration } from "../auth/useInteractiveRegistration";
import { JoinExistingCallModal } from "./JoinExistingCallModal"; import { JoinExistingCallModal } from "./JoinExistingCallModal";
import { useRecaptcha } from "../auth/useRecaptcha"; import { useRecaptcha } from "../auth/useRecaptcha";
import { Body, Caption, Link } from "../typography/Typography";
import { Form } from "../form/Form"; import { Form } from "../form/Form";
import styles from "./UnauthenticatedView.module.css"; import styles from "./UnauthenticatedView.module.css";
import commonStyles from "./common.module.css"; import commonStyles from "./common.module.css";
@ -34,6 +33,7 @@ import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { Config } from "../config/Config"; import { Config } from "../config/Config";
import { E2eeType } from "../e2ee/e2eeType"; import { E2eeType } from "../e2ee/e2eeType";
import { useOptInAnalytics } from "../settings/settings"; import { useOptInAnalytics } from "../settings/settings";
import { ExternalLink, Link } from "../button/Link";
export const UnauthenticatedView: FC = () => { export const UnauthenticatedView: FC = () => {
const { setClient } = useClient(); const { setClient } = useClient();
@ -178,18 +178,18 @@ export const UnauthenticatedView: FC = () => {
/> />
</FieldRow> </FieldRow>
{optInAnalytics === null && ( {optInAnalytics === null && (
<Caption className={styles.notice}> <Text size="sm" className={styles.notice}>
<AnalyticsNotice /> <AnalyticsNotice />
</Caption> </Text>
)} )}
<Caption className={styles.notice}> <Text size="sm" className={styles.notice}>
<Trans i18nKey="unauthenticated_view_eula_caption"> <Trans i18nKey="unauthenticated_view_eula_caption">
By clicking "Go", you agree to our{" "} By clicking "Go", you agree to our{" "}
<Link href={Config.get().eula}> <ExternalLink href={Config.get().eula}>
End User Licensing Agreement (EULA) End User Licensing Agreement (EULA)
</Link> </ExternalLink>
</Trans> </Trans>
</Caption> </Text>
{error && ( {error && (
<FieldRow> <FieldRow>
<ErrorMessage error={error} /> <ErrorMessage error={error} />
@ -207,19 +207,19 @@ export const UnauthenticatedView: FC = () => {
</Form> </Form>
</main> </main>
<footer className={styles.footer}> <footer className={styles.footer}>
<Body className={styles.mobileLoginLink}> <Text className={styles.mobileLoginLink}>
<Link color="primary" to="/login" data-testid="home_login"> <Link to="/login" data-testid="home_login">
{t("unauthenticated_view_login_button")} {t("unauthenticated_view_login_button")}
</Link> </Link>
</Body> </Text>
<Body> <Text>
<Trans i18nKey="unauthenticated_view_body"> <Trans i18nKey="unauthenticated_view_body">
Not registered yet?{" "} Not registered yet?{" "}
<Link color="primary" to="/register" data-testid="home_register"> <Link to="/register" data-testid="home_register">
Create an account Create an account
</Link> </Link>
</Trans> </Trans>
</Body> </Text>
</footer> </footer>
</div> </div>
{onFinished && ( {onFinished && (

View File

@ -237,16 +237,6 @@ body[data-platform="desktop"] {
line-height: var(--font-size-title); line-height: var(--font-size-title);
} }
a {
color: var(--cpd-color-text-action-accent);
text-decoration: none;
}
a:hover,
a:active {
opacity: 0.8;
}
hr { hr {
width: calc(100% - 24px); width: calc(100% - 24px);
border: none; border: none;

View File

@ -9,12 +9,11 @@ import { FC, FormEventHandler, ReactNode, useCallback, useState } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { Button } from "@vector-im/compound-web"; import { Button, Heading, Text } from "@vector-im/compound-web";
import styles from "./CallEndedView.module.css"; import styles from "./CallEndedView.module.css";
import feedbackStyle from "../input/FeedbackInput.module.css"; import feedbackStyle from "../input/FeedbackInput.module.css";
import { useProfile } from "../profile/useProfile"; import { useProfile } from "../profile/useProfile";
import { Body, Headline } from "../typography/Typography";
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header"; import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { FieldRow, InputField } from "../input/Input"; import { FieldRow, InputField } from "../input/Input";
@ -139,11 +138,11 @@ export const CallEndedView: FC<Props> = ({
return ( return (
<> <>
<main className={styles.main}> <main className={styles.main}>
<Headline className={styles.headline}> <Heading size="xl" weight="semibold" className={styles.headline}>
<Trans i18nKey="call_ended_view.body"> <Trans i18nKey="call_ended_view.body">
You were disconnected from the call You were disconnected from the call
</Trans> </Trans>
</Headline> </Heading>
<div className={styles.disconnectedButtons}> <div className={styles.disconnectedButtons}>
<Button onClick={reconnect}> <Button onClick={reconnect}>
{t("call_ended_view.reconnect_button")} {t("call_ended_view.reconnect_button")}
@ -154,9 +153,9 @@ export const CallEndedView: FC<Props> = ({
</div> </div>
</main> </main>
{!confineToRoom && ( {!confineToRoom && (
<Body className={styles.footer}> <Text className={styles.footer}>
<Link to="/"> {t("return_home_button")} </Link> <Link to="/"> {t("return_home_button")} </Link>
</Body> </Text>
)} )}
</> </>
); );
@ -164,7 +163,7 @@ export const CallEndedView: FC<Props> = ({
return ( return (
<> <>
<main className={styles.main}> <main className={styles.main}>
<Headline className={styles.headline}> <Heading size="xl" weight="semibold" className={styles.headline}>
{surveySubmitted {surveySubmitted
? t("call_ended_view.headline", { ? t("call_ended_view.headline", {
displayName, displayName,
@ -174,16 +173,16 @@ export const CallEndedView: FC<Props> = ({
}) + }) +
"\n" + "\n" +
t("call_ended_view.survey_prompt")} t("call_ended_view.survey_prompt")}
</Headline> </Heading>
{(!surveySubmitted || confineToRoom) && {(!surveySubmitted || confineToRoom) &&
PosthogAnalytics.instance.isEnabled() PosthogAnalytics.instance.isEnabled()
? qualitySurveyDialog ? qualitySurveyDialog
: createAccountDialog} : createAccountDialog}
</main> </main>
{!confineToRoom && ( {!confineToRoom && (
<Body className={styles.footer}> <Text className={styles.footer}>
<Link to="/"> {t("call_ended_view.not_now_button")} </Link> <Link to="/"> {t("call_ended_view.not_now_button")} </Link>
</Body> </Text>
)} )}
</> </>
); );

View File

@ -5,13 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details. Please see LICENSE in the repository root for full details.
*/ */
import { useCallback } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { MatrixError } from "matrix-js-sdk/src/matrix"; import { MatrixError } from "matrix-js-sdk/src/matrix";
import { useHistory } from "react-router-dom"; import { Heading, Text } from "@vector-im/compound-web";
import { Heading, Link, Text } from "@vector-im/compound-web";
import { Link } from "../button/Link";
import { import {
useLoadGroupCall, useLoadGroupCall,
GroupCallStatus, GroupCallStatus,
@ -35,15 +34,6 @@ export function GroupCallLoader({
const { t } = useTranslation(); const { t } = useTranslation();
const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers); const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers);
const history = useHistory();
const onHomeClick = useCallback(
(ev: React.MouseEvent) => {
ev.preventDefault();
history.push("/");
},
[history],
);
switch (groupCallState.kind) { switch (groupCallState.kind) {
case "loaded": case "loaded":
case "waitForInvite": case "waitForInvite":
@ -63,9 +53,7 @@ export function GroupCallLoader({
<Text>{t("group_call_loader.failed_text")}</Text> <Text>{t("group_call_loader.failed_text")}</Text>
{/* XXX: A 'create it for me' button would be the obvious UX here. Two screens already have {/* XXX: A 'create it for me' button would be the obvious UX here. Two screens already have
dupes of this flow, let's make a common component and put it here. */} dupes of this flow, let's make a common component and put it here. */}
<Link href="/" onClick={onHomeClick}> <Link to="/">{t("common.home")}</Link>
{t("common.home")}
</Link>
</FullScreenView> </FullScreenView>
); );
} else if (groupCallState.error instanceof CallTerminatedMessage) { } else if (groupCallState.error instanceof CallTerminatedMessage) {
@ -79,9 +67,7 @@ export function GroupCallLoader({
<Text size="sm">"{groupCallState.error.reason}"</Text> <Text size="sm">"{groupCallState.error.reason}"</Text>
</> </>
)} )}
<Link href="/" onClick={onHomeClick}> <Link to="/">{t("common.home")}</Link>
{t("common.home")}
</Link>
</FullScreenView> </FullScreenView>
); );
} else { } else {

View File

@ -15,7 +15,7 @@ import {
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { JoinRule } from "matrix-js-sdk/src/matrix"; import { JoinRule } from "matrix-js-sdk/src/matrix";
import { Heading, Link, Text } from "@vector-im/compound-web"; import { Heading, Text } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import type { IWidgetApiRequest } from "matrix-widget-api"; import type { IWidgetApiRequest } from "matrix-widget-api";
@ -40,6 +40,7 @@ import { useJoinRule } from "./useJoinRule";
import { InviteModal } from "./InviteModal"; import { InviteModal } from "./InviteModal";
import { useUrlParams } from "../UrlParams"; import { useUrlParams } from "../UrlParams";
import { E2eeType } from "../e2ee/e2eeType"; import { E2eeType } from "../e2ee/e2eeType";
import { Link } from "../button/Link";
declare global { declare global {
interface Window { interface Window {
@ -281,14 +282,6 @@ export const GroupCallView: FC<Props> = ({
); );
const onShareClick = joinRule === JoinRule.Public ? onShareClickFn : null; const onShareClick = joinRule === JoinRule.Public ? onShareClickFn : null;
const onHomeClick = useCallback(
(ev: React.MouseEvent) => {
ev.preventDefault();
history.push("/");
},
[history],
);
const { t } = useTranslation(); const { t } = useTranslation();
if (!isE2EESupportedBrowser() && e2eeSystem.kind !== E2eeType.NONE) { if (!isE2EESupportedBrowser() && e2eeSystem.kind !== E2eeType.NONE) {
@ -297,9 +290,7 @@ export const GroupCallView: FC<Props> = ({
<FullScreenView> <FullScreenView>
<Heading>{t("browser_media_e2ee_unsupported_heading")}</Heading> <Heading>{t("browser_media_e2ee_unsupported_heading")}</Heading>
<Text>{t("browser_media_e2ee_unsupported")}</Text> <Text>{t("browser_media_e2ee_unsupported")}</Text>
<Link href="/" onClick={onHomeClick}> <Link to="/">{t("common.home")}</Link>
{t("common.home")}
</Link>
</FullScreenView> </FullScreenView>
); );
} }

View File

@ -7,12 +7,11 @@ Please see LICENSE in the repository root for full details.
import { FC, useEffect } from "react"; import { FC, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button } from "@vector-im/compound-web"; import { Button, Text } from "@vector-im/compound-web";
import { Modal, Props as ModalProps } from "../Modal"; import { Modal, Props as ModalProps } from "../Modal";
import { FieldRow, ErrorMessage } from "../input/Input"; import { FieldRow, ErrorMessage } from "../input/Input";
import { useSubmitRageshake } from "../settings/submit-rageshake"; import { useSubmitRageshake } from "../settings/submit-rageshake";
import { Body } from "../typography/Typography";
interface Props extends Omit<ModalProps, "title" | "children"> { interface Props extends Omit<ModalProps, "title" | "children"> {
rageshakeRequestId: string; rageshakeRequestId: string;
@ -40,7 +39,7 @@ export const RageshakeRequestModal: FC<Props> = ({
open={open} open={open}
onDismiss={onDismiss} onDismiss={onDismiss}
> >
<Body>{t("rageshake_request_modal.body")}</Body> <Text>{t("rageshake_request_modal.body")}</Text>
<FieldRow> <FieldRow>
<Button <Button
onClick={(): void => onClick={(): void =>

View File

@ -9,16 +9,16 @@ import { FC, useCallback, useState } from "react";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Button } from "@vector-im/compound-web"; import { Button, Heading, Text } from "@vector-im/compound-web";
import styles from "./RoomAuthView.module.css"; import styles from "./RoomAuthView.module.css";
import { Body, Caption, Link, Headline } from "../typography/Typography";
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header"; import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
import { FieldRow, InputField, ErrorMessage } from "../input/Input"; import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { Form } from "../form/Form"; import { Form } from "../form/Form";
import { UserMenuContainer } from "../UserMenuContainer"; import { UserMenuContainer } from "../UserMenuContainer";
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser"; import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
import { Config } from "../config/Config"; import { Config } from "../config/Config";
import { ExternalLink, Link } from "../button/Link";
export const RoomAuthView: FC = () => { export const RoomAuthView: FC = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -63,9 +63,9 @@ export const RoomAuthView: FC = () => {
</Header> </Header>
<div className={styles.container}> <div className={styles.container}>
<main className={styles.main}> <main className={styles.main}>
<Headline className={styles.headline}> <Heading size="xl" weight="semibold" className={styles.headline}>
{t("lobby.join_button")} {t("lobby.join_button")}
</Headline> </Heading>
<Form className={styles.form} onSubmit={onSubmit}> <Form className={styles.form} onSubmit={onSubmit}>
<FieldRow> <FieldRow>
<InputField <InputField
@ -79,14 +79,14 @@ export const RoomAuthView: FC = () => {
autoComplete="off" autoComplete="off"
/> />
</FieldRow> </FieldRow>
<Caption> <Text size="sm">
<Trans i18nKey="room_auth_view_eula_caption"> <Trans i18nKey="room_auth_view_eula_caption">
By clicking "Join call now", you agree to our{" "} By clicking "Join call now", you agree to our{" "}
<Link href={Config.get().eula}> <ExternalLink href={Config.get().eula}>
End User Licensing Agreement (EULA) End User Licensing Agreement (EULA)
</Link> </ExternalLink>
</Trans> </Trans>
</Caption> </Text>
{error && ( {error && (
<FieldRow> <FieldRow>
<ErrorMessage error={error} /> <ErrorMessage error={error} />
@ -103,17 +103,14 @@ export const RoomAuthView: FC = () => {
<div id={recaptchaId} /> <div id={recaptchaId} />
</Form> </Form>
</main> </main>
<Body className={styles.footer}> <Text className={styles.footer}>
<Trans i18nKey="unauthenticated_view_body"> <Trans i18nKey="unauthenticated_view_body">
Not registered yet?{" "} Not registered yet?{" "}
<Link <Link to="/register" state={{ from: location }}>
color="primary"
to={{ pathname: "/register", state: { from: location } }}
>
Create an account Create an account
</Link> </Link>
</Trans> </Trans>
</Body> </Text>
</div> </div>
</> </>
); );

View File

@ -8,12 +8,11 @@ Please see LICENSE in the repository root for full details.
import { FC, useCallback } from "react"; import { FC, useCallback } from "react";
import { randomString } from "matrix-js-sdk/src/randomstring"; import { randomString } from "matrix-js-sdk/src/randomstring";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button } from "@vector-im/compound-web"; import { Button, Text } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { FieldRow, InputField, ErrorMessage } from "../input/Input"; import { FieldRow, InputField, ErrorMessage } from "../input/Input";
import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake"; import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake";
import { Body } from "../typography/Typography";
import feedbackStyles from "../input/FeedbackInput.module.css"; import feedbackStyles from "../input/FeedbackInput.module.css";
interface Props { interface Props {
@ -56,7 +55,7 @@ export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
return ( return (
<div> <div>
<h4>{t("settings.feedback_tab_h4")}</h4> <h4>{t("settings.feedback_tab_h4")}</h4>
<Body>{t("settings.feedback_tab_body")}</Body> <Text>{t("settings.feedback_tab_body")}</Text>
<form onSubmit={onSubmitFeedback}> <form onSubmit={onSubmitFeedback}>
<FieldRow> <FieldRow>
<InputField <InputField
@ -85,7 +84,7 @@ export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
)} )}
<FieldRow> <FieldRow>
{error && <ErrorMessage error={error} />} {error && <ErrorMessage error={error} />}
{sent && <Body> {t("settings.feedback_tab_thank_you")}</Body>} {sent && <Text>{t("settings.feedback_tab_thank_you")}</Text>}
</FieldRow> </FieldRow>
</form> </form>
</div> </div>

View File

@ -8,13 +8,12 @@ Please see LICENSE in the repository root for full details.
import { ChangeEvent, FC, ReactNode, useCallback } from "react"; import { ChangeEvent, FC, ReactNode, useCallback } from "react";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { Dropdown } from "@vector-im/compound-web"; import { Dropdown, Text } from "@vector-im/compound-web";
import { Modal } from "../Modal"; import { Modal } from "../Modal";
import styles from "./SettingsModal.module.css"; import styles from "./SettingsModal.module.css";
import { Tab, TabContainer } from "../tabs/Tabs"; import { Tab, TabContainer } from "../tabs/Tabs";
import { FieldRow, InputField } from "../input/Input"; import { FieldRow, InputField } from "../input/Input";
import { Caption } from "../typography/Typography";
import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { ProfileSettingsTab } from "./ProfileSettingsTab"; import { ProfileSettingsTab } from "./ProfileSettingsTab";
import { FeedbackSettingsTab } from "./FeedbackSettingsTab"; import { FeedbackSettingsTab } from "./FeedbackSettingsTab";
@ -102,14 +101,14 @@ export const SettingsModal: FC<Props> = ({
}; };
const optInDescription = ( const optInDescription = (
<Caption> <Text size="sm">
<Trans i18nKey="settings.opt_in_description"> <Trans i18nKey="settings.opt_in_description">
<AnalyticsNotice /> <AnalyticsNotice />
<br /> <br />
You may withdraw consent by unchecking this box. If you are currently in You may withdraw consent by unchecking this box. If you are currently in
a call, this setting will take effect at the end of the call. a call, this setting will take effect at the end of the call.
</Trans> </Trans>
</Caption> </Text>
); );
const devices = useMediaDevices(); const devices = useMediaDevices();

View File

@ -1,47 +0,0 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
.caption {
font-size: var(--font-size-caption);
line-height: var(--font-size-body);
}
.micro {
font-size: var(--font-size-micro);
line-height: var(--font-size-caption);
}
.regular {
font-weight: 400;
}
.semiBold {
font-weight: 600;
}
.bold {
font-weight: 700;
}
.link {
color: var(--cpd-color-text-link-external);
}
.link:hover {
text-decoration: underline;
opacity: initial;
}
.primary {
color: var(--cpd-color-text-action-accent);
}
.overflowEllipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@ -1,262 +0,0 @@
/*
Copyright 2022-2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { createElement, forwardRef, ReactNode } from "react";
import classNames from "classnames";
import { Link as RouterLink } from "react-router-dom";
import * as H from "history";
import styles from "./Typography.module.css";
interface TypographyProps {
children: ReactNode;
fontWeight?: string;
className?: string;
overflowEllipsis?: boolean;
as?: string;
}
export const Headline = forwardRef<HTMLHeadingElement, TypographyProps>(
(
{
as: Component = "h1",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Headline.displayName = "Headline";
export const Title = forwardRef<HTMLHeadingElement, TypographyProps>(
(
{
as: Component = "h2",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Title.displayName = "Title";
export const Subtitle = forwardRef<HTMLParagraphElement, TypographyProps>(
(
{
as: Component = "h3",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Subtitle.displayName = "Subtitle";
export const Body = forwardRef<HTMLParagraphElement, TypographyProps>(
(
{
as: Component = "p",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Body.displayName = "Body";
export const Caption = forwardRef<HTMLParagraphElement, TypographyProps>(
(
{
as: Component = "p",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles.caption,
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Caption.displayName = "Caption";
export const Micro = forwardRef<HTMLParagraphElement, TypographyProps>(
(
{
as: Component = "p",
children,
className,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
return createElement(
Component,
{
...rest,
className: classNames(
styles.micro,
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref,
},
children,
);
},
);
Micro.displayName = "Micro";
interface LinkProps extends TypographyProps {
to?: H.LocationDescriptor<unknown>;
color?: string;
href?: string;
}
export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
(
{
as,
children,
className,
color = "link",
href,
to,
fontWeight,
overflowEllipsis,
...rest
},
ref,
) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const Component: string | RouterLink = as || (to ? RouterLink : "a");
let externalLinkProps: { href: string; target: string; rel: string };
if (href) {
externalLinkProps = {
href,
target: "_blank",
rel: "noreferrer noopener",
};
}
return createElement(
Component,
{
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
...externalLinkProps,
...rest,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
to: to,
className: classNames(
styles[color],
styles[fontWeight ?? ""],
{ [styles.overflowEllipsis]: overflowEllipsis },
className,
),
ref: ref,
},
children,
);
},
);
Link.displayName = "Link";