浏览代码

feat(a11y): progress aria #205 (#414)

* feat(a11y): progress aria #205

* chore: lint code of progress
pointhalo 3 年之前
父节点
当前提交
84c1989cac

+ 120 - 66
content/feedback/progress/index-en-US.md

@@ -2,14 +2,13 @@
 localeCode: en-US
 order: 63
 category: Feedback
-title:  Progress
+title: Progress
 subTitle: Progress
 icon: doc-progress
 width: 60%
 brief: Show the current progress of the operation.
 ---
 
-
 ## When to use
 
 Display the current progress and state of the operation for the user when the operation takes a long time to complete
@@ -21,11 +20,13 @@ Display the current progress and state of the operation for the user when the op
 ```jsx
 import { Progress } from '@douyinfe/semi-ui';
 ```
+
 ### Standard progress bar
 
 Use `stroke` Property to control the filling color of the progress bar  
 Use `Percent` Property to control completed progress  
 Use `size` Property control progress bar size  
+Use `aria-label` Property to explain the specific role  
 If the preset size is not satisfied, You can pass height to customize the height of the progress bar through `style` property.
 
 ```jsx live=true
@@ -34,25 +35,24 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <div style={{ width: 200 }}>
-        <Progress percent={10} stroke='#fc8800' />
-        <br/>
-        <Progress percent={25} stroke='#f93920' />
-        <br/>
-        <Progress percent={50} />
-        <br/>
-        <Progress percent={80} />
-        <br/>
-        <Progress percent={80} size='large' />
-        <br/>
-        <Progress percent={80} style={{ height: '8px' }}/>
+        <Progress percent={10} stroke="#fc8800" aria-label="disk usage"/>
+        <br />
+        <Progress percent={25} stroke="#f93920" aria-label="disk usage"/>
+        <br />
+        <Progress percent={50} aria-label="disk usage"/>
+        <br />
+        <Progress percent={80} aria-label="disk usage"/>
+        <br />
+        <Progress percent={80} size="large" aria-label="disk usage"/>
+        <br />
+        <Progress percent={80} style={{ height: '8px' }} aria-label="disk usage"/>
     </div>
 );
 ```
 
 ### Show percentage text
 
-You can control whether to show percentage number through the `showInfo` property
-In addition, you can format the percentage text show through `format`.
+You can control whether to show percentage number through the `showInfo` property In addition, you can format the percentage text show through `format`.
 
 ```jsx live=true
 import React from 'react';
@@ -60,21 +60,20 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <div style={{ width: 200 }}>
-        <Progress percent={10} stroke='#fc8800' showInfo={true}/>
-        <br/>
-        <Progress percent={25} stroke='#f93920' showInfo={true}/>
-        <br/>
-        <Progress percent={50} showInfo={true}/>
-        <br/>
-        <Progress percent={50} showInfo={true} format={percent => (percent*10) + '‰'}/>
+        <Progress percent={10} stroke="#fc8800" showInfo={true} aria-label="disk usage"/>
+        <br />
+        <Progress percent={25} stroke="#f93920" showInfo={true} aria-label="disk usage"/>
+        <br />
+        <Progress percent={50} showInfo={true} aria-label="disk usage"/>
+        <br />
+        <Progress percent={50} showInfo={true} format={percent => percent * 10 + '‰'} aria-label="disk usage"/>
     </div>
 );
 ```
 
 ### Vertical progress bar
 
-You can use vertical progress bar by setting `direction='vertical'`
-If preset width is not satisfied, you can pass width to customize the width of the vertical progress bar through `style` property.
+You can use vertical progress bar by setting `direction='vertical'` If preset width is not satisfied, you can pass width to customize the width of the vertical progress bar through `style` property.
 
 ```jsx live=true
 import React from 'react';
@@ -82,11 +81,11 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <div style={{ height: 100, display: 'flex' }}>
-        <Progress percent={10} direction='vertical'/>
-        <Progress percent={25} direction='vertical' />
-        <Progress percent={50} direction='vertical' />
-        <Progress percent={80} direction='vertical' size='large' />
-        <Progress percent={80} direction='vertical' style={{ width: '8px' }}/>
+        <Progress percent={10} direction="vertical" aria-label="disk usage"/>
+        <Progress percent={25} direction="vertical" aria-label="disk usage"/>
+        <Progress percent={50} direction="vertical" aria-label="disk usage"/>
+        <Progress percent={80} direction="vertical" size="large" aria-label="disk usage"/>
+        <Progress percent={80} direction="vertical" style={{ width: '8px' }} aria-label="disk usage"/>
     </div>
 );
 ```
@@ -101,10 +100,10 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Progress percent={10} type='circle' style={{ margin: 5 }} />
-        <Progress percent={25} type='circle' style={{ margin: 5 }} />
-        <Progress percent={50} type='circle' style={{ margin: 5 }} />
-        <Progress percent={80} type='circle' style={{ margin: 5 }} />
+        <Progress percent={10} type="circle" style={{ margin: 5 }} aria-label="disk usage"/>
+        <Progress percent={25} type="circle" style={{ margin: 5 }} aria-label="disk usage"/>
+        <Progress percent={50} type="circle" style={{ margin: 5 }} aria-label="disk usage"/>
+        <Progress percent={80} type="circle" style={{ margin: 5 }} aria-label="disk usage"/>
     </div>
 );
 ```
@@ -118,10 +117,10 @@ import { Progress } from '@douyinfe/semi-ui';
 () => (
     <React.Fragment>
         <div>
-            <Progress percent={100} type='circle' width={100} style={{ margin: 5 }} />
+            <Progress percent={100} type="circle" width={100} style={{ margin: 5 }} aria-label="disk usage"/>
         </div>
         <div>
-            <Progress percent={100} type='circle' width={100} style={{ margin: 5 }} stroke='#f93920' />
+            <Progress percent={100} type="circle" width={100} style={{ margin: 5 }} stroke="#f93920" aria-label="disk usage"/>
         </div>
     </React.Fragment>
 );
@@ -137,10 +136,10 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <React.Fragment>
-        <Progress percent={10} type='circle' size='small' style={{ margin: 5 }} />
-        <Progress percent={25} type='circle' size='small' style={{ margin: 5 }} />
-        <Progress percent={50} type='circle' size='small' style={{ margin: 5 }} />
-        <Progress percent={80} type='circle' size='small' style={{ margin: 5 }} />
+        <Progress percent={10} type="circle" size="small" style={{ margin: 5 }} aria-label="disk usage"/>
+        <Progress percent={25} type="circle" size="small" style={{ margin: 5 }} aria-label="disk usage"/>
+        <Progress percent={50} type="circle" size="small" style={{ margin: 5 }} aria-label="disk usage"/>
+        <Progress percent={80} type="circle" size="small" style={{ margin: 5 }} aria-label="disk usage"/>
     </React.Fragment>
 );
 ```
@@ -157,11 +156,24 @@ import { IconChevronLeft, IconChevronRight } from '@douyinfe/semi-icons';
     return (
         <>
             <div>
-                <Progress percent={percent} showInfo/>
-                <Button icon={<IconChevronLeft />} theme="light" onClick={()=> {setPercent(percent - 10);}} disabled={percent === 0} />
-                <Button icon={<IconChevronRight />} theme="light" onClick={()=> {setPercent(percent + 10);}} disabled={percent >=100 } />
+                <Progress percent={percent} showInfo />
+                <Button
+                    icon={<IconChevronLeft />}
+                    theme="light"
+                    onClick={() => {
+                        setPercent(percent - 10);
+                    }}
+                    disabled={percent === 0}
+                />
+                <Button
+                    icon={<IconChevronRight />}
+                    theme="light"
+                    onClick={() => {
+                        setPercent(percent + 10);
+                    }}
+                    disabled={percent >= 100}
+                />
             </div>
-
         </>
     );
 };
@@ -176,9 +188,25 @@ import { IconChevronLeft, IconChevronRight } from '@douyinfe/semi-icons';
     const [cirPerc, setCirPerc] = useState(40);
     return (
         <div>
-            <div><Progress percent={cirPerc} type='circle'/></div>
-            <Button icon={<IconChevronLeft />} theme="light" onClick={()=> {setCirPerc(cirPerc - 10);}} disabled={cirPerc === 0}/>
-            <Button icon={<IconChevronRight />} theme="light" onClick={()=> {setCirPerc(cirPerc + 10);}} disabled={cirPerc >=100 }/>
+            <div>
+                <Progress percent={cirPerc} type="circle" aria-label="disk usage"/>
+            </div>
+            <Button
+                icon={<IconChevronLeft />}
+                theme="light"
+                onClick={() => {
+                    setCirPerc(cirPerc - 10);
+                }}
+                disabled={cirPerc === 0}
+            />
+            <Button
+                icon={<IconChevronRight />}
+                theme="light"
+                onClick={() => {
+                    setCirPerc(cirPerc + 10);
+                }}
+                disabled={cirPerc >= 100}
+            />
         </div>
     );
 };
@@ -195,9 +223,9 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <React.Fragment>
-        <Progress percent={75} showInfo type='circle' format={(per) => per + 'Days'} style={{ margin:10 }}/>
-        <Progress percent={100} showInfo type='circle' format={(per) => 'Done'} style={{ margin:10 }}/>
-        <Progress percent={50} type='circle' showInfo={false} style={{ margin:10 }}/>
+        <Progress percent={75} showInfo type="circle" format={per => per + 'Days'} style={{ margin: 10 }} aria-label="disk usage"/>
+        <Progress percent={100} showInfo type="circle" format={per => 'Done'} style={{ margin: 10 }} aria-label="disk usage"/>
+        <Progress percent={50} type="circle" showInfo={false} style={{ margin: 10 }} aria-label="disk usage"/>
     </React.Fragment>
 );
 ```
@@ -212,8 +240,8 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <React.Fragment>
-        <Progress percent={50} strokeLinecap='round' type='circle' style={{ margin: 10 }} />
-        <Progress percent={50} strokeLinecap='square' type='circle' style={{ margin:10 }} />
+        <Progress percent={50} strokeLinecap="round" type="circle" style={{ margin: 10 }} aria-label="disk usage"/>
+        <Progress percent={50} strokeLinecap="square" type="circle" style={{ margin: 10 }} aria-label="disk usage"/>
     </React.Fragment>
 );
 ```
@@ -221,20 +249,46 @@ import { Progress } from '@douyinfe/semi-ui';
 ## API Reference
 
 | PROPERTIES | Instructions | Type | Default |
-|--- | --- | --- | --- |
-|className | style class name | string | |
-|direction | The direction of the bar progress bar `horizontal`, `vertical` | string |'horizontal' |
-|format | Formatting function, the input parameter is the current percentage, the result of return will be directly rendered in the center of the circular progress bar | (percent: number) => ReactNode | (percent) => percent +'%' |
-|orbitStroke | Progress bar track fill color<br/>**provided after v1.0.0** | string |'var(--semi-color-fill-0)' |
-|percent | percentage of progress | number | |
-|showInfo | Whether to display the middle text in the circular progress bar, and whether to display the text on the right side of the bar-shaped progress bar | boolean | false |
-|size | size, optional `default`, `small` (only type=circle is effective), `large` (only type=line is effective) | string |'default' |
-|stroke | Fill color of progress bar | string |'var(--semi-color-success)' |
-|strokeLinecap | round corner `round`/square corner `square` (only effective in type='circle' mode) | string |'round' |
-|strokeWidth | When type is `line`, this property controls the height of the progress bar; when type is `circle`, this property controls the width of the progress bar | number | 4 |
-|style | style | CSSProperties | |
-|type | type, optional `line`, `circle` | string |'line' |
-|width | Width of circular progress bar | number | 72 when size='default', 24 for 'small' |
-
+| --- | --- | --- | --- |
+| aria-label | [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute) attribute. Used to add a label description to the current element to improve a11y<br/>**provided after v2.2.0** | string |  |
+| aria-labelledby | [aria-labelledby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute) attribute. Indicates that the id of some element is the label of the current element. It is used to determine the connection between controls or control groups and their labels, to improve a11y<br/>**provided after v2.2.0** | string |  |  |
+| aria-valuetext | [aria-labelledby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute) attribute. Used to improve a11y<br/>**provided after v2.2.0** | string |  |  |
+| className | style class name | string |  |
+| direction | The direction of the bar progress bar `horizontal`, `vertical` | string | 'horizontal' |
+| id | id attribute <br/>**provided after v2.2.0** | string |  |
+| format | Formatting function, the input parameter is the current percentage, the result of return will be directly rendered in the center of the circular progress bar | (percent: number) => ReactNode | (percent) => percent +'%' |
+| orbitStroke | Progress bar track fill color<br/>**provided after v1.0.0** | string | 'var(--semi-color-fill-0)' |
+| percent | percentage of progress | number |  |
+| showInfo | Whether to display the middle text in the circular progress bar, and whether to display the text on the right side of the bar-shaped progress bar | boolean | false |
+| size | size, optional `default`, `small` (only type=circle is effective), `large` (only type=line is effective) | string | 'default' |
+| stroke | Fill color of progress bar | string | 'var(--semi-color-success)' |
+| strokeLinecap | round corner `round`/square corner `square` (only effective in type='circle' mode) | string | 'round' |
+| strokeWidth | When type is `line`, this property controls the height of the progress bar; when type is `circle`, this property controls the width of the progress bar | number | 4 |
+| style | style | CSSProperties |  |
+| type | type, optional `line`, `circle` | string | 'line' |
+| width | Width of circular progress bar | number | 72 when size='default', 24 for 'small' |
+
+## Accessibility
+### Aria
+
+-   Progress has a `progressbar` role to indicate that it is a progress bar component.
+-   Progress will automatically set `aria-valuenow` as the progress percentage (`percent`) passed to the component to ensure that the screen reader can get the correct percentage value. In addition, Progress supports incoming `aria-valuetext`. When you pass in, according to W3C specifications, `aria-valuetext` will be used and consumed by screen readers instead of `aria-valuenow`
+-   Progress support `aria-label`, `aria-labelledby`
+    -   When there is a description element about the role of Progress outside of Progress, you can explicitly specify that the id of certain elements is the label of Progress through `aria-labelledby`
+    -   Otherwise, you should use aria-label to explain the specific meaning of the value represented by Progress
+
+```js
+// good case
+<p id="progressbar-label">Disk Usage</p>
+<Progress aria-labelledby="progressbar-label" percent={80} />
+
+// good case
+<Progress aria-label='Percent of disk usage' percent={80} />
+<Progress aria-label='Percent of file downloaded' percent={80} />
+
+// usage of aria-valuetext
+<Progress aria-label='Percent of disk usage' percent={80} aria-valuetext="Step 2: Copying files... "/> 
+```
 ## Design Tokens
+
 <DesignToken/>

+ 96 - 47
content/feedback/progress/index.md

@@ -25,6 +25,7 @@ import { Progress } from '@douyinfe/semi-ui';
 通过`stroke`属性来控制进度条的填充色  
 通过`percent`属性控制已完成的进度  
 通过`size`属性控制进度条尺寸  
+通过`aria-label`说明进度条具体代表含义  
 如果`size`预设的尺寸不满足,可以通过`style`传入 height 自定义进度条高度
 
 ```jsx live=true
@@ -33,17 +34,17 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <div style={{ width: 200 }}>
-        <Progress percent={10} stroke="#fc8800" />
+        <Progress percent={10} stroke="#fc8800" aria-label="disk usage" />
         <br />
-        <Progress percent={25} stroke="#f93920" />
+        <Progress percent={25} stroke="#f93920" aria-label="download progress" />
         <br />
-        <Progress percent={50} />
+        <Progress percent={50} aria-label="disk usage" />
         <br />
-        <Progress percent={80} />
+        <Progress percent={80} aria-label="download progress" />
         <br />
-        <Progress percent={80} size="large" />
+        <Progress percent={80} size="large" aria-label="disk usage" />
         <br />
-        <Progress percent={80} style={{ height: '8px' }} />
+        <Progress percent={80} style={{ height: '8px' }} aria-label="disk usage" />
     </div>
 );
 ```
@@ -58,13 +59,13 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <div style={{ width: 200 }}>
-        <Progress percent={10} stroke="#fc8800" showInfo={true} />
+        <Progress percent={10} stroke="#fc8800" showInfo={true} aria-label="disk usage" />
         <br />
-        <Progress percent={25} stroke="#f93920" showInfo={true} />
+        <Progress percent={25} stroke="#f93920" showInfo={true} aria-label="disk usage" />
         <br />
-        <Progress percent={50} showInfo={true} />
+        <Progress percent={50} showInfo={true} aria-label="disk usage" />
         <br />
-        <Progress percent={50} showInfo={true} format={percent => percent * 10 + '‰'} />
+        <Progress percent={50} showInfo={true} format={percent => percent * 10 + '‰'} aria-label="disk usage" />
     </div>
 );
 ```
@@ -79,11 +80,11 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <div style={{ height: 100, display: 'flex' }}>
-        <Progress percent={10} direction="vertical" />
-        <Progress percent={25} direction="vertical" />
-        <Progress percent={50} direction="vertical" />
-        <Progress percent={80} direction="vertical" size="large" />
-        <Progress percent={80} direction="vertical" style={{ width: '8px' }} />
+        <Progress percent={10} direction="vertical" aria-label="disk usage" />
+        <Progress percent={25} direction="vertical" aria-label="disk usage" />
+        <Progress percent={50} direction="vertical" aria-label="disk usage" />
+        <Progress percent={80} direction="vertical" size="large" aria-label="disk usage" />
+        <Progress percent={80} direction="vertical" style={{ width: '8px' }} aria-label="disk usage" />
     </div>
 );
 ```
