Add aria attributes where possible (#156)
authorMitch Lillie <mlillie87@gmail.com>
Sat, 6 Feb 2021 20:20:41 +0000 (12:20 -0800)
committerGitHub <noreply@github.com>
Sat, 6 Feb 2021 20:20:41 +0000 (15:20 -0500)
* Add aria attributes where possible

* Bump submodule to get aria translations

19 files changed:
lemmy-translations
src/shared/components/banner-icon-header.tsx
src/shared/components/comment-node.tsx
src/shared/components/communities.tsx
src/shared/components/image-upload-form.tsx
src/shared/components/inbox.tsx
src/shared/components/login.tsx
src/shared/components/main.tsx
src/shared/components/markdown-textarea.tsx
src/shared/components/password_change.tsx
src/shared/components/pictrs-image.tsx
src/shared/components/post-form.tsx
src/shared/components/post-listing.tsx
src/shared/components/private-message-form.tsx
src/shared/components/private-message.tsx
src/shared/components/search.tsx
src/shared/components/sidebar.tsx
src/shared/components/sort-select.tsx
src/shared/components/user.tsx

index a47ae3f825f74ad7a6106b92e21d3c66b10d3fe8..084ce539fff5253317d6460598b10a6867999c20 160000 (submodule)
@@ -1 +1 @@
-Subproject commit a47ae3f825f74ad7a6106b92e21d3c66b10d3fe8
+Subproject commit 084ce539fff5253317d6460598b10a6867999c20
index cb84eb2f968350517404e4e3eccebfd1484ac695..050ace14880ad3a98cb72314034b5a978f2f4ef6 100644 (file)
@@ -14,12 +14,13 @@ export class BannerIconHeader extends Component<BannerIconHeaderProps, any> {
   render() {
     return (
       <div class="position-relative mb-2">
-        {this.props.banner && <PictrsImage src={this.props.banner} />}
+        {this.props.banner && <PictrsImage src={this.props.banner} alt="" />}
         {this.props.icon && (
           <PictrsImage
             src={this.props.icon}
             iconOverlay
             pushup={!!this.props.banner}
+            alt=""
           />
         )}
       </div>
index 071c23889ef3f5173598358b9c944b7e988453e0..26eeaf0a874a978899d2cd4bf0c6bdd0741ed6e0 100644 (file)
@@ -198,6 +198,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
               <button
                 class="btn btn-sm text-muted"
                 onClick={linkEvent(this, this.handleCommentCollapse)}
+                aria-label={
+                  this.state.collapsed ? i18n.t('expand') : i18n.t('collapse')
+                }
               >
                 {this.state.collapsed ? '+' : '—'}
               </button>
@@ -248,6 +251,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                           ? i18n.t('mark_as_unread')
                           : i18n.t('mark_as_read')
                       }
+                      aria-label={
+                        this.commentOrMentionRead
+                          ? i18n.t('mark_as_unread')
+                          : i18n.t('mark_as_read')
+                      }
                     >
                       {this.state.readLoading ? (
                         this.loadingIcon
@@ -270,6 +278,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                         }`}
                         onClick={linkEvent(node, this.handleCommentUpvote)}
                         data-tippy-content={i18n.t('upvote')}
+                        aria-label={i18n.t('upvote')}
                       >
                         <svg class="icon icon-inline">
                           <use xlinkHref="#icon-arrow-up1"></use>
@@ -287,6 +296,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                           }`}
                           onClick={linkEvent(node, this.handleCommentDownvote)}
                           data-tippy-content={i18n.t('downvote')}
+                          aria-label={i18n.t('downvote')}
                         >
                           <svg class="icon icon-inline">
                             <use xlinkHref="#icon-arrow-down1"></use>
@@ -300,6 +310,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                         class="btn btn-link btn-animate text-muted"
                         onClick={linkEvent(this, this.handleReplyClick)}
                         data-tippy-content={i18n.t('reply')}
+                        aria-label={i18n.t('reply')}
                       >
                         <svg class="icon icon-inline">
                           <use xlinkHref="#icon-reply1"></use>
@@ -310,6 +321,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                           className="btn btn-link btn-animate text-muted"
                           onClick={linkEvent(this, this.handleShowAdvanced)}
                           data-tippy-content={i18n.t('more')}
+                          aria-label={i18n.t('more')}
                         >
                           <svg class="icon icon-inline">
                             <use xlinkHref="#icon-more-vertical"></use>
@@ -340,6 +352,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                             data-tippy-content={
                               cv.saved ? i18n.t('unsave') : i18n.t('save')
                             }
+                            aria-label={
+                              cv.saved ? i18n.t('unsave') : i18n.t('save')
+                            }
                           >
                             {this.state.saveLoading ? (
                               this.loadingIcon
@@ -357,6 +372,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                             className="btn btn-link btn-animate text-muted"
                             onClick={linkEvent(this, this.handleViewSource)}
                             data-tippy-content={i18n.t('view_source')}
+                            aria-label={i18n.t('view_source')}
                           >
                             <svg
                               class={`icon icon-inline ${
@@ -372,6 +388,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                                 class="btn btn-link btn-animate text-muted"
                                 onClick={linkEvent(this, this.handleEditClick)}
                                 data-tippy-content={i18n.t('edit')}
+                                aria-label={i18n.t('edit')}
                               >
                                 <svg class="icon icon-inline">
                                   <use xlinkHref="#icon-edit"></use>
@@ -388,6 +405,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                                     ? i18n.t('delete')
                                     : i18n.t('restore')
                                 }
+                                aria-label={
+                                  !cv.comment.deleted
+                                    ? i18n.t('delete')
+                                    : i18n.t('restore')
+                                }
                               >
                                 <svg
                                   class={`icon icon-inline ${
@@ -667,9 +689,15 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
         {this.state.showBanDialog && (
           <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
             <div class="form-group row">
-              <label class="col-form-label">{i18n.t('reason')}</label>
+              <label
+                class="col-form-label"
+                htmlFor={`mod-ban-reason-${cv.comment.id}`}
+              >
+                {i18n.t('reason')}
+              </label>
               <input
                 type="text"
+                id={`mod-ban-reason-${cv.comment.id}`}
                 class="form-control mr-2"
                 placeholder={i18n.t('reason')}
                 value={this.state.banReason}
index c852fe254b98ffed95cff5d755363e924318f508..e90c09decd8dbfb9aaf878cf2b78ff11b35b18b2 100644 (file)
@@ -158,6 +158,7 @@ export class Communities extends Component<any, CommunitiesState> {
                         {cv.subscribed ? (
                           <span
                             class="pointer btn-link"
+                            role="button"
                             onClick={linkEvent(
                               cv.community.id,
                               this.handleUnsubscribe
@@ -168,6 +169,7 @@ export class Communities extends Component<any, CommunitiesState> {
                         ) : (
                           <span
                             class="pointer btn-link"
+                            role="button"
                             onClick={linkEvent(
                               cv.community.id,
                               this.handleSubscribe
index c9ffea416c24e9afc4f0932b541cbc78d300e146..4b8866f3ee6c7a74bcbc87f856c143b7c84512fc 100644 (file)
@@ -2,6 +2,7 @@ import { Component, linkEvent } from 'inferno';
 import { pictrsUri } from '../env';
 import { UserService } from '../services';
 import { toast, randomStr } from '../utils';
+import { i18n } from '../i18next';
 
 interface ImageUploadFormProps {
   uploadTitle: string;
@@ -48,7 +49,10 @@ export class ImageUploadForm extends Component<
                   this.props.rounded ? 'rounded-circle' : ''
                 }`}
               />
-              <a onClick={linkEvent(this, this.handleRemoveImage)}>
+              <a
+                onClick={linkEvent(this, this.handleRemoveImage)}
+                aria-label={i18n.t('remove')}
+              >
                 <svg class="icon mini-overlay">
                   <use xlinkHref="#icon-x"></use>
                 </svg>
index 4390b8ec44124a299d844823f39c3d81df170ec0..ab0c312e4c2811c975473711192bc0f9b06d7807 100644 (file)
@@ -172,6 +172,7 @@ export class Inbox extends Component<any, InboxState> {
                     <li className="list-inline-item">
                       <span
                         class="pointer"
+                        role="button"
                         onClick={linkEvent(this, this.markAllAsRead)}
                       >
                         {i18n.t('mark_all_as_read')}
index acd05a301deb5a6bc1a0d427048e0ff564f23a86..7006ae71b0b656f5e6c511ae41760d1e130647fe 100644 (file)
@@ -326,6 +326,7 @@ export class Login extends Component<any, State> {
               class="rounded-top img-fluid"
               src={this.captchaPngSrc()}
               style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
+              alt={i18n.t('captcha')}
             />
             {this.state.captcha.ok.wav && (
               <button
index 1c7b7da97df44ce8b27c770ce080dd5276b5df5d..28795b560b41009b559edd4a237c95d491758eb6 100644 (file)
@@ -508,7 +508,9 @@ export class Main extends Component<any, MainState> {
           <li className="list-inline-item-action">
             <span
               class="pointer"
+              role="button"
               onClick={linkEvent(this, this.handleEditClick)}
+              aria-label={i18n.t('edit')}
               data-tippy-content={i18n.t('edit')}
             >
               <svg class="icon icon-inline">
index ded6dd1c11715e987bfd5e8d341052a64f679268..cffc43225733effe77fd32f2e158b2b114a3eeff 100644 (file)
@@ -233,6 +233,7 @@ export class MarkdownTextArea extends Component<
             <button
               class="btn btn-sm text-muted"
               data-tippy-content={i18n.t('header')}
+              aria-label={i18n.t('header')}
               onClick={linkEvent(this, this.handleInsertHeader)}
             >
               <svg class="icon icon-inline">
@@ -242,6 +243,7 @@ export class MarkdownTextArea extends Component<
             <button
               class="btn btn-sm text-muted"
               data-tippy-content={i18n.t('strikethrough')}
+              aria-label={i18n.t('strikethrough')}
               onClick={linkEvent(this, this.handleInsertStrikethrough)}
             >
               <svg class="icon icon-inline">
@@ -251,6 +253,7 @@ export class MarkdownTextArea extends Component<
             <button
               class="btn btn-sm text-muted"
               data-tippy-content={i18n.t('quote')}
+              aria-label={i18n.t('quote')}
               onClick={linkEvent(this, this.handleInsertQuote)}
             >
               <svg class="icon icon-inline">
@@ -260,6 +263,7 @@ export class MarkdownTextArea extends Component<
             <button
               class="btn btn-sm text-muted"
               data-tippy-content={i18n.t('list')}
+              aria-label={i18n.t('list')}
               onClick={linkEvent(this, this.handleInsertList)}
             >
               <svg class="icon icon-inline">
@@ -269,6 +273,7 @@ export class MarkdownTextArea extends Component<
             <button
               class="btn btn-sm text-muted"
               data-tippy-content={i18n.t('code')}
+              aria-label={i18n.t('code')}
               onClick={linkEvent(this, this.handleInsertCode)}
             >
               <svg class="icon icon-inline">
@@ -278,6 +283,7 @@ export class MarkdownTextArea extends Component<
             <button
               class="btn btn-sm text-muted"
               data-tippy-content={i18n.t('subscript')}
+              aria-label={i18n.t('subscript')}
               onClick={linkEvent(this, this.handleInsertSubscript)}
             >
               <svg class="icon icon-inline">
@@ -287,6 +293,7 @@ export class MarkdownTextArea extends Component<
             <button
               class="btn btn-sm text-muted"
               data-tippy-content={i18n.t('superscript')}
+              aria-label={i18n.t('superscript')}
               onClick={linkEvent(this, this.handleInsertSuperscript)}
             >
               <svg class="icon icon-inline">
@@ -296,6 +303,7 @@ export class MarkdownTextArea extends Component<
             <button
               class="btn btn-sm text-muted"
               data-tippy-content={i18n.t('spoiler')}
+              aria-label={i18n.t('spoiler')}
               onClick={linkEvent(this, this.handleInsertSpoiler)}
             >
               <svg class="icon icon-inline">
index 6ffa3e0775f50beac3614653a620c6e798b745e3..373d10f3c5cf5249aa8ef946ffadab5c94d62350 100644 (file)
@@ -80,11 +80,12 @@ export class PasswordChange extends Component<any, State> {
     return (
       <form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
         <div class="form-group row">
-          <label class="col-sm-2 col-form-label">
+          <label class="col-sm-2 col-form-label" htmlFor="new-password">
             {i18n.t('new_password')}
           </label>
           <div class="col-sm-10">
             <input
+              id="new-password"
               type="password"
               value={this.state.passwordChangeForm.password}
               onInput={linkEvent(this, this.handlePasswordChange)}
@@ -94,11 +95,12 @@ export class PasswordChange extends Component<any, State> {
           </div>
         </div>
         <div class="form-group row">
-          <label class="col-sm-2 col-form-label">
+          <label class="col-sm-2 col-form-label" htmlFor="verify-password">
             {i18n.t('verify_password')}
           </label>
           <div class="col-sm-10">
             <input
+              id="verify-password"
               type="password"
               value={this.state.passwordChangeForm.password_verify}
               onInput={linkEvent(this, this.handleVerifyPasswordChange)}
index b04472115587e5edf83e6b6784fcadd3622caeb1..1f5655d3e3da31f3d08cc523f7490970445b8f7a 100644 (file)
@@ -6,6 +6,7 @@ const maxImageSize = 3000;
 
 interface PictrsImageProps {
   src: string;
+  alt?: string;
   icon?: boolean;
   thumbnail?: boolean;
   nsfw?: boolean;
@@ -25,13 +26,14 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
         <source srcSet={this.src('jpg')} type="image/jpeg" />
         <img
           src={this.src('jpg')}
+          alt={this.alt()}
           className={`
         ${!this.props.icon && !this.props.iconOverlay && 'img-fluid '}
         ${
           this.props.thumbnail && !this.props.icon
             ? 'thumbnail rounded '
             : 'img-expanded '
-        } 
+        }
         ${this.props.thumbnail && this.props.nsfw && 'img-blur '}
         ${this.props.icon && 'rounded-circle img-icon mr-2 '}
         ${this.props.iconOverlay && 'ml-2 mb-0 rounded-circle avatar-overlay '}
@@ -71,4 +73,11 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
 
     return out;
   }
+
+  alt(): string {
+    if (this.props.icon) {
+      return '';
+    }
+    return this.props.alt || '';
+  }
 }
index 4295a623c6df801cef76580f2dd1d359803e8f66..7f5c7fbaa2fb57616aefa5c305b0ff305b85a86c 100644 (file)
@@ -180,6 +180,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
               {this.state.suggestedTitle && (
                 <div
                   class="mt-1 text-muted small font-weight-bold pointer"
+                  role="button"
                   onClick={linkEvent(this, this.copySuggestedTitle)}
                 >
                   {i18n.t('copy_suggested_title', {
@@ -227,7 +228,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
                 </svg>
               )}
               {isImage(this.state.postForm.url) && (
-                <img src={this.state.postForm.url} class="img-fluid" />
+                <img src={this.state.postForm.url} class="img-fluid" alt="" />
               )}
               {this.state.crossPosts.length > 0 && (
                 <>
index d75584bd4d528a9a5aeb2362eceac3e5b9becd0c..2d0192b883aa2e402d0272e7e1f716c711f100eb 100644 (file)
@@ -169,6 +169,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
       <PictrsImage
         src={src}
         thumbnail
+        alt=""
         nsfw={post_view.post.nsfw || post_view.community.nsfw}
       />
     );
@@ -200,6 +201,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
           class="float-right text-body pointer d-inline-block position-relative mb-2"
           data-tippy-content={i18n.t('expand_here')}
           onClick={linkEvent(this, this.handleImageExpandClick)}
+          role="button"
+          aria-label={i18n.t('expand_here')}
         >
           {this.imgThumb(this.getImageSrc())}
           <svg class="icon mini-overlay">
@@ -328,6 +331,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                   previewLines(post_view.post.body)
                 )}
                 data-tippy-allowHtml={true}
+                aria-label={i18n.t('upvote')}
                 to={`/post/${post_view.post.id}`}
               >
                 <svg class="mr-1 icon icon-inline">
@@ -350,6 +354,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
           }`}
           onClick={linkEvent(this, this.handlePostLike)}
           data-tippy-content={i18n.t('upvote')}
+          aria-label={i18n.t('upvote')}
         >
           <svg class="icon upvote">
             <use xlinkHref="#icon-arrow-up1"></use>
@@ -368,6 +373,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
             }`}
             onClick={linkEvent(this, this.handlePostDisLike)}
             data-tippy-content={i18n.t('downvote')}
+            aria-label={i18n.t('downvote')}
           >
             <svg class="icon downvote">
               <use xlinkHref="#icon-arrow-down1"></use>
@@ -504,6 +510,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
               <button
                 class="btn text-muted py-0 pr-0"
                 data-tippy-content={this.pointsTippy}
+                aria-label={i18n.t('downvote')}
               >
                 <small>
                   <svg class="icon icon-inline mr-1">
@@ -520,6 +527,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                 data-tippy-content={
                   post_view.saved ? i18n.t('unsave') : i18n.t('save')
                 }
+                aria-label={post_view.saved ? i18n.t('unsave') : i18n.t('save')}
               >
                 <small>
                   <svg
@@ -545,6 +553,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                 }`}
                 data-tippy-content={this.pointsTippy}
                 onClick={linkEvent(this, this.handlePostLike)}
+                aria-label={i18n.t('upvote')}
               >
                 <svg class="small icon icon-inline mr-2">
                   <use xlinkHref="#icon-arrow-up1"></use>
@@ -558,6 +567,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                   }`}
                   onClick={linkEvent(this, this.handlePostDisLike)}
                   data-tippy-content={this.pointsTippy}
+                  aria-label={i18n.t('downvote')}
                 >
                   <svg class="small icon icon-inline mr-2">
                     <use xlinkHref="#icon-arrow-down1"></use>
@@ -571,6 +581,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
             <button
               class="btn btn-link btn-animate text-muted py-0 pl-1 pr-0"
               onClick={linkEvent(this, this.handleSavePostClick)}
+              aria-label={post_view.saved ? i18n.t('unsave') : i18n.t('save')}
               data-tippy-content={
                 post_view.saved ? i18n.t('unsave') : i18n.t('save')
               }
@@ -586,6 +597,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
               <button
                 class="btn btn-link btn-animate text-muted py-0"
                 onClick={linkEvent(this, this.handleShowMoreMobile)}
+                aria-label={i18n.t('more')}
                 data-tippy-content={i18n.t('more')}
               >
                 <svg class="icon icon-inline">
@@ -639,6 +651,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                   data-tippy-content={
                     post_view.saved ? i18n.t('unsave') : i18n.t('save')
                   }
+                  aria-label={
+                    post_view.saved ? i18n.t('unsave') : i18n.t('save')
+                  }
                 >
                   <svg
                     class={`icon icon-inline ${
@@ -694,6 +709,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
               class="btn btn-link btn-animate text-muted py-0"
               onClick={linkEvent(this, this.handleShowAdvanced)}
               data-tippy-content={i18n.t('more')}
+              aria-label={i18n.t('more')}
             >
               <svg class="icon icon-inline">
                 <use xlinkHref="#icon-more-vertical"></use>
index 568886908cebf1a3f2c5ff62681a7be8c015d177..b4a43a1728718efd6fbc5d88284d95e77f610511 100644 (file)
@@ -116,8 +116,10 @@ export class PrivateMessageForm extends Component<
               {i18n.t('message')}
               <span
                 onClick={linkEvent(this, this.handleShowDisclaimer)}
+                role="button"
                 class="ml-2 pointer text-danger"
                 data-tippy-content={i18n.t('disclaimer')}
+                aria-label={i18n.t('disclaimer')}
               >
                 <svg class={`icon icon-inline`}>
                   <use xlinkHref="#icon-alert-triangle"></use>
index 0121ea80f2944723c3c938e8f620cb375ce005dc..7b22b741586d5c3ccd5d8a15c7380a7a29e0811e 100644 (file)
@@ -77,6 +77,7 @@ export class PrivateMessage extends Component<
             </li>
             <li className="list-inline-item">
               <div
+                role="button"
                 className="pointer text-monospace"
                 onClick={linkEvent(this, this.handleMessageCollapse)}
               >
@@ -123,6 +124,11 @@ export class PrivateMessage extends Component<
                             ? i18n.t('mark_as_unread')
                             : i18n.t('mark_as_read')
                         }
+                        aria-label={
+                          message_view.private_message.read
+                            ? i18n.t('mark_as_unread')
+                            : i18n.t('mark_as_read')
+                        }
                       >
                         <svg
                           class={`icon icon-inline ${
@@ -138,6 +144,7 @@ export class PrivateMessage extends Component<
                         class="btn btn-link btn-animate text-muted"
                         onClick={linkEvent(this, this.handleReplyClick)}
                         data-tippy-content={i18n.t('reply')}
+                        aria-label={i18n.t('reply')}
                       >
                         <svg class="icon icon-inline">
                           <use xlinkHref="#icon-reply1"></use>
@@ -153,6 +160,7 @@ export class PrivateMessage extends Component<
                         class="btn btn-link btn-animate text-muted"
                         onClick={linkEvent(this, this.handleEditClick)}
                         data-tippy-content={i18n.t('edit')}
+                        aria-label={i18n.t('edit')}
                       >
                         <svg class="icon icon-inline">
                           <use xlinkHref="#icon-edit"></use>
@@ -168,6 +176,11 @@ export class PrivateMessage extends Component<
                             ? i18n.t('delete')
                             : i18n.t('restore')
                         }
+                        aria-label={
+                          !message_view.private_message.deleted
+                            ? i18n.t('delete')
+                            : i18n.t('restore')
+                        }
                       >
                         <svg
                           class={`icon icon-inline ${
@@ -186,6 +199,7 @@ export class PrivateMessage extends Component<
                     class="btn btn-link btn-animate text-muted"
                     onClick={linkEvent(this, this.handleViewSource)}
                     data-tippy-content={i18n.t('view_source')}
+                    aria-label={i18n.t('view_source')}
                   >
                     <svg
                       class={`icon icon-inline ${
index f4ef43016ae6dea8bd1905f2695a35f4ecf6f5ba..1b61e3bf04591f3e2d2f3361ce0e35a205eb405a 100644 (file)
@@ -209,6 +209,7 @@ export class Search extends Component<any, SearchState> {
           class="form-control mr-2 mb-2"
           value={this.state.searchText}
           placeholder={`${i18n.t('search')}...`}
+          aria-label={i18n.t('search')}
           onInput={linkEvent(this, this.handleQChange)}
           required
           minLength={3}
@@ -233,8 +234,11 @@ export class Search extends Component<any, SearchState> {
           value={this.state.type_}
           onChange={linkEvent(this, this.handleTypeChange)}
           class="custom-select w-auto mb-2"
+          aria-label={i18n.t('type')}
         >
-          <option disabled>{i18n.t('type')}</option>
+          <option disabled aria-hidden="true">
+            {i18n.t('type')}
+          </option>
           <option value={SearchType.All}>{i18n.t('all')}</option>
           <option value={SearchType.Comments}>{i18n.t('comments')}</option>
           <option value={SearchType.Posts}>{i18n.t('posts')}</option>
@@ -382,7 +386,7 @@ export class Search extends Component<any, SearchState> {
         <span>
           <CommunityLink community={community_view.community} />
         </span>
-        <span>{` - 
+        <span>{` -
         ${i18n.t('number_of_subscribers', {
           count: community_view.counts.subscribers,
         })}
index 03675ddf6a56d4d4756d33899b1fbe98291fefd8..db96efa5685f79a7c396ad5802c7445ce8921411 100644 (file)
@@ -299,9 +299,11 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
             <>
               <li className="list-inline-item-action">
                 <span
+                  role="button"
                   class="pointer"
                   onClick={linkEvent(this, this.handleEditClick)}
                   data-tippy-content={i18n.t('edit')}
+                  aria-label={i18n.t('edit')}
                 >
                   <svg class="icon icon-inline">
                     <use xlinkHref="#icon-edit"></use>
@@ -313,6 +315,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
                   <li className="list-inline-item-action">
                     <span
                       class="pointer"
+                      role="button"
                       onClick={linkEvent(
                         this,
                         this.handleShowConfirmLeaveModTeamClick
@@ -329,6 +332,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
                     <li className="list-inline-item-action">
                       <span
                         class="pointer"
+                        role="button"
                         onClick={linkEvent(this, this.handleLeaveModTeamClick)}
                       >
                         {i18n.t('yes')}
@@ -337,6 +341,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
                     <li className="list-inline-item-action">
                       <span
                         class="pointer"
+                        role="button"
                         onClick={linkEvent(
                           this,
                           this.handleCancelLeaveModTeamClick
@@ -357,6 +362,11 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
                         ? i18n.t('delete')
                         : i18n.t('restore')
                     }
+                    aria-label={
+                      !community_view.community.deleted
+                        ? i18n.t('delete')
+                        : i18n.t('restore')
+                    }
                   >
                     <svg
                       class={`icon icon-inline ${
@@ -375,6 +385,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
               {!this.props.community_view.community.removed ? (
                 <span
                   class="pointer"
+                  role="button"
                   onClick={linkEvent(this, this.handleModRemoveShow)}
                 >
                   {i18n.t('remove')}
@@ -382,6 +393,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
               ) : (
                 <span
                   class="pointer"
+                  role="button"
                   onClick={linkEvent(this, this.handleModRemoveSubmit)}
                 >
                   {i18n.t('restore')}
index ef14a6e8ed402ff3f7a83069a77b13e4a9d86409..5ed2bc6e2679d05947cd757d9051122572cd53e7 100644 (file)
@@ -40,8 +40,9 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
           value={this.state.sort}
           onChange={linkEvent(this, this.handleSortChange)}
           class="custom-select w-auto mr-2 mb-2"
+          aria-label={i18n.t('sort_type')}
         >
-          <option disabled>{i18n.t('sort_type')}</option>
+          <option disabled aria-hidden="true">{i18n.t('sort_type')}</option>
           {!this.props.hideHot && [
             <option value={SortType.Hot}>{i18n.t('hot')}</option>,
             <option value={SortType.Active}>{i18n.t('active')}</option>,
@@ -52,7 +53,7 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
               {i18n.t('most_comments')}
             </option>
           )}
-          <option disabled>─────</option>
+          <option disabled aria-hidden="true">─────</option>
           <option value={SortType.TopDay}>{i18n.t('top_day')}</option>
           <option value={SortType.TopWeek}>{i18n.t('top_week')}</option>
           <option value={SortType.TopMonth}>{i18n.t('top_month')}</option>
index e838851925f665b9070db28cdb559a084e06ad4d..0dbe2524cdadd8f6cab1b627d3387d8762af412a 100644 (file)
@@ -526,28 +526,36 @@ export class User extends Component<any, UserState> {
                 />
               </div>
               <div class="form-group">
-                <label>{i18n.t('language')}</label>
+                <label htmlFor="user-language">{i18n.t('language')}</label>
                 <select
+                  id="user-language"
                   value={this.state.userSettingsForm.lang}
                   onChange={linkEvent(this, this.handleUserSettingsLangChange)}
                   class="ml-2 custom-select w-auto"
                 >
-                  <option disabled>{i18n.t('language')}</option>
+                  <option disabled aria-hidden="true">
+                    {i18n.t('language')}
+                  </option>
                   <option value="browser">{i18n.t('browser_default')}</option>
-                  <option disabled>──</option>
+                  <option disabled aria-hidden="true">
+                    ──
+                  </option>
                   {languages.map(lang => (
                     <option value={lang.code}>{lang.name}</option>
                   ))}
                 </select>
               </div>
               <div class="form-group">
-                <label>{i18n.t('theme')}</label>
+                <label htmlFor="user-theme">{i18n.t('theme')}</label>
                 <select
+                  id="user-theme"
                   value={this.state.userSettingsForm.theme}
                   onChange={linkEvent(this, this.handleUserSettingsThemeChange)}
                   class="ml-2 custom-select w-auto"
                 >
-                  <option disabled>{i18n.t('theme')}</option>
+                  <option disabled aria-hidden="true">
+                    {i18n.t('theme')}
+                  </option>
                   <option value="browser">{i18n.t('browser_default')}</option>
                   {themes.map(theme => (
                     <option value={theme}>{theme}</option>
@@ -584,11 +592,12 @@ export class User extends Component<any, UserState> {
                 />
               </form>
               <div class="form-group row">
-                <label class="col-lg-5 col-form-label">
+                <label class="col-lg-5 col-form-label" htmlFor="display-name">
                   {i18n.t('display_name')}
                 </label>
                 <div class="col-lg-7">
                   <input
+                    id="display-name"
                     type="text"
                     class="form-control"
                     placeholder={i18n.t('optional')}
@@ -636,13 +645,14 @@ export class User extends Component<any, UserState> {
                 </div>
               </div>
               <div class="form-group row">
-                <label class="col-lg-5 col-form-label">
+                <label class="col-lg-5 col-form-label" htmlFor="matrix-user-id">
                   <a href={elementUrl} target="_blank" rel="noopener">
                     {i18n.t('matrix_user_id')}
                   </a>
                 </label>
                 <div class="col-lg-7">
                   <input
+                    id="matrix-user-id"
                     type="text"
                     class="form-control"
                     placeholder="@user:example.com"