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:
parent
6d92e982dc
commit
b108bf799d
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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 }
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -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%;
|
||||
`;
|
||||
|
@ -0,0 +1,6 @@
|
||||
|
||||
.note-count-label {
|
||||
color: var(--joplin-color-faded2);
|
||||
padding-left: 8px;
|
||||
user-select: none;
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head id="joplin-container-root-head">
|
||||
<meta charset="UTF-8">
|
||||
<title>Note viewer</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
|
Loading…
Reference in New Issue
Block a user