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:
parent
b69c58f59d
commit
6db09974d6
@ -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",
|
||||||
|
@ -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 && (
|
||||||
|
@ -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}
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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"
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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"
|
||||||
|
@ -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}
|
||||||
|
@ -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"
|
||||||
|
@ -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}
|
||||||
|
@ -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
|
||||||
|
@ -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}`}
|
||||||
>
|
>
|
||||||
|
@ -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: [
|
||||||
|
@ -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}
|
||||||
|
@ -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} />
|
||||||
))}
|
))}
|
||||||
|
@ -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',
|
||||||
|
};
|
||||||
|
@ -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"
|
||||||
>
|
>
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
Loading…
Reference in New Issue
Block a user