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 { .mx_SettingsSubsection_description {
margin-top: $spacing-8;
}
.mx_SettingsSubsection_text {
width: 100%; width: 100%;
box-sizing: inherit; box-sizing: inherit;
line-height: $font-24px; font-size: $font-15px;
margin-bottom: $spacing-24;
color: $secondary-content; color: $secondary-content;
} }
.mx_SettingsSubsection_content { .mx_SettingsSubsection_content {
width: 100%; 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 { .mx_SettingsSubsectionHeading {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
padding-bottom: $spacing-8;
gap: $spacing-8; gap: $spacing-8;
} }

View File

@ -24,10 +24,20 @@ export interface SettingsSubsectionProps extends HTMLAttributes<HTMLDivElement>
children?: React.ReactNode; 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"> <div {...rest} className="mx_SettingsSubsection">
{typeof heading === "string" ? <SettingsSubsectionHeading heading={heading} /> : <>{heading}</>} {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 className="mx_SettingsSubsection_content">{children}</div>
</div> </div>
); );

View File

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

View File

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

View File

@ -18,7 +18,11 @@ exports[`<SecurityRecommendations /> renders both cards when user has both unver
<div <div
class="mx_SettingsSubsection_description" 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>
<div <div
class="mx_SettingsSubsection_content" class="mx_SettingsSubsection_content"
@ -139,7 +143,11 @@ exports[`<SecurityRecommendations /> renders inactive devices section when user
<div <div
class="mx_SettingsSubsection_description" 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>
<div <div
class="mx_SettingsSubsection_content" class="mx_SettingsSubsection_content"
@ -260,7 +268,11 @@ exports[`<SecurityRecommendations /> renders unverified devices section when use
<div <div
class="mx_SettingsSubsection_description" 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>
<div <div
class="mx_SettingsSubsection_content" class="mx_SettingsSubsection_content"

View File

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