fix(a11y): add aria-labels to unlabeled controls and table headers
This commit is contained in:
@@ -141,6 +141,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
|
|||||||
<select
|
<select
|
||||||
value={actionFilter}
|
value={actionFilter}
|
||||||
onChange={(e) => { setActionFilter(e.target.value); setPage(1) }}
|
onChange={(e) => { setActionFilter(e.target.value); setPage(1) }}
|
||||||
|
aria-label="Filter by action"
|
||||||
className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
|
className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
|
||||||
>
|
>
|
||||||
{ACTION_TYPES.map((a) => (
|
{ACTION_TYPES.map((a) => (
|
||||||
@@ -157,6 +158,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
|
|||||||
type="date"
|
type="date"
|
||||||
value={dateFrom}
|
value={dateFrom}
|
||||||
onChange={(e) => { setDateFrom(e.target.value); setPage(1) }}
|
onChange={(e) => { setDateFrom(e.target.value); setPage(1) }}
|
||||||
|
aria-label="Filter from date"
|
||||||
className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
|
className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,6 +170,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
|
|||||||
type="date"
|
type="date"
|
||||||
value={dateTo}
|
value={dateTo}
|
||||||
onChange={(e) => { setDateTo(e.target.value); setPage(1) }}
|
onChange={(e) => { setDateTo(e.target.value); setPage(1) }}
|
||||||
|
aria-label="Filter to date"
|
||||||
className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
|
className="h-8 rounded-md border border-border bg-surface px-2 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -180,6 +183,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
|
|||||||
placeholder="Search user..."
|
placeholder="Search user..."
|
||||||
value={userSearch}
|
value={userSearch}
|
||||||
onChange={(e) => setUserSearch(e.target.value)}
|
onChange={(e) => setUserSearch(e.target.value)}
|
||||||
|
aria-label="Filter by user"
|
||||||
className="h-8 rounded-md border border-border bg-surface pl-7 pr-2 text-xs text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent w-40"
|
className="h-8 rounded-md border border-border bg-surface pl-7 pr-2 text-xs text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent w-40"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,23 +227,23 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
|
|||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border bg-elevated/50">
|
<tr className="border-b border-border bg-elevated/50">
|
||||||
<th className="w-8 px-3 py-2" />
|
<th scope="col" className="w-8 px-3 py-2"><span className="sr-only">Expand</span></th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
<th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
||||||
Timestamp
|
Timestamp
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
<th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
||||||
User
|
User
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
<th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
||||||
Action
|
Action
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
<th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
||||||
Resource
|
Resource
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
<th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
||||||
Device
|
Device
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
<th scope="col" className="px-3 py-2 text-left text-xs font-medium text-text-muted">
|
||||||
IP Address
|
IP Address
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -271,6 +275,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
|
|||||||
setPerPage(Number(e.target.value))
|
setPerPage(Number(e.target.value))
|
||||||
setPage(1)
|
setPage(1)
|
||||||
}}
|
}}
|
||||||
|
aria-label="Rows per page"
|
||||||
className="h-7 rounded border border-border bg-surface px-1.5 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
|
className="h-7 rounded border border-border bg-surface px-1.5 text-xs text-text-primary focus:outline-none focus:ring-1 focus:ring-accent"
|
||||||
>
|
>
|
||||||
{PER_PAGE_OPTIONS.map((n) => (
|
{PER_PAGE_OPTIONS.map((n) => (
|
||||||
@@ -289,16 +294,18 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setPage(1)}
|
onClick={() => setPage(1)}
|
||||||
disabled={page <= 1}
|
disabled={page <= 1}
|
||||||
|
aria-label="First page"
|
||||||
className="rounded p-1 hover:bg-elevated disabled:opacity-30"
|
className="rounded p-1 hover:bg-elevated disabled:opacity-30"
|
||||||
>
|
>
|
||||||
<ChevronsLeft className="h-4 w-4" />
|
<ChevronsLeft className="h-4 w-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setPage(page - 1)}
|
onClick={() => setPage(page - 1)}
|
||||||
disabled={page <= 1}
|
disabled={page <= 1}
|
||||||
|
aria-label="Previous page"
|
||||||
className="rounded p-1 hover:bg-elevated disabled:opacity-30"
|
className="rounded p-1 hover:bg-elevated disabled:opacity-30"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<span className="px-2">
|
<span className="px-2">
|
||||||
Page {page} of {totalPages}
|
Page {page} of {totalPages}
|
||||||
@@ -306,16 +313,18 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setPage(page + 1)}
|
onClick={() => setPage(page + 1)}
|
||||||
disabled={page >= totalPages}
|
disabled={page >= totalPages}
|
||||||
|
aria-label="Next page"
|
||||||
className="rounded p-1 hover:bg-elevated disabled:opacity-30"
|
className="rounded p-1 hover:bg-elevated disabled:opacity-30"
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="h-4 w-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setPage(totalPages)}
|
onClick={() => setPage(totalPages)}
|
||||||
disabled={page >= totalPages}
|
disabled={page >= totalPages}
|
||||||
|
aria-label="Last page"
|
||||||
className="rounded p-1 hover:bg-elevated disabled:opacity-30"
|
className="rounded p-1 hover:bg-elevated disabled:opacity-30"
|
||||||
>
|
>
|
||||||
<ChevronsRight className="h-4 w-4" />
|
<ChevronsRight className="h-4 w-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export function DeviceFilters({ tenantId }: DeviceFiltersProps) {
|
|||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className="pl-8"
|
className="pl-8"
|
||||||
placeholder="Search hostname, IP..."
|
placeholder="Search hostname, IP..."
|
||||||
|
aria-label="Search devices"
|
||||||
defaultValue={searchText}
|
defaultValue={searchText}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -77,13 +77,13 @@ export function UserList({ tenantId }: Props) {
|
|||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border bg-surface">
|
<tr className="border-b border-border bg-surface">
|
||||||
<th className="text-left px-3 py-2 text-xs font-medium text-text-muted">Name</th>
|
<th scope="col" className="text-left px-3 py-2 text-xs font-medium text-text-muted">Name</th>
|
||||||
<th className="text-left px-3 py-2 text-xs font-medium text-text-muted">Email</th>
|
<th scope="col" className="text-left px-3 py-2 text-xs font-medium text-text-muted">Email</th>
|
||||||
<th className="text-left px-3 py-2 text-xs font-medium text-text-muted">Role</th>
|
<th scope="col" className="text-left px-3 py-2 text-xs font-medium text-text-muted">Role</th>
|
||||||
<th className="text-left px-3 py-2 text-xs font-medium text-text-muted">Last Login</th>
|
<th scope="col" className="text-left px-3 py-2 text-xs font-medium text-text-muted">Last Login</th>
|
||||||
<th className="text-left px-3 py-2 text-xs font-medium text-text-muted">Status</th>
|
<th scope="col" className="text-left px-3 py-2 text-xs font-medium text-text-muted">Status</th>
|
||||||
{isTenantAdmin(currentUser) && (
|
{isTenantAdmin(currentUser) && (
|
||||||
<th className="px-3 py-2 text-xs font-medium text-text-muted w-8"></th>
|
<th scope="col" className="px-3 py-2 text-xs font-medium text-text-muted w-8"><span className="sr-only">Actions</span></th>
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -130,8 +130,9 @@ export function UserList({ tenantId }: Props) {
|
|||||||
onClick={() => handleDeactivate(u.id, u.email)}
|
onClick={() => handleDeactivate(u.id, u.email)}
|
||||||
className="text-text-muted hover:text-error transition-colors"
|
className="text-text-muted hover:text-error transition-colors"
|
||||||
title="Deactivate user"
|
title="Deactivate user"
|
||||||
|
aria-label={`Deactivate ${u.email}`}
|
||||||
>
|
>
|
||||||
<UserX className="h-3.5 w-3.5" />
|
<UserX className="h-3.5 w-3.5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user