1
0
Эх сурвалжийг харах

Feat/a11y input 2 (#906)

* feat: input a11y keyboard #205

* test: add one input keyboard cypress test case

Co-authored-by: shijia.me <[email protected]>
走鹃 3 жил өмнө
parent
commit
d49c76171a

+ 91 - 50
content/input/input/index-en-US.md

@@ -25,7 +25,7 @@ import React from 'react';
 import { Input } from '@douyinfe/semi-ui';
 
 () => (
-    <Input defaultValue='hi' autofocus></Input>
+    <Input defaultValue='hi'></Input>
 );
 ```
 
@@ -387,59 +387,88 @@ Answers to some questions:
 
 > Other attributes are same with html `<input>`
 
-| Property       | Instructions                                                                                  | type                            | Default   |
-|----------------|-----------------------------------------------------------------------------------------------|---------------------------------|-----------|
-| addonAfter     | Addon after input box                                                                         | ReactNode               |           |
-| addonBefore    | Addon before input box                                                                        | ReactNode               |           |
-| className      | Class name                                                                                    | string                          |           |
-| defaultValue   | Default value                                                                                 | ReactText                          |           |
-| disabled       | Toggle whether to disable input                                                               | boolean                         | false     |
-| getValueLength | Custom calculated character string length                                            | (value: string) => number        |      |
-| hideSuffix     | Toggle whether to hide suffix if clear icon is shown,by default the two icon are side by side | boolean                         | false     |
-| mode           | The mode of the input box. The optional value is `password`                                   | string                          |           |
-| mode           | mode of input,optional: `password` **>= v1.3.0**                                              | string                          |           |
-| prefix         | Prefix                                                                                        | ReactNode               |           |
-| showClear      | Show clear button **>=1.0.0**                                                                 | boolean                         | false     |
-| size           | Size, one of `large`, `default`, `small`                                                      | string                          | `default` |
-| style          | Inline style                                                                                  | CSSProperties                          |           |
-| suffix         | Suffix                                                                                        | ReactNode               |           |
-| type           | Input type attribute, same with html `<input>`                                                | string                          | text      |
-| validateStatus | Validate status for styling only, one of `default`, `error`, `warning`                        | string                          | `default` |
-| value          | Current value of input box                                                                    | ReactText                          |           |
-| onBlur         | Callback invoked when input loses focus                                                       | function(e:event)                        |           |
-| onChange       | Callback invoked when input value changes                                                     | function(value:string, e:event) |           |
-| onClear        | Callback invoked when clicking clear icon                                                     | function(e:event)                        |           |
-| onEnterPress   | Callback invoked when pressing enter(keypress)                                                | function(e:event)               |           |
-| onFocus        | Callback invoked when input gets focus                                                        | function(e:event)                        |           |
-| onKeyDown      | Callback invoked when keydown                                                                 | function(e:event)               |           |
-| onKeyPress     | Callback invoked when keypress                                                                | function(e:event)               |           |
-| onKeyUp        | Callback invoked when keyup                                                                   | function(e:event)               |           |
+| Property          | Instructions                                                                                  | type                            | Default   |
+|-------------------|-----------------------------------------------------------------------------------------------|---------------------------------|-----------|
+| aria-describedby  | Set the aria-describedby property                                                             | string                          | -         |
+| aria-errormessage | Set the aria-errormessage property                                                            | string                          | -         |
+| aria-invalid      | Set aria-invalid property                                                                     | string                          | -         |
+| aria-label        | Set aria-label properties                                                                     | string                          | -         |
+| aria-labelledby   | Set aria-labelledby properties                                                                | string                          | -         |
+| aria-required     | Set aria-required properties                                                                  | string                          | -         |
+| addonAfter        | Addon after input box                                                                         | ReactNode                       |           |
+| addonBefore       | Addon before input box                                                                        | ReactNode                       |           |
+| className         | Class name                                                                                    | string                          |           |
+| defaultValue      | Default value                                                                                 | ReactText                       |           |
+| disabled          | Toggle whether to disable input                                                               | boolean                         | false     |
+| getValueLength    | Custom calculated character string length                                                     | (value: string) => number       |           |
+| hideSuffix        | Toggle whether to hide suffix if clear icon is shown,by default the two icon are side by side | boolean                         | false     |
+| mode              | The mode of the input box. The optional value is `password`                                   | string                          |           |
+| mode              | mode of input,optional: `password` **>= v1.3.0**                                              | string                          |           |
+| prefix            | Prefix                                                                                        | ReactNode                       |           |
+| showClear         | Show clear button **>=1.0.0**                                                                 | boolean                         | false     |
+| size              | Size, one of `large`, `default`, `small`                                                      | string                          | `default` |
+| style             | Inline style                                                                                  | CSSProperties                   |           |
+| suffix            | Suffix                                                                                        | ReactNode                       |           |
+| type              | Input type attribute, same with html `<input>`                                                | string                          | text      |
+| validateStatus    | Validate status for styling only, one of `default`, `error`, `warning`                        | string                          | `default` |
+| value             | Current value of input box                                                                    | ReactText                       |           |
+| onBlur            | Callback invoked when input loses focus                                                       | function(e:event)               |           |
+| onChange          | Callback invoked when input value changes                                                     | function(value:string, e:event) |           |
+| onClear           | Callback invoked when clicking clear icon                                                     | function(e:event)               |           |
+| onEnterPress      | Callback invoked when pressing enter(keypress)                                                | function(e:event)               |           |
+| onFocus           | Callback invoked when input gets focus                                                        | function(e:event)               |           |
+| onKeyDown         | Callback invoked when keydown                                                                 | function(e:event)               |           |
+| onKeyPress        | Callback invoked when keypress                                                                | function(e:event)               |           |
+| onKeyUp           | Callback invoked when keyup                                                                   | function(e:event)               |           |
 ### TextArea
 
 > Other attributes are same with html `<textarea>`
 
-| Property     | Instructions                                                                                                           | type                            | Default |
-|--------------|------------------------------------------------------------------------------------------------------------------------|---------------------------------|---------|
-| autosize     | Toggle whether to allow autosize when content height changes                                                           | boolean                         | false   |
-| className    | Class name                                                                                                             | string                          | -       |
-| cols         | The visible width of the text control, in average character widths. If it is specified, it must be a positive integer. | number                          | -       |
-| disabled     | Disabled                                                                                                               | boolean                         | false   |
-| getValueLength | Custom calculated character string length                                            | (value: string) => number        |      |
-| maxCount     | The maximum number of characters and display count                                                                     | number                          | -       |
-| placeholder  | Content to be appear by default                                                                                        | string                          | -       |
-| readonly     | Read-only, not editable                                                                                                | boolean                         | false   |
-| rows         | The number of visible text lines for the control.                                                                      | number                          | 4       |
-| showClear      | Show clear button **>=1.30.0**                                                                 | boolean                         | false     |
-| style        | Inline style                                                                                                           | CSSProperties                          | -       |
-| onBlur       | Callback invoked when input loses focus                                                                                | (e:event) => void               | -       |
-| onChange     | Callback invoked when input value changes                                                                              | (value:string, e:event) => void  |         |
-| onClear      | Callback invoked when clicking clear icon  **>=1.30.0**                                                       | (e:event) => void                        |           |
-| onEnterPress | Callback invoked when pressing enter                                                                                   | (e:event) => void                        | -       |
-| onFocus      | Callback invoked when input gets focus                                                                                 | (e:event) => void               | -       |
-| onKeyDown    | Callback invoked when keydown, html event                                                                              | (e:event) => void               | -       |
-| onKeyPress   | Callback invoked when keypress, html event                                                                             | (e:event) => void               | -       |
-| onKeyUp      | Callback invoked when keyup, html event                                                                                | (e:event) => void               | -       |
-| onResize     | Callback invoked when height changes in autosize mode **v>=0.37.0**                                                    | ({ height:number }) => void     | -       |
+| Property          | Instructions                                                                                                           | Type                            | Default |
+|-------------------|------------------------------------------------------------------------------------------------------------------------|---------------------------------|---------|
+| aria-describedby  | Set the aria-describedby property                                                                                      | string                          | -       |
+| aria-errormessage | Set the aria-errormessage property                                                                                     | string                          | -       |
+| aria-invalid      | Set aria-invalid property                                                                                              | string                          | -       |
+| aria-label        | Set aria-label properties                                                                                              | string                          | -       |
+| aria-labelledby   | Set aria-labelledby properties                                                                                         | string                          | -       |
+| aria-required     | Set aria-required properties                                                                                           | string                          | -       |
+| autosize          | Toggle whether to allow autosize when content height changes                                                           | boolean                         | false   |
+| className         | Class name                                                                                                             | string                          | -       |
+| cols              | The visible width of the text control, in average character widths. If it is specified, it must be a positive integer. | number                          | -       |
+| disabled          | Disabled                                                                                                               | boolean                         | false   |
+| getValueLength    | Custom calculated character string length                                                                              | (value: string) => number       |         |
+| maxCount          | The maximum number of characters and display count                                                                     | number                          | -       |
+| placeholder       | Content to be appear by default                                                                                        | string                          | -       |
+| readonly          | Read-only, not editable                                                                                                | boolean                         | false   |
+| rows              | The number of visible text lines for the control.                                                                      | number                          | 4       |
+| showClear         | Display the clear button when the input box has content and hover or focus **>=1.30.0**                                                                                         | boolean                         | false   |
+| style             | Inline style                                                                                                           | CSSProperties                   | -       |
+| onBlur            | Callback invoked when input loses focus                                                                                | (e:event) => void               | -       |
+| onChange          | Callback invoked when input value changes                                                                              | (value:string, e:event) => void |         |
+| onClear           | Callback invoked when clicking clear icon  **>=1.30.0**                                                                | (e:event) => void               |         |
+| onEnterPress      | Callback invoked when pressing enter                                                                                   | (e:event) => void               | -       |
+| onFocus           | Callback invoked when input gets focus                                                                                 | (e:event) => void               | -       |
+| onKeyDown         | Callback invoked when keydown, html event                                                                              | (e:event) => void               | -       |
+| onKeyPress        | Callback invoked when keypress, html event                                                                             | (e:event) => void               | -       |
+| onKeyUp           | Callback invoked when keyup, html event                                                                                | (e:event) => void               | -       |
+| onResize          | Callback invoked when height changes in autosize mode **v>=0.37.0**                                                    | ({ height:number }) => void     | -       |
+
+### InputGroup
+
+Common attributes will be set to the child elements of InputGroup, such as disabled, onFocus, etc. If the child sets the same attribute separately, the child has a higher priority.
+
+
+| Property      | Instructions                                | Type                                                          | Default |
+|---------------|--------------------------------------------|---------------------------------------------------------------|---------------|
+| className     | class name of the group                    | string                                                        | -             |
+| disabled      | disabled                                   | boolean                                                       | -             |
+| label         | Label property of InputGroup               | [LabelProps](https://semi.design/en-US/input/form#Form.Label) | -             |
+| labelPosition | label position, optional top or left       | string                                                        | -             |
+| size          | input box size, large, default, small      | string                                                        | 'default'     |
+| style         | styles for the group                       | CSSProperties                                                 | -             |
+| onBlur        | Callback when input box loses focus        | (e:event) => void                                             | -             |
+| onFocus       | The callback when the input box is focused | (e:event) => void                                             | -             |
+
 ## Methods
 
 | Name    | Description  |
@@ -447,6 +476,18 @@ Answers to some questions:
 | blur()  | Remove focus |
 | focus() | Get focus    |
 
+## Accessibility
+
+### ARIA
+
+- When validateStatus is error, the aria-invalid of the input box is true
+- When used in Form, field label is Input's aria-label
+
+### Keyboard and focus
+
+- Input can be focused, keyboard users can use Tab and Shift + Tab to switch focus
+- The password button can be focused and activated with Enter or Space key after focusing
+
 ## Design Tokens
 <DesignToken/>
 

+ 44 - 2
content/input/input/index.md

@@ -25,7 +25,7 @@ import React from 'react';
 import { Input } from '@douyinfe/semi-ui';
 
 () => (
-    <Input defaultValue='hi' autofocus></Input>
+    <Input defaultValue='hi'></Input>
 );
 ```
 
@@ -399,6 +399,12 @@ import { Input, Typography, Form, TextArea, Button } from '@douyinfe/semi-ui';
 
 | 属性           | 说明                                                            | 类型                            | 默认值    |
 |----------------|---------------------------------------------------------------|---------------------------------|-----------|
+| aria-describedby   | 设置 aria-describedby 属性           | string                         | -  |
+| aria-errormessage   | 设置 aria-errormessage 属性           | string                         | -  |
+| aria-invalid   | 设置 aria-invalid 属性           | string                         | -  |
+| aria-label   | 设置 aria-label 属性           | string                         | -  |
+| aria-labelledby   | 设置 aria-labelledby 属性           | string                         | -  |
+| aria-required   | 设置 aria-required 属性           | string                         | -  |
 | addonAfter     | 后置标签                                                        | ReactNode               |           |
 | addonBefore    | 前置标签                                                        | ReactNode               |           |
 | className      | 类名                                                            | string                          |           |
@@ -408,7 +414,7 @@ import { Input, Typography, Form, TextArea, Button } from '@douyinfe/semi-ui';
 | hideSuffix     | 清除按钮与后缀标签并存时隐藏后缀标签,默认为false两者并列        | boolean                         | false     |
 | mode           | 输入框的模式,可选值password **>=v1.3.0**                        | string                          |           |
 | prefix         | 前缀标签                                                        | ReactNode               |           |
-| showClear      | 支持清除 **>=1.0.0**                                            | boolean                         | false     |
+| showClear      | 输入框有内容且 hover 或 focus 时展示清除按钮 **>=1.0.0**                                            | boolean                         | false     |
 | size           | 输入框大小,large、default、small                                  | string                          | 'default' |
 | style          | 样式                                                            | CSSProperties                          |           |
 | suffix         | 后缀标签                                                        | ReactNode               |           |
@@ -429,6 +435,12 @@ import { Input, Typography, Form, TextArea, Button } from '@douyinfe/semi-ui';
 
 | 属性         | 说明                               | 类型                            | 默认值 |
 |--------------|----------------------------------|---------------------------------|--------|
+| aria-describedby   | 设置 aria-describedby 属性           | string                         | -  |
+| aria-errormessage   | 设置 aria-errormessage 属性           | string                         | -  |
+| aria-invalid   | 设置 aria-invalid 属性           | string                         | -  |
+| aria-label   | 设置 aria-label 属性           | string                         | -  |
+| aria-labelledby   | 设置 aria-labelledby 属性           | string                         | -  |
+| aria-required   | 设置 aria-required 属性           | string                         | -  |
 | autosize     | 是否随着自动适应内容高度           | boolean                         | false  |
 | className    | 类名                               | string                          | -      |
 | cols         | 默认列数                           | number                          | 无     |
@@ -449,6 +461,23 @@ import { Input, Typography, Form, TextArea, Button } from '@douyinfe/semi-ui';
 | onKeyPress   | keypress 回调,html 事件            | (e:event) => void               | -      |
 | onKeyUp      | keyup 回调,html 事件               | (e:event) => void               | -      |
 | onResize     | 触发高度变化时的回调 **>=v0.37.0** | ({ height:number }) => void    | -      |
+
+### InputGroup
+
+通用属性将设置到 InputGroup 的子级元素上,例如 disabled、onFocus 等,如果子级单独设置了相同属性,子级的优先级更高。
+
+
+| 属性          | 说明                           | 类型                                                          | 默认值    |
+|---------------|------------------------------|---------------------------------------------------------------|-----------|
+| className     | 组的类名                       | string                                                        | -         |
+| disabled      | 禁用                           | boolean                                                       | -         |
+| label         | InputGroup 的 label 属性       | [LabelProps](https://semi.design/zh-CN/input/form#Form.Label) | -         |
+| labelPosition | label 位置,可选 top 或 left    | string                                                        | -         |
+| size          | 输入框大小,large、default、small | string                                                        | 'default' |
+| style         | 组的样式                       | CSSProperties                                                 | -         |
+| onBlur        | 输入框失去焦点时的回调         | (e:event) => void                                             | -         |
+| onFocus       | 输入框 focus 时的回调          | (e:event) => void                                             | -         |
+
 ## Methods
 
 | 名称    | 描述     |
@@ -456,6 +485,19 @@ import { Input, Typography, Form, TextArea, Button } from '@douyinfe/semi-ui';
 | blur()  | 移出焦点 |
 | focus() | 获取焦点 |
 
+## Accessibility
+
+### ARIA
+
+- 当 validateStatus 为 error 时,输入框的 aria-invalid 为 true
+- 在 Form 中使用时,field label 是 Input 的 aria-label
+
+### 键盘和焦点
+
+- Input 可被获取焦点,键盘用户可以使用 Tab 及 Shift  + Tab 切换焦点
+- 密码按钮可以被聚焦,聚焦后使用 Enter 或者空格键激活
+
+
 ## 设计变量
 <DesignToken/>
 

+ 21 - 0
cypress/integration/input.spec.js

@@ -0,0 +1,21 @@
+// input.spec.js created with Cypress
+//
+// Start writing your Cypress tests below!
+// If you're unfamiliar with how Cypress works,
+// check out the link below and learn how to write your first test:
+// https://on.cypress.io/writing-first-test
+
+describe('input', () => {
+    it('trigger password button', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=input--input-a-11-y&args=&viewMode=story');
+        cy.get('[data-cy=password]').click();
+        cy.get('[data-cy=password]').tab();
+        cy.get('.semi-input-modebtn').should('be.focused');
+        cy.get('.semi-input-modebtn').type('{ }');
+        cy.get('[data-cy=password]').should('have.value', 'Semi Design');
+        cy.get('[data-cy=password]').tab();
+        cy.get('.semi-input-modebtn').should('be.focused');
+        cy.get('.semi-input-modebtn').type('{enter}');
+        cy.get('[data-cy=password]').should('have.value', 'Semi Design');
+    });
+});

+ 3 - 12
packages/semi-foundation/input/foundation.ts

@@ -2,7 +2,6 @@ import BaseFoundation, { DefaultAdapter, noopFunction } from '../base/foundation
 import { strings } from './constants';
 import { noop, set, isNumber, isString, isFunction } from 'lodash';
 
-import isEnterPress from '../utils/isEnterPress';
 import { ENTER_KEY } from './../utils/keyCode';
 
 export interface InputDefaultAdapter {
@@ -296,21 +295,13 @@ class InputFoundation extends BaseFoundation<InputAdapter> {
         }
     }
 
-    /**
-     * A11y: simulate clear button click
-     */
-    /* istanbul ignore next */
-    handleClearEnterPress(e: any) {
-        if (isEnterPress(e)) {
-            this.handleClear(e);
-        }
-    }
-
     /**
      * A11y: simulate password button click
      */
     handleModeEnterPress(e: any) {
-        if (isEnterPress(e)) {
+        // trigger by Enter or Space key
+        if (['Enter', ' '].includes(e?.key)) {
+            this.handlePreventMouseDown(e);
             this.handleClickEye(e);
         }
     }

+ 6 - 0
packages/semi-foundation/input/input.scss

@@ -173,6 +173,12 @@ $module: #{$prefix}-input;
                 color: $color-input-icon-hover;
             }
         }
