diff --git a/.eslintrc.yml b/.eslintrc.yml index f0a9c93..c98f60c 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -4,14 +4,16 @@ parserOptions: project: tsconfig.json sourceType: module +plugins: + - simple-import-sort + - jsx-a11y + extends: - 'plugin:@typescript-eslint/recommended' - 'prettier' - prettier/@typescript-eslint - react-app - -plugins: - - simple-import-sort + - 'plugin:jsx-a11y/recommended' env: node: true diff --git a/src/components/APIConfig.js b/src/components/APIConfig.js index 282da42..bcda06e 100644 --- a/src/components/APIConfig.js +++ b/src/components/APIConfig.js @@ -78,6 +78,7 @@ function APIConfig({ apiConfig, dispatch }) { ); return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions
diff --git a/src/components/CollapsibleSectionHeader.module.css b/src/components/CollapsibleSectionHeader.module.css index 854a0c6..b654f35 100644 --- a/src/components/CollapsibleSectionHeader.module.css +++ b/src/components/CollapsibleSectionHeader.module.css @@ -2,10 +2,15 @@ display: flex; align-items: center; + &:focus { + outline: none; + } + .arrow { display: inline-flex; transform: rotate(0deg); transition: transform 0.3s; + &.isOpen { transform: rotate(180deg); } diff --git a/src/components/CollapsibleSectionHeader.js b/src/components/CollapsibleSectionHeader.tsx similarity index 53% rename from src/components/CollapsibleSectionHeader.js rename to src/components/CollapsibleSectionHeader.tsx index 794aafb..77e7596 100644 --- a/src/components/CollapsibleSectionHeader.js +++ b/src/components/CollapsibleSectionHeader.tsx @@ -1,23 +1,40 @@ import cx from 'clsx'; -import React from 'react'; +import * as React from 'react'; import { ChevronDown } from 'react-feather'; +import { keyCodes } from '../misc/keycode'; import Button from './Button'; import s from './CollapsibleSectionHeader.module.css'; import { SectionNameType } from './shared/Basic'; type Props = { - name: string, - type: string, - qty?: number, - toggle?: () => void, - isOpen?: boolean, + name: string; + type: string; + qty?: number; + toggle?: () => void; + isOpen?: boolean; }; export default function Header({ name, type, toggle, isOpen, qty }: Props) { + const handleKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + e.preventDefault(); + if (e.keyCode === keyCodes.Enter || e.keyCode === keyCodes.Space) { + toggle(); + } + }, + [toggle] + ); return ( -
-
+
+
diff --git a/src/components/Selection.js b/src/components/Selection.js index 8302de3..764e7b3 100644 --- a/src/components/Selection.js +++ b/src/components/Selection.js @@ -11,21 +11,21 @@ export default function Selection({ onChange, }) { return ( - // TODO a11y - // tabIndex="0"
{optionPropsList.map((props, idx) => { const className = cx(s.item, { [s.itemActive]: idx === selectedIndex }); + const doSelect = (ev) => { + ev.preventDefault(); + if (idx !== selectedIndex) onChange(idx); + }; return (
{ - ev.preventDefault(); - if (idx !== selectedIndex) { - onChange(idx); - } - }} + tabIndex={0} + role="menuitem" + onKeyDown={doSelect} + onClick={doSelect} >
diff --git a/src/components/SideBar.module.css b/src/components/SideBar.module.css index 275a007..7ecb3c3 100644 --- a/src/components/SideBar.module.css +++ b/src/components/SideBar.module.css @@ -90,6 +90,7 @@ justify-content: center; align-items: center; + outline: none; padding: 5px; color: var(--color-text); border-radius: 100%; @@ -104,7 +105,6 @@ .themeSwitchContainer { appearance: none; - outline: none; user-select: none; background: none; cursor: pointer; diff --git a/src/components/proxies/ClosePrevConns.tsx b/src/components/proxies/ClosePrevConns.tsx index 567a3e5..5617efe 100644 --- a/src/components/proxies/ClosePrevConns.tsx +++ b/src/components/proxies/ClosePrevConns.tsx @@ -29,6 +29,7 @@ export function ClosePrevConns({ }; return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions

Close Connections?

diff --git a/src/components/proxies/Proxy.module.css b/src/components/proxies/Proxy.module.css index f233193..03c4df8 100644 --- a/src/components/proxies/Proxy.module.css +++ b/src/components/proxies/Proxy.module.css @@ -9,6 +9,12 @@ flex-direction: column; justify-content: space-between; + outline: none; + border: 1px solid transparent; + &:focus { + border: 1px solid var(--color-focus-blue); + } + max-width: 280px; @media (--breakpoint-not-small) { min-width: 200px; diff --git a/src/components/proxies/Proxy.tsx b/src/components/proxies/Proxy.tsx index d456da5..0cbf561 100644 --- a/src/components/proxies/Proxy.tsx +++ b/src/components/proxies/Proxy.tsx @@ -1,5 +1,6 @@ import cx from 'clsx'; import * as React from 'react'; +import { keyCodes } from 'src/misc/keycode'; import { getDelay, getProxies, NonProxyTypes } from '../../store/proxies'; import { connect } from '../StateProvider'; @@ -76,17 +77,35 @@ function ProxySmallImpl({ } return ret; }, [name, latency]); + + const doSelect = React.useCallback(() => { + isSelectable && onClick && onClick(name); + }, [name, onClick, isSelectable]); + + const className = useMemo(() => { + return cx(s0.proxySmall, { + [s0.now]: now, + [s0.selectable]: isSelectable, + }); + }, [isSelectable, now]); + + const handleKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (e.keyCode === keyCodes.Enter) { + doSelect(); + } + }, + [doSelect] + ); + return (

{ - isSelectable && onClick && onClick(name); - }} + onClick={doSelect} + onKeyDown={handleKeyDown} + role={isSelectable ? 'menuitem' : ''} /> ); } @@ -100,16 +119,32 @@ function ProxyImpl({ onClick, }: ProxyProps) { const color = useMemo(() => getLabelColor(latency), [latency]); + const doSelect = React.useCallback(() => { + isSelectable && onClick && onClick(name); + }, [name, onClick, isSelectable]); + const handleKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (e.keyCode === keyCodes.Enter) { + doSelect(); + } + }, + [doSelect] + ); + const className = useMemo(() => { + return cx(s0.proxy, { + [s0.now]: now, + [s0.error]: latency && latency.error, + [s0.selectable]: isSelectable, + }); + }, [isSelectable, now, latency]); + return (
{ - isSelectable && onClick && onClick(name); - }} + tabIndex={0} + className={className} + onClick={doSelect} + onKeyDown={handleKeyDown} + role={isSelectable ? 'menuitem' : ''} >
{name}
diff --git a/src/components/shared/Select.tsx b/src/components/shared/Select.tsx index 03ac084..376fb3c 100644 --- a/src/components/shared/Select.tsx +++ b/src/components/shared/Select.tsx @@ -10,6 +10,7 @@ type Props = { export default function Select({ options, selected, onChange }: Props) { return ( + // eslint-disable-next-line jsx-a11y/no-onchange