Feat/621 a11y storybook add on (#705)

* chore(ui-toolkit): add aria label to icon for a11y (#621)

* chore(ui-toolkit): add labels for form-groups for a11y (#621)

* fix(ui-toolkit): fix form inputs storybook for a11y (#621)

* feat(ui-toolkit): add strict eslint a11y and components config (#621)

* chore(ui-toolkit): add translate t to form labels
This commit is contained in:
Elmar 2022-07-07 12:01:03 +01:00 committed by GitHub
parent b69c58f59d
commit 6db09974d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 184 additions and 64 deletions

View File

@ -2,9 +2,21 @@
"root": true, "root": true,
"ignorePatterns": ["**/*"], "ignorePatterns": ["**/*"],
"plugins": ["@nrwl/nx", "eslint-plugin-unicorn", "jsx-a11y", "jest"], "plugins": ["@nrwl/nx", "eslint-plugin-unicorn", "jsx-a11y", "jest"],
"settings": {
"jsx-a11y": {
"components": {
"Button": "button",
"Input": "input",
"Select": "select",
"Radio": "radio",
"TextArea": "textarea"
}
}
},
"overrides": [ "overrides": [
{ {
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"extends": ["plugin:jsx-a11y/strict"],
"rules": { "rules": {
"@nrwl/nx/enforce-module-boundaries": [ "@nrwl/nx/enforce-module-boundaries": [
"error", "error",

View File

@ -55,14 +55,18 @@ export const Search = () => {
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
className="flex-1 flex self-center md:ml-16 md:mr-12 md:justify-end" className="flex-1 flex self-center md:ml-16 md:mr-12 md:justify-end"
> >
<FormGroup className="relative w-full md:w-2/3 mb-0"> <FormGroup
label={t('Search by block number or transaction hash')}
className="relative w-full md:w-2/3 mb-0"
labelClassName="sr-only"
labelFor="search"
>
<Input <Input
{...register('search')} {...register('search')}
id="search" id="search"
data-testid="search" data-testid="search"
hasError={Boolean(error?.message)} hasError={Boolean(error?.message)}
type="text" type="text"
autoFocus={true}
placeholder={t('Enter block number or transaction hash')} placeholder={t('Enter block number or transaction hash')}
/> />
{error?.message && ( {error?.message && (

View File

@ -6,6 +6,7 @@ import {
FormGroup, FormGroup,
Lozenge, Lozenge,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -174,7 +175,11 @@ export const TokenInput = ({
return ( return (
<> <>
<FormGroup label="" labelFor={inputName}> <FormGroup
labelClassName="sr-only"
label={t('Input Amount')}
labelFor={inputName}
>
<AmountInput <AmountInput
amount={amount} amount={amount}
setAmount={setAmount} setAmount={setAmount}

View File

@ -224,8 +224,13 @@ export const StakingForm = ({
return ( return (
<> <>
<h2>{t('Manage your stake')}</h2> <h2>{t('Manage your stake')}</h2>
<FormGroup> <FormGroup
label={t('Select if you want to add or remove stake')}
labelFor="radio-stake-options"
labelClassName="sr-only"
>
<RadioGroup <RadioGroup
name="radio-stake-options"
onChange={(value) => { onChange={(value) => {
// @ts-ignore value does exist on target // @ts-ignore value does exist on target
setAction(value); setAction(value);

View File

@ -8,6 +8,7 @@ export const Navbar = () => {
return ( return (
<nav className="flex items-center"> <nav className="flex items-center">
<Link href="/" passHref={true}> <Link href="/" passHref={true}>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a className="px-[26px]"> <a className="px-[26px]">
<Vega className="fill-black dark:fill-white" /> <Vega className="fill-black dark:fill-white" />
</a> </a>
@ -43,6 +44,7 @@ const NavLink = ({ name, path, exact, testId = name }: NavLinkProps) => {
); );
return ( return (
<Link data-testid={testId} href={path} passHref={true}> <Link data-testid={testId} href={path} passHref={true}>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a className={linkClasses}>{name}</a> <a className={linkClasses}>{name}</a>
</Link> </Link>
); );

View File

@ -72,7 +72,8 @@ export const WithdrawPageContainer = ({
{hasIncompleteWithdrawals ? ( {hasIncompleteWithdrawals ? (
<p className="mb-12"> <p className="mb-12">
{t('You have incomplete withdrawals.')}{' '} {t('You have incomplete withdrawals.')}{' '}
<Link href="/portfolio/withdrawals"> <Link href="/portfolio/withdrawals" passHref={true}>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a <a
className="underline" className="underline"
data-testid="complete-withdrawals-prompt" data-testid="complete-withdrawals-prompt"

View File

@ -1,4 +1,5 @@
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import { validateSize } from '../utils/validate-size'; import { validateSize } from '../utils/validate-size';
import type { DealTicketAmountProps } from './deal-ticket-amount'; import type { DealTicketAmountProps } from './deal-ticket-amount';
@ -15,8 +16,9 @@ export const DealTicketLimitAmount = ({
return ( return (
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
<div className="flex-1"> <div className="flex-1">
<FormGroup label="Amount"> <FormGroup label={t('Amount')} labelFor="input-order-size-limit">
<Input <Input
id="input-order-size-limit"
className="w-full" className="w-full"
type="number" type="number"
step={step} step={step}
@ -32,8 +34,13 @@ export const DealTicketLimitAmount = ({
</div> </div>
<div>@</div> <div>@</div>
<div className="flex-1"> <div className="flex-1">
<FormGroup label={`Price (${quoteName})`} labelAlign="right"> <FormGroup
labelFor="input-price-quote"
label={t(`Price (${quoteName})`)}
labelAlign="right"
>
<Input <Input
id="input-price-quote"
className="w-full" className="w-full"
type="number" type="number"
step={step} step={step}

View File

@ -1,4 +1,5 @@
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import { validateSize } from '../utils/validate-size'; import { validateSize } from '../utils/validate-size';
import type { DealTicketAmountProps } from './deal-ticket-amount'; import type { DealTicketAmountProps } from './deal-ticket-amount';
@ -16,8 +17,9 @@ export const DealTicketMarketAmount = ({
return ( return (
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
<div className="flex-1"> <div className="flex-1">
<FormGroup label="Amount"> <FormGroup label={t('Amount')} labelFor="input-order-size-market">
<Input <Input
id="input-order-size-market"
className="w-full" className="w-full"
type="number" type="number"
step={step} step={step}

View File

@ -1,5 +1,6 @@
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
import { formatForInput } from '@vegaprotocol/react-helpers'; import { formatForInput } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/react-helpers';
interface ExpirySelectorProps { interface ExpirySelectorProps {
value?: Date; value?: Date;
@ -11,7 +12,7 @@ export const ExpirySelector = ({ value, onSelect }: ExpirySelectorProps) => {
const dateFormatted = formatForInput(date); const dateFormatted = formatForInput(date);
const minDate = formatForInput(date); const minDate = formatForInput(date);
return ( return (
<FormGroup label="Expiry time/date" labelFor="expiration"> <FormGroup label={t('Expiry time/date')} labelFor="expiration">
<Input <Input
data-testid="date-picker-field" data-testid="date-picker-field"
id="expiration" id="expiration"

View File

@ -1,6 +1,7 @@
import { FormGroup } from '@vegaprotocol/ui-toolkit'; import { FormGroup } from '@vegaprotocol/ui-toolkit';
import { OrderSide } from '@vegaprotocol/wallet'; import { OrderSide } from '@vegaprotocol/wallet';
import { Toggle } from '@vegaprotocol/ui-toolkit'; import { Toggle } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
interface SideSelectorProps { interface SideSelectorProps {
value: OrderSide; value: OrderSide;
@ -14,8 +15,9 @@ export const SideSelector = ({ value, onSelect }: SideSelectorProps) => {
})); }));
return ( return (
<FormGroup label="Direction"> <FormGroup label={t('Direction')} labelFor="order-side-toggle">
<Toggle <Toggle
id="order-side-toggle"
name="order-side" name="order-side"
toggles={toggles} toggles={toggles}
checkedValue={value} checkedValue={value}

View File

@ -1,5 +1,6 @@
import { FormGroup, Select } from '@vegaprotocol/ui-toolkit'; import { FormGroup, Select } from '@vegaprotocol/ui-toolkit';
import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet'; import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
import { t } from '@vegaprotocol/react-helpers';
interface TimeInForceSelectorProps { interface TimeInForceSelectorProps {
value: OrderTimeInForce; value: OrderTimeInForce;
@ -22,8 +23,9 @@ export const TimeInForceSelector = ({
); );
return ( return (
<FormGroup label="Time in force"> <FormGroup label={t('Time in force')} labelFor="select-time-in-force">
<Select <Select
id="select-time-in-force"
value={value} value={value}
onChange={(e) => onSelect(e.target.value as OrderTimeInForce)} onChange={(e) => onSelect(e.target.value as OrderTimeInForce)}
className="w-full" className="w-full"

View File

@ -1,4 +1,5 @@
import { FormGroup } from '@vegaprotocol/ui-toolkit'; import { FormGroup } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import { OrderType } from '@vegaprotocol/wallet'; import { OrderType } from '@vegaprotocol/wallet';
import { Toggle } from '@vegaprotocol/ui-toolkit'; import { Toggle } from '@vegaprotocol/ui-toolkit';
@ -14,8 +15,9 @@ const toggles = Object.entries(OrderType).map(([label, value]) => ({
export const TypeSelector = ({ value, onSelect }: TypeSelectorProps) => { export const TypeSelector = ({ value, onSelect }: TypeSelectorProps) => {
return ( return (
<FormGroup label="Order type"> <FormGroup label={t('Order type')} labelFor="order-type">
<Toggle <Toggle
id="order-type"
name="order-type" name="order-type"
toggles={toggles} toggles={toggles}
checkedValue={value} checkedValue={value}

View File

@ -190,9 +190,9 @@ export const DepositForm = ({
)} )}
</FormGroup> </FormGroup>
{selectedAsset && limits && ( {selectedAsset && limits && (
<FormGroup> <div className="mb-20">
<DepositLimits limits={limits} /> <DepositLimits limits={limits} />
</FormGroup> </div>
)} )}
<FormGroup label={t('Amount')} labelFor="amount" className="relative"> <FormGroup label={t('Amount')} labelFor="amount" className="relative">
<Input <Input

View File

@ -18,6 +18,14 @@ export const SelectMarketList = ({
data, data,
onSelect, onSelect,
}: SelectMarketListDataProps) => { }: SelectMarketListDataProps) => {
const handleKeyPress = (
event: React.KeyboardEvent<HTMLAnchorElement>,
id: string
) => {
if (event.key === 'Enter') {
return onSelect(id);
}
};
const thClassNames = (direction: 'left' | 'right') => const thClassNames = (direction: 'left' | 'right') =>
`px-8 text-${direction} font-sans font-normal text-ui-small leading-9 mb-0 text-dark/80 dark:text-white/80`; `px-8 text-${direction} font-sans font-normal text-ui-small leading-9 mb-0 text-dark/80 dark:text-white/80`;
const tdClassNames = const tdClassNames =
@ -54,8 +62,9 @@ export const SelectMarketList = ({
> >
<td className={`${boldUnderlineClassNames} relative`}> <td className={`${boldUnderlineClassNames} relative`}>
<Link href={`/markets/${id}`} passHref={true}> <Link href={`/markets/${id}`} passHref={true}>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} {/* eslint-disable-next-line jsx-a11y/anchor-is-valid,jsx-a11y/no-static-element-interactions */}
<a <a
onKeyPress={(event) => handleKeyPress(event, id)}
onClick={() => onSelect(id)} onClick={() => onSelect(id)}
data-testid={`market-link-${id}`} data-testid={`market-link-${id}`}
> >

View File

@ -2,6 +2,22 @@ import '../src/styles.scss';
export const parameters = { export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' }, actions: { argTypesRegex: '^on[A-Z].*' },
backgrounds: { disable: true }, backgrounds: { disable: true },
a11y: {
config: {
rules: [
{
// Disabled only for storybook because we display both the dark and light variants of the components on the same page without differentiating the ids, so it will always error.
id: 'duplicate-id-aria',
selector: '[data-testid="form-group"] > label',
},
{
// Disabled because we can't control the radix radio group component and it claims to be accessible to begin with, so hopefully no issues.
id: 'button-name',
selector: '[role=radiogroup] > button',
},
],
},
},
/*themes: { /*themes: {
default: 'dark', default: 'dark',
list: [ list: [

View File

@ -4,9 +4,10 @@ import classnames from 'classnames';
interface FormGroupProps { interface FormGroupProps {
children: ReactNode; children: ReactNode;
label?: string; label: string; // For accessibility reasons this must always be set for screen readers. If you want it to not show, then add labelClassName="sr-only"
labelFor?: string; labelFor: string; // Same as above
labelAlign?: 'left' | 'right'; labelAlign?: 'left' | 'right';
labelClassName?: string;
labelDescription?: string; labelDescription?: string;
className?: string; className?: string;
hasError?: boolean; hasError?: boolean;
@ -18,6 +19,7 @@ export const FormGroup = ({
labelFor, labelFor,
labelDescription, labelDescription,
labelAlign = 'left', labelAlign = 'left',
labelClassName,
className, className,
hasError, hasError,
}: FormGroupProps) => { }: FormGroupProps) => {
@ -27,23 +29,25 @@ export const FormGroup = ({
className={classnames(className, { 'mb-20': !className?.includes('mb') })} className={classnames(className, { 'mb-20': !className?.includes('mb') })}
> >
{label && ( {label && (
<label htmlFor={labelFor}> <label className={labelClassName} htmlFor={labelFor}>
<div {
className={classNames( <div
'mb-4 text-body-large text-black dark:text-white', className={classNames(
{ 'mb-4 text-body-large text-black dark:text-white',
'border-l-4 border-danger pl-8': hasError, {
'text-right': labelAlign === 'right', 'border-l-4 border-danger pl-8': hasError,
} 'text-right': labelAlign === 'right',
)} }
> )}
<div className="font-bold mb-2">{label}</div> >
{labelDescription && ( <div className="font-bold mb-2">{label}</div>
<div className={classNames({ 'text-danger': hasError })}> {labelDescription && (
{labelDescription} <div className={classNames({ 'text-danger': hasError })}>
</div> {labelDescription}
)} </div>
</div> )}
</div>
}
</label> </label>
)} )}
{children} {children}

View File

@ -8,9 +8,10 @@ interface IconProps {
name: IconName; name: IconName;
className?: string; className?: string;
size?: 16 | 20 | 24 | 32 | 48 | 64; size?: 16 | 20 | 24 | 32 | 48 | 64;
ariaLabel?: string;
} }
export const Icon = ({ size = 16, name, className }: IconProps) => { export const Icon = ({ size = 16, name, className, ariaLabel }: IconProps) => {
const effectiveClassName = classNames( const effectiveClassName = classNames(
'inline-block', 'inline-block',
'fill-current', 'fill-current',
@ -26,7 +27,13 @@ export const Icon = ({ size = 16, name, className }: IconProps) => {
); );
const viewbox = size <= 16 ? '0 0 16 16' : '0 0 20 20'; const viewbox = size <= 16 ? '0 0 16 16' : '0 0 20 20';
return ( return (
<svg className={effectiveClassName} viewBox={viewbox}> // For more information on accessibility for svg see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/img_role#svg_and_roleimg
<svg
role="img"
aria-label={ariaLabel || `${name} icon`}
className={effectiveClassName}
viewBox={viewbox}
>
{(size <= 16 ? IconSvgPaths16 : IconSvgPaths20)[name].map((d, key) => ( {(size <= 16 ? IconSvgPaths16 : IconSvgPaths20)[name].map((d, key) => (
<path fillRule="evenodd" clipRule="evenodd" d={d} key={key} /> <path fillRule="evenodd" clipRule="evenodd" d={d} key={key} />
))} ))}

View File

@ -1,11 +1,16 @@
import type { Story, Meta } from '@storybook/react'; import type { Story, Meta } from '@storybook/react';
import { Input } from './input'; import { Input } from './input';
import { FormGroup } from '../form-group';
export default { export default {
component: Input, component: Input,
title: 'Input', title: 'Input',
} as Meta; } as Meta;
const Template: Story = (args) => <Input {...args} value="I type words" />; const Template: Story = (args) => (
<FormGroup labelClassName="sr-only" label="Hello" labelFor={args.id}>
<Input value="I type words" {...args} />
</FormGroup>
);
const customElementPlaceholder = ( const customElementPlaceholder = (
<span <span
@ -20,40 +25,57 @@ const customElementPlaceholder = (
); );
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = {}; Default.args = {
id: 'input-default',
};
export const WithError = Template.bind({}); export const WithError = Template.bind({});
WithError.args = { WithError.args = {
hasError: true, hasError: true,
id: 'input-has-error',
}; };
export const Disabled = Template.bind({}); export const Disabled = Template.bind({});
Disabled.args = { Disabled.args = {
disabled: true, disabled: true,
id: 'input-disabled',
}; };
export const TypeDate = Template.bind({}); export const TypeDate = Template.bind({});
TypeDate.args = { TypeDate.args = {
type: 'date', type: 'date',
id: 'input-date',
}; };
export const TypeDateTime = Template.bind({}); export const TypeDateTime = Template.bind({});
TypeDateTime.args = { TypeDateTime.args = {
type: 'datetime-local', type: 'datetime-local',
id: 'input-datetime-local',
}; };
export const IconPrepend: Story = () => ( export const IconPrepend = Template.bind({});
<Input value="I type words" prependIconName="search" /> IconPrepend.args = {
); prependIconName: 'search',
id: 'input-icon-prepend',
};
export const IconAppend: Story = () => ( export const IconAppend = Template.bind({});
<Input value="I type words and even more words" appendIconName="search" /> IconAppend.args = {
); value: 'I type words and even more words',
appendIconName: 'search',
id: 'input-icon-append',
};
export const ElementPrepend: Story = () => ( export const ElementPrepend = Template.bind({});
<Input value="<- custom element" prependElement={customElementPlaceholder} /> ElementPrepend.args = {
); value: '<- custom element',
prependElement: customElementPlaceholder,
id: 'input-element-prepend',
};
export const ElementAppend: Story = () => ( export const ElementAppend = Template.bind({});
<Input value="custom element ->" appendElement={customElementPlaceholder} /> ElementAppend.args = {
); value: 'custom element ->',
appendElement: customElementPlaceholder,
id: 'input-element-append',
};

View File

@ -3,15 +3,17 @@ import classNames from 'classnames';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
interface RadioGroupProps { interface RadioGroupProps {
name?: string;
children: ReactNode; children: ReactNode;
defaultValue?: string; defaultValue?: string;
value?: string; value?: string;
onChange?: (value: string) => void; onChange?: (value: string) => void;
} }
export const RadioGroup = ({ children, onChange }: RadioGroupProps) => { export const RadioGroup = ({ children, onChange, name }: RadioGroupProps) => {
return ( return (
<RadioGroupPrimitive.Root <RadioGroupPrimitive.Root
name={name}
onValueChange={onChange} onValueChange={onChange}
className="flex flex-row gap-24" className="flex flex-row gap-24"
> >

View File

@ -1,5 +1,6 @@
import type { Story, Meta } from '@storybook/react'; import type { Story, Meta } from '@storybook/react';
import { Select } from './select'; import { Select } from './select';
import { FormGroup } from '../form-group';
export default { export default {
component: Select, component: Select,
@ -7,22 +8,28 @@ export default {
} as Meta; } as Meta;
const Template: Story = (args) => ( const Template: Story = (args) => (
<Select {...args}> <FormGroup labelClassName="sr-only" label="Hello" labelFor={args.id}>
<option value="Option 1">Option 1</option> <Select {...args}>
<option value="Option 2">Option 2</option> <option value="Option 1">Option 1</option>
<option value="Option 3">Option 3</option> <option value="Option 2">Option 2</option>
</Select> <option value="Option 3">Option 3</option>
</Select>
</FormGroup>
); );
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = {}; Default.args = {
id: 'select-default',
};
export const WithError = Template.bind({}); export const WithError = Template.bind({});
WithError.args = { WithError.args = {
id: 'select-has-error',
hasError: true, hasError: true,
}; };
export const Disabled = Template.bind({}); export const Disabled = Template.bind({});
Disabled.args = { Disabled.args = {
id: 'select-disabled',
disabled: true, disabled: true,
}; };

View File

@ -1,4 +1,5 @@
import type { Story, Meta } from '@storybook/react'; import type { Story, Meta } from '@storybook/react';
import { FormGroup } from '../form-group';
import { TextArea } from './text-area'; import { TextArea } from './text-area';
export default { export default {
@ -6,21 +7,25 @@ export default {
title: 'TextArea', title: 'TextArea',
} as Meta; } as Meta;
const Template: Story = (args) => ( const Template: Story = (args, context) => (
<TextArea {...args} className="h-48"> <FormGroup labelClassName="sr-only" label="Hello" labelFor={args.id}>
I type words <TextArea {...args} className="h-48" defaultValue="I type words" />
</TextArea> </FormGroup>
); );
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = {}; Default.args = {
id: 'text-area-default',
};
export const WithError = Template.bind({}); export const WithError = Template.bind({});
WithError.args = { WithError.args = {
id: 'text-area-error',
hasError: true, hasError: true,
}; };
export const Disabled = Template.bind({}); export const Disabled = Template.bind({});
Disabled.args = { Disabled.args = {
id: 'text-area-disabled',
disabled: true, disabled: true,
}; };

View File

@ -8,6 +8,7 @@ interface ToggleProps {
} }
export interface ToggleInputProps { export interface ToggleInputProps {
id?: string;
name: string; name: string;
toggles: ToggleProps[]; toggles: ToggleProps[];
className?: string; className?: string;
@ -16,6 +17,7 @@ export interface ToggleInputProps {
} }
export const Toggle = ({ export const Toggle = ({
id,
name, name,
toggles, toggles,
className, className,
@ -45,9 +47,10 @@ export const Toggle = ({
{toggles.map(({ label, value }, key) => { {toggles.map(({ label, value }, key) => {
const isSelected = value === checkedValue; const isSelected = value === checkedValue;
return ( return (
<label key={key} className={labelClasses}> <label key={key} className={labelClasses} htmlFor={label}>
<input <input
type="radio" type="radio"
id={label}
name={name} name={name}
value={value} value={value}
onChange={onChange} onChange={onChange}

View File

@ -34,6 +34,7 @@ const TestComponent = () => {
{keypairs?.length ? ( {keypairs?.length ? (
<ul data-testid="keypair-list"> <ul data-testid="keypair-list">
{keypairs.map((kp) => ( {keypairs.map((kp) => (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions
<li key={kp.pub} onClick={() => selectPublicKey(kp.pub)}> <li key={kp.pub} onClick={() => selectPublicKey(kp.pub)}>
{kp.pub} {kp.pub}
</li> </li>

View File

@ -76,7 +76,6 @@ export function RestConnectorForm({
{...register('wallet', { required: t('Required') })} {...register('wallet', { required: t('Required') })}
id="wallet" id="wallet"
type="text" type="text"
autoFocus={true}
/> />
{errors.wallet?.message && ( {errors.wallet?.message && (
<InputError intent="danger" className="mt-4"> <InputError intent="danger" className="mt-4">