@@ -98,10 +99,10 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <div>
-        <Progress percent={10} type="circle" style={{ margin: 5 }} />
-        <Progress percent={25} type="circle" style={{ margin: 5 }} />
-        <Progress percent={50} type="circle" style={{ margin: 5 }} />
-        <Progress percent={80} type="circle" style={{ margin: 5 }} />
+        <Progress percent={10} type="circle" style={{ margin: 5 }} aria-label="disk usage" />
+        <Progress percent={25} type="circle" style={{ margin: 5 }} aria-label="disk usage" />
+        <Progress percent={50} type="circle" style={{ margin: 5 }} aria-label="disk usage" />
+        <Progress percent={80} type="circle" style={{ margin: 5 }} aria-label="disk usage" />
     </div>
 );
 ```
@@ -115,10 +116,17 @@ import { Progress } from '@douyinfe/semi-ui';
 () => (
     <React.Fragment>
         <div>
-            <Progress percent={100} type="circle" width={100} style={{ margin: 5 }} />
+            <Progress percent={100} type="circle" width={100} style={{ margin: 5 }} aria-label="disk usage" />
         </div>
         <div>
-            <Progress percent={100} type="circle" width={100} style={{ margin: 5 }} stroke="#f93920" />
+            <Progress
+                percent={100}
+                type="circle"
+                width={100}
+                style={{ margin: 5 }}
+                stroke="#f93920"
+                aria-label="disk usage"
+            />
         </div>
     </React.Fragment>
 );
@@ -134,10 +142,10 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <React.Fragment>
-        <Progress percent={10} type="circle" size="small" style={{ margin: 5 }} />
-        <Progress percent={25} type="circle" size="small" style={{ margin: 5 }} />
-        <Progress percent={50} type="circle" size="small" style={{ margin: 5 }} />
-        <Progress percent={80} type="circle" size="small" style={{ margin: 5 }} />
+        <Progress percent={10} type="circle" size="small" style={{ margin: 5 }} aria-label="disk usage" />
+        <Progress percent={25} type="circle" size="small" style={{ margin: 5 }} aria-label="disk usage" />
+        <Progress percent={50} type="circle" size="small" style={{ margin: 5 }} aria-label="disk usage" />
+        <Progress percent={80} type="circle" size="small" style={{ margin: 5 }} aria-label="disk usage" />
     </React.Fragment>
 );
 ```
