Use semantic headings in user settings Help & About (#10752)

* split SettingsSection out of SettingsTab, replace usage

* correct copyright

* use semantic headings in GeneralRoomSettingsTab

* use SettingsTab and SettingsSubsection in room settings

* fix VoipRoomSettingsTab

* use SettingsSection components in space settings

* settingssubsection text component

* use semantic headings in HelpUserSetttings tab

* use ExternalLink components for external links

* test

* strict

* lint
pull/28788/head^2
Kerry 2023-05-04 10:35:43 +12:00 committed by GitHub
parent bbdca11a02
commit 692d73dfe8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 233 additions and 211 deletions

View File

@ -20,13 +20,30 @@ limitations under the License.
}
.mx_SettingsSubsection_description {
margin-top: $spacing-8;
}
.mx_SettingsSubsection_text {
width: 100%;
box-sizing: inherit;
line-height: $font-24px;
margin-bottom: $spacing-24;
font-size: $font-15px;
color: $secondary-content;
}
.mx_SettingsSubsection_content {
width: 100%;
display: grid;
grid-gap: $spacing-8;
grid-template-columns: 1fr;
justify-items: flex-start;
margin-top: $spacing-24;
summary {
color: $accent;
}
details[open] {
summary {
margin-bottom: $spacing-8;
}
}
}

View File

@ -17,7 +17,6 @@ limitations under the License.
.mx_SettingsSubsectionHeading {
display: flex;
flex-direction: row;
padding-bottom: $spacing-8;
gap: $spacing-8;
}

View File

