diff --git a/docs/components/progress.md b/docs/components/progress.md index d904127..7b5f7e4 100644 --- a/docs/components/progress.md +++ b/docs/components/progress.md @@ -16,10 +16,17 @@ } `" /> +## 可访问性 + +Progress 默认渲染为 `role="progressbar"`,并根据 `percent` 同步 `aria-valuemin`、`aria-valuemax`、`aria-valuenow` 和 `aria-valuetext`。当页面中有多个进度条时,建议通过 `aria-label` 或 `aria-labelledby` 提供可区分名称。 + ## API -| 属性 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| percent | 百分比 | `number` | - | -| status | 状态 | `'normal' \| 'success' \| 'exception'` | `'normal'` | -| showInfo | 显示百分比文字 | `boolean` | `true` | +| 属性 | 说明 | 类型 | 默认值 | +| --------------- | ------------------------------ | -------------------------------------- | ------------- | +| percent | 百分比 | `number` | - | +| status | 状态 | `'normal' \| 'success' \| 'exception'` | `'normal'` | +| showInfo | 显示百分比文字 | `boolean` | `true` | +| aria-label | 进度条可访问名称 | `string` | `'Progress'` | +| aria-labelledby | 使用外部标签命名进度条 | `string` | - | +| aria-valuetext | 自定义屏幕阅读器读取的进度文本 | `string` | `${percent}%` | diff --git a/packages/ui/src/components/data/Progress/Progress.test.tsx b/packages/ui/src/components/data/Progress/Progress.test.tsx new file mode 100644 index 0000000..4e2354d --- /dev/null +++ b/packages/ui/src/components/data/Progress/Progress.test.tsx @@ -0,0 +1,47 @@ +import { render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' + +import { Progress } from './Progress' + +describe('Progress', () => { + it('exposes determinate progressbar semantics from percent', () => { + render() + + const progress = screen.getByRole('progressbar', { name: 'Upload progress' }) + + expect(progress).toHaveAttribute('aria-valuemin', '0') + expect(progress).toHaveAttribute('aria-valuemax', '100') + expect(progress).toHaveAttribute('aria-valuenow', '42') + expect(progress).toHaveAttribute('aria-valuetext', '42%') + }) + + it('keeps progressbar values in sync with the clamped visual percentage', () => { + render() + + const progress = screen.getByRole('progressbar', { name: 'Progress' }) + + expect(progress).toHaveAttribute('aria-valuenow', '100') + expect(progress).toHaveAttribute('aria-valuetext', '100%') + expect(screen.getByText('100%')).toBeInTheDocument() + }) + + it('supports external labelling and custom value text', () => { + render( +
+ Sync progress + +
, + ) + + const progress = screen.getByRole('progressbar', { name: 'Sync progress' }) + + expect(progress).toHaveAttribute('aria-valuenow', '64') + expect(progress).toHaveAttribute('aria-valuetext', '64 percent complete') + expect(screen.queryByText('64%')).not.toBeInTheDocument() + }) +}) diff --git a/packages/ui/src/components/data/Progress/Progress.tsx b/packages/ui/src/components/data/Progress/Progress.tsx index ea2711a..5d8fd08 100644 --- a/packages/ui/src/components/data/Progress/Progress.tsx +++ b/packages/ui/src/components/data/Progress/Progress.tsx @@ -8,13 +8,34 @@ export interface ProgressProps extends HTMLAttributes { } export const Progress = forwardRef(function Progress( - { className, percent, status = 'normal', showInfo = true, ...props }, + { + className, + percent, + status = 'normal', + showInfo = true, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + 'aria-valuetext': ariaValueText, + ...props + }, ref, ) { const safePercent = Math.max(0, Math.min(100, percent)) + const accessibleLabel = ariaLabel ?? (ariaLabelledBy ? undefined : 'Progress') return ( -
+
(function Progr style={{ width: `${safePercent}%` }} />
- {showInfo ?
{safePercent}%
: null} + {showInfo ? ( +
{safePercent}%
+ ) : null}
) })