diff --git a/docs/components/toast.md b/docs/components/toast.md index 79f45f5..9decf4b 100644 --- a/docs/components/toast.md +++ b/docs/components/toast.md @@ -7,14 +7,18 @@ +## 可访问性 + +Toast 默认会把 `info`、`success`、`warning` 作为 `role="status"` 的礼貌通知呈现;`error` 默认作为 `role="alert"` 的紧急通知呈现。需要更细粒度控制时,可以显式传入 `role` 与 `aria-live`。 + ## API | 属性 | 说明 | 类型 | 默认值 | @@ -22,4 +26,6 @@ | open | 是否显示 | `boolean` | `false` | | duration | 显示时长(ms),0 表示不自动关闭 | `number` | `3000` | | status | 状态 | `'info' \| 'success' \| 'warning' \| 'error'` | `'info'` | +| role | live region 语义,默认 error 为 alert,其余状态为 status | `'status' \| 'alert'` | - | +| aria-live | live region 播报优先级,传入后覆盖默认值 | `'off' \| 'polite' \| 'assertive'` | - | | onClose | 关闭回调 | `() => void` | - | diff --git a/packages/ui/src/components/feedback/Toast/Toast.test.tsx b/packages/ui/src/components/feedback/Toast/Toast.test.tsx index b290059..7b88741 100644 --- a/packages/ui/src/components/feedback/Toast/Toast.test.tsx +++ b/packages/ui/src/components/feedback/Toast/Toast.test.tsx @@ -17,6 +17,30 @@ describe('Toast', () => { expect(toast).toHaveTextContent('Saved successfully') }) + it('renders error updates as alert regions', () => { + render( + + Save failed + , + ) + + const toast = screen.getByRole('alert') + expect(toast).not.toHaveAttribute('aria-live') + expect(toast).toHaveTextContent('Save failed') + }) + + it('allows callers to override live region urgency', () => { + render( + + Session expiring + , + ) + + const toast = screen.getByRole('alert') + expect(toast).toHaveAttribute('aria-live', 'assertive') + expect(toast).toHaveTextContent('Session expiring') + }) + it('uses a 3000ms default auto-close duration', () => { vi.useFakeTimers() const onClose = vi.fn() diff --git a/packages/ui/src/components/feedback/Toast/Toast.tsx b/packages/ui/src/components/feedback/Toast/Toast.tsx index 9fe06ef..1742169 100644 --- a/packages/ui/src/components/feedback/Toast/Toast.tsx +++ b/packages/ui/src/components/feedback/Toast/Toast.tsx @@ -1,17 +1,35 @@ import { forwardRef, type HTMLAttributes, useEffect } from 'react' import { cn } from '../../../utils/cn' +type ToastStatus = 'info' | 'success' | 'warning' | 'error' +type ToastRole = 'status' | 'alert' + export interface ToastProps extends HTMLAttributes { open?: boolean duration?: number onClose?: () => void - status?: 'info' | 'success' | 'warning' | 'error' + status?: ToastStatus + /** 轻提示的 live region 语义,默认 error 为 alert,其余状态为 status */ + role?: ToastRole } export const Toast = forwardRef(function Toast( - { className, open = false, duration = 3000, onClose, status = 'info', children, ...props }, + { + className, + open = false, + duration = 3000, + onClose, + status = 'info', + role, + 'aria-live': ariaLive, + children, + ...props + }, ref, ) { + const toastRole = role ?? (status === 'error' ? 'alert' : 'status') + const toastAriaLive = ariaLive ?? (toastRole === 'status' ? 'polite' : undefined) + useEffect(() => { if (!open || duration <= 0) { return @@ -29,8 +47,8 @@ export const Toast = forwardRef(function Toast( return (