+
+        &:focus-visible {
+            border-radius: $radius-input_wrapper;
+            outline: $width-input_icon-outline solid $color-input_icon-outline;
+            outline-offset: $width-input_icon-outlineOffset;
+        }
     }
 
     &__with-suffix-icon.#{$module}-wrapper-clearable:not(.#{$module}-wrapper__with-suffix-hidden) {

+ 0 - 10
packages/semi-foundation/input/textareaFoundation.ts

@@ -233,14 +233,4 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdapter>
         this._adapter.notifyClear(e);
         this.stopPropagation(e);
     }
-
-    /**
-     * A11y: simulate clear button click
-     */
-    /* istanbul ignore next */
-    handleClearEnterPress(e: any) {
-        if (isEnterPress(e)) {
-            this.handleClear(e);
-        }
-    }
 }

+ 3 - 0
packages/semi-foundation/input/variables.scss

@@ -11,6 +11,7 @@ $color-input_default-border-active: $color-input_default-bg-active; // 输入框
 
 $color-input_default-bg-focus: var(--semi-color-fill-0); // 输入框背景颜色 - 选中
 $color-input_default-border-focus: var(--semi-color-focus-border); // 输入框描边颜色 - 选中
+$color-input_icon-outline: var(--semi-color-primary-light-active); // 输入框 icon outline 颜色
 
 // error
 $color-input_danger-bg-default: var(--semi-color-danger-light-default); // 错误输入框背景颜色 - 默认
@@ -75,6 +76,8 @@ $width-input_prepend-border: $border-thickness-control; // 前置标签描边宽
 $width-input_group_pseudo-border: $border-thickness-control;
 $width-input_wrapper-border: $border-thickness-control-focus; // 输入框描边宽度
 $width-input_wrapper_focus-border: $border-thickness-control-focus; // 输入框描边宽度 - 选中态
+$width-input_icon-outline: 2px; // 输入框 icon outline 宽度
+$width-input_icon-outlineOffset: -1px; // 输入框 icon outline-offset 宽度
 
 $radius-input_wrapper: var(--semi-border-radius-small); // 输入框圆角大小
 $spacing-input_icon-marginLeft: -$spacing-base-tight; // 输入框图标左侧内边距

+ 32 - 3
packages/semi-ui/input/_story/input.stories.js

@@ -22,6 +22,7 @@ import {
   Switch,
   Form,
   Space,
+  Radio
 } from '../../index';
 import './input.scss';
 import RTLWrapper from '../../configProvider/_story/RTLDirection/RTLWrapper';
@@ -863,8 +864,7 @@ export const ClearButton = () => {
             Suffix
           </Typography.Text>
         }
-        showClear
-      ></Input>
+      />
       <Input showClear defaultValue="semi" />
       <Input showClear mode="password" defaultValue="semi" />
       <Input mode="password" defaultValue="semi" />
@@ -906,4 +906,33 @@ export const TextAreaAutosize = () => {
     </div>
   )
 };
-TextAreaAutosize.storyName = "textarea autosize";
+TextAreaAutosize.storyName = "textarea autosize";
+
+export const InputA11y = () => {
+  return (
+    <div style={{ width: 300 }}>
+      <Input prefix="search" defaultValue="Semi Design" showClear />
+      <br/><br/>
+      <Input aria-required prefix="search" defaultValue="Semi Design" showClear suffix="semi" />
+      <br/><br/>
+      <Input data-cy="password" defaultValue="Semi Design" mode="password" />
+      <br/><br/>
+      <Input defaultValue="Semi Design" mode="password" disabled />
+      <br/><br/>
+      <Input defaultValue='this value is too long' validateStatus='error' showClear></Input>
+      <br/><br/>
+      <TextArea defaultValue='semi' showClear />
+      <TextArea aria-required defaultValue='不能为空' showClear />
+      <InputGroup label={{ text: '成绩信息' }}>
+          <Input placeholder="Name" style={{ width: 100 }} />
+          <Input placeholder="Score" style={{ width: 140 }} />
+      </InputGroup>
+      <br/><br/>
+      <Form onSubmit={() => alert('submit')}>
+        <button>submit</button>
+        <Form.Input field="password" label="密码按钮上敲击 Enter 测试是否会触发 Form submit" mode="password" />
+      </Form>
+    </div>
+  );
+}
+InputA11y.storyName = "input a11y";

