Refine `SettingsSection` & `SettingsTab`

florianduros/encryption-tab
Florian Duros 2024-12-04 14:35:24 +01:00
parent aa44cadb02
commit 3e77b3d537
No known key found for this signature in database
GPG Key ID: A5BBB4041B493F15
9 changed files with 181 additions and 9 deletions

View File

@ -346,6 +346,8 @@
@import "./views/settings/_SetIdServer.pcss";
@import "./views/settings/_SetIntegrationManager.pcss";
@import "./views/settings/_SettingsFieldset.pcss";
@import "./views/settings/_SettingsHeader.pcss";
@import "./views/settings/_SettingsSubheader.pcss";
@import "./views/settings/_SpellCheckLanguages.pcss";
@import "./views/settings/_ThemeChoicePanel.pcss";
@import "./views/settings/_UpdateCheckButton.pcss";

View File

@ -0,0 +1,19 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
.mx_SettingsHeader {
display: flex;
align-items: center;
gap: var(--cpd-space-2x);
/* Override margin from common.pcss */
margin: 0;
> span {
font: var(--cpd-font-body-sm-medium);
color: var(--cpd-color-text-action-accent);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
.mx_SettingsSubheader {
display: flex;
flex-direction: column;
gap: var(--cpd-space-2x);
> span {
display: flex;
align-items: center;
gap: var(--cpd-space-2x);
font: var(--cpd-font-body-sm-medium);
}
.mx_SettingsSubheader_success {
color: var(--cpd-color-text-success-primary);
}
.mx_SettingsSubheader_error {
color: var(--cpd-color-text-critical-primary);
}
}

View File

@ -15,6 +15,20 @@ Please see LICENSE files in the repository root for full details.
a {
color: $links;
}
&.mx_SettingsSection_newUi {
display: flex;
flex-direction: column;
gap: var(--cpd-space-6x);
align-items: start;
}
.mx_SettingsSection_header {
display: flex;
flex-direction: column;
gap: var(--cpd-space-3x);
color: var(--cpd-color-text-secondary);
}
}
.mx_SettingsSection_subSections {

View File

@ -0,0 +1,33 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import React, { JSX } from "react";
import { Heading } from "@vector-im/compound-web";
import { _t } from "../../../languageHandler";
/**
* The heading for a settings section.
*/
interface SettingsHeaderProps {
/**
* Whether the user has a recommended tag.
*/
hasRecommendedTag?: boolean;
/**
* The label for the header.
*/
label: string;
}
export function SettingsHeader({ hasRecommendedTag = false, label }: SettingsHeaderProps): JSX.Element {
return (
<Heading className="mx_SettingsHeader" as="h2" size="sm" weight="semibold">
{label} {hasRecommendedTag && <span>{_t("common|recommended")}</span>}
</Heading>
);
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import React, { JSX } from "react";
import CheckCircleIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
import classNames from "classnames";
interface SettingsSubheaderProps {
/**
* The subheader text.
*/
label?: string;
/**
* The state of the subheader.
*/
state: "success" | "error";
/**
* The message to display next to the state icon.
*/
stateMessage: string;
}
/**
* A styled subheader for settings.
*/
export function SettingsSubheader({ label, state, stateMessage }: SettingsSubheaderProps): JSX.Element {
return (
<div className="mx_SettingsSubheader">
{label}
<span
className={classNames({
mx_SettingsSubheader_success: state === "success",
mx_SettingsSubheader_error: state === "error",
})}
>
{state === "success" ? (
<CheckCircleIcon width="20" height="20" />
) : (
<ErrorIcon width="20" height="20" />
)}
{stateMessage}
</span>
</div>
);
}

View File

@ -10,19 +10,24 @@ import classnames from "classnames";
import React, { HTMLAttributes } from "react";
import Heading from "../../typography/Heading";
import { SettingsHeader } from "../SettingsHeader";
export interface SettingsSectionProps extends HTMLAttributes<HTMLDivElement> {
heading?: string | React.ReactNode;
subHeading?: string | React.ReactNode;
children?: React.ReactNode;
legacy?: boolean;
}
function renderHeading(heading: string | React.ReactNode | undefined): React.ReactNode | undefined {
function renderHeading(heading: string | React.ReactNode | undefined, legacy: boolean): React.ReactNode | undefined {
switch (typeof heading) {
case "string":
return (
return legacy ? (
<Heading as="h2" size="3">
{heading}
</Heading>
) : (
<SettingsHeader label={heading} />
);
case "undefined":
return undefined;
@ -48,9 +53,29 @@ function renderHeading(heading: string | React.ReactNode | undefined): React.Rea
* </SettingsTab>
* ```
*/
export const SettingsSection: React.FC<SettingsSectionProps> = ({ className, heading, children, ...rest }) => (
<div {...rest} className={classnames("mx_SettingsSection", className)}>
{renderHeading(heading)}
<div className="mx_SettingsSection_subSections">{children}</div>
export const SettingsSection: React.FC<SettingsSectionProps> = ({
className,
heading,
subHeading,
legacy = true,
children,
...rest
}) => (
<div
{...rest}
className={classnames("mx_SettingsSection", className, {
mx_SettingsSection_newUi: !legacy,
})}
>
{heading &&
(subHeading ? (
<div className="mx_SettingsSection_header">
{renderHeading(heading, legacy)}
{subHeading}
</div>
) : (
renderHeading(heading, legacy)
))}
{legacy ? <div className="mx_SettingsSection_subSections">{children}</div> : children}
</div>
);

View File

@ -6,8 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React, { HTMLAttributes } from "react";
import classNames from "classnames";
export interface SettingsTabProps extends Omit<HTMLAttributes<HTMLDivElement>, "className"> {
export interface SettingsTabProps extends HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
}
@ -29,8 +30,8 @@ export interface SettingsTabProps extends Omit<HTMLAttributes<HTMLDivElement>, "
* </SettingsTab>
* ```
*/
const SettingsTab: React.FC<SettingsTabProps> = ({ children, ...rest }) => (
<div {...rest} className="mx_SettingsTab">
const SettingsTab: React.FC<SettingsTabProps> = ({ children, className, ...rest }) => (
<div {...rest} className={classNames("mx_SettingsTab", className)}>
<div className="mx_SettingsTab_sections">{children}</div>
</div>
);

View File

@ -542,6 +542,7 @@
"qr_code": "QR Code",
"random": "Random",
"reactions": "Reactions",
"recommended": "Recommended",
"report_a_bug": "Report a bug",
"room": "Room",
"room_name": "Room name",