From 0d139e6133a8a1766b23310e619fc5ae38da6737 Mon Sep 17 00:00:00 2001 From: David Wheatley Date: Tue, 16 Mar 2021 14:50:36 +0000 Subject: [PATCH] [A11Y] Add aria-label to dropdown toggles (#2668) Implement custom accessible dropdown toggle labels for forum components Making the a11y label more specific to the specific action it performs is critical for good UX with assistive technologies. --- js/src/common/components/Dropdown.js | 10 +++++++++- js/src/common/components/SplitDropdown.js | 7 ++++++- js/src/forum/components/DiscussionListItem.js | 1 + js/src/forum/components/DiscussionPage.js | 1 + js/src/forum/components/HeaderSecondary.js | 1 + js/src/forum/components/IndexPage.js | 2 ++ js/src/forum/components/NotificationsDropdown.js | 2 ++ js/src/forum/components/Post.js | 1 + js/src/forum/components/SessionDropdown.js | 2 ++ js/src/forum/components/UserCard.js | 1 + locale/core.yml | 12 ++++++++++++ 11 files changed, 38 insertions(+), 2 deletions(-) diff --git a/js/src/common/components/Dropdown.js b/js/src/common/components/Dropdown.js index 008c4305e0..b05784415a 100644 --- a/js/src/common/components/Dropdown.js +++ b/js/src/common/components/Dropdown.js @@ -13,6 +13,7 @@ import listItems from '../helpers/listItems'; * - `icon` The name of an icon to show in the dropdown toggle button. * - `caretIcon` The name of an icon to show on the right of the button. * - `label` The label of the dropdown toggle button. Defaults to 'Controls'. + * - `accessibleToggleLabel` The label used to describe the dropdown toggle button to assistive readers. Defaults to 'Toggle dropdown menu'. * - `onhide` * - `onshow` * @@ -25,6 +26,7 @@ export default class Dropdown extends Component { attrs.menuClassName = attrs.menuClassName || ''; attrs.label = attrs.label || ''; attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-caret-down'; + attrs.accessibleToggleLabel = attrs.accessibleToggleLabel || app.translator.trans('core.lib.dropdown.toggle_dropdown_accessible_label'); } oninit(vnode) { @@ -92,7 +94,13 @@ export default class Dropdown extends Component { */ getButton(children) { return ( - ); diff --git a/js/src/common/components/SplitDropdown.js b/js/src/common/components/SplitDropdown.js index a8a937ac53..e898c95ad7 100644 --- a/js/src/common/components/SplitDropdown.js +++ b/js/src/common/components/SplitDropdown.js @@ -24,7 +24,12 @@ export default class SplitDropdown extends Dropdown { return [ Button.component(buttonAttrs, firstChild.children), - , diff --git a/js/src/forum/components/DiscussionListItem.js b/js/src/forum/components/DiscussionListItem.js index 078b4b8fc8..1fc0adfb39 100644 --- a/js/src/forum/components/DiscussionListItem.js +++ b/js/src/forum/components/DiscussionListItem.js @@ -87,6 +87,7 @@ export default class DiscussionListItem extends Component { icon: 'fas fa-ellipsis-v', className: 'DiscussionListItem-controls', buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right', + accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'), }, controls ) diff --git a/js/src/forum/components/DiscussionPage.js b/js/src/forum/components/DiscussionPage.js index 80dd5e8e02..7516df018d 100644 --- a/js/src/forum/components/DiscussionPage.js +++ b/js/src/forum/components/DiscussionPage.js @@ -189,6 +189,7 @@ export default class DiscussionPage extends Page { icon: 'fas fa-ellipsis-v', className: 'App-primaryControl', buttonClassName: 'Button--primary', + accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'), }, DiscussionControls.controls(this.discussion, this).toArray() ) diff --git a/js/src/forum/components/HeaderSecondary.js b/js/src/forum/components/HeaderSecondary.js index be8b39cfd7..398444e7f1 100644 --- a/js/src/forum/components/HeaderSecondary.js +++ b/js/src/forum/components/HeaderSecondary.js @@ -57,6 +57,7 @@ export default class HeaderSecondary extends Component { SelectDropdown.component( { buttonClassName: 'Button Button--link', + accessibleToggleLabel: app.translator.trans('core.forum.header.locale_dropdown_accessible_label'), }, locales ), diff --git a/js/src/forum/components/IndexPage.js b/js/src/forum/components/IndexPage.js index 75cd32eb94..72eb244eb7 100644 --- a/js/src/forum/components/IndexPage.js +++ b/js/src/forum/components/IndexPage.js @@ -172,6 +172,7 @@ export default class IndexPage extends Page { { buttonClassName: 'Button', className: 'App-titleControl', + accessibleToggleLabel: app.translator.trans('core.forum.index.toggle_sidenav_dropdown_accessible_label'), }, this.navItems(this).toArray() ) @@ -227,6 +228,7 @@ export default class IndexPage extends Page { { buttonClassName: 'Button', label: sortOptions[app.search.params().sort] || Object.keys(sortMap).map((key) => sortOptions[key])[0], + accessibleToggleLabel: app.translator.trans('core.forum.index_sort.toggle_dropdown_accessible_label'), }, Object.keys(sortOptions).map((value) => { const label = sortOptions[value]; diff --git a/js/src/forum/components/NotificationsDropdown.js b/js/src/forum/components/NotificationsDropdown.js index 1e0a2465ec..7099ab66f3 100644 --- a/js/src/forum/components/NotificationsDropdown.js +++ b/js/src/forum/components/NotificationsDropdown.js @@ -9,6 +9,8 @@ export default class NotificationsDropdown extends Dropdown { attrs.menuClassName = attrs.menuClassName || 'Dropdown-menu--right'; attrs.label = attrs.label || app.translator.trans('core.forum.notifications.tooltip'); attrs.icon = attrs.icon || 'fas fa-bell'; + // For best a11y support, both `title` and `aria-label` should be used + attrs.accessibleToggleLabel = attrs.accessibleToggleLabel || app.translator.trans('core.forum.notifications.toggle_dropdown_accessible_label'); super.initAttrs(attrs); } diff --git a/js/src/forum/components/Post.js b/js/src/forum/components/Post.js index ebc084f730..537ca97fb8 100644 --- a/js/src/forum/components/Post.js +++ b/js/src/forum/components/Post.js @@ -61,6 +61,7 @@ export default class Post extends Component { icon="fas fa-ellipsis-h" onshow={() => this.$('.Post-actions').addClass('open')} onhide={() => this.$('.Post-actions').removeClass('open')} + accessibleToggleLabel={app.translator.trans('core.forum.post_controls.toggle_dropdown_accessible_label')} > {controls} diff --git a/js/src/forum/components/SessionDropdown.js b/js/src/forum/components/SessionDropdown.js index 6c1477ad6c..86d554824a 100644 --- a/js/src/forum/components/SessionDropdown.js +++ b/js/src/forum/components/SessionDropdown.js @@ -17,6 +17,8 @@ export default class SessionDropdown extends Dropdown { attrs.className = 'SessionDropdown'; attrs.buttonClassName = 'Button Button--user Button--flat'; attrs.menuClassName = 'Dropdown-menu--right'; + + attrs.accessibleToggleLabel = app.translator.trans('core.forum.header.session_dropdown_accessible_label'); } view(vnode) { diff --git a/js/src/forum/components/UserCard.js b/js/src/forum/components/UserCard.js index b5ff527820..4b1003a605 100644 --- a/js/src/forum/components/UserCard.js +++ b/js/src/forum/components/UserCard.js @@ -40,6 +40,7 @@ export default class UserCard extends Component { menuClassName: 'Dropdown-menu--right', buttonClassName: this.attrs.controlsButtonClassName, label: app.translator.trans('core.forum.user_controls.button'), + accessibleToggleLabel: app.translator.trans('core.forum.user_controls.toggle_dropdown_accessible_label'), icon: 'fas fa-ellipsis-v', }, controls diff --git a/locale/core.yml b/locale/core.yml index 59efb372b5..8e7821d7af 100644 --- a/locale/core.yml +++ b/locale/core.yml @@ -278,6 +278,7 @@ core: rename_button: => core.ref.rename reply_button: => core.ref.reply restore_button: => core.ref.restore + toggle_dropdown_accessible_label: Toggle discussion actions dropdown menu # These translations are used in the discussion list. discussion_list: @@ -316,10 +317,12 @@ core: header: admin_button: Administration back_to_index_tooltip: Back to Discussion List + locale_dropdown_accessible_label: Change forum locale log_in_link: => core.ref.log_in log_out_button: => core.ref.log_out profile_button: Profile search_placeholder: Search Forum + session_dropdown_accessible_label: Toggle session options dropdown menu settings_button: => core.ref.settings sign_up_link: => core.ref.sign_up @@ -332,6 +335,7 @@ core: meta_title_text: => core.ref.all_discussions refresh_tooltip: Refresh start_discussion_button: => core.ref.start_a_discussion + toggle_sidenav_dropdown_accessible_label: Toggle navigation dropdown menu # These translations are used by the sorting control above the discussion list. index_sort: @@ -339,6 +343,7 @@ core: newest_button: Newest oldest_button: Oldest relevance_button: Relevance + toggle_dropdown_accessible_label: Change discussion list sorting top_button: Top # These translations are used in the Log In modal dialog. @@ -359,6 +364,7 @@ core: mark_all_as_read_tooltip: => core.ref.mark_all_as_read mark_as_read_tooltip: Mark as Read title: => core.ref.notifications + toggle_dropdown_accessible_label: View notifications tooltip: => core.ref.notifications # These translations are used by tooltips displayed for individual posts. @@ -375,6 +381,7 @@ core: edit_button: => core.ref.edit hide_confirmation: "Are you sure you want to delete this post?" restore_button: => core.ref.restore + toggle_dropdown_accessible_label: Toggle post controls dropdown menu # These translations are used in the scrubber to the right of the post stream. post_scrubber: @@ -448,6 +455,7 @@ core: delete_error_message: "Deletion of user {username} ({email}) failed" delete_success_message: "User {username} ({email}) was deleted" edit_button: => core.ref.edit + toggle_dropdown_accessible_label: Toggle user controls dropdown menu # These translations are used in the alert that is shown when a new user has not confirmed their email address. user_email_confirmation: @@ -462,6 +470,10 @@ core: badge: hidden_tooltip: Hidden + # These translations are used in the dropdown component. + dropdown: + toggle_dropdown_accessible_label: Toggle dropdown menu + # These translations are displayed as error messages. error: dependent_extensions_message: "Cannot disable {extension} until the following dependent extensions are disabled: {extensions}"