Skip to content

Commit 50dd330

Browse files
ErwanDecosterCopilotChristophe-iExec
authored
feat: better accessibility (#110)
* feat: add tab navigation for app details and deals in AppsRoute component * feat: add AppAccessTable component and integrate into AppsRoute * feat: enhance AppAccessTable and AppDealsTable with loading and outdated state management * feat: rename columns to appColumns and remove app address and price column * feat: add tab navigation for dataset details, deals, and access in DatasetsRoute * feat: integrate loading and outdated state management in DatasetDealsTable * feat: add DatasetAccessTable component and integrate it into DatasetsRoute * feat: add tab navigation for workerpool details, deals, and access * feat: enhance WorkerpoolDealsTable with loading and outdated state management * feat: add WorkerpoolAccessTable component and integrate it into WorkerpoolsRoute * feat: refactor loading and outdated state management in access and deals tables using useEffect * feat: add access tables for apps, datasets, and workerpools on address page * fix: correct import path for WorkerpoolAccessTable component * refactor: remove console logs from access data fetching functions * feat: enhance pagination logic to display all pages for 7 or fewer total pages * feat: update pagination logic to support mobile-first design and dynamic page visibility * feat: update access data fetching logic to support pagination and total count for apps, datasets, and workerpools * fix: prevent rendering of pagination controls for invalid states * refactor: prettier * fix: stabilize pagination rendering during lading state * fix: normalize case for order restrictions in CopyButton components * fix: useTabParam() name in dataset Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: correct error message wording in loading alerts across multiple tables * feat: add accessibility attributes to buttons in CopyButton, Navbar, and SchemaSearch components * feat: add accessibility attributes to ChainLink and Button components in Footer, Navbar, and AddressChip * feat: add accessibility attributes to social links in Footer and Block Explorer link in SmartLinkGroup; add role to Tabs component * feat: enhance accessibility in SearcherBar component; add ARIA roles and attributes to input and error messages * fix: fix typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: improve accessibility roles in Navbar and Tabs components * fix: update package-lock file * fix: remove unnecessary id attributes for accessibility improvements * fix: remove useless id attribute or ensure uniqueness. Also fix acessibility for tabs adding missing aria-selected attribute * fix: remove useless implicite role=searchbox and invalid tabIndex --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Christophe Dolivet <christophe.dolivet@iex.ec>
1 parent b7a1448 commit 50dd330

10 files changed

Lines changed: 85 additions & 24 deletions

File tree

src/components/CopyButton.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ const CopyButton = ({
6565
onMouseEnter={handleMouseEnter}
6666
onMouseLeave={handleMouseLeave}
6767
className="hover:before:bg-muted active:before:bg-secondary relative z-0 -mx-1 -my-1 flex items-center gap-1 px-1 py-1 transition-colors before:absolute before:inset-0 before:-z-10 before:rounded-lg before:duration-200 active:before:scale-x-[0.98] active:before:scale-y-[0.94]"
68+
type="button"
69+
aria-label="Copy"
6870
>
6971
{buttonText && <span className="mr-1 text-sm">{buttonText}</span>}
7072
<Copy className="size-4 flex-none" />

src/components/Footer.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,40 @@ import { Button } from './ui/button';
1515

1616
function SocialLinksItems({ className }: { className?: string }) {
1717
const socialLinks = [
18-
{ href: 'https://twitter.com/iEx_ec', icon: <SiX size={16} /> },
19-
{ href: 'https://discord.gg/pbt9m98wnU', icon: <SiDiscord size={16} /> },
20-
{ href: 'https://t.me/iexec_rlc_official', icon: <SiTelegram size={16} /> },
18+
{
19+
href: 'https://twitter.com/iEx_ec',
20+
icon: <SiX size={16} />,
21+
ariaLabel: 'Twitter',
22+
},
23+
{
24+
href: 'https://discord.gg/pbt9m98wnU',
25+
icon: <SiDiscord size={16} />,
26+
ariaLabel: 'Discord',
27+
},
28+
{
29+
href: 'https://t.me/iexec_rlc_official',
30+
icon: <SiTelegram size={16} />,
31+
ariaLabel: 'Telegram',
32+
},
2133
{
2234
href: 'https://www.youtube.com/channel/UCwWxZWvKVHn3CXnmDooLWtA',
2335
icon: <SiYoutube size={16} />,
36+
ariaLabel: 'YouTube',
2437
},
2538
{
2639
href: 'https://www.linkedin.com/company/iex.ec/',
2740
icon: <Linkedin size={16} />,
41+
ariaLabel: 'LinkedIn',
42+
},
43+
{
44+
href: 'https://medium.com/iex-ec',
45+
icon: <SiMedium size={16} />,
46+
ariaLabel: 'Medium',
2847
},
29-
{ href: 'https://medium.com/iex-ec', icon: <SiMedium size={16} /> },
3048
];
3149
return (
3250
<div className={cn('flex', className)}>
33-
{socialLinks.map(({ href, icon }, idx) => (
51+
{socialLinks.map(({ href, icon, ariaLabel }, idx) => (
3452
<Button
3553
key={idx}
3654
asChild
@@ -41,7 +59,7 @@ function SocialLinksItems({ className }: { className?: string }) {
4159
href={href}
4260
target="_blank"
4361
rel="noopener noreferrer"
44-
aria-label="Social link"
62+
aria-label={ariaLabel}
4563
>
4664
{icon}
4765
</a>
@@ -78,8 +96,12 @@ export function Footer({ className }: { className?: string }) {
7896
)}
7997
>
8098
<div className="flex flex-col items-center justify-between gap-10 xl:flex-row">
81-
<ChainLink to="/" className="flex items-center gap-2 font-mono">
82-
<img src={iExecLogo} width="25" height="25" alt="iExec logo" />
99+
<ChainLink
100+
to="/"
101+
aria-label="Home"
102+
className="flex items-center gap-2 font-mono"
103+
>
104+
<img src={iExecLogo} width="25" height="25" alt="" />
83105
<span>iExec Explorer</span>
84106
</ChainLink>
85107

src/components/ModeToggle.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function ModeToggle() {
3434
size="none"
3535
role="radio"
3636
aria-checked={selected}
37-
aria-label={label}
37+
aria-label={`Toggle ${label} mode`}
3838
title={label}
3939
className={cn(
4040
'text-foreground hover:bg-muted p-1',

src/components/PaginatedNavigation.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export const PaginatedNavigation = ({
9595
pages.push(1, 'ellipsis', currentPage, 'ellipsis', stableTotalPages);
9696
}
9797
} else {
98+
// Desktop: full pagination logic
9899
pages.push(1);
99100

100101
if (currentPage <= 3) {

src/components/SmartLinkGroup.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export default function SmartLinkGroup({
114114
href={`${getBlockExplorerUrl(chainId)}/${blockExplorerPath[type]}`}
115115
target="_blank"
116116
rel="noopener noreferrer"
117+
aria-label="Open in Block Explorer"
117118
>
118119
<ExternalLink className="text-foreground" />
119120
</a>

src/components/navbar/AddressChip.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export function AddressChip({ address }: { address: string }) {
4242
<Button
4343
className="dark:bg-popover bg-primary-foreground hover:bg-muted text-primary dark:text-brand duration-300"
4444
variant="secondary"
45+
aria-label="Copy connected wallet address"
4546
onClick={handleCopy}
4647
onMouseEnter={handleMouseEnter}
4748
onMouseLeave={handleMouseLeave}

src/components/navbar/NavBar.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,12 @@ export function Navbar() {
3333

3434
return (
3535
<nav className="flex items-center justify-between py-6">
36-
<ChainLink to="/" className="-m-2 flex items-center gap-2 p-2 font-mono">
37-
<img src={iExecLogo} width="25" height="25" alt="iExec logo" />
36+
<ChainLink
37+
to="/"
38+
aria-label="Home"
39+
className="-m-2 flex items-center gap-2 p-2 font-mono"
40+
>
41+
<img src={iExecLogo} width="25" height="25" alt="" />
3842
<span className="hidden lg:block">iExec Explorer</span>
3943
</ChainLink>
4044
<div className="mr-8 flex items-center gap-4 md:mr-0">
@@ -76,6 +80,7 @@ export function Navbar() {
7680
<button
7781
type="button"
7882
className="inline-flex items-center gap-2"
83+
aria-label="Logout"
7984
onClick={() => logout()}
8085
>
8186
Logout
@@ -103,6 +108,7 @@ export function Navbar() {
103108
className="absolute -inset-2 size-10 cursor-pointer appearance-none bg-transparent"
104109
name="menu"
105110
id="menu"
111+
aria-label="Toggle navigation menu"
106112
checked={isMenuOpen}
107113
readOnly
108114
/>
@@ -113,15 +119,21 @@ export function Navbar() {
113119

114120
<div className="border-secondary bg-background pointer-events-auto fixed inset-y-0 left-0 z-10 flex w-full -translate-x-full flex-col overflow-auto rounded-r-3xl border-r px-6 pt-6 duration-200 group-has-[:checked]:translate-x-0 lg:w-[255px] lg:translate-x-0">
115121
<div className="-m-2 mr-6 flex items-center justify-between gap-2 py-2 pl-2">
116-
<ChainLink to="/" className="font-mono" onClick={handleMenuToggle}>
117-
<img src={iExecLogo} width="25" height="25" alt="iExec logo" />
122+
<ChainLink
123+
to="/"
124+
aria-label="Home"
125+
className="font-mono"
126+
onClick={handleMenuToggle}
127+
>
128+
<img src={iExecLogo} width="25" height="25" alt="" />
118129
</ChainLink>
119130
{isConnected ? (
120131
<div className="flex max-w-[1260px] items-center gap-2">
121132
<AddressChip address={userAddress!} />
122133

123134
<button
124135
type="button"
136+
aria-label="Logout"
125137
className="hover:drop-shadow-link-hover p-1"
126138
onClick={() => logout()}
127139
>

src/modules/Tabs.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export function Tabs({
6262
left: `${indicatorStyle.left}px`,
6363
width: `${indicatorStyle.width}px`,
6464
}}
65+
role="tablist"
6566
/>
6667

6768
{tabLabels.map((label, index) => {
@@ -73,6 +74,8 @@ export function Tabs({
7374
key={label}
7475
data-tab-index={index}
7576
variant="link"
77+
role="tab"
78+
aria-selected={currentTab === index}
7679
size={'none'}
7780
onClick={() => {
7881
if (!isDisabled) onTabChange(index);

src/modules/datasets/SchemaSearch.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export function SchemaSearch({
4646
return (
4747
<div className="rounded-2xl border">
4848
<button
49+
aria-label="Toggle schema search filters"
4950
className={cn('flex w-full items-center gap-2 px-10 py-6 duration-200')}
5051
onClick={() => setIsOpen(!isOpen)}
5152
>
@@ -103,7 +104,11 @@ export function SchemaSearch({
103104
>
104105
<span className={cn('inline-block')}>{schema.path}</span>
105106
<span className={cn('inline-block')}>: {schema.type}</span>
106-
<button onClick={() => onRemoveFilter(index)}>
107+
<button
108+
type="button"
109+
aria-label={`Remove filter ${schema.path} of type ${schema.type}`}
110+
onClick={() => onRemoveFilter(index)}
111+
>
107112
<X className="ml-1" size={12} />
108113
</button>
109114
</span>

src/modules/search/SearcherBar.tsx

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { cn } from '@/lib/utils';
33
import { useMutation } from '@tanstack/react-query';
44
import { useNavigate, useParams } from '@tanstack/react-router';
55
import { Search } from 'lucide-react';
6-
import { useEffect, useRef, useState } from 'react';
6+
import { useEffect, useId, useRef, useState } from 'react';
77
import { Button } from '@/components/ui/button';
88
import { Input } from '@/components/ui/input';
99
import { getIExec, getReadonlyIExec } from '@/externals/iexecSdkClient';
@@ -20,6 +20,7 @@ export function SearcherBar({
2020
initialSearch?: string;
2121
}) {
2222
const { isConnected } = useUserStore();
23+
const searchErrorId = useId();
2324
const [inputValue, setInputValue] = useState('');
2425
const [shake, setShake] = useState(false);
2526
const [errorCount, setErrorCount] = useState(0);
@@ -154,6 +155,9 @@ export function SearcherBar({
154155
onChange={(e) => setInputValue(e.target.value)}
155156
onKeyDown={handleKeyDown}
156157
disabled={isPending}
158+
type="search"
159+
aria-label="Search for addresses, deal IDs, task IDs, or transaction hashes"
160+
aria-describedby={localError || error ? searchErrorId : undefined}
157161
className={cn(
158162
'bg-muted border-secondary w-full rounded-2xl py-5.5 pl-12 sm:py-6.5',
159163
isConnected && 'sm:pr-32',
@@ -164,7 +168,12 @@ export function SearcherBar({
164168
placeholder="Search address, deal id, task id, transaction hash..."
165169
/>
166170
{(localError || error) && (
167-
<p className="bg-danger text-danger-foreground border-danger-border absolute -bottom-8 rounded-full border px-4">
171+
<p
172+
id={searchErrorId}
173+
role="alert"
174+
aria-live="polite"
175+
className="bg-danger text-danger-foreground border-danger-border absolute -bottom-8 rounded-full border px-4"
176+
>
168177
{localError ? localError.message : error?.message}
169178
</p>
170179
)}
@@ -174,14 +183,19 @@ export function SearcherBar({
174183
/>
175184
</div>
176185

177-
<div
178-
className={cn(
179-
'mt-4 flex justify-center gap-4 sm:hidden',
180-
isError && 'mt-10'
181-
)}
182-
>
183-
<div className="flex justify-center">
184-
<Button variant="outline" onClick={handleSearch} disabled={isPending}>
186+
<div className={cn('mt-4 flex justify-center gap-4', isError && 'mt-10')}>
187+
<div className="flex justify-center sm:sr-only">
188+
<Button
189+
variant="outline"
190+
onClick={handleSearch}
191+
disabled={isPending}
192+
type="button"
193+
aria-label={
194+
isPending
195+
? 'Searching in progress'
196+
: 'Search for the entered value'
197+
}
198+
>
185199
{isPending ? 'Searching...' : 'Search'}
186200
</Button>
187201
</div>

0 commit comments

Comments
 (0)