Fix P1 accessibility issues in Button and Card

Button:
- Add aria-busy={loading} for assistive technology
- Add visually-hidden "Loading" text for screen readers
- Mark CircularProgress as aria-hidden (decorative)

Card:
- Add tabIndex={0} and role="button" when interactive
- Fix Record<string, any> → Theme type for type safety

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 16:56:33 +11:00
parent 2f9b719f01
commit b2349d6c78
2 changed files with 33 additions and 7 deletions

View File

@@ -50,6 +50,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
variant={variant} variant={variant}
size={size} size={size}
disabled={loading || disabled} disabled={loading || disabled}
aria-busy={loading || undefined}
sx={[ sx={[
underline && underline &&
variant === 'text' && { variant === 'text' && {
@@ -63,12 +64,30 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
> >
{children} {children}
{loading && ( {loading && (
<>
<CircularProgress <CircularProgress
size={16} size={16}
color="inherit" color="inherit"
thickness={3} thickness={3}
aria-hidden
sx={{ ml: 1 }} sx={{ ml: 1 }}
/> />
<span
style={{
position: 'absolute',
width: 1,
height: 1,
padding: 0,
margin: -1,
overflow: 'hidden',
clip: 'rect(0, 0, 0, 0)',
whiteSpace: 'nowrap',
borderWidth: 0,
}}
>
Loading
</span>
</>
)} )}
</MuiButton> </MuiButton>
); );

View File

@@ -2,6 +2,7 @@ import React from 'react';
import MuiCard from '@mui/material/Card'; import MuiCard from '@mui/material/Card';
import type { CardProps as MuiCardProps } from '@mui/material/Card'; import type { CardProps as MuiCardProps } from '@mui/material/Card';
import CardContent from '@mui/material/CardContent'; import CardContent from '@mui/material/CardContent';
import type { Theme } from '@mui/material/styles';
// ─── Types ─────────────────────────────────────────────────────────────────── // ─── Types ───────────────────────────────────────────────────────────────────
@@ -56,11 +57,17 @@ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
// Map FA variant names to MUI Card variant // Map FA variant names to MUI Card variant
const muiVariant = variant === 'outlined' ? 'outlined' : undefined; const muiVariant = variant === 'outlined' ? 'outlined' : undefined;
// Interactive cards need keyboard operability
const interactiveProps = interactive
? { tabIndex: 0 as const, role: 'button' as const }
: {};
return ( return (
<MuiCard <MuiCard
ref={ref} ref={ref}
variant={muiVariant} variant={muiVariant}
elevation={0} elevation={0}
{...interactiveProps}
sx={[ sx={[
// Selected state: brand border + warm background // Selected state: brand border + warm background
// Border width is always 2px (set in theme) — only colour changes here // Border width is always 2px (set in theme) — only colour changes here
@@ -81,7 +88,7 @@ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
// Focus-visible for keyboard accessibility on interactive cards // Focus-visible for keyboard accessibility on interactive cards
interactive && { interactive && {
'&:focus-visible': { '&:focus-visible': {
outline: (theme: Record<string, any>) => outline: (theme: Theme) =>
`2px solid ${theme.palette.primary.main}`, `2px solid ${theme.palette.primary.main}`,
outlineOffset: '2px', outlineOffset: '2px',
}, },