1
0
mirror of https://github.com/flarum/core.git synced 2025-07-30 21:20:24 +02:00

[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.
This commit is contained in:
David Wheatley
2021-03-16 14:50:36 +00:00
committed by GitHub
parent 0e6a60bd5b
commit 0d139e6133
11 changed files with 38 additions and 2 deletions

View File

@@ -13,6 +13,7 @@ import listItems from '../helpers/listItems';
* - `icon` The name of an icon to show in the dropdown toggle button. * - `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. * - `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'. * - `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` * - `onhide`
* - `onshow` * - `onshow`
* *
@@ -25,6 +26,7 @@ export default class Dropdown extends Component {
attrs.menuClassName = attrs.menuClassName || ''; attrs.menuClassName = attrs.menuClassName || '';
attrs.label = attrs.label || ''; attrs.label = attrs.label || '';
attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-caret-down'; 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) { oninit(vnode) {
@@ -92,7 +94,13 @@ export default class Dropdown extends Component {
*/ */
getButton(children) { getButton(children) {
return ( return (
<button className={'Dropdown-toggle ' + this.attrs.buttonClassName} data-toggle="dropdown" onclick={this.attrs.onclick}> <button
className={'Dropdown-toggle ' + this.attrs.buttonClassName}
aria-haspopup="menu"
aria-label={this.attrs.accessibleToggleLabel}
data-toggle="dropdown"
onclick={this.attrs.onclick}
>
{this.getButtonContent(children)} {this.getButtonContent(children)}
</button> </button>
); );

View File

@@ -24,7 +24,12 @@ export default class SplitDropdown extends Dropdown {
return [ return [
Button.component(buttonAttrs, firstChild.children), Button.component(buttonAttrs, firstChild.children),
<button className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName} data-toggle="dropdown"> <button
className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName}
aria-haspopup="menu"
aria-label={this.attrs.accessibleToggleLabel}
data-toggle="dropdown"
>
{icon(this.attrs.icon, { className: 'Button-icon' })} {icon(this.attrs.icon, { className: 'Button-icon' })}
{icon('fas fa-caret-down', { className: 'Button-caret' })} {icon('fas fa-caret-down', { className: 'Button-caret' })}
</button>, </button>,

View File

@@ -87,6 +87,7 @@ export default class DiscussionListItem extends Component {
icon: 'fas fa-ellipsis-v', icon: 'fas fa-ellipsis-v',
className: 'DiscussionListItem-controls', className: 'DiscussionListItem-controls',
buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right', buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right',
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
}, },
controls controls
) )

View File

@@ -189,6 +189,7 @@ export default class DiscussionPage extends Page {
icon: 'fas fa-ellipsis-v', icon: 'fas fa-ellipsis-v',
className: 'App-primaryControl', className: 'App-primaryControl',
buttonClassName: 'Button--primary', buttonClassName: 'Button--primary',
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
}, },
DiscussionControls.controls(this.discussion, this).toArray() DiscussionControls.controls(this.discussion, this).toArray()
) )

View File

