Skip to content

Commit

Permalink
fix(ui): enhance overall accessibility and ARIA support (#289)
Browse files Browse the repository at this point in the history
* fix(ui): enhance overall accessibility and ARIA support

- Add ARIA labels and roles to form inputs and buttons
- Enhance color contrast for text elements in dark mode
- Improve semantic structure of headings in card components
- Update `InfoPopover` for better screen reader support
- Refactor form fields in `AddValidatorForm` for accessibility
- Adjust text colors in `Footer` for better visibility

* fix(ui): add ARIA labels to Select components

- Add aria-label attributes to `SelectTrigger` components in:
  - AddValidatorForm
  - NodeSelect
  - SelectAccount
  - UnstakeModal
  - EditEntryGating
  - StakingDetails
- Update placeholder text in `UnstakeModal`'s `SelectValue`
  • Loading branch information
drichar authored Sep 30, 2024
1 parent 2db31c0 commit 91c8569
Show file tree
Hide file tree
Showing 27 changed files with 254 additions and 114 deletions.
6 changes: 5 additions & 1 deletion ui/src/components/AddPoolModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -484,11 +484,15 @@ export function AddPoolModal({
hidden: !showRewardTokenInfo,
})}
/>
<FormLabel className={cn({ 'text-muted-foreground/50': currentStep < 3 })}>
<FormLabel
htmlFor="link-pool-to-nfd-input"
className={cn({ 'text-muted-foreground/50': currentStep < 3 })}
>
Link Pool to NFD
</FormLabel>
<CollapsibleContent className="space-y-2">
<NfdLookup
id="link-pool-to-nfd-input"
form={form}
name="nfdToLink"
nfd={nfdToLink}
Expand Down
4 changes: 3 additions & 1 deletion ui/src/components/AddStakeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -529,11 +529,13 @@ export function AddStakeModal({
name="amountToStake"
render={({ field }) => (
<FormItem className="w-2/3">
<FormLabel>Amount to Stake</FormLabel>
<FormLabel htmlFor="amount-to-stake-input">Amount to Stake</FormLabel>
<FormControl>
<div className="relative">
<Input
id="amount-to-stake-input"
className="pr-16"
placeholder="0.000000"
{...field}
onChange={(e) => {
field.onChange(e) // Inform react-hook-form of the change
Expand Down
138 changes: 89 additions & 49 deletions ui/src/components/AddValidatorForm.tsx

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion ui/src/components/AssetLookup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface AssetLookupProps<
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> {
form: UseFormReturn<TFieldValues>
id: string
name: TName
asset: Asset | null
setAsset: (asset: Asset | null) => void
Expand All @@ -33,6 +34,7 @@ export function AssetLookup<
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
form,
id,
name,
asset,
setAsset,
Expand Down Expand Up @@ -100,7 +102,7 @@ export function AssetLookup<

const renderLabel = () => {
if (typeof label === 'string') {
return <FormLabel>{label}</FormLabel>
return <FormLabel htmlFor={id}>{label}</FormLabel>
}

if (label) {
Expand All @@ -121,9 +123,11 @@ export function AssetLookup<
<div className="flex-1 relative">
<FormControl>
<Input
id={id}
className={cn(isFetching || asset ? 'pr-28' : '')}
autoComplete="new-password"
spellCheck="false"
placeholder="Enter asset ID"
{...field}
onChange={(e) => {
field.onChange(e) // Inform react-hook-form of the change
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function Footer() {
))}
</div>
<div className="mt-8 md:order-1 md:mt-0">
<p className="text-center text-sm leading-5 text-stone-500">
<p className="text-center text-sm leading-5 text-stone-600 dark:text-stone-400">
Réti Pooling v{__APP_VERSION__} <span className="mx-1 opacity-50">|</span>{' '}
<a
href="https://github.com/TxnLab/reti"
Expand Down
18 changes: 14 additions & 4 deletions ui/src/components/InfoPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,25 @@ import { cn } from '@/utils/ui'
interface InfoPopoverProps extends PopoverContentProps {
children?: React.ReactNode
className?: string
label: string
}

export function InfoPopover({ children, className, ...contentProps }: InfoPopoverProps) {
export function InfoPopover({ children, className, label, ...contentProps }: InfoPopoverProps) {
return (
<Popover>
<PopoverTrigger className={cn('text-muted-foreground hover:text-foreground', className)}>
<CircleHelp className="h-4 w-4 sm:h-3 sm:w-3" />
<PopoverTrigger
className={cn('text-muted-foreground hover:text-foreground', className)}
aria-label={`Info: ${label}`}
>
<CircleHelp className="h-4 w-4 sm:h-3 sm:w-3" aria-hidden="true" />
</PopoverTrigger>
<PopoverContent side="top" sideOffset={8} className="text-sm" {...contentProps}>
<PopoverContent
side="top"
sideOffset={8}
className="text-sm"
role="tooltip"
{...contentProps}
>
{children}
</PopoverContent>
</Popover>
Expand Down
9 changes: 7 additions & 2 deletions ui/src/components/NfdAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import { getNfdAvatarUrl } from '@/utils/nfd'
interface NfdAvatarProps {
nfd: Nfd
className?: string
alt?: string
}

const NfdAvatar: React.FC<NfdAvatarProps> = React.memo(function NfdAvatar({ nfd, className = '' }) {
const NfdAvatar: React.FC<NfdAvatarProps> = React.memo(function NfdAvatar({
nfd,
className = '',
alt,
}) {
return (
<Avatar className={className}>
<AvatarImage src={getNfdAvatarUrl(nfd)} alt={nfd.name} />
<AvatarImage src={getNfdAvatarUrl(nfd)} alt={alt || nfd.name} />
</Avatar>
)
})
Expand Down
3 changes: 3 additions & 0 deletions ui/src/components/NfdLookup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface NfdLookupProps<
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> {
form: UseFormReturn<TFieldValues>
id: string
name: TName
nfd: Nfd | null
setNfd: (nfd: Nfd | null) => void
Expand All @@ -42,6 +43,7 @@ export function NfdLookup<
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
form,
id,
name,
nfd,
setNfd,
Expand Down Expand Up @@ -143,6 +145,7 @@ export function NfdLookup<
<div className="flex-1 relative">
<FormControl>
<Input
id={id}
className={cn(isFetchingNfd || nfd ? 'pr-10' : '')}
placeholder=""
autoComplete="new-password"
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/NfdThumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const NfdThumbnailBase = React.memo(
const renderChildren = () => (
<>
<div className="flex-shrink-0">
<NfdAvatar nfd={nfd} className="h-6 w-6" />
<NfdAvatar nfd={nfd} className="h-6 w-6" alt="" />
</div>
<div className={cn({ truncate })}>{nfd.name}</div>
</>
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/NodeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface NodeSelectProps {
export function NodeSelect({ nodesInfo, value, onValueChange }: NodeSelectProps) {
return (
<Select onValueChange={onValueChange} value={value}>
<SelectTrigger className="w-[180px]">
<SelectTrigger className="w-[180px]" aria-label="Select a node">
<SelectValue placeholder="Select a node" />
</SelectTrigger>
<SelectContent>
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/SelectAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface SelectAccountProps {
export function SelectAccount({ accounts, activeAccount, onValueChange }: SelectAccountProps) {
return (
<Select value={activeAccount?.address} onValueChange={onValueChange}>
<SelectTrigger className="w-[180px] bg-background/50">
<SelectTrigger className="w-[180px] bg-background/50" aria-label="Select an account">
<SelectValue placeholder="Select account" />
</SelectTrigger>
<SelectContent>
Expand Down
13 changes: 9 additions & 4 deletions ui/src/components/UnstakeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ export function UnstakeModal({ validator, setValidator, stakesByValidator }: Uns
) : (
<>
<Select onValueChange={handleSetSelectedPool} value={selectedPoolId}>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
<SelectTrigger aria-label="Select a pool">
<SelectValue placeholder="Select a pool" />
</SelectTrigger>
<SelectContent>
{stakerPoolsData
Expand Down Expand Up @@ -315,10 +315,15 @@ export function UnstakeModal({ validator, setValidator, stakesByValidator }: Uns
name="amountToUnstake"
render={({ field }) => (
<FormItem>
<FormLabel>Amount to Unstake</FormLabel>
<FormLabel htmlFor="amount-to-unstake-input">Amount to Unstake</FormLabel>
<FormControl>
<div className="relative">
<Input className="pr-16" {...field} />
<Input
id="amount-to-unstake-input"
className="pr-16"
placeholder="0.000000"
{...field}
/>
<Button
type="button"
size="sm"
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/ValidatorDetails/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function Details({ validator }: DetailsProps) {
<div className="grid gap-4 lg:grid-cols-2">
<Card className="lg:col-span-3">
<CardHeader>
<CardTitle>Validator Details</CardTitle>
<CardTitle as="h2">Validator Details</CardTitle>
</CardHeader>
<CardContent className="w-full">
<div className="border-t border-foreground-muted">
Expand Down
3 changes: 2 additions & 1 deletion ui/src/components/ValidatorDetails/EditCommissionAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,11 @@ export function EditCommissionAccount({ validator }: EditCommissionAccountProps)
name="validatorCommissionAddress"
render={({ field }) => (
<FormItem>
<FormLabel>Commission account</FormLabel>
<FormLabel htmlFor="commission-account-input">Commission account</FormLabel>
<div className="flex items-center gap-x-3">
<FormControl>
<Input
id="commission-account-input"
className="font-mono"
placeholder=""
autoComplete="new-password"
Expand Down
57 changes: 43 additions & 14 deletions ui/src/components/ValidatorDetails/EditEntryGating.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,10 @@ export function EditEntryGating({ validator }: EditEntryGatingProps) {
<FormItem>
<FormLabel>
Gating type
<InfoPopover className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0">
<InfoPopover
className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0"
label="Gating type"
>
Require stakers to hold a qualified asset to enter pool (optional)
</InfoPopover>
</FormLabel>
Expand Down Expand Up @@ -377,7 +380,7 @@ export function EditEntryGating({ validator }: EditEntryGatingProps) {
}}
>
<FormControl>
<SelectTrigger>
<SelectTrigger aria-label="Select asset gating type">
<SelectValue placeholder="Select asset gating type" />
</SelectTrigger>
</FormControl>
Expand All @@ -404,15 +407,23 @@ export function EditEntryGating({ validator }: EditEntryGatingProps) {
name="entryGatingAddress"
render={({ field }) => (
<FormItem className={cn({ hidden: !showCreatorAddressField })}>
<FormLabel>
<FormLabel htmlFor="edit-gating-creator-account-input">
Asset creator account
<InfoPopover className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0">
<InfoPopover
className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0"
label="Asset creator account"
>
Must hold asset created by this account to enter pool
</InfoPopover>
<span className="text-primary">*</span>
</FormLabel>
<FormControl>
<Input className="font-mono" placeholder="" {...field} />
<Input
id="edit-gating-creator-account-input"
className="font-mono placeholder:font-sans"
placeholder="Enter creator account address"
{...field}
/>
</FormControl>
<FormMessage>{errors.entryGatingAddress?.message}</FormMessage>
</FormItem>
Expand All @@ -424,12 +435,19 @@ export function EditEntryGating({ validator }: EditEntryGatingProps) {
<div key={field.id} className="flex items-end">
<AssetLookup
form={form}
id={`edit-gating-asset-${index}-input`}
name={`entryGatingAssets.${index}.value`}
className="flex-1"
label={
<FormLabel className={cn(index !== 0 && 'sr-only')}>
<FormLabel
htmlFor={`edit-gating-asset-${index}-input`}
className={cn(index !== 0 && 'sr-only')}
>
Asset ID
<InfoPopover className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0">
<InfoPopover
className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0"
label="Asset ID"
>
Must hold asset with this ID to enter pool
</InfoPopover>
<span className="text-primary">*</span>
Expand Down Expand Up @@ -482,16 +500,20 @@ export function EditEntryGating({ validator }: EditEntryGatingProps) {
name="entryGatingNfdCreator"
render={({ field }) => (
<FormItem className={cn({ hidden: !showCreatorNfdField })}>
<FormLabel>
<FormLabel htmlFor="edit-gating-creator-nfd-input">
Asset creator NFD
<InfoPopover className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0">
<InfoPopover
className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0"
label="Asset creator NFD"
>
Must hold asset created by an account linked to this NFD to enter pool
</InfoPopover>
<span className="text-primary">*</span>
</FormLabel>
<div className="relative">
<FormControl>
<Input
id="edit-gating-creator-nfd-input"
className={cn(isFetchingNfdCreator || nfdCreatorAppId > 0 ? 'pr-10' : '')}
placeholder=""
autoComplete="new-password"
Expand Down Expand Up @@ -542,9 +564,12 @@ export function EditEntryGating({ validator }: EditEntryGatingProps) {
name="entryGatingNfdParent"
render={({ field }) => (
<FormItem className={cn({ hidden: !showParentNfdField })}>
<FormLabel>
<FormLabel htmlFor="edit-gating-parent-nfd-input">
Root/parent NFD
<InfoPopover className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0">
<InfoPopover
className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0"
label="Root/parent NFD"
>
Must hold a segment of this root/parent NFD to enter pool
</InfoPopover>
<span className="text-primary">*</span>
Expand All @@ -553,6 +578,7 @@ export function EditEntryGating({ validator }: EditEntryGatingProps) {
<div className="flex-1 relative">
<FormControl>
<Input
id="edit-gating-parent-nfd-input"
className={cn(
'',
isFetchingNfdParent || nfdParentAppId > 0 ? 'pr-10' : '',
Expand Down Expand Up @@ -640,14 +666,17 @@ export function EditEntryGating({ validator }: EditEntryGatingProps) {
<FormItem
className={cn('sm:col-start-2 sm:col-end-3', { hidden: !showMinBalanceField })}
>
<FormLabel>
<FormLabel htmlFor="edit-gating-min-balance-input">
Minimum balance
<InfoPopover className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0">
<InfoPopover
className="mx-1.5 relative top-0.5 sm:mx-1 sm:top-0"
label="Minimum balance"
>
Optional minimum required balance of the entry gating asset.
</InfoPopover>
</FormLabel>
<FormControl>
<Input {...field} />
<Input id="edit-gating-min-balance-input" placeholder="0.000000" {...field} />
</FormControl>
<FormDescription>No minimum if left blank</FormDescription>
<FormMessage>{errors.gatingAssetMinBalance?.message}</FormMessage>
Expand Down
7 changes: 4 additions & 3 deletions ui/src/components/ValidatorDetails/EditManagerAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,13 @@ export function EditManagerAccount({ validator }: EditManagerAccountProps) {
name="manager"
render={({ field }) => (
<FormItem>
<FormLabel>Manager account</FormLabel>
<FormLabel htmlFor="edit-manager-input">Manager account</FormLabel>
<div className="flex items-center gap-x-3">
<FormControl>
<Input
className="font-mono"
placeholder=""
id="edit-manager-input"
className="font-mono placeholder:font-sans"
placeholder="Enter new manager account address"
autoComplete="new-password"
spellCheck="false"
{...field}
Expand Down
Loading

0 comments on commit 91c8569

Please sign in to comment.