mirror of https://github.com/laurent22/joplin.git
Mobile: Add alt text/roles to some buttons to improve accessibility (#6616)
parent
f64d046c62
commit
c58ce8e2da
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue