mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-12-28 03:13:14 +00:00
fix(tabs): Fix tab semantics and a11y (#1382)
* fix: Fix tab semantics for Settings page * fix: Use new tabpanel markup for admin settings * fix: Remove unused currentTab behavior * fix: Remove Bootstrap tab JS dependency * fix: Add tabpanel role to rate limit tab panels * fix: Fix style of tabs --------- Co-authored-by: SleeplessOne1917 <abias1122@gmail.com> Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
parent
f19271eba9
commit
40eefb0c67
|
@ -1,8 +1,9 @@
|
||||||
|
import classNames from "classnames";
|
||||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||||
|
|
||||||
interface TabItem {
|
interface TabItem {
|
||||||
key: string;
|
key: string;
|
||||||
getNode: () => InfernoNode;
|
getNode: (isSelected: boolean) => InfernoNode;
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,24 +31,33 @@ export default class Tabs extends Component<TabsProps, TabsState> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ul className="nav nav-tabs mb-2">
|
<ul className="nav nav-tabs mb-2" role="tablist">
|
||||||
{this.props.tabs.map(({ key, label }) => (
|
{this.props.tabs.map(({ key, label }) => (
|
||||||
<li key={key} className="nav-item">
|
<li key={key} className="nav-item">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`nav-link btn${
|
className={classNames("nav-link", {
|
||||||
this.state?.currentTab === key ? " active" : ""
|
active: this.state?.currentTab === key,
|
||||||
}`}
|
})}
|
||||||
onClick={linkEvent({ ctx: this, tab: key }, handleSwitchTab)}
|
onClick={linkEvent({ ctx: this, tab: key }, handleSwitchTab)}
|
||||||
|
aria-controls={`${key}-tab-pane`}
|
||||||
|
{...(this.state?.currentTab === key && {
|
||||||
|
...{
|
||||||
|
"aria-current": "page",
|
||||||
|
"aria-selected": "true",
|
||||||
|
},
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
{this.props.tabs
|
<div className="tab-content">
|
||||||
.find(tab => tab.key === this.state?.currentTab)
|
{this.props.tabs.map(({ key, getNode }) => {
|
||||||
?.getNode()}
|
return getNode(this.state?.currentTab === key);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import classNames from "classnames";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import {
|
import {
|
||||||
BannedPersonsResponse,
|
BannedPersonsResponse,
|
||||||
|
@ -130,22 +131,30 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
{
|
{
|
||||||
key: "site",
|
key: "site",
|
||||||
label: i18n.t("site"),
|
label: i18n.t("site"),
|
||||||
getNode: () => (
|
getNode: isSelected => (
|
||||||
<div className="row">
|
<div
|
||||||
<div className="col-12 col-md-6">
|
className={classNames("tab-pane show", {
|
||||||
<SiteForm
|
active: isSelected,
|
||||||
showLocal={showLocal(this.isoData)}
|
})}
|
||||||
allowedInstances={federationData?.allowed}
|
role="tabpanel"
|
||||||
blockedInstances={federationData?.blocked}
|
id="site-tab-pane"
|
||||||
onSaveSite={this.handleEditSite}
|
>
|
||||||
siteRes={this.state.siteRes}
|
<div className="row">
|
||||||
themeList={this.state.themeList}
|
<div className="col-12 col-md-6">
|
||||||
loading={this.state.loading}
|
<SiteForm
|
||||||
/>
|
showLocal={showLocal(this.isoData)}
|
||||||
</div>
|
allowedInstances={federationData?.allowed}
|
||||||
<div className="col-12 col-md-6">
|
blockedInstances={federationData?.blocked}
|
||||||
{this.admins()}
|
onSaveSite={this.handleEditSite}
|
||||||
{this.bannedUsers()}
|
siteRes={this.state.siteRes}
|
||||||
|
themeList={this.state.themeList}
|
||||||
|
loading={this.state.loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
{this.admins()}
|
||||||
|
{this.bannedUsers()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
@ -153,40 +162,64 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
{
|
{
|
||||||
key: "rate_limiting",
|
key: "rate_limiting",
|
||||||
label: "Rate Limiting",
|
label: "Rate Limiting",
|
||||||
getNode: () => (
|
getNode: isSelected => (
|
||||||
<RateLimitForm
|
<div
|
||||||
rateLimits={
|
className={classNames("tab-pane", {
|
||||||
this.state.siteRes.site_view.local_site_rate_limit
|
active: isSelected,
|
||||||
}
|
})}
|
||||||
onSaveSite={this.handleEditSite}
|
role="tabpanel"
|
||||||
loading={this.state.loading}
|
id="rate_limiting-tab-pane"
|
||||||
/>
|
>
|
||||||
),
|
<RateLimitForm
|
||||||
},
|
rateLimits={
|
||||||
{
|
this.state.siteRes.site_view.local_site_rate_limit
|
||||||
key: "taglines",
|
}
|
||||||
label: i18n.t("taglines"),
|
|
||||||
getNode: () => (
|
|
||||||
<div className="row">
|
|
||||||
<TaglineForm
|
|
||||||
taglines={this.state.siteRes.taglines}
|
|
||||||
onSaveSite={this.handleEditSite}
|
onSaveSite={this.handleEditSite}
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "taglines",
|
||||||
|
label: i18n.t("taglines"),
|
||||||
|
getNode: isSelected => (
|
||||||
|
<div
|
||||||
|
className={classNames("tab-pane", {
|
||||||
|
active: isSelected,
|
||||||
|
})}
|
||||||
|
role="tabpanel"
|
||||||
|
id="taglines-tab-pane"
|
||||||
|
>
|
||||||
|
<div className="row">
|
||||||
|
<TaglineForm
|
||||||
|
taglines={this.state.siteRes.taglines}
|
||||||
|
onSaveSite={this.handleEditSite}
|
||||||
|
loading={this.state.loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "emojis",
|
key: "emojis",
|
||||||
label: i18n.t("emojis"),
|
label: i18n.t("emojis"),
|
||||||
getNode: () => (
|
getNode: isSelected => (
|
||||||
<div className="row">
|
<div
|
||||||
<EmojiForm
|
className={classNames("tab-pane", {
|
||||||
onCreate={this.handleCreateEmoji}
|
active: isSelected,
|
||||||
onDelete={this.handleDeleteEmoji}
|
})}
|
||||||
onEdit={this.handleEditEmoji}
|
role="tabpanel"
|
||||||
loading={this.state.emojiLoading}
|
id="emojis-tab-pane"
|
||||||
/>
|
>
|
||||||
|
<div className="row">
|
||||||
|
<EmojiForm
|
||||||
|
onCreate={this.handleCreateEmoji}
|
||||||
|
onDelete={this.handleDeleteEmoji}
|
||||||
|
onEdit={this.handleEditEmoji}
|
||||||
|
loading={this.state.emojiLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import classNames from "classnames";
|
||||||
import { Component, FormEventHandler, linkEvent } from "inferno";
|
import { Component, FormEventHandler, linkEvent } from "inferno";
|
||||||
import { EditSite, LocalSiteRateLimit } from "lemmy-js-client";
|
import { EditSite, LocalSiteRateLimit } from "lemmy-js-client";
|
||||||
import { i18n } from "../../i18next";
|
import { i18n } from "../../i18next";
|
||||||
|
@ -19,6 +20,7 @@ interface RateLimitsProps {
|
||||||
handleRateLimitPerSecond: FormEventHandler<HTMLInputElement>;
|
handleRateLimitPerSecond: FormEventHandler<HTMLInputElement>;
|
||||||
rateLimitValue?: number;
|
rateLimitValue?: number;
|
||||||
rateLimitPerSecondValue?: number;
|
rateLimitPerSecondValue?: number;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RateLimitFormProps {
|
interface RateLimitFormProps {
|
||||||
|
@ -49,9 +51,10 @@ function RateLimits({
|
||||||
handleRateLimitPerSecond,
|
handleRateLimitPerSecond,
|
||||||
rateLimitPerSecondValue,
|
rateLimitPerSecondValue,
|
||||||
rateLimitValue,
|
rateLimitValue,
|
||||||
|
className,
|
||||||
}: RateLimitsProps) {
|
}: RateLimitsProps) {
|
||||||
return (
|
return (
|
||||||
<div className="mb-3 row">
|
<div role="tabpanel" className={classNames("mb-3 row", className)}>
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label htmlFor="rate-limit">{i18n.t("rate_limit")}</label>
|
<label htmlFor="rate-limit">{i18n.t("rate_limit")}</label>
|
||||||
<input
|
<input
|
||||||
|
@ -142,8 +145,11 @@ export default class RateLimitsForm extends Component<
|
||||||
tabs={rateLimitTypes.map(rateLimitType => ({
|
tabs={rateLimitTypes.map(rateLimitType => ({
|
||||||
key: rateLimitType,
|
key: rateLimitType,
|
||||||
label: i18n.t(`rate_limit_${rateLimitType}`),
|
label: i18n.t(`rate_limit_${rateLimitType}`),
|
||||||
getNode: () => (
|
getNode: isSelected => (
|
||||||
<RateLimits
|
<RateLimits
|
||||||
|
className={classNames("tab-pane show", {
|
||||||
|
active: isSelected,
|
||||||
|
})}
|
||||||
handleRateLimit={linkEvent(
|
handleRateLimit={linkEvent(
|
||||||
{ rateLimitType, ctx: this },
|
{ rateLimitType, ctx: this },
|
||||||
handleRateLimitChange
|
handleRateLimitChange
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { debounce } from "@utils/helpers";
|
import { debounce } from "@utils/helpers";
|
||||||
|
import classNames from "classnames";
|
||||||
import { NoOptionI18nKeys } from "i18next";
|
import { NoOptionI18nKeys } from "i18next";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import {
|
import {
|
||||||
|
@ -265,34 +266,50 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
userSettings() {
|
userSettings(isSelected) {
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div
|
||||||
<div className="col-12 col-md-6">
|
className={classNames("tab-pane show", {
|
||||||
<div className="card border-secondary mb-3">
|
active: isSelected,
|
||||||
<div className="card-body">{this.saveUserSettingsHtmlForm()}</div>
|
})}
|
||||||
|
role="tabpanel"
|
||||||
|
id="settings-tab-pane"
|
||||||
|
>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="card border-secondary mb-3">
|
||||||
|
<div className="card-body">{this.saveUserSettingsHtmlForm()}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="col-12 col-md-6">
|
||||||
<div className="col-12 col-md-6">
|
<div className="card border-secondary mb-3">
|
||||||
<div className="card border-secondary mb-3">
|
<div className="card-body">{this.changePasswordHtmlForm()}</div>
|
||||||
<div className="card-body">{this.changePasswordHtmlForm()}</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
blockCards() {
|
blockCards(isSelected) {
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div
|
||||||
<div className="col-12 col-md-6">
|
className={classNames("tab-pane", {
|
||||||
<div className="card border-secondary mb-3">
|
active: isSelected,
|
||||||
<div className="card-body">{this.blockUserCard()}</div>
|
})}
|
||||||
|
role="tabpanel"
|
||||||
|
id="blocks-tab-pane"
|
||||||
|
>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="card border-secondary mb-3">
|
||||||
|
<div className="card-body">{this.blockUserCard()}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="col-12 col-md-6">
|
||||||
<div className="col-12 col-md-6">
|
<div className="card border-secondary mb-3">
|
||||||
<div className="card border-secondary mb-3">
|
<div className="card-body">{this.blockCommunityCard()}</div>
|
||||||
<div className="card-body">{this.blockCommunityCard()}</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue