Skip to content

Commit d25eac8

Browse files
authored
fix: expose accessible SVG semantics (#325)
* fix: expose accessible progress semantics * test: cover line progress semantics * fix: forward line SVG id
1 parent cd730a6 commit d25eac8

6 files changed

Lines changed: 90 additions & 2 deletions

File tree

‎README.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ export default () => (
5252
);
5353
```
5454

55+
The SVG is decorative by default. When using it as the progress indicator itself, provide progress semantics and an accessible name with standard SVG/ARIA props:
56+
57+
```tsx | pure
58+
<Line
59+
percent={42}
60+
role="progressbar"
61+
aria-label="Upload progress"
62+
aria-valuemin={0}
63+
aria-valuemax={100}
64+
aria-valuenow={42}
65+
/>
66+
```
67+
5568
## Examples
5669

5770
Run the local dumi site:
@@ -88,6 +101,8 @@ Then open `http://localhost:8000`.
88101
| `styles` | Partial<Record<'root' \| 'rail' \| 'track', React.CSSProperties>> | - | Semantic styles for internal slots. |
89102
| `transition` | string | - | CSS transition for track updates. |
90103

104+
Standard SVG attributes, including `role` and `aria-*`, are forwarded to the root SVG.
105+
91106
## Development
92107

93108
```bash

‎README.zh-CN.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ export default () => (
5252
);
5353
```
5454

55+
SVG 默认作为装饰元素。当 SVG 本身作为进度指示器时,可通过标准 SVG/ARIA 属性提供进度语义和无障碍名称:
56+
57+
```tsx | pure
58+
<Line
59+
percent={42}
60+
role="progressbar"
61+
aria-label="上传进度"
62+
aria-valuemin={0}
63+
aria-valuemax={100}
64+
aria-valuenow={42}
65+
/>
66+
```
67+
5568
## 示例
5669

5770
运行本地 dumi 站点:
@@ -88,6 +101,8 @@ npm start
88101
| `styles` | Partial<Record<'root' \| 'rail' \| 'track', React.CSSProperties>> | - | 内部插槽的语义化样式。 |
89102
| `transition` | string | - | 用于跟踪更新的 CSS 过渡。 |
90103

104+
标准 SVG 属性(包括 `role` 和 `aria-*`)会透传到根 SVG。
105+
91106
## 本地开发
92107

93108
```bash

‎src/Line.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@ const Line: React.FC<ProgressProps> = (props) => {
5050

5151
return (
5252
<svg
53+
id={id}
5354
className={clsx(`${prefixCls}-line`, className)}
5455
viewBox={viewBoxString}
5556
preserveAspectRatio="none"
5657
style={style}
58+
role="presentation"
5759
{...restProps}
5860
>
5961
<path

‎src/interface.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
export type SemanticName = 'root' | 'rail' | 'track';
22

3-
export interface ProgressProps {
3+
export interface ProgressProps extends Omit<
4+
React.SVGAttributes<SVGSVGElement>,
5+
'strokeWidth' | 'strokeLinecap'
6+
> {
47
id?: string;
8+
/** Component geometry width, rather than the root SVG stroke-width attribute. */
59
strokeWidth?: number;
610
railWidth?: number;
711
className?: string;
@@ -10,6 +14,7 @@ export interface ProgressProps {
1014
percent?: number | number[];
1115
strokeColor?: StrokeColorType;
1216
railColor?: string;
17+
/** Component path line cap, rather than the root SVG stroke-linecap attribute. */
1318
strokeLinecap?: StrokeLinecapType;
1419
prefixCls?: string;
1520
style?: React.CSSProperties;

‎tests/__snapshots__/index.spec.js.snap‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ exports[`Progress Diff Line should match snapshot 1`] = `
321321
<svg
322322
class="rc-progress-line"
323323
preserveAspectRatio="none"
324+
role="presentation"
324325
viewBox="0 0 100 1"
325326
>
326327
<path
@@ -347,6 +348,7 @@ exports[`Progress Diff Line should match snapshot 1`] = `
347348
<svg
348349
class="rc-progress-line"
349350
preserveAspectRatio="none"
351+
role="presentation"
350352
viewBox="0 0 100 1"
351353
>
352354
<path
@@ -373,6 +375,7 @@ exports[`Progress Diff Line should match snapshot 1`] = `
373375
<svg
374376
class="rc-progress-line"
375377
preserveAspectRatio="none"
378+
role="presentation"
376379
viewBox="0 0 100 1"
377380
>
378381
<path
@@ -402,6 +405,7 @@ exports[`Progress Line change with animation 1`] = `
402405
<svg
403406
class="rc-progress-line"
404407
preserveAspectRatio="none"
408+
role="presentation"
405409
viewBox="0 0 100 1"
406410
>
407411
<path
@@ -430,6 +434,7 @@ exports[`Progress Line change with animation 2`] = `
430434
<svg
431435
class="rc-progress-line"
432436
preserveAspectRatio="none"
437+
role="presentation"
433438
viewBox="0 0 100 1"
434439
>
435440
<path

‎tests/semantic.spec.tsx‎

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,54 @@
11
import React from 'react';
22
import { render } from '@testing-library/react';
3-
import { Circle, type ProgressProps } from '../src';
3+
import { Circle, Line, type ProgressProps } from '../src';
44

55
describe('Semantic', () => {
6+
it('hides decorative SVGs and supports explicit progress semantics', () => {
7+
const { container } = render(
8+
<>
9+
<Line percent={25} />
10+
<Circle
11+
percent={50}
12+
role="progressbar"
13+
aria-label="Upload progress"
14+
aria-valuemin={0}
15+
aria-valuemax={100}
16+
aria-valuenow={50}
17+
/>
18+
</>,
19+
);
20+
21+
const [line, circle] = container.querySelectorAll('svg');
22+
expect(line).toHaveAttribute('role', 'presentation');
23+
expect(circle).toHaveAttribute('role', 'progressbar');
24+
expect(circle).toHaveAccessibleName('Upload progress');
25+
expect(circle).toHaveAttribute('aria-valuemin', '0');
26+
expect(circle).toHaveAttribute('aria-valuemax', '100');
27+
expect(circle).toHaveAttribute('aria-valuenow', '50');
28+
});
29+
30+
it('supports explicit progress semantics for lines', () => {
31+
const { container } = render(
32+
<Line
33+
id="download-progress"
34+
percent={25}
35+
role="progressbar"
36+
aria-label="Download progress"
37+
aria-valuemin={0}
38+
aria-valuemax={100}
39+
aria-valuenow={25}
40+
/>,
41+
);
42+
43+
const line = container.querySelector('svg');
44+
expect(line).toHaveAttribute('id', 'download-progress');
45+
expect(line).toHaveAttribute('role', 'progressbar');
46+
expect(line).toHaveAccessibleName('Download progress');
47+
expect(line).toHaveAttribute('aria-valuemin', '0');
48+
expect(line).toHaveAttribute('aria-valuemax', '100');
49+
expect(line).toHaveAttribute('aria-valuenow', '25');
50+
});
51+
652
describe('Circle', () => {
753
function test(
854
name: string,

0 commit comments

Comments
 (0)