]> Untitled Git - lemmy-ui.git/commitdiff
fix(tabs): Fix tab semantics and a11y (#1382)
authorJay Sitter <jsit@users.noreply.github.com>
Wed, 21 Jun 2023 12:27:27 +0000 (08:27 -0400)
committerGitHub <noreply@github.com>
Wed, 21 Jun 2023 12:27:27 +0000 (08:27 -0400)
* 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>
src/shared/components/common/tabs.tsx
src/shared/components/home/admin-settings.tsx
src/shared/components/home/rate-limit-form.tsx
src/shared/components/person/settings.tsx

index 17980a476ce2b6f448a9c56039da29b7cd6b0dcc..3c2726a56727f0e1447d44f8d4ff290aa9f905c8 100644 (file)
@@ -1,8 +1,9 @@
+import classNames from "classnames";
 import { Component, InfernoNode, linkEvent } from "inferno";
 
 interface TabItem {
   key: string;
-  getNode: () => InfernoNode;
+  getNode: (isSelected: boolean) => InfernoNode;
   label: string;
 }
 
@@ -30,24 +31,33 @@ export default class Tabs extends Component<TabsProps, TabsState> {
   render() {
     return (
       <div>
-        <ul className="nav nav-tabs mb-2">
+        <ul className="nav nav-tabs mb-2" role="tablist">
           {this.props.tabs.map(({ key, label }) => (
             <li key={key} className="nav-item">
               <button
                 type="button"
-                className={`nav-link btn${
-                  this.state?.currentTab === key ? " active" : ""
-                }`}
+                className={classNames("nav-link", {
+                  active: this.state?.currentTab === key,
+                })}
                 onClick={linkEvent({ ctx: this, tab: key }, handleSwitchTab)}
+                aria-controls={`${key}-tab-pane`}
+                {...(this.state?.currentTab === key && {
+                  ...{
+                    "aria-current": "page",
+                    "aria-selected": "true",
+                  },
+                })}
               >
                 {label}
               </button>
             </li>
           ))}
         </ul>
-        {this.props.tabs
-          .find(tab => tab.key === this.state?.currentTab)
-          ?.getNode()}
+        <div className="tab-content">
+          {this.props.tabs.map(({ key, getNode }) => {
+            return getNode(this.state?.currentTab === key);
+          })}
+        </div>
       </div>
     );
   }
index 1de9f8751a2178beb6e59cd061423a8e00750e3f..23454ab9db8934c98fc0e560a01ac0b497d8e4c6 100644 (file)
@@ -1,3 +1,4 @@
+import classNames from "classnames";
 import { Component, linkEvent } from "inferno";
 import {
   BannedPersonsResponse,
@@ -130,22 +131,30 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
             {
               key: "site",
               label: i18n.t("site"),
-              getNode: () => (
-                <div className="row">
-                  <div className="col-12 col-md-6">
-                    <SiteForm
-                      showLocal={showLocal(this.isoData)}
-                      allowedInstances={federationData?.allowed}
-                      blockedInstances={federationData?.blocked}
-                      onSaveSite={this.handleEditSite}
-                      siteRes={this.state.siteRes}
-                      themeList={this.state.themeList}
-                      loading={this.state.loading}
-                    />
-                  </div>
-                  <div className="col-12 col-md-6">
-                    {this.admins()}
-                    {this.bannedUsers()}
+              getNode: isSelected => (
+                <div
+                  className={classNames("tab-pane show", {
+                    active: isSelected,
+                  })}
+                  role="tabpanel"
+                  id="site-tab-pane"
+                >
+                  <div className="row">
+                    <div className="col-12 col-md-6">
+                      <SiteForm
+                        showLocal={showLocal(this.isoData)}
+                        allowedInstances={federationData?.allowed}
+                        blockedInstances={federationData?.blocked}
+                        onSaveSite={this.handleEditSite}
+                        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>
               ),
@@ -153,40 +162,64 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
             {
               key: "rate_limiting",
               label: "Rate Limiting",
-              getNode: () => (
-                <RateLimitForm
-                  rateLimits={
-                    this.state.siteRes.site_view.local_site_rate_limit
-                  }
-                  onSaveSite={this.handleEditSite}
-                  loading={this.state.loading}
-                />
+              getNode: isSelected => (
+                <div
+                  className={classNames("tab-pane", {
+                    active: isSelected,
+                  })}
+                  role="tabpanel"
+                  id="rate_limiting-tab-pane"
+                >
+                  <RateLimitForm
+                    rateLimits={
+                      this.state.siteRes.site_view.local_site_rate_limit
+                    }
+                    onSaveSite={this.handleEditSite}
+                    loading={this.state.loading}
+                  />
+                </div>
               ),
             },
             {
               key: "taglines",
               label: i18n.t("taglines"),
-              getNode: () => (
-                <div className="row">
-                  <TaglineForm
-                    taglines={this.state.siteRes.taglines}
-                    onSaveSite={this.handleEditSite}
-                    loading={this.state.loading}
-                  />
+              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",
               label: i18n.t("emojis"),
-              getNode: () => (
-                <div className="row">
-                  <EmojiForm
-                    onCreate={this.handleCreateEmoji}
-                    onDelete={this.handleDeleteEmoji}
-                    onEdit={this.handleEditEmoji}
-                    loading={this.state.emojiLoading}
-                  />
+              getNode: isSelected => (
+                <div
+                  className={classNames("tab-pane", {
+                    active: isSelected,
+                  })}
+                  role="tabpanel"
+                  id="emojis-tab-pane"
+                >
+                  <div className="row">
+                    <EmojiForm
+                      onCreate={this.handleCreateEmoji}
+                      onDelete={this.handleDeleteEmoji}
+                      onEdit={this.handleEditEmoji}
+                      loading={this.state.emojiLoading}
+                    />
+                  </div>
                 </div>
               ),
             },
index 1b2e281b243402bd7802269e7c07a22a379493ce..11c1a8e85b17f936eb148e98d3969823016b9c43 100644 (file)
@@ -1,3 +1,4 @@
+import classNames from "classnames";
 import { Component, FormEventHandler, linkEvent } from "inferno";
 import { EditSite, LocalSiteRateLimit } from "lemmy-js-client";
 import { i18n } from "../../i18next";
@@ -19,6 +20,7 @@ interface RateLimitsProps {
   handleRateLimitPerSecond: FormEventHandler<HTMLInputElement>;
   rateLimitValue?: number;
   rateLimitPerSecondValue?: number;
+  className?: string;
 }
 
 interface RateLimitFormProps {
@@ -49,9 +51,10 @@ function RateLimits({
   handleRateLimitPerSecond,
   rateLimitPerSecondValue,
   rateLimitValue,
+  className,
 }: RateLimitsProps) {
   return (
-    <div className="mb-3 row">
+    <div role="tabpanel" className={classNames("mb-3 row", className)}>
       <div className="col-md-6">
         <label htmlFor="rate-limit">{i18n.t("rate_limit")}</label>
         <input
@@ -142,8 +145,11 @@ export default class RateLimitsForm extends Component<
           tabs={rateLimitTypes.map(rateLimitType => ({
             key: rateLimitType,
             label: i18n.t(`rate_limit_${rateLimitType}`),
-            getNode: () => (
+            getNode: isSelected => (
               <RateLimits
+                className={classNames("tab-pane show", {
+                  active: isSelected,
+                })}
                 handleRateLimit={linkEvent(
                   { rateLimitType, ctx: this },
                   handleRateLimitChange
index 5c3fc345c9433c556a07a9ddd50a80aba47b02f6..9acba57abb95dccaa5a995703737d8b75081c445 100644 (file)
@@ -1,4 +1,5 @@
 import { debounce } from "@utils/helpers";
+import classNames from "classnames";
 import { NoOptionI18nKeys } from "i18next";
 import { Component, linkEvent } from "inferno";
 import {
@@ -265,34 +266,50 @@ export class Settings extends Component<any, SettingsState> {
     );
   }
 
-  userSettings() {
+  userSettings(isSelected) {
     return (
-      <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
+        className={classNames("tab-pane show", {
+          active: isSelected,
+        })}
+        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 className="col-12 col-md-6">
-          <div className="card border-secondary mb-3">
-            <div className="card-body">{this.changePasswordHtmlForm()}</div>
+          <div className="col-12 col-md-6">
+            <div className="card border-secondary mb-3">
+              <div className="card-body">{this.changePasswordHtmlForm()}</div>
+            </div>
           </div>
         </div>
       </div>
     );
   }
 
-  blockCards() {
+  blockCards(isSelected) {
     return (
-      <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
+        className={classNames("tab-pane", {
+          active: isSelected,
+        })}
+        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 className="col-12 col-md-6">
-          <div className="card border-secondary mb-3">
-            <div className="card-body">{this.blockCommunityCard()}</div>
+          <div className="col-12 col-md-6">
+            <div className="card border-secondary mb-3">
+              <div className="card-body">{this.blockCommunityCard()}</div>
+            </div>
           </div>
         </div>
       </div>