@@ -57,6 +57,7 @@ export default class HeaderSecondary extends Component {
SelectDropdown.component( SelectDropdown.component(
{ {
buttonClassName: 'Button Button--link', buttonClassName: 'Button Button--link',
accessibleToggleLabel: app.translator.trans('core.forum.header.locale_dropdown_accessible_label'),
}, },
locales locales
), ),

View File

@@ -172,6 +172,7 @@ export default class IndexPage extends Page {
{ {
buttonClassName: 'Button', buttonClassName: 'Button',
className: 'App-titleControl', className: 'App-titleControl',
accessibleToggleLabel: app.translator.trans('core.forum.index.toggle_sidenav_dropdown_accessible_label'),
}, },
this.navItems(this).toArray() this.navItems(this).toArray()
) )
@@ -227,6 +228,7 @@ export default class IndexPage extends Page {
{ {
buttonClassName: 'Button', buttonClassName: 'Button',
label: sortOptions[app.search.params().sort] || Object.keys(sortMap).map((key) => sortOptions[key])[0], 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) => { Object.keys(sortOptions).map((value) => {
const label = sortOptions[value]; const label = sortOptions[value];

View File

@@ -9,6 +9,8 @@ export default class NotificationsDropdown extends Dropdown {
attrs.menuClassName = attrs.menuClassName || 'Dropdown-menu--right'; attrs.menuClassName = attrs.menuClassName || 'Dropdown-menu--right';
attrs.label = attrs.label || app.translator.trans('core.forum.notifications.tooltip'); attrs.label = attrs.label || app.translator.trans('core.forum.notifications.tooltip');
attrs.icon = attrs.icon || 'fas fa-bell'; 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); super.initAttrs(attrs);
} }

View File

@@ -61,6 +61,7 @@ export default class Post extends Component {
icon="fas fa-ellipsis-h" icon="fas fa-ellipsis-h"
onshow={() => this.$('.Post-actions').addClass('open')} onshow={() => this.$('.Post-actions').addClass('open')}
onhide={() => this.$('.Post-actions').removeClass('open')} onhide={() => this.$('.Post-actions').removeClass('open')}
accessibleToggleLabel={app.translator.trans('core.forum.post_controls.toggle_dropdown_accessible_label')}
> >
{controls} {controls}
</Dropdown> </Dropdown>

View File

@@ -17,6 +17,8 @@ export default class SessionDropdown extends Dropdown {
attrs.className = 'SessionDropdown'; attrs.className = 'SessionDropdown';
attrs.buttonClassName = 'Button Button--user Button--flat'; attrs.buttonClassName = 'Button Button--user Button--flat';
attrs.menuClassName = 'Dropdown-menu--right'; attrs.menuClassName = 'Dropdown-menu--right';
attrs.accessibleToggleLabel = app.translator.trans('core.forum.header.session_dropdown_accessible_label');
} }
view(vnode) { view(vnode) {

View File

@@ -40,6 +40,7 @@ export default class UserCard extends Component {
menuClassName: 'Dropdown-menu--right', menuClassName: 'Dropdown-menu--right',
buttonClassName: this.attrs.controlsButtonClassName, buttonClassName: this.attrs.controlsButtonClassName,
label: app.translator.trans('core.forum.user_controls.button'), 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', icon: 'fas fa-ellipsis-v',
}, },
controls controls

View File

@@ -278,6 +278,7 @@ core:
rename_button: => core.ref.rename rename_button: => core.ref.rename
reply_button: => core.ref.reply reply_button: => core.ref.reply
restore_button: => core.ref.restore restore_button: => core.ref.restore
toggle_dropdown_accessible_label: Toggle discussion actions dropdown menu
# These translations are used in the discussion list. # These translations are used in the discussion list.
discussion_list: discussion_list:
@@ -316,10 +317,12 @@ core:
header: header:
admin_button: Administration admin_button: Administration
back_to_index_tooltip: Back to Discussion List back_to_index_tooltip: Back to Discussion List
locale_dropdown_accessible_label: Change forum locale
log_in_link: => core.ref.log_in log_in_link: => core.ref.log_in
log_out_button: => core.ref.log_out log_out_button: => core.ref.log_out
profile_button: Profile profile_button: Profile
search_placeholder: Search Forum search_placeholder: Search Forum
session_dropdown_accessible_label: Toggle session options dropdown menu
settings_button: => core.ref.settings settings_button: => core.ref.settings
sign_up_link: => core.ref.sign_up sign_up_link: => core.ref.sign_up
@@ -332,6 +335,7 @@ core:
meta_title_text: => core.ref.all_discussions meta_title_text: => core.ref.all_discussions
refresh_tooltip: Refresh refresh_tooltip: Refresh
start_discussion_button: => core.ref.start_a_discussion 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. # These translations are used by the sorting control above the discussion list.
index_sort: index_sort:
@@ -339,6 +343,7 @@ core:
newest_button: Newest newest_button: Newest
oldest_button: Oldest oldest_button: Oldest
relevance_button: Relevance relevance_button: Relevance
toggle_dropdown_accessible_label: Change discussion list sorting
top_button: Top top_button: Top
# These translations are used in the Log In modal dialog. # 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_all_as_read_tooltip: => core.ref.mark_all_as_read
mark_as_read_tooltip: Mark as Read mark_as_read_tooltip: Mark as Read
title: => core.ref.notifications title: => core.ref.notifications
toggle_dropdown_accessible_label: View notifications
tooltip: => core.ref.notifications tooltip: => core.ref.notifications
# These translations are used by tooltips displayed for individual posts. # These translations are used by tooltips displayed for individual posts.
@@ -375,6 +381,7 @@ core:
edit_button: => core.ref.edit edit_button: => core.ref.edit
hide_confirmation: "Are you sure you want to delete this post?" hide_confirmation: "Are you sure you want to delete this post?"
restore_button: => core.ref.restore 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. # These translations are used in the scrubber to the right of the post stream.
post_scrubber: post_scrubber:
@@ -448,6 +455,7 @@ core:
delete_error_message: "Deletion of user <i>{username} ({email})</i> failed" delete_error_message: "Deletion of user <i>{username} ({email})</i> failed"
delete_success_message: "User <i>{username} ({email})</i> was deleted" delete_success_message: "User <i>{username} ({email})</i> was deleted"
edit_button: => core.ref.edit 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. # These translations are used in the alert that is shown when a new user has not confirmed their email address.
user_email_confirmation: user_email_confirmation:
@@ -462,6 +470,10 @@ core:
badge: badge:
hidden_tooltip: Hidden 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. # These translations are displayed as error messages.
error: error:
dependent_extensions_message: "Cannot disable {extension} until the following dependent extensions are disabled: {extensions}" dependent_extensions_message: "Cannot disable {extension} until the following dependent extensions are disabled: {extensions}"