1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Accessibility: Add missing labels and role information to several controls (#10788)

This commit is contained in:
Henry Heino 2024-07-28 06:53:32 -07:00 committed by GitHub
parent 6d92e982dc
commit b108bf799d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 54 additions and 24 deletions

View File

@ -174,7 +174,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
textDecoration: 'none',
backgroundColor: theme.backgroundColor,
padding: '.14em',
display: 'flex',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
marginLeft: '0.5em',
@ -281,11 +281,13 @@ class NotePropertiesDialog extends React.Component<Props, State> {
public createNoteField(key: keyof FormNote, value: any) {
const styles = this.styles(this.props.themeId);
const theme = themeStyle(this.props.themeId);
const labelComp = <label style={{ ...theme.textStyle, ...theme.controlBoxLabel }}>{this.formatLabel(key)}</label>;
const labelText = this.formatLabel(key);
const labelComp = <label role='rowheader' style={{ ...theme.textStyle, ...theme.controlBoxLabel }}>{labelText}</label>;
let controlComp = null;
let editComp = null;
let editCompHandler = null;
let editCompIcon = null;
let editComDescription = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onKeyDown = (event: any) => {
@ -320,6 +322,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
void this.saveProperty();
};
editCompIcon = 'fa-save';
editComDescription = _('Save changes');
} else {
controlComp = (
<input
@ -374,28 +377,35 @@ class NotePropertiesDialog extends React.Component<Props, State> {
this.editPropertyButtonClick(key, value);
};
editCompIcon = 'fa-edit';
editComDescription = _('Edit');
}
// Add the copy icon and the 'copy on click' event
if (key === 'id') {
editCompIcon = 'fa-copy';
editCompHandler = () => clipboard.writeText(value);
editComDescription = _('Copy');
}
}
if (editCompHandler && !this.isReadOnly()) {
editComp = (
<a href="#" onClick={editCompHandler} style={styles.editPropertyButton}>
<a
href="#"
onClick={editCompHandler}
style={styles.editPropertyButton}
aria-label={editComDescription}
title={editComDescription}
>
<i className={`fas ${editCompIcon}`} aria-hidden="true"></i>
</a>
);
}
return (
<div key={key} style={theme.controlBox} className="note-property-box">
<div role='row' key={key} style={theme.controlBox} className="note-property-box">
{labelComp}
{controlComp}
{editComp}
<span role='cell'>{controlComp} {editComp}</span>
</div>
);
}
@ -439,8 +449,10 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return (
<div style={theme.dialogModalLayer}>
<div style={theme.dialogBox}>
<div style={theme.dialogTitle}>{_('Note properties')}</div>
<div>{noteComps}</div>
<div style={theme.dialogTitle} id='note-properties-dialog-title'>{_('Note properties')}</div>
<div role='table' aria-labelledby='note-properties-dialog-title'>
{noteComps}
</div>
<DialogButtonRow themeId={this.props.themeId} okButtonShow={!this.isReadOnly()} okButtonRef={this.okButton} onClick={this.buttonRow_click}/>
</div>
</div>

View File

@ -2,12 +2,14 @@ import * as React from 'react';
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
import ExpandLink from './ExpandLink';
import { StyledListItem, StyledListItemAnchor, StyledNoteCount, StyledShareIcon, StyledSpanFix } from '../styles';
import { StyledListItem, StyledListItemAnchor, StyledShareIcon, StyledSpanFix } from '../styles';
import { ItemClickListener, ItemContextMenuListener, ItemDragListener } from '../types';
import FolderIconBox from '../../FolderIconBox';
import { getTrashFolderIcon, getTrashFolderId } from '@joplin/lib/services/trash';
import Folder from '@joplin/lib/models/Folder';
import { ModelType } from '@joplin/lib/BaseModel';
import { _ } from '@joplin/lib/locale';
import NoteCount from './NoteCount';
const renderFolderIcon = (folderIcon: FolderIcon) => {
if (!folderIcon) {
@ -47,8 +49,8 @@ interface FolderItemProps {
function FolderItem(props: FolderItemProps) {
const { hasChildren, showFolderIcon, isExpanded, parentId, depth, selected, folderId, folderTitle, folderIcon, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_, shareId } = props;
const noteCountComp = noteCount ? <StyledNoteCount className="note-count-label">{noteCount}</StyledNoteCount> : null;
const shareIcon = shareId && !parentId ? <StyledShareIcon className="fas fa-share-alt"></StyledShareIcon> : null;
const shareTitle = _('Shared');
const shareIcon = shareId && !parentId ? <StyledShareIcon aria-label={shareTitle} title={shareTitle} className="fas fa-share-alt"/> : null;
const draggable = ![getTrashFolderId(), Folder.conflictFolderId()].includes(folderId);
const doRenderFolderIcon = () => {
@ -69,6 +71,7 @@ function FolderItem(props: FolderItemProps) {
isConflictFolder={folderId === Folder.conflictFolderId()}
href="#"
selected={selected}
aria-selected={selected}
shareId={shareId}
data-id={folderId}
data-type={ModelType.Folder}
@ -80,7 +83,7 @@ function FolderItem(props: FolderItemProps) {
onDoubleClick={onFolderToggleClick_}
>
{doRenderFolderIcon()}<StyledSpanFix className="title">{folderTitle}</StyledSpanFix>
{shareIcon} {noteCountComp}
{shareIcon} <NoteCount count={noteCount}/>
</StyledListItemAnchor>
</StyledListItem>
);

View File

@ -61,7 +61,7 @@ const HeaderItem: React.FC<Props> = props => {
tabIndex={0}
ref={props.anchorRef}
>
<StyledHeaderIcon className={item.iconName}/>
<StyledHeaderIcon aria-label='' className={item.iconName}/>
<StyledHeaderLabel>{item.label}</StyledHeaderLabel>
</StyledHeader>
{ item.onPlusButtonClick && addButton }

View File

@ -1,5 +1,5 @@
import * as React from 'react';
import { StyledNoteCount } from '../styles';
import { _n } from '@joplin/lib/locale';
interface Props {
@ -8,7 +8,8 @@ interface Props {
const NoteCount: React.FC<Props> = props => {
const count = props.count;
return count ? <StyledNoteCount className="note-count-label">{count}</StyledNoteCount> : null;
const title = _n('Contains %d note', 'Contains %d notes', count, count);
return count ? <div role='note' aria-label={title} title={title} className="note-count-label">{count}</div> : null;
};
export default NoteCount;

View File

@ -33,10 +33,12 @@ const TagItem = (props: Props) => {
}, [props.onClick, tag]);
return (
<StyledListItem selected={selected}
<StyledListItem
selected={selected}
className={`list-item-container ${selected ? 'selected' : ''}`}
onDrop={props.onTagDrop}
data-tag-id={tag.id}
aria-selected={selected}
>
<EmptyExpandLink/>
<StyledListItemAnchor

View File

@ -1,4 +1,5 @@
@use 'styles/folder-and-tag-list.scss';
@use 'styles/note-count-label.scss';
@use 'styles/sidebar-expand-icon.scss';
@use 'styles/sidebar-expand-link.scss';
@use 'styles/sidebar-header-container.scss';

View File

@ -95,12 +95,6 @@ export const StyledShareIcon = styled.i`
margin-left: 8px;
`;
export const StyledNoteCount = styled.div`
color: ${(props: StyleProps) => props.theme.colorFaded2};
padding-left: 8px;
user-select: none;
`;
export const StyledSynchronizeButton = styled(Button)`
width: 100%;
`;

View File

@ -0,0 +1,6 @@
.note-count-label {
color: var(--joplin-color-faded2);
padding-left: 8px;
user-select: none;
}

View File

@ -50,16 +50,22 @@ export default function ToolbarButton(props: Props) {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis' };
const disabled = !isEnabled;
return (
<StyledRoot
className={classes.join(' ')}
disabled={!isEnabled}
title={tooltip}
href="#"
hasTitle={!!title}
onClick={() => {
if (isEnabled && onClick) onClick();
}}
// At least on MacOS, the disabled HTML prop isn't sufficient for the screen reader
// to read the element as disable. For this, aria-disabled is necessary.
disabled={disabled}
aria-disabled={!isEnabled}
role='button'
>
{icon}
<span style={style}>{title}</span>

View File

@ -59,6 +59,7 @@ export interface OnChangeEvent {
export default function(props: Props) {
const iconName = !props.searchStarted ? CommandService.instance().iconName('search') : 'fa fa-times';
const iconLabel = !props.searchStarted ? _('Search') : _('Clear search');
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onChange = useCallback((event: any) => {
@ -79,7 +80,10 @@ export default function(props: Props) {
spellCheck={false}
disabled={props.disabled}
/>
<SearchButton onClick={props.onSearchButtonClick}>
<SearchButton
aria-label={iconLabel}
onClick={props.onSearchButtonClick}
>
<SearchButtonIcon className={iconName}/>
</SearchButton>
</Root>

View File

@ -2,6 +2,7 @@
<html>
<head id="joplin-container-root-head">
<meta charset="UTF-8">
<title>Note viewer</title>
<style>
body {