Fix P2 accessibility issues across atom components
- Input: Record<string, any> → Theme type for boxShadow theme accessor - Button: Document aria-label requirement for icon-only usage - Badge: Document aria-label requirement for icon-only badges - Switch: Strengthen a11y docs — always wrap in FormControlLabel with example - Radio: Strengthen a11y docs — always use RadioGroup + FormControlLabel with example - Chip: Document aria-label requirement for icon-only deletable chips Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,9 @@ const softColors: Record<BadgeColor, (t: Theme) => { bg: string; text: string }>
|
|||||||
* Variant options:
|
* Variant options:
|
||||||
* - `soft` (default) — tonal background, coloured text. Calmer, preferred for FA.
|
* - `soft` (default) — tonal background, coloured text. Calmer, preferred for FA.
|
||||||
* - `filled` — solid background, white text. For high-priority emphasis.
|
* - `filled` — solid background, white text. For high-priority emphasis.
|
||||||
|
*
|
||||||
|
* **Accessibility**: If a Badge contains only an icon (no text children),
|
||||||
|
* provide an `aria-label` prop so screen readers can announce the status.
|
||||||
*/
|
*/
|
||||||
export const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
|
export const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ export interface ButtonProps extends MuiButtonProps {
|
|||||||
* - `outlined` + `secondary` — Outlined grey (neutral border)
|
* - `outlined` + `secondary` — Outlined grey (neutral border)
|
||||||
* - `text` + `primary` — Ghost / text button (copper text)
|
* - `text` + `primary` — Ghost / text button (copper text)
|
||||||
* - `text` + `secondary` — Ghost secondary (grey text)
|
* - `text` + `secondary` — Ghost secondary (grey text)
|
||||||
|
*
|
||||||
|
* **Accessibility**: Icon-only buttons (no visible text) require an
|
||||||
|
* `aria-label` prop. Without it, screen readers announce nothing.
|
||||||
|
* Example: `<Button startIcon={<DeleteIcon />} aria-label="Delete item" />`
|
||||||
*/
|
*/
|
||||||
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ export interface ChipProps extends MuiChipProps {
|
|||||||
*
|
*
|
||||||
* Selected state:
|
* Selected state:
|
||||||
* - `selected` prop applies brand styling (filled primary bg or outlined primary border)
|
* - `selected` prop applies brand styling (filled primary bg or outlined primary border)
|
||||||
|
*
|
||||||
|
* **Accessibility**: If a Chip has no visible label text (icon-only) and is
|
||||||
|
* deletable, provide an `aria-label` so screen readers can announce what
|
||||||
|
* is being removed.
|
||||||
*/
|
*/
|
||||||
export const Chip = React.forwardRef<HTMLDivElement, ChipProps>(
|
export const Chip = React.forwardRef<HTMLDivElement, ChipProps>(
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import OutlinedInput from '@mui/material/OutlinedInput';
|
|||||||
import type { OutlinedInputProps } from '@mui/material/OutlinedInput';
|
import type { OutlinedInputProps } from '@mui/material/OutlinedInput';
|
||||||
import FormHelperText from '@mui/material/FormHelperText';
|
import FormHelperText from '@mui/material/FormHelperText';
|
||||||
import InputAdornment from '@mui/material/InputAdornment';
|
import InputAdornment from '@mui/material/InputAdornment';
|
||||||
|
import type { Theme } from '@mui/material/styles';
|
||||||
|
|
||||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ export const Input = React.forwardRef<HTMLDivElement, InputProps>(
|
|||||||
borderColor: 'success.main',
|
borderColor: 'success.main',
|
||||||
},
|
},
|
||||||
'&.Mui-focused': {
|
'&.Mui-focused': {
|
||||||
boxShadow: (theme: Record<string, any>) =>
|
boxShadow: (theme: Theme) =>
|
||||||
`0 0 0 3px ${theme.palette.common.white}, 0 0 0 5px ${theme.palette.success.main}`,
|
`0 0 0 3px ${theme.palette.common.white}, 0 0 0 5px ${theme.palette.success.main}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,10 +18,19 @@ export interface RadioProps extends MuiRadioProps {}
|
|||||||
* From Parsons 1.0 Figma radio component — 16px circle with brand dot.
|
* From Parsons 1.0 Figma radio component — 16px circle with brand dot.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* - Always use inside a RadioGroup for proper keyboard navigation
|
|
||||||
* - Pair with FormControlLabel for accessible labels
|
|
||||||
* - For binary on/off, use Switch instead
|
* - For binary on/off, use Switch instead
|
||||||
* - For multi-select, use Checkbox (planned) or Chip
|
* - For multi-select, use Checkbox (planned) or Chip
|
||||||
|
*
|
||||||
|
* **Accessibility**: Always use inside a `RadioGroup` and wrap each Radio
|
||||||
|
* in `FormControlLabel`. A standalone Radio without a label or group is
|
||||||
|
* inaccessible — screen readers cannot announce the option or allow
|
||||||
|
* keyboard arrow-key navigation between options.
|
||||||
|
* ```tsx
|
||||||
|
* <RadioGroup value={value} onChange={handleChange}>
|
||||||
|
* <FormControlLabel value="burial" control={<Radio />} label="Burial" />
|
||||||
|
* <FormControlLabel value="cremation" control={<Radio />} label="Cremation" />
|
||||||
|
* </RadioGroup>
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const Radio = React.forwardRef<HTMLButtonElement, RadioProps>(
|
export const Radio = React.forwardRef<HTMLButtonElement, RadioProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
|
|||||||
@@ -20,8 +20,14 @@ export interface SwitchProps extends MuiSwitchProps {}
|
|||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* - Use for boolean settings ("Include catering", "Add memorial video")
|
* - Use for boolean settings ("Include catering", "Add memorial video")
|
||||||
* - Pair with a label via FormControlLabel for accessibility
|
|
||||||
* - For mutually exclusive options, use Radio instead
|
* - For mutually exclusive options, use Radio instead
|
||||||
|
*
|
||||||
|
* **Accessibility**: Always wrap in `FormControlLabel` with a `label` prop.
|
||||||
|
* A standalone Switch without a visible label is inaccessible — screen
|
||||||
|
* readers cannot announce what the toggle controls.
|
||||||
|
* ```tsx
|
||||||
|
* <FormControlLabel control={<Switch />} label="Include catering" />
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
|
export const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user