From 37d2b0ce5c09658d5e49be84b891d9a0d83f6f5c Mon Sep 17 00:00:00 2001 From: simeng-li Date: Sun, 9 Oct 2022 15:07:09 +0800 Subject: [PATCH] feat(console): add a11y lint to ac (#2066) --- packages/console/package.json | 2 +- .../AppContent/components/UserInfo/index.tsx | 12 +- .../console/src/components/AppError/index.tsx | 6 + .../src/components/CopyToClipboard/index.tsx | 6 + .../components/DeleteConfirmModal/index.tsx | 1 + .../console/src/components/Drawer/index.tsx | 2 + .../src/components/Dropdown/DropdownItem.tsx | 14 +- .../console/src/components/Dropdown/index.tsx | 9 +- .../src/components/MultiTextInput/index.tsx | 9 +- .../src/components/RadioGroup/Radio.tsx | 2 + .../console/src/components/Select/index.tsx | 7 + .../src/components/TabNav/TabNavItem.tsx | 11 +- .../mdx-components/DetailsSummary/index.tsx | 23 +- .../console/src/mdx-components/Step/index.tsx | 20 +- .../components/CreateForm/index.tsx | 1 + .../src/pages/ConnectorDetails/index.tsx | 1 + .../components/ConnectorName/index.tsx | 1 + .../components/CreateForm/index.tsx | 2 +- .../components/GetStartedProgress/index.tsx | 24 +- .../console/src/pages/GetStarted/index.tsx | 24 +- .../SignInExperience/components/Preview.tsx | 14 +- .../UserDetails/components/UserConnectors.tsx | 2 +- .../console/src/pages/UserDetails/index.tsx | 1 + .../Users/components/CreateForm/index.tsx | 1 + packages/console/src/pages/Users/index.tsx | 1 + packages/console/src/utilities/a11y.ts | 21 ++ pnpm-lock.yaml | 270 +++++++++++++++++- 27 files changed, 438 insertions(+), 49 deletions(-) create mode 100644 packages/console/src/utilities/a11y.ts diff --git a/packages/console/package.json b/packages/console/package.json index 567343277..c621bc026 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -29,7 +29,7 @@ "@parcel/transformer-sass": "2.7.0", "@parcel/transformer-svg-react": "2.7.0", "@silverhand/eslint-config": "1.0.0", - "@silverhand/eslint-config-react": "1.0.0", + "@silverhand/eslint-config-react": "1.1.0", "@silverhand/essentials": "^1.2.1", "@silverhand/ts-config": "1.0.0", "@silverhand/ts-config-react": "1.0.0", diff --git a/packages/console/src/components/AppContent/components/UserInfo/index.tsx b/packages/console/src/components/AppContent/components/UserInfo/index.tsx index 5586296a1..9913a48ad 100644 --- a/packages/console/src/components/AppContent/components/UserInfo/index.tsx +++ b/packages/console/src/components/AppContent/components/UserInfo/index.tsx @@ -1,12 +1,13 @@ import { useLogto, IdTokenClaims } from '@logto/react'; import classNames from 'classnames'; -import { useEffect, useRef, useState, MouseEvent } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import Dropdown, { DropdownItem } from '@/components/Dropdown'; import { Ring as Spinner } from '@/components/Spinner'; import { generateAvatarPlaceHolderById } from '@/consts/avatars'; import SignOut from '@/icons/SignOut'; +import { onKeyDownHandler } from '@/utilities/a11y'; import UserInfoSkeleton from '../UserInfoSkeleton'; import * as styles from './index.module.scss'; @@ -43,13 +44,18 @@ const UserInfo = () => { <>
{ + setShowDropdown(true); + })} onClick={() => { setShowDropdown(true); }} > {/* TODO: revert after SDK updated */} - + avatar
{username}
@@ -66,7 +72,7 @@ const UserInfo = () => { } - onClick={(event: MouseEvent) => { + onClick={(event) => { event.stopPropagation(); if (isLoading) { diff --git a/packages/console/src/components/AppError/index.tsx b/packages/console/src/components/AppError/index.tsx index 23bee6bfc..046906126 100644 --- a/packages/console/src/components/AppError/index.tsx +++ b/packages/console/src/components/AppError/index.tsx @@ -6,6 +6,7 @@ import ErrorDark from '@/assets/images/error-dark.svg'; import Error from '@/assets/images/error.svg'; import { useTheme } from '@/hooks/use-theme'; import { KeyboardArrowDown, KeyboardArrowUp } from '@/icons/Arrow'; +import { onKeyDownHandler } from '@/utilities/a11y'; import * as styles from './index.module.scss'; @@ -33,7 +34,12 @@ const AppError = ({ title, errorCode, errorMessage, callStack, children }: Props {errorMessage} {callStack && ( { + setIsDetailsOpen(!isDetailsOpen); + })} onClick={() => { setIsDetailsOpen(!isDetailsOpen); }} diff --git a/packages/console/src/components/CopyToClipboard/index.tsx b/packages/console/src/components/CopyToClipboard/index.tsx index 9fb8b7d89..66a0c4414 100644 --- a/packages/console/src/components/CopyToClipboard/index.tsx +++ b/packages/console/src/components/CopyToClipboard/index.tsx @@ -5,6 +5,7 @@ import { TFuncKey, useTranslation } from 'react-i18next'; import Copy from '@/icons/Copy'; import Eye from '@/icons/Eye'; import EyeClosed from '@/icons/EyeClosed'; +import { onKeyDownHandler } from '@/utilities/a11y'; import IconButton from '../IconButton'; import Tooltip from '../Tooltip'; @@ -57,6 +58,11 @@ const CopyToClipboard = ({ return (
{ + event.stopPropagation(); + })} onClick={(event) => { event.stopPropagation(); }} diff --git a/packages/console/src/components/DeleteConfirmModal/index.tsx b/packages/console/src/components/DeleteConfirmModal/index.tsx index 6772d6872..d0471158c 100644 --- a/packages/console/src/components/DeleteConfirmModal/index.tsx +++ b/packages/console/src/components/DeleteConfirmModal/index.tsx @@ -40,6 +40,7 @@ const DeleteConfirmModal = ({ {children} {expectedInput && ( { return ( ) => void; + onClick?: (event: MouseEvent | KeyboardEvent) => void; className?: string; children: ReactNode | Record; icon?: ReactNode; @@ -20,7 +22,13 @@ const DropdownItem = ({ iconClassName, type = 'default', }: Props) => ( -
  • +
  • {icon && {icon}} {children}
  • diff --git a/packages/console/src/components/Dropdown/index.tsx b/packages/console/src/components/Dropdown/index.tsx index 30c10056f..ddb0a10bf 100644 --- a/packages/console/src/components/Dropdown/index.tsx +++ b/packages/console/src/components/Dropdown/index.tsx @@ -3,6 +3,7 @@ import { ReactNode, RefObject, useRef } from 'react'; import ReactModal from 'react-modal'; import usePosition, { HorizontalAlignment } from '@/hooks/use-position'; +import { onKeyDownHandler } from '@/utilities/a11y'; import * as styles from './index.module.scss'; @@ -61,7 +62,13 @@ const Dropdown = ({ >
    {title &&
    {title}
    } -
      +
        {children}
    diff --git a/packages/console/src/components/MultiTextInput/index.tsx b/packages/console/src/components/MultiTextInput/index.tsx index bfe003174..0a191f4cc 100644 --- a/packages/console/src/components/MultiTextInput/index.tsx +++ b/packages/console/src/components/MultiTextInput/index.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import * as textButtonStyles from '@/components/TextButton/index.module.scss'; import Minus from '@/icons/Minus'; +import { onKeyDownHandler } from '@/utilities/a11y'; import ConfirmModal from '../ConfirmModal'; import IconButton from '../IconButton'; @@ -85,7 +86,13 @@ const MultiTextInput = ({ title, value, onChange, onKeyPress, error, placeholder )}
    ))} -
    +
    {t('general.add_another')}
    ({ className )} role="button" + tabIndex={0} + onKeyDown={onKeyDownHandler(() => { + if (!isReadOnly) { + setIsOpen(true); + } + })} onClick={() => { if (!isReadOnly) { setIsOpen(true); diff --git a/packages/console/src/components/TabNav/TabNavItem.tsx b/packages/console/src/components/TabNav/TabNavItem.tsx index e7cf06289..77214ff78 100644 --- a/packages/console/src/components/TabNav/TabNavItem.tsx +++ b/packages/console/src/components/TabNav/TabNavItem.tsx @@ -1,6 +1,8 @@ import classNames from 'classnames'; import { Link, useLocation } from 'react-router-dom'; +import { onKeyDownHandler } from '@/utilities/a11y'; + import * as styles from './TabNavItem.module.scss'; type Props = { @@ -16,7 +18,14 @@ const TabNavItem = ({ children, href, isActive, onClick }: Props) => { return (
    - {href ? {children} : {children}} + {href ? ( + {children} + ) : ( + // eslint-disable-next-line jsx-a11y/anchor-is-valid + + {children} + + )}
    ); }; diff --git a/packages/console/src/mdx-components/DetailsSummary/index.tsx b/packages/console/src/mdx-components/DetailsSummary/index.tsx index a02bfeef5..4065e08b5 100644 --- a/packages/console/src/mdx-components/DetailsSummary/index.tsx +++ b/packages/console/src/mdx-components/DetailsSummary/index.tsx @@ -1,8 +1,9 @@ import classNames from 'classnames'; -import { ReactNode, useState } from 'react'; +import { ReactNode, useState, useCallback } from 'react'; import AnimateHeight, { Height } from 'react-animate-height'; import ArrowRight from '@/assets/images/triangle-right.svg'; +import { onKeyDownHandler } from '@/utilities/a11y'; import * as styles from './index.module.scss'; @@ -15,14 +16,26 @@ const DetailsSummary = ({ children }: Props) => { const [isExpanded, setIsExpanded] = useState(false); const [height, setHeight] = useState(0); + const onClickHandler = useCallback(() => { + setIsExpanded(!isExpanded); + setHeight(height === 0 ? 'auto' : 0); + }, [height, isExpanded]); + return (
    { - setIsExpanded(!isExpanded); - setHeight(height === 0 ? 'auto' : 0); - }} + onKeyDown={onKeyDownHandler({ + Esc: () => { + setIsExpanded(false); + setHeight(0); + }, + Enter: onClickHandler, + ' ': onClickHandler, + })} + onClick={onClickHandler} > {summary} diff --git a/packages/console/src/mdx-components/Step/index.tsx b/packages/console/src/mdx-components/Step/index.tsx index d154d4f5c..90d3e1a29 100644 --- a/packages/console/src/mdx-components/Step/index.tsx +++ b/packages/console/src/mdx-components/Step/index.tsx @@ -1,6 +1,6 @@ import { AdminConsoleKey } from '@logto/phrases'; import classNames from 'classnames'; -import { PropsWithChildren, useEffect, useRef, useState } from 'react'; +import { PropsWithChildren, useEffect, useRef, useState, useCallback } from 'react'; import Button from '@/components/Button'; import Card from '@/components/Card'; @@ -10,6 +10,7 @@ import IconButton from '@/components/IconButton'; import Index from '@/components/Index'; import Spacer from '@/components/Spacer'; import { KeyboardArrowDown, KeyboardArrowUp } from '@/icons/Arrow'; +import { onKeyDownHandler } from '@/utilities/a11y'; import * as styles from './index.module.scss'; @@ -54,13 +55,24 @@ const Step = ({ } }, [isExpanded]); + const onToggle = useCallback(() => { + setIsExpanded((expand) => !expand); + }, [setIsExpanded]); + return (
    { - setIsExpanded(!isExpanded); - }} + onKeyDown={onKeyDownHandler({ + Esc: () => { + setIsExpanded(false); + }, + Enter: onToggle, + ' ': onToggle, + })} + onClick={onToggle} > {
    {
    logo
    diff --git a/packages/console/src/pages/Connectors/components/ConnectorName/index.tsx b/packages/console/src/pages/Connectors/components/ConnectorName/index.tsx index 4dc8bd6f8..01838562b 100644 --- a/packages/console/src/pages/Connectors/components/ConnectorName/index.tsx +++ b/packages/console/src/pages/Connectors/components/ConnectorName/index.tsx @@ -75,6 +75,7 @@ const ConnectorName = ({ type, connectors, onClickSetup }: Props) => {
    logo { >
    - + logo
    diff --git a/packages/console/src/pages/GetStarted/components/GetStartedProgress/index.tsx b/packages/console/src/pages/GetStarted/components/GetStartedProgress/index.tsx index 0068292ef..b70a373ef 100644 --- a/packages/console/src/pages/GetStarted/components/GetStartedProgress/index.tsx +++ b/packages/console/src/pages/GetStarted/components/GetStartedProgress/index.tsx @@ -9,6 +9,7 @@ import Dropdown, { DropdownItem } from '@/components/Dropdown'; import Index from '@/components/Index'; import { useTheme } from '@/hooks/use-theme'; import useUserPreferences from '@/hooks/use-user-preferences'; +import { onKeyDownHandler } from '@/utilities/a11y'; import useGetStartedMetadata from '../../hook'; import * as styles from './index.module.scss'; @@ -28,14 +29,27 @@ const GetStartedProgress = () => { return null; } + const showDropDown = () => { + setShowDropdown(true); + }; + + const hideDropDown = () => { + setShowDropdown(false); + }; + return ( <>
    { - setShowDropdown(true); - }} + onKeyDown={onKeyDownHandler({ + Esc: hideDropDown, + Enter: showDropDown, + ' ': showDropDown, + })} + onClick={showDropDown} > @@ -52,9 +66,7 @@ const GetStartedProgress = () => { horizontalAlign="end" title={t('get_started.progress_dropdown_title')} titleClassName={styles.dropdownTitle} - onClose={() => { - setShowDropdown(false); - }} + onClose={hideDropDown} > {data.map(({ id, title, isComplete, onClick }, index) => ( { navigate('/dashboard'); }; + const showConfirmModalHandler = () => { + setShowConfirmModal(true); + }; + + const hideConfirmModalHandler = () => { + setShowConfirmModal(false); + }; + return (
    @@ -36,10 +45,15 @@ const GetStarted = () => { {t('get_started.subtitle_part2')} { - setShowConfirmModal(true); - }} + onClick={showConfirmModalHandler} + onKeyDown={onKeyDownHandler({ + Enter: showConfirmModalHandler, + ' ': showConfirmModalHandler, + Esc: hideConfirmModalHandler, + })} > {t('get_started.hide_this')} @@ -70,9 +84,7 @@ const GetStarted = () => { confirmButtonType="primary" confirmButtonText="get_started.hide_this" onConfirm={hideGetStarted} - onCancel={() => { - setShowConfirmModal(false); - }} + onCancel={hideConfirmModalHandler} > {t('get_started.confirm_message')} diff --git a/packages/console/src/pages/SignInExperience/components/Preview.tsx b/packages/console/src/pages/SignInExperience/components/Preview.tsx index 37921366b..bc516ec28 100644 --- a/packages/console/src/pages/SignInExperience/components/Preview.tsx +++ b/packages/console/src/pages/SignInExperience/components/Preview.tsx @@ -195,12 +195,14 @@ const Preview = ({ signInExperience, className }: Props) => {
    )} - { - // The missing of attribute "sandbox" is intended since the source is trusted - /* eslint-disable react/iframe-missing-sandbox */ - } -