Add a11y linter (#130)

This commit is contained in:
Mihail Novikov
2018-07-06 20:13:24 +04:00
committed by Aleksei Gurianov
parent 9949632cd7
commit b08e351da0
9 changed files with 127 additions and 37 deletions
+10 -2
View File
@@ -1,7 +1,15 @@
module.exports = {
parser: 'babel-eslint',
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
plugins: ['react', 'prettier'],
extends: [
'eslint:recommended',
'plugin:jsx-a11y/recommended',
'plugin:prettier/recommended',
],
plugins: [
'react',
'jsx-a11y',
'prettier',
],
env: {
browser: true,
node: true,
+13
View File
@@ -0,0 +1,13 @@
const handleBtnKeyPress = (event, handler) => {
if (event.key === ' ' || event.key === 'Enter') {
event.preventDefault();
handler && handler();
}
};
export const getHandleClickProps = handler => ({
role: 'button',
tabIndex: 0,
onClick: handler,
onKeyPress: event => handleBtnKeyPress(event, handler),
});
+8 -9
View File
@@ -2,6 +2,7 @@
import { h, Component } from 'preact';
import { PROVIDER_NAMES } from 'common/constants';
import { getHandleClickProps } from 'common/accessibility';
export default class AuthPanel extends Component {
constructor(props) {
@@ -41,11 +42,11 @@ export default class AuthPanel extends Component {
{loggedIn && (
<div className="auth-panel__column">
You signed in as{' '}
<strong className="auth-panel__username" onClick={this.toggleUserId}>
<strong {...getHandleClickProps(this.toggleUserId)} className="auth-panel__username">
{user.name}
</strong>
{isUserIdVisible && <span className="auth-panel__user-id"> ({user.id})</span>}.{' '}
<span className="auth-panel__pseudo-link" role="link" tabIndex="0" onClick={props.onSignOut}>
<span {...getHandleClickProps(props.onSignOut)} className="auth-panel__pseudo-link" role="link" >
Sign out?
</span>
</div>
@@ -62,9 +63,8 @@ export default class AuthPanel extends Component {
{comma}
<span
className="auth-panel__pseudo-link"
{...getHandleClickProps(() => props.onSignIn(provider))}
role="link"
tabIndex="0"
onClick={() => props.onSignIn(provider)}
>
{PROVIDER_NAMES[provider]}
</span>
@@ -79,9 +79,8 @@ export default class AuthPanel extends Component {
{user.admin && (
<span
className="auth-panel__pseudo-link auth-panel__admin-action"
{...getHandleClickProps(this.toggleBlockedVisibility)}
role="link"
tabIndex="0"
onClick={this.toggleBlockedVisibility}
>
{isBlockedVisible ? 'Hide' : 'Show'} blocked
</span>
@@ -91,16 +90,16 @@ export default class AuthPanel extends Component {
<span className="auth-panel__sort">
Sort by{' '}
<label className="auth-panel__select-label">
<span className="auth-panel__select-label">
{sortArray.find(x => x.selected).label}
<select className="auth-panel__select" onChange={this.onSortChange}>
<select className="auth-panel__select" onChange={this.onSortChange} onBlur={this.onSortChange}>
{sortArray.map(sort => (
<option value={sort.value} selected={sort.selected}>
{sort.label}
</option>
))}
</select>
</label>
</span>
</span>
</div>
</div>
@@ -2,6 +2,7 @@
import { h, Component } from 'preact';
import api from 'common/api';
import { getHandleClickProps } from 'common/accessibility';
export default class BlockedUsers extends Component {
constructor(props) {
@@ -51,12 +52,16 @@ export default class BlockedUsers extends Component {
<span className="blocked-users__username">{user.name}</span>{' '}
<span className="blocked-users__user-id">({user.id})</span>
{isUserUnblocked && (
<span className="blocked-users__action" onClick={() => this.block(user)}>
<span
{...getHandleClickProps(() => this.block(user))}
className="blocked-users__action">
block
</span>
)}
{!isUserUnblocked && (
<span className="blocked-users__action" onClick={() => this.unblock(user)}>
<span
{...getHandleClickProps(() => this.unblock(user))}
className="blocked-users__action">
unblock
</span>
)}
+21 -22
View File
@@ -2,6 +2,7 @@
import { h, Component } from 'preact';
import api from 'common/api';
import { getHandleClickProps } from 'common/accessibility';
import { API_BASE, BASE_URL, COMMENT_NODE_CLASSNAME_PREFIX } from 'common/constants';
import { url } from 'common/settings';
import store from 'common/store';
@@ -416,7 +417,11 @@ export default class Comment extends Component {
)}
{mods.view !== 'user' && (
<span className="comment__username" title={o.user.id} onClick={this.toggleUserInfoVisibility}>
<span
{...getHandleClickProps(this.toggleUserInfoVisibility)}
className="comment__username"
title={o.user.id}
>
{o.user.name}
</span>
)}
@@ -424,7 +429,7 @@ export default class Comment extends Component {
{isAdmin &&
mods.view !== 'user' && (
<span
onClick={o.user.verified ? this.onUnverifyClick : this.onVerifyClick}
{...getHandleClickProps(o.user.verified ? this.onUnverifyClick : this.onVerifyClick)}
aria-label="Toggle verification"
title={o.user.verified ? 'Verified user' : 'Unverified user'}
className={b('comment__verification', {}, { active: o.user.verified, clickable: true })}
@@ -449,7 +454,9 @@ export default class Comment extends Component {
aria-label="Go to parent comment"
title="Go to parent comment"
onClick={this.scrollToParent}
/>
>
{' '}
</a>
)}
{isAdmin && userBlocked && mods.view !== 'user' && <span className="comment__status">Blocked</span>}
@@ -459,9 +466,8 @@ export default class Comment extends Component {
{!mods.disabled &&
mods.view !== 'user' && (
<span
{...getHandleClickProps(this.toggleCollapse)}
className={b('comment__action', {}, { type: 'collapse', selected: mods.collapsed })}
tabIndex="0"
onClick={this.toggleCollapse}
>
{mods.collapsed ? '+' : ''}
</span>
@@ -474,10 +480,8 @@ export default class Comment extends Component {
{},
{ type: 'up', selected: scoreIncreased, disabled: isGuest || isCurrentUser }
)}
role="button"
aria-disabled={isGuest || isCurrentUser}
tabIndex="0"
onClick={isGuest || isCurrentUser ? null : this.increaseScore}
{...getHandleClickProps(isGuest || isCurrentUser ? null : this.increaseScore)}
title={
isGuest
? 'Only authorized users are allowed to vote'
@@ -495,15 +499,13 @@ export default class Comment extends Component {
</span>
<span
{...getHandleClickProps(isGuest || isCurrentUser ? null : this.decreaseScore)}
className={b(
'comment__vote',
{},
{ type: 'down', selected: scoreDecreased, disabled: isGuest || isCurrentUser }
)}
role="button"
aria-disabled={isGuest || isCurrentUser ? 'true' : 'false'}
tabIndex="0"
onClick={isGuest || isCurrentUser ? null : this.decreaseScore}
title={
isGuest
? 'Only authorized users are allowed to vote'
@@ -524,7 +526,7 @@ export default class Comment extends Component {
!mods.disabled &&
!isGuest &&
mods.view !== 'user' && (
<span className="comment__action" role="button" tabIndex="0" onClick={this.toggleReplying}>
<span {...getHandleClickProps(this.toggleReplying)} className="comment__action">
{isReplying ? 'Cancel' : 'Reply'}
</span>
)}
@@ -536,10 +538,8 @@ export default class Comment extends Component {
(!!editTimeLeft || isEditing) &&
mods.view !== 'user' && (
<span
{...getHandleClickProps(this.toggleEditing)}
className="comment__action comment__action_type_edit"
role="button"
tabIndex="0"
onClick={this.toggleEditing}
>
{isEditing ? 'Cancel' : 'Edit'}
{editTimeLeft && ` (${editTimeLeft})`}
@@ -550,31 +550,31 @@ export default class Comment extends Component {
isAdmin && (
<span className="comment__controls">
{!pinned && (
<span className="comment__control" role="button" tabIndex="0" onClick={this.onPinClick}>
<span {...getHandleClickProps(this.onPinClick)} className="comment__control">
Pin
</span>
)}
{pinned && (
<span className="comment__control" role="button" tabIndex="0" onClick={this.onUnpinClick}>
<span {...getHandleClickProps(this.onUnpinClick)} className="comment__control">
Unpin
</span>
)}
{userBlocked && (
<span className="comment__control" role="button" tabIndex="0" onClick={this.onUnblockClick}>
<span {...getHandleClickProps(this.onUnblockClick)} className="comment__control">
Unblock
</span>
)}
{!userBlocked && (
<span className="comment__control" role="button" tabIndex="0" onClick={this.onBlockClick}>
<span {...getHandleClickProps(this.onBlockClick)} className="comment__control">
Block
</span>
)}
{!deleted && (
<span className="comment__control" role="button" tabIndex="0" onClick={this.onDeleteClick}>
<span {...getHandleClickProps(this.onDeleteClick)} className="comment__control">
Delete
</span>
)}
@@ -587,7 +587,7 @@ export default class Comment extends Component {
{isReplying &&
mods.view !== 'user' && (
<Input mix="comment__input" onSubmit={this.onReply} onCancel={this.toggleReplying} pid={o.id} autoFocus />
<Input mix="comment__input" onSubmit={this.onReply} onCancel={this.toggleReplying} pid={o.id} />
)}
{isEditing &&
@@ -600,7 +600,6 @@ export default class Comment extends Component {
id={o.id}
value={o.orig}
errorMessage={!editTimeLeft && 'Editing time has expired.'}
autoFocus
/>
)}
</article>
+1 -1
View File
@@ -134,7 +134,7 @@ export default class Input extends Component {
const { mods = {}, value = null, errorMessage } = props;
return (
<form className={b('input', props)} onSubmit={this.send} role="form" aria-label="New comment">
<form className={b('input', props)} onSubmit={this.send} aria-label="New comment">
<div className="input__field-wrapper">
<textarea
className="input__field"
+2 -1
View File
@@ -2,6 +2,7 @@
import { h, Component } from 'preact';
import api from 'common/api';
import { getHandleClickProps } from 'common/accessibility';
import Comment from 'components/comment';
import Preloader from 'components/preloader';
@@ -45,7 +46,7 @@ export default class UserInfo extends Component {
)}
<div>
<span className="user-info__close" onClick={onClose}>
<span {...getHandleClickProps(onClose)} className="user-info__close">
Close
</span>
</div>
+64
View File
@@ -260,6 +260,16 @@
"sprintf-js": "~1.0.2"
}
},
"aria-query": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
"integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
"dev": true,
"requires": {
"ast-types-flow": "0.0.7",
"commander": "^2.11.0"
}
},
"arr-diff": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
@@ -374,6 +384,12 @@
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"dev": true
},
"ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
"integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
"dev": true
},
"astral-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
@@ -454,6 +470,15 @@
"is-buffer": "^1.1.5"
}
},
"axobject-query": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.1.tgz",
"integrity": "sha1-Bd+nBa2orZ25k/polvItOVsLCgc=",
"dev": true,
"requires": {
"ast-types-flow": "0.0.7"
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -2978,6 +3003,12 @@
"es5-ext": "^0.10.9"
}
},
"damerau-levenshtein": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
"integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=",
"dev": true
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -3415,6 +3446,12 @@
"minimalistic-crypto-utils": "^1.0.0"
}
},
"emoji-regex": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
"integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==",
"dev": true
},
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@@ -3713,6 +3750,33 @@
}
}
},
"eslint-plugin-jsx-a11y": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.0.tgz",
"integrity": "sha512-hnhf28u7Z9zlh7Y56tETrwnPeBvXgcqlP7ntHvZsWQs/n/p/vPnfNMNFWTqJAFcbd8PrDEifX1NRGHsjnUmqMw==",
"dev": true,
"requires": {
"aria-query": "^3.0.0",
"array-includes": "^3.0.3",
"ast-types-flow": "^0.0.7",
"axobject-query": "^2.0.1",
"damerau-levenshtein": "^1.0.4",
"emoji-regex": "^6.5.1",
"has": "^1.0.3",
"jsx-ast-utils": "^2.0.1"
},
"dependencies": {
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
}
}
},
"eslint-plugin-prettier": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.1.tgz",
+1
View File
@@ -36,6 +36,7 @@
"cross-env": "^5.2.0",
"css-loader": "^0.28.11",
"eslint": "^4.19.1",
"eslint-plugin-jsx-a11y": "^6.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-prettier": "^2.6.1",
"eslint-plugin-react": "^7.10.0",