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,9 +134,9 @@ 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 © " +
@ -153,13 +153,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
</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"
target="_blank"
>
{sub}
</ExternalLink>
), ),
terms: (sub) => ( terms: (sub) => (
<ExternalLink <ExternalLink
@ -188,11 +182,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
{sub} {sub}
</ExternalLink> </ExternalLink>
), ),
author: (sub) => ( author: (sub) => <ExternalLink href="https://mozilla.org">{sub}</ExternalLink>,
<ExternalLink href="https://mozilla.org" rel="noreferrer noopener" target="_blank">
{sub}
</ExternalLink>
),
terms: (sub) => ( terms: (sub) => (
<ExternalLink <ExternalLink
href="https://www.apache.org/licenses/LICENSE-2.0" href="https://www.apache.org/licenses/LICENSE-2.0"
@ -213,22 +203,10 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
{}, {},
{ {
twemoji: (sub) => ( twemoji: (sub) => (
<ExternalLink <ExternalLink href="https://twemoji.twitter.com/">{sub}</ExternalLink>
href="https://twemoji.twitter.com/"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
), ),
author: (sub) => ( author: (sub) => (
<ExternalLink <ExternalLink href="https://twemoji.twitter.com/">{sub}</ExternalLink>
href="https://twemoji.twitter.com/"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
), ),
terms: (sub) => ( terms: (sub) => (
<ExternalLink <ExternalLink
@ -243,7 +221,8 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
)} )}
</li> </li>
</ul> </ul>
</div> </SettingsSubsectionText>
</SettingsSubsection>
); );
} }
@ -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,13 +284,16 @@ 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={
<>
<SettingsSubsectionText>
{_t( {_t(
"If you've submitted a bug via GitHub, debug logs can help " + "If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. ", "us track down the problem. ",
)} )}
</SettingsSubsectionText>
{_t( {_t(
"Debug logs contain application " + "Debug logs contain application " +
"usage data including your username, the IDs or aliases of " + "usage data including your username, the IDs or aliases of " +
@ -323,48 +301,43 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
"last interacted with, and the usernames of other users. " + "last interacted with, and the usernames of other users. " +
"They do not contain messages.", "They do not contain messages.",
)} )}
</div> </>
}
>
<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>
<div className="mx_SettingsTab_subsectionText">{faqText}</div>
<AccessibleButton kind="primary" onClick={this.onKeyboardShortcutsClicked}> <AccessibleButton kind="primary" onClick={this.onKeyboardShortcutsClicked}>
{_t("Keyboard Shortcuts")} {_t("Keyboard Shortcuts")}
</AccessibleButton> </AccessibleButton>
</div> </SettingsSubsection>
<div className="mx_SettingsTab_section"> <SettingsSubsection heading={_t("Versions")}>
<span className="mx_SettingsTab_subheading">{_t("Versions")}</span> <SettingsSubsectionText>
<div className="mx_SettingsTab_subsectionText">
<CopyableText getTextToCopy={this.getVersionTextToCopy}> <CopyableText getTextToCopy={this.getVersionTextToCopy}>
{appVersion} {appVersion}
<br /> <br />
@ -372,14 +345,12 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
<br /> <br />
</CopyableText> </CopyableText>
{updateButton} {updateButton}
</div> </SettingsSubsectionText>
</div> </SettingsSubsection>
{this.renderLegal()} {this.renderLegal()}
{this.renderCredits()} {this.renderCredits()}
<div className="mx_SettingsTab_section"> <SettingsSubsection heading={_t("Advanced")}>
<span className="mx_SettingsTab_subheading">{_t("Advanced")}</span> <SettingsSubsectionText>
<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,7 +372,9 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
code: (sub) => <code>{sub}</code>, code: (sub) => <code>{sub}</code>,
}, },
)} )}
</div> </SettingsSubsectionText>
)}
<SettingsSubsectionText>
<details> <details>
<summary>{_t("Access Token")}</summary> <summary>{_t("Access Token")}</summary>
<b> <b>
@ -414,12 +387,13 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
{MatrixClientPeg.get().getAccessToken()} {MatrixClientPeg.get().getAccessToken()}
</CopyableText> </CopyableText>
</details> </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

@ -17,9 +17,13 @@ exports[`<SecurityRecommendations /> renders both cards when user has both unver
</div> </div>
<div <div
class="mx_SettingsSubsection_description" class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
> >
Improve your account security by following these recommendations. Improve your account security by following these recommendations.
</div> </div>
</div>
<div <div
class="mx_SettingsSubsection_content" class="mx_SettingsSubsection_content"
> >
@ -138,9 +142,13 @@ exports[`<SecurityRecommendations /> renders inactive devices section when user
</div> </div>
<div <div
class="mx_SettingsSubsection_description" class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
> >
Improve your account security by following these recommendations. Improve your account security by following these recommendations.
</div> </div>
</div>
<div <div
class="mx_SettingsSubsection_content" class="mx_SettingsSubsection_content"
> >
@ -259,9 +267,13 @@ exports[`<SecurityRecommendations /> renders unverified devices section when use
</div> </div>
<div <div
class="mx_SettingsSubsection_description" class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
> >
Improve your account security by following these recommendations. Improve your account security by following these recommendations.
</div> </div>
</div>
<div <div
class="mx_SettingsSubsection_content" class="mx_SettingsSubsection_content"
> >

View File

@ -16,9 +16,13 @@ exports[`<SettingsSubsection /> renders with plain text description 1`] = `
</div> </div>
<div <div
class="mx_SettingsSubsection_description" class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
> >
This describes the subsection This describes the subsection
</div> </div>
</div>
<div <div
class="mx_SettingsSubsection_content" class="mx_SettingsSubsection_content"
> >
@ -71,6 +75,9 @@ exports[`<SettingsSubsection /> renders with react element description 1`] = `
</div> </div>
<div <div
class="mx_SettingsSubsection_description" class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
> >
<p> <p>
This describes the section This describes the section
@ -81,6 +88,7 @@ exports[`<SettingsSubsection /> renders with react element description 1`] = `
</a> </a>
</p> </p>
</div> </div>
</div>
<div <div
class="mx_SettingsSubsection_content" class="mx_SettingsSubsection_content"
> >