Mobile: Add alt text/roles to some buttons to improve accessibility (#6616)

pull/6618/head^2
Henry Heino 2022-06-26 10:23:41 -07:00 committed by GitHub
parent f64d046c62
commit c58ce8e2da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 114 additions and 17 deletions

View File

@ -60,12 +60,31 @@ class ActionButtonComponent extends React.Component {
renderIconMultiStates() {
const button = this.props.buttons[this.state.buttonIndex];
return <Icon name={button.icon} style={styles.actionButtonIcon} />;
return <Icon
name={button.icon}
style={styles.actionButtonIcon}
accessibilityLabel={button.title}
/>;
}
renderIcon() {
const mainButton = this.props.mainButton ? this.props.mainButton : {};
return mainButton.icon ? <Icon name={mainButton.icon} style={styles.actionButtonIcon} /> : <Icon name="md-add" style={styles.actionButtonIcon} />;
const iconName = mainButton.icon ?? 'md-add';
// Icons don't have alt text by default. We need to add it:
const iconTitle = mainButton.title ?? _('Add new');
// TODO: If the button toggles a sub-menu, state whether the submenu is open
// or closed.
return (
<Icon
name={iconName}
style={styles.actionButtonIcon}
accessibilityLabel={iconTitle}
/>
);
}
render() {
@ -99,8 +118,14 @@ class ActionButtonComponent extends React.Component {
const buttonTitle = button.title ? button.title : '';
const key = `${buttonTitle.replace(/\s/g, '_')}_${button.icon}`;
buttonComps.push(
// TODO: By default, ReactNativeActionButton also adds a title, which is focusable
// by the screen reader. As such, each item currently is double-focusable
<ReactNativeActionButton.Item key={key} buttonColor={button.color} title={buttonTitle} onPress={button.onPress}>
<Icon name={button.icon} style={styles.actionButtonIcon} />
<Icon
name={button.icon}
style={styles.actionButtonIcon}
accessibilityLabel={buttonTitle}
/>
</ReactNativeActionButton.Item>
);
}

View File

@ -61,7 +61,14 @@ class Checkbox extends Component {
// if (style.display) thStyle.display = style.display;
return (
<TouchableHighlight onPress={() => this.onPress()} style={thStyle}>
<TouchableHighlight
onPress={() => this.onPress()}
style={thStyle}
accessibilityRole="checkbox"
accessibilityState={{
checked: this.state.checked,
}}
accessibilityLabel={this.props.accessibilityLabel ?? ''}>
<Icon name={iconName} style={checkboxIconStyle} />
</TouchableHighlight>
);

View File

@ -6,6 +6,7 @@ const { Checkbox } = require('./checkbox.js');
const Note = require('@joplin/lib/models/Note').default;
const time = require('@joplin/lib/time').default;
const { themeStyle } = require('./global-style.js');
const { _ } = require('@joplin/lib/locale');
class NoteItemComponent extends Component {
constructor() {
@ -128,13 +129,20 @@ class NoteItemComponent extends Component {
const selectionWrapperStyle = isSelected ? this.styles().selectionWrapperSelected : this.styles().selectionWrapper;
const noteTitle = Note.displayTitle(note);
return (
<TouchableOpacity onPress={() => this.onPress()} onLongPress={() => this.onLongPress()} activeOpacity={0.5}>
<View style={selectionWrapperStyle}>
<View style={opacityStyle}>
<View style={listItemStyle}>
<Checkbox style={checkboxStyle} checked={checkboxChecked} onChange={checked => this.todoCheckbox_change(checked)} />
<Text style={listItemTextStyle}>{Note.displayTitle(note)}</Text>
<Checkbox
style={checkboxStyle}
checked={checkboxChecked}
onChange={checked => this.todoCheckbox_change(checked)}
accessibilityLabel={_('to-do: %s', noteTitle)}
/>
<Text style={listItemTextStyle}>{noteTitle}</Text>
</View>
</View>
</View>

View File

@ -225,7 +225,12 @@ class ScreenHeaderComponent extends React.PureComponent {
render() {
function sideMenuButton(styles, onPress) {
return (
<TouchableOpacity onPress={onPress}>
<TouchableOpacity
onPress={onPress}
accessibilityLabel={_('Sidebar')}
accessibilityHint={_('Show/hide the sidebar')}
accessibilityRole="button">
<View style={styles.sideMenuButton}>
<Icon name="md-menu" style={styles.topIcon} />
</View>
@ -235,9 +240,18 @@ class ScreenHeaderComponent extends React.PureComponent {
function backButton(styles, onPress, disabled) {
return (
<TouchableOpacity onPress={onPress} disabled={disabled}>
<TouchableOpacity
onPress={onPress}
disabled={disabled}
accessibilityLabel={_('Back')}
accessibilityHint={_('Navigate to the previous view')}
accessibilityRole="button">
<View style={disabled ? styles.backButtonDisabled : styles.backButton}>
<Icon name="md-arrow-back" style={styles.topIcon} />
<Icon
name="md-arrow-back"
style={styles.topIcon}
/>
</View>
</TouchableOpacity>
);
@ -249,7 +263,14 @@ class ScreenHeaderComponent extends React.PureComponent {
const icon = disabled ? <Icon name="md-checkmark" style={styles.savedButtonIcon} /> : <Image style={styles.saveButtonIcon} source={require('./SaveIcon.png')} />;
return (
<TouchableOpacity onPress={onPress} disabled={disabled} style={{ padding: 0 }}>
<TouchableOpacity
onPress={onPress}
disabled={disabled}
style={{ padding: 0 }}
accessibilityLabel={_('Save changes')}
accessibilityHint={disabled ? _('Any changes have been saved') : null}
accessibilityRole="button">
<View style={disabled ? styles.saveButtonDisabled : styles.saveButton}>{icon}</View>
</TouchableOpacity>
);
@ -262,7 +283,11 @@ class ScreenHeaderComponent extends React.PureComponent {
const viewStyle = options.disabled ? this.styles().iconButtonDisabled : this.styles().iconButton;
return (
<TouchableOpacity onPress={options.onPress} style={{ padding: 0 }} disabled={!!options.disabled}>
<TouchableOpacity
onPress={options.onPress}
style={{ padding: 0 }}
disabled={!!options.disabled}
accessibilityRole="button">
<View style={viewStyle}>{icon}</View>
</TouchableOpacity>
);
@ -287,7 +312,11 @@ class ScreenHeaderComponent extends React.PureComponent {
function selectAllButton(styles, onPress) {
return (
<TouchableOpacity onPress={onPress}>
<TouchableOpacity
onPress={onPress}
accessibilityLabel={_('Select all')}
accessibilityRole="button">
<View style={styles.iconButton}>
<Icon name="md-checkmark-circle-outline" style={styles.topIcon} />
</View>
@ -297,7 +326,11 @@ class ScreenHeaderComponent extends React.PureComponent {
function searchButton(styles, onPress) {
return (
<TouchableOpacity onPress={onPress}>
<TouchableOpacity
onPress={onPress}
accessibilityLabel={_('Search')}
accessibilityRole="button">
<View style={styles.iconButton}>
<Icon name="md-search" style={styles.topIcon} />
</View>
@ -307,7 +340,15 @@ class ScreenHeaderComponent extends React.PureComponent {
function deleteButton(styles, onPress, disabled) {
return (
<TouchableOpacity onPress={onPress} disabled={disabled}>
<TouchableOpacity
onPress={onPress}
disabled={disabled}
accessibilityLabel={_('Delete')}
accessibilityHint={
disabled ? null : _('Delete selected notes')
}
accessibilityRole="button">
<View style={disabled ? styles.iconButtonDisabled : styles.iconButton}>
<Icon name="md-trash" style={styles.topIcon} />
</View>
@ -317,7 +358,15 @@ class ScreenHeaderComponent extends React.PureComponent {
function duplicateButton(styles, onPress, disabled) {
return (
<TouchableOpacity onPress={onPress} disabled={disabled}>
<TouchableOpacity
onPress={onPress}
disabled={disabled}
accessibilityLabel={_('Duplicate')}
accessibilityHint={
disabled ? null : _('Duplicate selected notes')
}
accessibilityRole="button">
<View style={disabled ? styles.iconButtonDisabled : styles.iconButton}>
<Icon name="md-copy" style={styles.topIcon} />
</View>
@ -327,7 +376,11 @@ class ScreenHeaderComponent extends React.PureComponent {
function sortButton(styles, onPress) {
return (
<TouchableOpacity onPress={onPress}>
<TouchableOpacity
onPress={onPress}
accessibilityLabel={_('Sort notes by')}
accessibilityRole="button">
<View style={styles.iconButton}>
<Icon name="filter-outline" style={styles.topIcon} />
</View>

View File

@ -250,7 +250,8 @@ class SideMenuContentComponent extends Component {
let iconWrapper = null;
const iconName = this.props.collapsedFolderIds.indexOf(folder.id) >= 0 ? 'chevron-down' : 'chevron-up';
const collapsed = this.props.collapsedFolderIds.indexOf(folder.id) >= 0;
const iconName = collapsed ? 'chevron-down' : 'chevron-up';
const iconComp = <Icon name={iconName} style={this.styles().folderIcon} />;
iconWrapper = !hasChildren ? null : (
@ -260,6 +261,9 @@ class SideMenuContentComponent extends Component {
onPress={() => {
if (hasChildren) this.folder_togglePress(folder);
}}
accessibilityLabel={collapsed ? _('Expand folder') : _('Collapse folder')}
accessibilityRole="togglebutton"
>
{iconComp}
</TouchableOpacity>