+ 7 - 13
packages/semi-ui/input/index.tsx

@@ -221,11 +221,6 @@ class Input extends BaseComponent<InputProps, InputState> {
         this.foundation.handleClear(e);
     };
 
-    /* istanbul ignore next */
-    handleClearEnterPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
-        this.foundation.handleClearEnterPress(e);
-    };
-
     handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
         this.foundation.handleClick(e);
     };
@@ -306,13 +301,10 @@ class Input extends BaseComponent<InputProps, InputState> {
         // use onMouseDown to fix issue 1203
         if (allowClear) {
             return (
+                // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
                 <div
-                    role="button"
-                    tabIndex={0}
-                    aria-label="Clear input value"
                     className={clearCls}
                     onMouseDown={this.handleClear}
-                    onKeyPress={this.handleClearEnterPress}
                 >
                     <IconClear />
                 </div>
@@ -322,11 +314,12 @@ class Input extends BaseComponent<InputProps, InputState> {
     }
 
     renderModeBtn() {
-        const { value, isFocus, isHovering, eyeClosed } = this.state;
+        const { eyeClosed } = this.state;
         const { mode, disabled } = this.props;
         const modeCls = cls(`${prefixCls}-modebtn`);
         const modeIcon = eyeClosed ? <IconEyeClosedSolid /> : <IconEyeOpened />;
-        const showModeBtn = mode === 'password' && value && !disabled && (isFocus || isHovering);
+        // alway show password button for a11y
+        const showModeBtn = mode === 'password' && !disabled;
         const ariaLabel = eyeClosed ? 'Show password' : 'Hidden password';
         if (showModeBtn) {
             return (
@@ -360,8 +353,8 @@ class Input extends BaseComponent<InputProps, InputState> {
             [`${prefixCls}-prefix-icon`]: isSemiIcon(labelNode),
         });
 
-        // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
         return (
+            // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
             <div
                 className={prefixWrapperCls}
                 onMouseDown={this.handlePreventMouseDown}
@@ -391,8 +384,8 @@ class Input extends BaseComponent<InputProps, InputState> {
             [`${prefixCls}-suffix-icon`]: isSemiIcon(suffix),
             [`${prefixCls}-suffix-hidden`]: suffixAllowClear && Boolean(hideSuffix),
         });
-        // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
         return (
+            // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
             <div
                 className={suffixWrapperCls}
                 onMouseDown={this.handlePreventMouseDown}
@@ -411,6 +404,7 @@ class Input extends BaseComponent<InputProps, InputState> {
             autofocus,
             className,
             disabled,
+            defaultValue,
             placeholder,
             prefix,
             mode,

+ 3 - 1
packages/semi-ui/input/inputGroup.tsx

@@ -66,9 +66,11 @@ export default class inputGroup extends BaseComponent<InputGroupProps, InputGrou
         // const labelCls = cls(label.className, '');
         const defaultName = 'input-group';
         return (
-            <div role="group" aria-label="Input group" aria-disabled={this.props.disabled} className={groupWrapperCls}>
+            <div className={groupWrapperCls}>
                 {label && label.text ? <Label name={defaultName} {...label} /> : null}
                 <span
+                    role="group"
+                    aria-disabled={this.props.disabled}
                     id={label && label.name || defaultName}
                     className={groupCls}
                     style={this.props.style}

+ 2 - 15
packages/semi-ui/input/textarea.tsx

@@ -198,11 +198,6 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
         this.foundation.handleClear(e);
     };
 
-    /* istanbul ignore next */
-    handleClearEnterPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
-        this.foundation.handleClearEnterPress(e);
-    }
-
     renderClearBtn() {
         const { showClear } = this.props;
         const displayClearBtn = this.foundation.isAllowClear();
@@ -211,13 +206,10 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
         });
         if (showClear) {
             return (
+                // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
                 <div
-                    role="button"
-                    tabIndex={0}
-                    aria-label="Clear textarea value"
                     className={clearCls}
                     onClick={this.handleClear}
-                    onKeyPress={this.handleClearEnterPress}
                 >
                     <IconClear />
                 </div>
@@ -244,12 +236,7 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
                 }
             );
             counter = (
-                <div
-                    aria-label="Textarea value length counter"
-                    aria-valuemax={maxCount}
-                    aria-valuenow={current}
-                    className={countCls}
-                >
+                <div className={countCls}>
                     {current}{total ? '/' : null}{total}
                 </div>
             );

+ 80 - 0
yarn.lock

@@ -1460,6 +1460,16 @@
   resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
   integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
 
+"@douyinfe/[email protected]":
+  version "2.12.0"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.12.0.tgz#6674f30e22fab6559f573d822c441bc99753e2a1"
+  integrity sha512-/BqguKql4bjfo4uhy/lfR/0wojBG30N13g3S/hEwETC2iFVwQab3Io5/uuzJpa+ARUcrbKJYbGesPDwM0lyR/g==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+    "@douyinfe/semi-animation" "2.12.0"
+    "@douyinfe/semi-animation-styled" "2.12.0"
+    classnames "^2.2.6"
+
 "@douyinfe/[email protected]":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.9.1.tgz#f2e4c6ef7899729ee6145edf0579598ba195bfdd"
@@ -1470,6 +1480,13 @@
     "@douyinfe/semi-animation-styled" "2.9.1"
     classnames "^2.2.6"
 
+"@douyinfe/[email protected]":
+  version "2.12.0"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.12.0.tgz#27df4be8f23553791f5954d7f9529f26a7d76211"
+  integrity sha512-VdMh6xKiqFBi9ATyQhdyDSXK5QLvJBBUjIqOuTjgxSVmx884GChnK2nzHSIhGrlFDpM8mJd1gQ2uDQ7jdAFJgg==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+
 "@douyinfe/[email protected]":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.9.1.tgz#0a4a3c521626118b209604b2d6447fbcaa4839a4"
@@ -1493,6 +1510,21 @@
     "@babel/runtime-corejs3" "^7.15.4"
     bezier-easing "^2.1.0"
 
+"@douyinfe/[email protected]":
+  version "2.12.0"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.12.0.tgz#76b00351b8c0438ae974c3a218c560766e1db2e4"
+  integrity sha512-HuH9Qafm+VzZATguaT+r3nMSpfw9vL57erXu7jcDr7YqQkz3VgDX7c6/qHwIkWdeCuiLAyjNb886K+AmFuY8hg==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+    "@douyinfe/semi-animation" "2.12.0"
+    async-validator "^3.5.0"
+    classnames "^2.2.6"
+    date-fns "^2.9.0"
+    date-fns-tz "^1.0.10"
+    lodash "^4.17.21"
+    memoize-one "^5.2.1"
+    scroll-into-view-if-needed "^2.2.24"
+
 "@douyinfe/[email protected]":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.9.1.tgz#1300bb97d6ceb92274ca4c9e6c66c5c16dc284ea"
@@ -1508,6 +1540,14 @@
     memoize-one "^5.2.1"
     scroll-into-view-if-needed "^2.2.24"
 
+"@douyinfe/[email protected]", "@douyinfe/semi-icons@^2.0.0":
+  version "2.12.0"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.12.0.tgz#9af44b9c9366a9034d6215364efca3649d9dc779"
+  integrity sha512-PQEFzOtTyft+mWiiXxRg/p5v5VtDfS0fXFmq7r+uZMdEP5PDR/CicMnRp6iNjBhtBveT92TkGCnK40YAptImww==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+    classnames "^2.2.6"
+
 "@douyinfe/[email protected]", "@douyinfe/semi-icons@latest":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.9.1.tgz#7a04e1a77070220b04f63e6f65aac30155ed8ddd"
@@ -1516,6 +1556,13 @@
     "@babel/runtime-corejs3" "^7.15.4"
     classnames "^2.2.6"
 
+"@douyinfe/[email protected]":
+  version "2.12.0"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.12.0.tgz#be55cdf71c74fd23496fec0cb03a01f2c7f6c445"
+  integrity sha512-JHTxEGp8LZ1bLyi2yosD0fPuJYy5y/yJUByNTmRVTXIwIPemqOGF0n9ryySbV4g2fLjan1hUuJ0mSgsppAq5pQ==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+
 "@douyinfe/[email protected]":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.9.1.tgz#1a448d1854ee1beeba57ea612da052b549ea105f"
@@ -1579,6 +1626,13 @@
     monaco-themes "^0.3.3"
     react-live "^2.2.2"
 
+"@douyinfe/[email protected]":
+  version "2.12.0"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.12.0.tgz#15f8905f39e2dd016e025bb728d6e604dbd1b471"
+  integrity sha512-R80krfri+X9tlopaOthInAAJ73uGrRZsRBEmrpIkXd2+l26Xv3Y3J+MlyeaynbWSVlqKB1Ol/GHc7mcS6R3wdg==
+  dependencies:
+    glob "^7.1.6"
+
 "@douyinfe/[email protected]":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.9.1.tgz#734113e9783ca58b69afe1769005e7e57e5a4da7"
@@ -1586,6 +1640,32 @@
   dependencies:
     glob "^7.1.6"
 
+"@douyinfe/semi-ui@^2.0.0":
+  version "2.12.0"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.12.0.tgz#94fd7f72c4804210f016955228f0d739e1c4adb5"
+  integrity sha512-3JmSgQQ41WSYcx0ow27BAEsF5jQ2t3IcCJeAs3gpe4P0ioUT1BMH9XzMkKxohcTuFbJ9Sh4+H1cZYCRjltNhqQ==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.15.4"
+    "@douyinfe/semi-animation" "2.12.0"
+    "@douyinfe/semi-animation-react" "2.12.0"
+    "@douyinfe/semi-foundation" "2.12.0"
+    "@douyinfe/semi-icons" "2.12.0"
+    "@douyinfe/semi-illustrations" "2.12.0"
+    "@douyinfe/semi-theme-default" "2.12.0"
+    "@types/react-window" "^1.8.2"
+    async-validator "^3.5.0"
+    classnames "^2.2.6"
+    copy-text-to-clipboard "^2.1.1"
+    date-fns "^2.9.0"
+    date-fns-tz "^1.0.10"
+    lodash "^4.17.21"
+    react-resizable "^1.8.0"
+    react-sortable-hoc "^2.0.0"
+    react-window "^1.8.2"
+    resize-observer-polyfill "^1.5.1"
+    scroll-into-view-if-needed "^2.2.24"
+    utility-types "^3.10.0"
+
 "@douyinfe/semi-ui@latest":
   version "2.9.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.9.1.tgz#505d4783ea1fa73d307b75f62091030f1fee9332"