@@ -154,7 +162,7 @@ import { IconChevronLeft, IconChevronRight } from '@douyinfe/semi-icons';
     return (
         <>
             <div>
-                <Progress percent={percent} showInfo />
+                <Progress percent={percent} showInfo aria-label="disk usage" />
                 <Button
                     icon={<IconChevronLeft />}
                     theme="light"
@@ -187,7 +195,7 @@ import { IconChevronLeft, IconChevronRight } from '@douyinfe/semi-icons';
     return (
         <div>
             <div>
-                <Progress percent={cirPerc} type="circle" />
+                <Progress percent={cirPerc} type="circle" aria-label="disk usage" />
             </div>
             <Button
                 icon={<IconChevronLeft />}
@@ -221,9 +229,23 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <React.Fragment>
-        <Progress percent={75} showInfo type="circle" format={per => per + 'Days'} style={{ margin: 10 }} />
-        <Progress percent={100} showInfo type="circle" format={per => 'Done'} style={{ margin: 10 }} />
-        <Progress percent={50} type="circle" showInfo={false} style={{ margin: 10 }} />
+        <Progress
+            percent={75}
+            showInfo
+            type="circle"
+            format={per => per + 'Days'}
+            style={{ margin: 10 }}
+            aria-label="disk usage"
+        />
+        <Progress
+            percent={100}
+            showInfo
+            type="circle"
+            format={per => 'Done'}
+            style={{ margin: 10 }}
+            aria-label="disk usage"
+        />
+        <Progress percent={50} type="circle" showInfo={false} style={{ margin: 10 }} aria-label="disk usage" />
     </React.Fragment>
 );
 ```
@@ -238,29 +260,56 @@ import { Progress } from '@douyinfe/semi-ui';
 
 () => (
     <React.Fragment>
-        <Progress percent={50} strokeLinecap="round" type="circle" style={{ margin: 10 }} />
-        <Progress percent={50} strokeLinecap="square" type="circle" style={{ margin: 10 }} />
+        <Progress percent={50} strokeLinecap="round" type="circle" style={{ margin: 10 }} aria-label="disk usage" />
+        <Progress percent={50} strokeLinecap="square" type="circle" style={{ margin: 10 }} aria-label="disk usage" />
     </React.Fragment>
 );
 ```
 
 ## API 参考
 
-|属性 | 说明 | 类型 | 默认值 |
-|--- | --- | --- | --- |
-|className | 样式类名 | string |  |
-|direction | 条状进度条方向 `horizontal`、`vertical` | string | 'horizontal' |
-|format | 格式化函数,入参为当前百分比,return 的结果将会直接渲染在圆形进度条中心 | (percent: number) => ReactNode | (percent) => percent + '%' |
-|orbitStroke | 进度条轨道填充色<br/>**v1.0.0 后提供** | string | 'var(--semi-color-fill-0)' |
-|percent | 进度百分比 | number |  |
-|showInfo | 环形进度条是否显示中间文本,条状进度条后右侧是否显示文本 | boolean | false |
-|size | 尺寸,可选`default`、`small`(仅 type=circle 生效)、`large`(仅 type=line 生效) | string | 'default' |
-|stroke | 进度条填充色 | string | 'var(--semi-color-success)' |
-|strokeLinecap | 圆角`round`/方角`square`(仅在 type='circle'模式下生效) | string | 'round' |
-|strokeWidth | type 为`line`时,该属性控制进度条高度; type 为`circle`时,该属性控制进度条宽度 | number | 4 |
-|style | 样式 | CSSProperties |  |
-|type | 类型,可选`line`、`circle` | string | 'line' |
-|width | 环形进度条宽度 | number | size='default'时为 72,'small'为 24 |
+| 属性 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| aria-label | [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute)属性,用来给当前元素加上的标签描述, 用于提升可访问性<br/>**v2.2.0 后提供** | string |  |
+| aria-labelledby | [aria-labelledby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute)属性,表明某些元素的 id 是当前元素的标签。它被用来确定控件或控件组与它们标签之间的联系, 提升可访问性<br/>**v2.2.0 后提供** | string |  |  |
+| aria-valuetext | [aria-valuetext](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-valuetext_attribute)属性,用于提升可访问性<br/>**v2.2.0 后提供** | string |  |  |
+| className | 样式类名 | string |  |
+| direction | 条状进度条方向 `horizontal`、`vertical` | string | 'horizontal' |
+| format | 格式化函数,入参为当前百分比,return 的结果将会直接渲染在圆形进度条中心 | (percent: number) => ReactNode | (percent) => percent + '%' |
+| id | id 标识<br/>**v2.2.0 后提供** | string |  |
+| orbitStroke | 进度条轨道填充色<br/>**v1.0.0 后提供** | string | 'var(--semi-color-fill-0)' |
+| percent | 进度百分比 | number |  |
+| showInfo | 环形进度条是否显示中间文本,条状进度条后右侧是否显示文本 | boolean | false |
+| size | 尺寸,可选`default`、`small`(仅 type=circle 生效)、`large`(仅 type=line 生效) | string | 'default' |
+| stroke | 进度条填充色 | string | 'var(--semi-color-success)' |
+| strokeLinecap | 圆角`round`/方角`square`(仅在 type='circle'模式下生效) | string | 'round' |
+| strokeWidth | type 为`line`时,该属性控制进度条高度; type 为`circle`时,该属性控制进度条宽度 | number | 4 |
+| style | 样式 | CSSProperties |  |
+| type | 类型,可选`line`、`circle` | string | 'line' |
+| width | 环形进度条宽度 | number | size='default'时为 72,'small'为 24 |
+
+## Accessibility
+
+### Aria
+
+-   Progress 具有 `progressbar` role 来表示它是一个进度条组件。
+-   Progress 会自动将 `aria-valuenow` 设置为传递给组件的进度百分比(`percent`),以确保屏幕阅读器可以获取正确的百分比数值。另外,Progress 支持传入 `aria-valuetext`,当你传入时,根据 W3C 规范,`aria-valuetext` 将优先被屏幕阅读器使用消费,而不是 `aria-valuenow`
+-   Progress 支持传入 `aria-label`、`aria-labelledby`
+    -   当 Progress 外部存在关于 Progress 作用的描述元素时,你可以通过 aria-labelledby 显式指定某些元素的 id 是 Progress 的标签
+    -   否则你应当通过 aria-label 说明 Progress 所代表的具体数值含义
+
+```js
+// good case
+<p id="progressbar-label">Disk Usage</p>
+<Progress aria-labelledby="progressbar-label" percent={80} />
+
+// good case
+<Progress aria-label='Percent of disk usage' percent={80} />
+<Progress aria-label='Percent of file downloaded' percent={80} />
+
+// usage of aria-valuetext
+<Progress aria-label='Percent of disk usage' percent={80} aria-valuetext="Step 2: Copying files... "/> 
+```
 
 ## 设计变量
 

+ 18 - 18
packages/semi-ui/progress/_story/progress.stories.js

@@ -8,9 +8,9 @@ export default {
 export const _Progress = () => (
   <div style={{ width: 200 }}>
     {/* <Progress percent={10} style= {{ height: 10 }}/> */}
-    <Progress percent={25} />
-    <Progress percent={50} />
-    <Progress percent={80} />
+    <Progress percent={25} aria-label="disk usage"/>
+    <Progress percent={50} aria-label="disk usage"/>
+    <Progress percent={80} aria-label="disk usage"/>
   </div>
 );
 
@@ -20,10 +20,10 @@ _Progress.story = {
 
 export const Vertical = () => (
   <div style={{ height: 200 }}>
-    <Progress percent={10} direction="vertical" style={{ width: 10 }} />
-    <Progress percent={25} direction="vertical" />
-    <Progress percent={50} direction="vertical" />
-    <Progress percent={80} direction="vertical" />
+    <Progress percent={10} direction="vertical" style={{ width: 10 }} aria-label="disk usage"/>
+    <Progress percent={25} direction="vertical" aria-label="disk usage"/>
+    <Progress percent={50} direction="vertical" aria-label="disk usage"/>
+    <Progress percent={80} direction="vertical" aria-label="disk usage"/>
   </div>
 );
 
@@ -33,10 +33,10 @@ Vertical.story = {
 
 export const CircleProgress = () => (
   <React.Fragment>
-    <Progress percent={10} type="circle" />
-    <Progress percent={25} type="circle" />
-    <Progress percent={50} type="circle" />
-    <Progress percent={80} type="circle" />
+    <Progress percent={10} type="circle" aria-label="disk usage"/>
+    <Progress percent={25} type="circle" aria-label="disk usage"/>
+    <Progress percent={50} type="circle" aria-label="disk usage"/>
+    <Progress percent={80} type="circle" aria-label="disk usage"/>
   </React.Fragment>
 );
 
@@ -46,10 +46,10 @@ CircleProgress.story = {
 
 export const CircleProgressSmall = () => (
   <React.Fragment>
-    <Progress percent={10} type="circle" size="small" />
-    <Progress percent={25} type="circle" size="small" />
-    <Progress percent={50} type="circle" size="small" />
-    <Progress percent={80} type="circle" size="small" />
+    <Progress percent={10} type="circle" size="small" aria-label="disk usage"/>
+    <Progress percent={25} type="circle" size="small" aria-label="disk usage"/>
+    <Progress percent={50} type="circle" size="small" aria-label="disk usage"/>
+    <Progress percent={80} type="circle" size="small" aria-label="disk usage"/>
   </React.Fragment>
 );
 
@@ -60,9 +60,9 @@ CircleProgressSmall.story = {
 export const ProgressShowInfo = () => (
   <div style={{ width: 200 }}>
     {/* <Progress percent={10} style= {{ height: 10 }}/> */}
-    <Progress percent={25} showInfo />
-    <Progress percent={50} showInfo />
-    <Progress percent={80} showInfo />
+    <Progress percent={25} showInfo aria-label="disk usage"/>
+    <Progress percent={50} showInfo aria-label="disk usage"/>
+    <Progress percent={80} showInfo aria-label="disk usage"/>
   </div>
 );
 

+ 58 - 20
packages/semi-ui/progress/index.tsx

@@ -9,9 +9,13 @@ import { Motion } from '../_base/base';
 const prefixCls = cssClasses.PREFIX;
 
 export interface ProgressProps {
+    'aria-label'?: string | undefined;
+    'aria-labelledby'?: string | undefined;
+    'aria-valuetext'?: string | undefined;
     className?: string;
     direction?: 'horizontal' | 'vertical';
     format?: (percent: number) => React.ReactNode;
+    id?: string;
     motion?: Motion;
     orbitStroke?: string;
     percent?: number;
@@ -31,9 +35,13 @@ export interface ProgressState {
 
 class Progress extends Component<ProgressProps, ProgressState> {
     static propTypes = {
+        'aria-label': PropTypes.string,
+        'aria-labelledby': PropTypes.string,
+        'aria-valuetext': PropTypes.string,
         className: PropTypes.string,
         direction: PropTypes.oneOf(strings.directions),
         format: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
+        id: PropTypes.string,
         motion: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.object]),
         orbitStroke: PropTypes.string,
         percent: PropTypes.number,
@@ -51,7 +59,7 @@ class Progress extends Component<ProgressProps, ProgressState> {
     static defaultProps = {
         className: '',
         direction: strings.DEFAULT_DIRECTION,
-        format: (text: string): string => `${text }%`,
+        format: (text: string): string => `${text}%`,
         motion: true,
         orbitStroke: 'var(--semi-color-fill-0)',
         percent: 0,
@@ -124,12 +132,15 @@ class Progress extends Component<ProgressProps, ProgressState> {
     }
 
     renderCircleProgress(): ReactNode {
-        const { strokeLinecap, style, className, strokeWidth, format, size, stroke, showInfo, percent, orbitStroke } = this.props;
+        const { strokeLinecap, style, className, strokeWidth, format, size, stroke, showInfo, percent, orbitStroke, id } = this.props;
+        const ariaLabel = this.props['aria-label'];
+        const ariaLabelledBy = this.props['aria-labelledby'];
+        const ariaValueText = this.props['aria-valuetext'];
         const { percentNumber } = this.state;
         const classNames = {
-            wrapper: cls(`${prefixCls }-circle`, className),
-            svg: cls(`${prefixCls }-circle-ring`),
-            circle: cls(`${prefixCls }-circle-ring-inner`)
+            wrapper: cls(`${prefixCls}-circle`, className),
+            svg: cls(`${prefixCls}-circle-ring`),
+            circle: cls(`${prefixCls}-circle-ring-inner`)
         };
         const perc = this.calcPercent(percent);
         const percNumber = this.calcPercent(percentNumber);
@@ -152,8 +163,19 @@ class Progress extends Component<ProgressProps, ProgressState> {
         const text = format(percNumber);
 
         return (
-            <div className={classNames.wrapper} style={style}>
-                <svg key={size} className={classNames.svg} height={width} width={width}>
+            <div
+                id={id}
+                className={classNames.wrapper} 
+                style={style} 
+                role='progressbar' 
+                aria-valuemin={0} 
+                aria-valuemax={100}
+                aria-valuenow={percNumber}
+                aria-labelledby={ariaLabelledBy}
+                aria-label={ariaLabel}
+                aria-valuetext={ariaValueText}
+            >
+                <svg key={size} className={classNames.svg} height={width} width={width} aria-hidden>
                     <circle
                         strokeDashoffset={0}
                         strokeWidth={strokeWidth}
@@ -164,6 +186,7 @@ class Progress extends Component<ProgressProps, ProgressState> {
                         r={radius}
                         cx={cx}
                         cy={cy}
+                        aria-hidden
                     />
                     <circle
                         className={classNames.circle}
@@ -176,9 +199,10 @@ class Progress extends Component<ProgressProps, ProgressState> {
                         r={radius}
                         cx={cx}
                         cy={cy}
+                        aria-hidden
                     />
                 </svg>
-                {showInfo && size !== 'small' ? (<span className={`${prefixCls }-circle-text`}>{text}</span>) : null}
+                {showInfo && size !== 'small' ? (<span className={`${prefixCls}-circle-text`}>{text}</span>) : null}
             </div>
         );
     }
@@ -196,17 +220,20 @@ class Progress extends Component<ProgressProps, ProgressState> {
     }
 
     renderLineProgress(): ReactNode {
-        const { className, style, stroke, direction, format, showInfo, size, percent, orbitStroke } = this.props;
+        const { className, style, stroke, direction, format, showInfo, size, percent, orbitStroke, id } = this.props;
+        const ariaLabel = this.props['aria-label'];
+        const ariaLabelledBy = this.props['aria-labelledby'];
+        const ariaValueText = this.props['aria-valuetext'];
         const { percentNumber } = this.state;
         const progressWrapperCls = cls(prefixCls, className, {
-            [`${prefixCls }-horizontal`]: direction === strings.DEFAULT_DIRECTION,
-            [`${prefixCls }-vertical`]: direction !== strings.DEFAULT_DIRECTION,
-            [`${prefixCls }-large`]: size === 'large',
+            [`${prefixCls}-horizontal`]: direction === strings.DEFAULT_DIRECTION,
+            [`${prefixCls}-vertical`]: direction !== strings.DEFAULT_DIRECTION,
+            [`${prefixCls}-large`]: size === 'large',
         });
         const progressTrackCls = cls({
-            [`${prefixCls }-track`]: true,
+            [`${prefixCls}-track`]: true,
         });
-        const innerCls = cls(`${prefixCls }-track-inner`);
+        const innerCls = cls(`${prefixCls}-track-inner`);
 
         const perc = this.calcPercent(percent);
         const percNumber = this.calcPercent(percentNumber);
@@ -215,19 +242,30 @@ class Progress extends Component<ProgressProps, ProgressState> {
             backgroundColor: stroke
         };
         if (direction === strings.DEFAULT_DIRECTION) {
-            innerStyle.width = `${perc }%`;
+            innerStyle.width = `${perc}%`;
         } else {
-            innerStyle.height = `${perc }%`;
+            innerStyle.height = `${perc}%`;
         }
 
         const text = format(percNumber);
 
         return (
-            <div className={progressWrapperCls} style={style}>
-                <div className={progressTrackCls} style={orbitStroke ? { backgroundColor: orbitStroke } : {}}>
-                    <div className={innerCls} style={innerStyle} />
+            <div
+                id={id}
+                className={progressWrapperCls}
+                style={style}
+                role='progressbar'
+                aria-valuemin={0}
+                aria-valuemax={100}
+                aria-valuenow={perc}
+                aria-labelledby={ariaLabelledBy}
+                aria-label={ariaLabel}
+                aria-valuetext={ariaValueText}
+            >
+                <div className={progressTrackCls} style={orbitStroke ? { backgroundColor: orbitStroke } : {}} aria-hidden>
+                    <div className={innerCls} style={innerStyle} aria-hidden />
                 </div>
-                {showInfo ? <div className={`${prefixCls }-line-text`}>{text}</div> : null}
+                {showInfo ? <div className={`${prefixCls}-line-text`}>{text}</div> : null}
             </div>
         );
     }