@ -24,10 +24,20 @@ export interface SettingsSubsectionProps extends HTMLAttributes<HTMLDivElement>
children?: React.ReactNode;
}
const SettingsSubsection: React.FC<SettingsSubsectionProps> = ({ heading, description, children, ...rest }) => (
export const SettingsSubsectionText: React.FC<HTMLAttributes<HTMLDivElement>> = ({ children, ...rest }) => (
<div {...rest} className="mx_SettingsSubsection_text">
{children}
</div>
);
export const SettingsSubsection: React.FC<SettingsSubsectionProps> = ({ heading, description, children, ...rest }) => (
<div {...rest} className="mx_SettingsSubsection">
{typeof heading === "string" ? <SettingsSubsectionHeading heading={heading} /> : <>{heading}</>}
{!!description && <div className="mx_SettingsSubsection_description">{description}</div>}
{!!description && (
<div className="mx_SettingsSubsection_description">
<SettingsSubsectionText>{description}</SettingsSubsectionText>
</div>
)}
<div className="mx_SettingsSubsection_content">{children}</div>
</div>
);

View File

@ -31,6 +31,9 @@ import { Action } from "../../../../../dispatcher/actions";
import { UserTab } from "../../../dialogs/UserTab";
import dis from "../../../../../dispatcher/dispatcher";
import CopyableText from "../../../elements/CopyableText";
import SettingsTab from "../SettingsTab";
import { SettingsSection } from "../../shared/SettingsSection";
import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection";
import ExternalLink from "../../../elements/ExternalLink";
interface IProps {
@ -115,18 +118,15 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
for (const tocEntry of tocLinks) {
legalLinks.push(
<div key={tocEntry.url}>
<ExternalLink href={tocEntry.url} rel="noreferrer noopener" target="_blank">
{tocEntry.text}
</ExternalLink>
<ExternalLink href={tocEntry.url}>{tocEntry.text}</ExternalLink>
</div>,
);
}
return (
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Legal")}</span>
<div className="mx_SettingsTab_subsectionText">{legalLinks}</div>
</div>
<SettingsSubsection heading={_t("Legal")}>
<SettingsSubsectionText>{legalLinks}</SettingsSubsectionText>
</SettingsSubsection>
);
}
@ -134,116 +134,95 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
// Note: This is not translated because it is legal text.
// Also, &nbsp; is ugly but necessary.
return (
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Credits")}</span>
<ul className="mx_SettingsTab_subsectionText">
<li>
{_t(
"The <photo>default cover photo</photo> is © " +
"<author>Jesús Roncero</author> used under the terms of <terms>CC-BY-SA 4.0</terms>.",
{},
{
photo: (sub) => (
<ExternalLink
href="themes/element/img/backgrounds/lake.jpg"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
author: (sub) => (
<ExternalLink
href="https://www.flickr.com/golan"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
terms: (sub) => (
<ExternalLink
href="https://creativecommons.org/licenses/by-sa/4.0/"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
},
)}
</li>
<li>
{_t(
"The <colr>twemoji-colr</colr> font is © <author>Mozilla Foundation</author> " +
"used under the terms of <terms>Apache 2.0</terms>.",
{},
{
colr: (sub) => (
<ExternalLink
href="https://github.com/matrix-org/twemoji-colr"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
author: (sub) => (
<ExternalLink href="https://mozilla.org" rel="noreferrer noopener" target="_blank">
{sub}
</ExternalLink>
),
terms: (sub) => (
<ExternalLink
href="https://www.apache.org/licenses/LICENSE-2.0"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
},
)}
</li>
<li>
{_t(
"The <twemoji>Twemoji</twemoji> emoji art is © " +
"<author>Twitter, Inc and other contributors</author> used under the terms of " +
"<terms>CC-BY 4.0</terms>.",
{},
{
twemoji: (sub) => (
<ExternalLink
href="https://twemoji.twitter.com/"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
author: (sub) => (
<ExternalLink
href="https://twemoji.twitter.com/"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
terms: (sub) => (
<ExternalLink
href="https://creativecommons.org/licenses/by/4.0/"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
},
)}
</li>
</ul>
</div>
<SettingsSubsection heading={_t("Credits")}>
<SettingsSubsectionText>
<ul>
<li>
{_t(
"The <photo>default cover photo</photo> is © " +
"<author>Jesús Roncero</author> used under the terms of <terms>CC-BY-SA 4.0</terms>.",
{},
{
photo: (sub) => (
<ExternalLink
href="themes/element/img/backgrounds/lake.jpg"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
author: (sub) => (
<ExternalLink href="https://www.flickr.com/golan">{sub}</ExternalLink>
),
terms: (sub) => (
<ExternalLink
href="https://creativecommons.org/licenses/by-sa/4.0/"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
},
)}
</li>
<li>
{_t(
"The <colr>twemoji-colr</colr> font is © <author>Mozilla Foundation</author> " +
"used under the terms of <terms>Apache 2.0</terms>.",
{},
{
colr: (sub) => (
<ExternalLink
href="https://github.com/matrix-org/twemoji-colr"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
author: (sub) => <ExternalLink href="https://mozilla.org">{sub}</ExternalLink>,
terms: (sub) => (
<ExternalLink
href="https://www.apache.org/licenses/LICENSE-2.0"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
},
)}
</li>
<li>
{_t(
"The <twemoji>Twemoji</twemoji> emoji art is © " +
"<author>Twitter, Inc and other contributors</author> used under the terms of " +
"<terms>CC-BY 4.0</terms>.",
{},
{
twemoji: (sub) => (
<ExternalLink href="https://twemoji.twitter.com/">{sub}</ExternalLink>
),
author: (sub) => (
<ExternalLink href="https://twemoji.twitter.com/">{sub}</ExternalLink>
),
terms: (sub) => (
<ExternalLink
href="https://creativecommons.org/licenses/by/4.0/"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
},
)}
</li>
</ul>
</SettingsSubsectionText>
</SettingsSubsection>
);
}
@ -268,11 +247,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
brand,
},
{
a: (sub) => (
<ExternalLink href="https://element.io/help" rel="noreferrer noopener" target="_blank">
{sub}
</ExternalLink>
),
a: (sub) => <ExternalLink href="https://element.io/help">{sub}</ExternalLink>,
},
);
if (SdkConfig.get("welcome_user_id") && getCurrentLanguage().startsWith("en")) {
@ -309,77 +284,73 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
let bugReportingSection;
if (SdkConfig.get().bug_report_endpoint_url) {
bugReportingSection = (
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Bug reporting")}</span>
<div className="mx_SettingsTab_subsectionText">
{_t(
"If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. ",
)}
{_t(
"Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
"the rooms you have visited, which UI elements you " +
"last interacted with, and the usernames of other users. " +
"They do not contain messages.",
)}
</div>
<SettingsSubsection
heading={_t("Bug reporting")}
description={
<>
<SettingsSubsectionText>
{_t(
"If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. ",
)}
</SettingsSubsectionText>
{_t(
"Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
"the rooms you have visited, which UI elements you " +
"last interacted with, and the usernames of other users. " +
"They do not contain messages.",
)}
</>
}
>
<AccessibleButton onClick={this.onBugReport} kind="primary">
{_t("Submit debug logs")}
</AccessibleButton>
<div className="mx_SettingsTab_subsectionText">
<SettingsSubsectionText>
{_t(
"To report a Matrix-related security issue, please read the Matrix.org " +
"<a>Security Disclosure Policy</a>.",
{},
{
a: (sub) => (
<ExternalLink
href="https://matrix.org/security-disclosure-policy/"
rel="noreferrer noopener"
target="_blank"
>
<ExternalLink href="https://matrix.org/security-disclosure-policy/">
{sub}
</ExternalLink>
),
},
)}
</div>
</div>
</SettingsSubsectionText>
</SettingsSubsection>
);
}
const { appVersion, olmVersion } = this.getVersionInfo();
return (
<div className="mx_SettingsTab mx_HelpUserSettingsTab">
<div className="mx_SettingsTab_heading">{_t("Help & About")}</div>
{bugReportingSection}
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("FAQ")}</span>
<div className="mx_SettingsTab_subsectionText">{faqText}</div>
<AccessibleButton kind="primary" onClick={this.onKeyboardShortcutsClicked}>
{_t("Keyboard Shortcuts")}
</AccessibleButton>
</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Versions")}</span>
<div className="mx_SettingsTab_subsectionText">
<CopyableText getTextToCopy={this.getVersionTextToCopy}>
{appVersion}
<br />
{olmVersion}
<br />
</CopyableText>
{updateButton}
</div>
</div>
{this.renderLegal()}
{this.renderCredits()}
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Advanced")}</span>
<div className="mx_SettingsTab_subsectionText">
<div>
<SettingsTab>
<SettingsSection heading={_t("Help & About")}>
{bugReportingSection}
<SettingsSubsection heading={_t("FAQ")} description={faqText}>
<AccessibleButton kind="primary" onClick={this.onKeyboardShortcutsClicked}>
{_t("Keyboard Shortcuts")}
</AccessibleButton>
</SettingsSubsection>
<SettingsSubsection heading={_t("Versions")}>
<SettingsSubsectionText>
<CopyableText getTextToCopy={this.getVersionTextToCopy}>
{appVersion}
<br />
{olmVersion}
<br />
</CopyableText>
{updateButton}
</SettingsSubsectionText>
</SettingsSubsection>
{this.renderLegal()}
{this.renderCredits()}
<SettingsSubsection heading={_t("Advanced")}>
<SettingsSubsectionText>
{_t(
"Homeserver is <code>%(homeserverUrl)s</code>",
{
@ -389,10 +360,10 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
code: (sub) => <code>{sub}</code>,
},
)}
</div>
<div>
{MatrixClientPeg.get().getIdentityServerUrl() &&
_t(
</SettingsSubsectionText>
{MatrixClientPeg.get().getIdentityServerUrl() && (
<SettingsSubsectionText>
{_t(
"Identity server is <code>%(identityServerUrl)s</code>",
{
identityServerUrl: MatrixClientPeg.get().getIdentityServerUrl(),
@ -401,25 +372,28 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
code: (sub) => <code>{sub}</code>,
},
)}
</div>
<details>
<summary>{_t("Access Token")}</summary>
<b>
{_t(
"Your access token gives full access to your account." +
" Do not share it with anyone.",
)}
</b>
<CopyableText getTextToCopy={() => MatrixClientPeg.get().getAccessToken()}>
{MatrixClientPeg.get().getAccessToken()}
</CopyableText>
</details>
</SettingsSubsectionText>
)}
<SettingsSubsectionText>
<details>
<summary>{_t("Access Token")}</summary>
<b>
{_t(
"Your access token gives full access to your account." +
" Do not share it with anyone.",
)}
</b>
<CopyableText getTextToCopy={() => MatrixClientPeg.get().getAccessToken()}>
{MatrixClientPeg.get().getAccessToken()}
</CopyableText>
</details>
</SettingsSubsectionText>
<AccessibleButton onClick={this.onClearCacheAndReload} kind="danger">
{_t("Clear cache and reload")}
</AccessibleButton>
</div>
</div>
</div>
</SettingsSubsection>
</SettingsSection>
</SettingsTab>
);
}
}

View File

@ -74,7 +74,9 @@ describe("<UserSettingsDialog />", () => {
const getActiveTabLabel = (container: Element) =>
container.querySelector(".mx_TabbedView_tabLabel_active")?.textContent;
const getActiveTabHeading = (container: Element) => container.querySelector(".mx_SettingsTab_heading")?.textContent;
const getActiveTabHeading = (container: Element) =>
container.querySelector(".mx_SettingsTab_heading")?.textContent ||
container.querySelector(".mx_SettingsSection .mx_Heading_h2")?.textContent;
it("should render general settings tab when no initialTabId", () => {
const { container } = render(getComponent());

View File

@ -18,7 +18,11 @@ exports[`<SecurityRecommendations /> renders both cards when user has both unver
<div
class="mx_SettingsSubsection_description"
>
Improve your account security by following these recommendations.
<div
class="mx_SettingsSubsection_text"
>
Improve your account security by following these recommendations.
</div>
</div>
<div
class="mx_SettingsSubsection_content"
@ -139,7 +143,11 @@ exports[`<SecurityRecommendations /> renders inactive devices section when user
<div
class="mx_SettingsSubsection_description"
>
Improve your account security by following these recommendations.
<div
class="mx_SettingsSubsection_text"
>
Improve your account security by following these recommendations.
</div>
</div>
<div
class="mx_SettingsSubsection_content"
@ -260,7 +268,11 @@ exports[`<SecurityRecommendations /> renders unverified devices section when use
<div
class="mx_SettingsSubsection_description"
>
Improve your account security by following these recommendations.
<div
class="mx_SettingsSubsection_text"
>
Improve your account security by following these recommendations.
</div>
</div>
<div
class="mx_SettingsSubsection_content"

View File

@ -17,7 +17,11 @@ exports[`<SettingsSubsection /> renders with plain text description 1`] = `
<div
class="mx_SettingsSubsection_description"
>
This describes the subsection
<div
class="mx_SettingsSubsection_text"
>
This describes the subsection
</div>
</div>
<div
class="mx_SettingsSubsection_content"
@ -72,14 +76,18 @@ exports[`<SettingsSubsection /> renders with react element description 1`] = `
<div
class="mx_SettingsSubsection_description"
>
<p>
This describes the section
<a
href="/#"
>
link
</a>
</p>
<div
class="mx_SettingsSubsection_text"
>
<p>
This describes the section
<a
href="/#"
>
link
</a>
</p>
</div>
</div>
<div
class="mx_SettingsSubsection_content"