Browse Source

Merge branch 'release' into main

linyan 3 years ago
parent
commit
f6fcac2a61
68 changed files with 1187 additions and 178 deletions
  1. 2 1
      .eslintrc.js
  2. 30 16
      content/input/autocomplete/index.md
  3. 6 0
      content/input/checkbox/index-en-US.md
  4. 6 0
      content/input/checkbox/index.md
  5. 16 10
      content/input/inputnumber/index-en-US.md
  6. 15 10
      content/input/inputnumber/index.md
  7. 11 0
      content/start/accessibility/index-en-US.md
  8. 10 0
      content/start/accessibility/index.md
  9. 16 1
      content/start/changelog/index-en-US.md
  10. 17 1
      content/start/changelog/index.md
  11. 39 0
      cypress/integration/checkbox.spec.js
  12. 20 1
      cypress/integration/inputNumber.spec.js
  13. 16 0
      cypress/integration/navigation.spec.js
  14. 36 1
      cypress/integration/table.spec.js
  15. 46 0
      cypress/integration/tabs.spec.js
  16. 33 0
      cypress/integration/tag.spec.js
  17. 49 0
      cypress/integration/upload.spec.js
  18. 1 1
      lerna.json
  19. 5 4
      package.json
  20. 1 1
      packages/semi-animation-react/README.md
  21. 2 2
      packages/semi-animation-react/package.json
  22. 1 1
      packages/semi-animation-styled/package.json
  23. 1 1
      packages/semi-animation/README.md
  24. 1 1
      packages/semi-animation/package.json
  25. 6 0
      packages/semi-eslint-plugin/.eslintrc.json
  26. 55 0
      packages/semi-eslint-plugin/README-zh_CN.md
  27. 55 0
      packages/semi-eslint-plugin/README.md
  28. 44 0
      packages/semi-eslint-plugin/__tests__/index.js
  29. 43 0
      packages/semi-eslint-plugin/package.json
  30. 5 0
      packages/semi-eslint-plugin/src/index.ts
  31. 5 0
      packages/semi-eslint-plugin/src/rules/index.ts
  32. 91 0
      packages/semi-eslint-plugin/src/rules/no-import.ts
  33. 12 0
      packages/semi-eslint-plugin/tsconfig.json
  34. 1 1
      packages/semi-foundation/avatar/mixin.scss
  35. 4 4
      packages/semi-foundation/button/button.scss
  36. 27 19
      packages/semi-foundation/button/variables.scss
  37. 14 1
      packages/semi-foundation/checkbox/checkbox.scss
  38. 30 0
      packages/semi-foundation/checkbox/checkboxFoundation.ts
  39. 2 0
      packages/semi-foundation/checkbox/variables.scss
  40. 1 1
      packages/semi-foundation/inputNumber/constants.ts
  41. 2 2
      packages/semi-foundation/inputNumber/foundation.ts
  42. 2 0
      packages/semi-foundation/navigation/foundation.ts
  43. 1 1
      packages/semi-foundation/package.json
  44. 1 1
      packages/semi-foundation/radio/variables.scss
  45. 1 1
      packages/semi-foundation/steps/variables.scss
  46. 1 1
      packages/semi-foundation/switch/variables.scss
  47. 1 1
      packages/semi-foundation/tabs/foundation.ts
  48. 1 1
      packages/semi-foundation/tag/mixin.scss
  49. 14 0
      packages/semi-foundation/tagInput/foundation.ts
  50. 2 0
      packages/semi-foundation/upload/foundation.ts
  51. 2 2
      packages/semi-icons/package.json
  52. 1 1
      packages/semi-illustrations/package.json
  53. 2 2
      packages/semi-next/package.json
  54. 1 1
      packages/semi-scss-compile/package.json
  55. 1 1
      packages/semi-theme-default/package.json
  56. 26 7
      packages/semi-ui/checkbox/checkbox.tsx
  57. 11 1
      packages/semi-ui/checkbox/checkboxInner.tsx
  58. 2 3
      packages/semi-ui/inputNumber/__test__/inputNumber.test.js
  59. 53 0
      packages/semi-ui/inputNumber/_story/inputNumber.stories.js
  60. 0 4
      packages/semi-ui/inputNumber/index.tsx
  61. 7 7
      packages/semi-ui/package.json
  62. 1 1
      packages/semi-ui/popover/index.tsx
  63. 36 0
      packages/semi-ui/tabs/_story/tabs.stories.js
  64. 1 1
      packages/semi-ui/tag/_story/tag.stories.js
  65. 38 11
      packages/semi-ui/tagInput/index.tsx
  66. 152 0
      packages/semi-ui/upload/_story/upload.stories.js
  67. 1 1
      packages/semi-webpack/package.json
  68. 51 49
      yarn.lock

+ 2 - 1
.eslintrc.js

@@ -48,7 +48,7 @@ module.exports = {
             parserOptions: {
                 project: ['./tsconfig.eslint.json'],
             },
-            plugins: ['react', 'jest', 'react-hooks', 'import', '@typescript-eslint'],
+            plugins: ['react', 'jest', 'react-hooks', 'import', '@typescript-eslint', 'semi-design'],
             rules: {
                 // 因为历史原因,现有项目基本全部是4个空格
                 indent: 'off',
@@ -79,6 +79,7 @@ module.exports = {
                 'jsx-a11y/no-noninteractive-element-interactions': ['warn'],
                 'jsx-a11y/no-autofocus': ['warn'],
                 'object-curly-spacing': ['error', 'always'],
+                'semi-design/no-import': 'error'
             }
         },
     ],

+ 30 - 16
content/input/autocomplete/index.md

@@ -264,22 +264,36 @@ import { AutoComplete } from '@douyinfe/semi-ui';
 import React from 'react';
 import { AutoComplete } from '@douyinfe/semi-ui';
 
-() => (
-    <div>
-        <AutoComplete
-            data={[1, 2, 3, 4]}
-            position="top"
-            placeholder="选项菜单在上方显示"
-            style={{ width: 200, margin: 10 }}
-        ></AutoComplete>
-        <AutoComplete
-            data={[1, 2, 3, 4]}
-            position="rightTop"
-            placeholder="选项菜单在右侧显示"
-            style={{ width: 200, margin: 10 }}
-        ></AutoComplete>
-    </div>
-);
+() => {
+    const [data, setData] = useState([]);
+
+    const change = (input) => {
+        let newData = ['gmail.com', '163.com', 'qq.com'].map(domain => `${input}@${domain}`);
+        if (!input) {
+            newData = [];
+        }
+        setData(newData);
+    }
+    return (
+        <div>
+            <AutoComplete
+                data={data}
+                position="top"
+                onSearch={change}
+                placeholder="选项菜单在上方显示"
+                style={{ width: 200, margin: 10 }}
+            ></AutoComplete>
+            <AutoComplete
+                data={data}
+                position="rightTop"
+                onSearch={change}
+                placeholder="选项菜单在右侧显示"
+                style={{ width: 200, margin: 10 }}
+            ></AutoComplete>
+        </div>
+    )
+};
+
 ```
 
 ### 禁用

+ 6 - 0
content/input/checkbox/index-en-US.md

@@ -442,6 +442,12 @@ import { CheckboxGroup, Checkbox, Row, Col } from '@douyinfe/semi-ui';
 - `aria-disabled` indicates the current disabled state, which is consistent with the value of the `disabled` prop
 - `aria-checked` indicates the current checked state
 
+### Keyboard and focus
+- Checkbox can be focused, keyboard users can use Tab and Shift + Tab to switch focus.
+- The Checkbox that gets the focus can switch the selected and unselected states through Space.
+- The click area of ​​Checkbox is larger than the box itself and contains the text behind the box; for checkboxes with auxiliary text, the auxiliary text is also included in the click area.
+- Disabled Checkbox is not focusable.
+
 ## Design Tokens
 <DesignToken/>
 

+ 6 - 0
content/input/checkbox/index.md

@@ -424,6 +424,12 @@ import { Checkbox, CheckboxGroup, Row, Col } from '@douyinfe/semi-ui';
 - `aria-disabled` 表示当前的禁用状态,与 `disabled` prop 的值保持一致
 - `aria-checked` 表示当前的选中状态
 
+### 键盘和焦点
+- Checkbox 可被获取焦点,键盘用户可以使用 Tab 及 Shift  + Tab 切换焦点。
+- 当前获取的焦点为 Checkbox 时,可以通过 Space 切换选中和未选状态。
+- Checkbox 的点击区域大于框本身,包含了框后的文案;带辅助文本的 checkbox,辅助文本也包含在点击区域内。
+- 禁用的 Checkbox 不可获取焦点。
+
 ## 设计变量
 <DesignToken/>
 

+ 16 - 10
content/input/inputnumber/index-en-US.md

@@ -69,16 +69,12 @@ class App extends React.Component {
                 <InputNumber defaultValue={2} disabled />
                 <br/><br/>
 
-                <label>Set autofocus to true </label>
-                <InputNumber defaultValue={3} autofocus />
-                <br/><br/>
-
                 <label>Set precision to 2 </label>
                 <InputNumber precision={2} defaultValue={1.234} />
                 <br/><br/>
 
                 <label>Set innerButtons=true </label>
-                <InputNumber innerButtons={true} suffix={'Hour'} defaultValue={1} style={{ width: 190}} />
+                <InputNumber innerButtons={true} suffix={'Hour'} defaultValue={1} style={{ width: 190 }} />
                 <br/>
 
             </div>
@@ -97,7 +93,7 @@ import React from 'react';
 import { InputNumber } from '@douyinfe/semi-ui';
 
 () => (
-    <InputNumber innerButtons style={{ width: 190}} />
+    <InputNumber innerButtons style={{ width: 190 }} />
 );
 ```
 
@@ -108,7 +104,7 @@ import React from 'react';
 import { InputNumber } from '@douyinfe/semi-ui';
 
 () => (
-    <InputNumber hideButtons style={{ width: 190}} />
+    <InputNumber hideButtons style={{ width: 190 }} />
 );
 
 ```
@@ -221,7 +217,7 @@ function Demo () {
 | prefixCls    | Prefix content                                                                                  | string\|ReactNode                 |           |            |
 | pressInterval| How often will the click event be triggered when the button is long pressed, in milliseconds                                   | number                 |   250        |           |
 | pressTimeout | When the button is long pressed, how long will the click event be triggered after the delay, in milliseconds                                               | number                 |     250      |           |
-| shiftStep    | Step size for pressing the shift key, it can be a decimal.                            | number                            | 1         | **1.5.0** |
+| shiftStep    | Step size for pressing the shift key, it can be a decimal. The default value was adjusted from 1 to 10 in v2.13                     | number                            | 10         | **1.5.0** |
 | showClear    | Do you show the clear button?                                                                   | boolean                           | false     | **0.35.0** |
 | size         | Enter box size, optional value: "default"\|"small"\|"large"                                     | string                            | 'default' |            |
 | step         | Each time you change the number of steps, it can be a decimal.                                  | number                            | 1         |            |
@@ -241,10 +237,20 @@ function Demo () {
 | focus() | Get the focus.  |
 
 ## Accessibility
+
+Guideline: https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/
+
 ### ARIA
 
-- Added button role to the increase and decrease buttons to indicate that it is a button that can be clicked
-- Use aria-valuenow for the current value, aria-valuemax for the maximum acceptable value, and aria-valuemin for the minimum acceptable value
+- InputNumber has `spinbutton` role
+- spinbutton uses `aria-valuenow` for current value, `aria-valuemax` for acceptable maximum value, and `aria-valuemin` for acceptable minimum value
+- When InputNumber is used in Form, the value of the input box's `aria-labeledby` reference is Field label
+
+### Keyboard and Focus
+
+- InputNumber can get focus, keyboard users can use `Tab` and `Shift + Tab` to switch focus (Increase and decrease buttons are not focusable)
+- Keyboard users can press up key ⬆️ or down key ⬇️ and the input value will increase or decrease by `step` (default is 1)
+- Hold down Shift + Up ⬆️ or Down ⬇️ , the input value will increase or decrease by `shiftStep` (default is 10)
 
 ## Design Tokens
 <DesignToken/>

+ 15 - 10
content/input/inputnumber/index.md

@@ -55,16 +55,12 @@ import { InputNumber } from '@douyinfe/semi-ui';
         <InputNumber defaultValue={2} disabled />
         <br/><br/>
 
-        <label>自动获得焦点 autofocus=true </label>
-        <InputNumber defaultValue={3} autofocus />
-        <br/><br/>
-
         <label>设置了小数位数 precision=2 </label>
         <InputNumber precision={2} defaultValue={1.234} />
         <br/><br/>
 
         <label>设置了 innerButtons=true </label>
-        <InputNumber innerButtons={true} suffix={'小时'} defaultValue={1} style={{ width: 190}} />
+        <InputNumber innerButtons={true} suffix={'小时'} defaultValue={1} style={{ width: 190 }} />
         <br/>
     </div>
 );
@@ -80,7 +76,7 @@ import React from 'react';
 import { InputNumber } from '@douyinfe/semi-ui';
 
 () => (
-    <InputNumber innerButtons style={{ width: 190}} />
+    <InputNumber innerButtons style={{ width: 190 }} />
 );
 ```
 
@@ -91,7 +87,7 @@ import React from 'react';
 import { InputNumber } from '@douyinfe/semi-ui';
 
 () => (
-    <InputNumber hideButtons style={{ width: 190}} />
+    <InputNumber hideButtons style={{ width: 190 }} />
 );
 ```
 
@@ -195,7 +191,7 @@ function Demo () {
 | prefixCls    | 前缀内容                                                       | string\|ReactNode                 |           |           |
 | pressInterval| 长按按钮时,多久触发一次点击事件,单位毫秒                                   | number                 |   250        |           |
 | pressTimeout | 长按按钮时,延迟多久后触发点击事件,单位毫秒                                                      | number                 |     250      |           |
-| shiftStep    | 按住 shift 键每次改变步数,可以为小数                           | number                            | 1         | **1.5.0** |
+| shiftStep    | 按住 shift 键每次改变步数,可以为小数,v2.13 默认值由 1 调整为 10                           | number                            | 10         | **1.5.0** |
 | showClear    | 是否显示清除按钮                                               | boolean                           | false     | **0.35.0**   |
 | size         | 输入框大小,可选值:"default"\|"small"\|"large"                | string                            | 'default' |           |
 | step         | 每次改变步数,可以为小数                                       | number                            | 1         |           |
@@ -217,10 +213,19 @@ function Demo () {
 
 ## Accessibility
 
+参考标准:https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/
+
 ### ARIA
 
-- 增减按钮添加了 button role,以表示是可以点击的按钮
-- 使用 aria-valuenow 表示当前值,aria-valuemax 表示可以接受的最大值,aria-valuemin 表示可以接受的最小值
+- 数字输入框具有 spinbutton role
+- spinbutton 使用 aria-valuenow 表示当前值,aria-valuemax 表示可以接受的最大值,aria-valuemin 表示可以接受的最小值
+- 当 InputNumber 在 Form 中使用时,输入框的 aria-labeledby 指向 Field label
+
+### 键盘和焦点
+
+- InputNumber 可被获取焦点,键盘用户可以使用 Tab 及 Shift + Tab 切换焦点(增加/减少按钮不可以被键盘聚焦)
+- 键盘用户可以按上键 ⬆️ 或下键 ⬇️ ,输入值将增加或减少 step(默认值为 1)
+- 按住 Shift + 上键 ⬆️ 或下键 ⬇️ ,输入值将增加或减少 shiftStep(默认值为 10)
 
 ## 设计变量
 <DesignToken/>

+ 11 - 0
content/start/accessibility/index-en-US.md

@@ -7,6 +7,17 @@ order: 5
 brief: Accessible design is about making it easy for everyone to interact with products, including those with disabilities, to provide a better experience for everyone. The Semi design system is designed to remove barriers and create inclusive product experiences that work for all.
 ---
 
+## Accessibility-specific themes
+Semi has customized a set of accessibility friendly themes [@semi-bot/semi-theme-a11y](https://semi.design/dsm_store/theme?dsmID=2243)  
+Compared with the default theme, the A11y theme increases the contrast of each color of the basic color wheel, and increases the font size of the font token. If there is a higher requirement for contrast, this theme is recommended.   
+
+```
+// install
+npm i @semi-bot/semi-theme-a11y
+```
+
+Access steps for reference [Customized Themes](https://semi.design/en-US/start/customize-theme#When%20using%20webpack%20as%20a%20build%20tool)
+
 ## Understand user needs
 
 To design and develop inclusive products, you first need to understand the different needs of different users and consider the aids and methods they use.

+ 10 - 0
content/start/accessibility/index.md

@@ -7,6 +7,16 @@ order: 5
 brief: 无障碍设计是让所有人都可以轻松地与产品互动,包括那些残障人群,为每个人提供更好的体验。Semi 设计系统旨在消除障碍并创造适合所有人的包容性产品体验。
 ---
 
+## 无障碍专用主题
+Semi 针对无障碍场景,专门定制了一套专用的 A11y 主题 [@semi-bot/semi-theme-a11y](https://semi.design/dsm_store/theme?dsmID=2243)  
+与默认主题相比,A11y 主题增加了基础色盘各个颜色的对比度,并且加大了字体 token 的字号,如果对对比度有更高要求时,建议选用该主题。  
+
+```
+// 安装
+npm i @semi-bot/semi-theme-a11y
+```
+
+接入使用步骤参考 [定制主题](https://semi.design/zh-CN/start/customize-theme#%E6%8E%A5%E5%85%A5%E4%B8%BB%E9%A2%98)
 ## 了解用户需求
 
 要设计开发包容性产品,首先需要了解不同用户的不同需求并考虑他们使用的辅助工具和方法。

+ 16 - 1
content/start/changelog/index-en-US.md

@@ -16,6 +16,21 @@ Version:Major.Minor.Patch
 
 ---
 
+#### 🎉 2.13.0-beta.0 (2022-06-14)
+- 【Feat】
+    - `InputNumber` adds A11y keyboard and focus adaptation. which supports holding shift and up and down arrows at the same time in the input box to adjust a larger number range [#205](https://github.com/DouyinFE/semi-design/issues/205)
+    - `Checkbox` add A11y keyboard and focus adaptation  [#205](https://github.com/DouyinFE/semi-design/issues/205)
+- 【Fix】
+    - Fixed the problem that when Select filter and showClear are true, when clicking the clear icon, only onClear is triggered, but onSearch is not triggered, and after clearing the search item through the clear icon, the candidate Option list is not reset  [#867](https://github.com/DouyinFE/semi-design/issues/867)
+    - Fix the problem that onSearch is not triggered when Select filter is true, lose focus, and input input is reset automatically  [#867](https://github.com/DouyinFE/semi-design/issues/867)
+    - Fixed the issue that if InputNumber is set to require validation, validation will be triggered when it is initialized in the form, and the behavior is inconsistent with other field component [@rojer95](https://github.com/rojer95)
+    - Fixed AutoComplete defaultActiveFirstOption not taking effect in some scenarios [#892](https://github.com/DouyinFE/semi-design/issues/892)
+- 【Breaking Change】
+    - Adjust the default value of `InputNumber` shiftStep, from 1 to 10
+- 【Design Token】
+    - Update the color white in the style files of Avatar, Button, Radio, Steps, Switch, Tag and other components to --semi-white
+    - Button component adds text color token in borderless mode [#898](https://github.com/DouyinFE/semi-design/pull/898)
+
 #### 🎉 2.12.0 (2022-06-06)
 - 【Fix】
     - Fix the issue that the last item style is abnormal when Timeline is nested [#865](https://github.com/DouyinFE/semi-design/issues/865)
@@ -274,7 +289,7 @@ Version:Major.Minor.Patch
 - 【Feat】
     - Select supports autoClearSearchValue, allowing the current search keywords to be retained after selection Checklist
     - Slider add cursor grabbing style when drag Checklist
-    - 40+ components add accessibility semantic support [#205](https://github.com/DouyinFE/semi-design/issues/205)
+    - A11y: 40+ components add accessibility semantic support [#205](https://github.com/DouyinFE/semi-design/issues/205)
         - Button adds aria-label attribute, when disabled, Button has aria-disabled attribute
         - The role of Checkbox is checkbox, the role of CheckboxGroup is list, its direct child element is listitem, and the aria-label attribute is added to explain the function of the selection box; aria-disabled means the current disabled state; aria-checked means the current selected state
         - aria-hidden is true for Empty illustrations

+ 17 - 1
content/start/changelog/index.md

@@ -15,6 +15,22 @@ Semi 版本号遵循**Semver**规范(主版本号-次版本号-修订版本号
 
 ---
 
+#### 🎉 2.13.0-beta.0 (2022-06-14)
+- 【Feat】
+    - InputNumber 新增 A11y 键盘适配。支持在输入框同时按住 shift 和上下箭头调整较大的数字范围
+    - Checkbox 新增 A11y 键盘和焦点适配 [#205](https://github.com/DouyinFE/semi-design/issues/205)
+- 【Fix】
+    - 修复 Select filter 、showClear 为 true 时,点击 clear icon时,只触发 onClear,未触发 onSearch 的问题及通过 clear icon 清除搜索项后,未重置候选Option列表的问题  [#867](https://github.com/DouyinFE/semi-design/issues/867)
+    - 修复 Select filter 为true,失去焦点,input 输入被自动重置时,未触发 onSearch 的问题  [#867](https://github.com/DouyinFE/semi-design/issues/867)
+    - 修复 InputNumber 如果设置了必填验证, 在表单中初始化时会触发验证,行为与其他表单不一致的问题 [@rojer95](https://github.com/rojer95)
+    - 修复 AutoComplete defaultActiveFirstOption 某些场景不生效的问题 [#892](https://github.com/DouyinFE/semi-design/issues/892)
+- 【Breaking Change】
+    - InputNumber 调整 shiftStep 默认值,由 1 调整为 10
+- 【Design Token】
+    - 更新 Avatar、Button、Radio、Steps、Switch、Tag 等组件样式文件中的 color white 为 --semi-white
+    - Button 组件新增 borderless 模式下文字颜色 token [#898](https://github.com/DouyinFE/semi-design/pull/898)
+
+
 #### 🎉 2.12.0 (2022-06-06)
 - 【Fix】
     - 修复 Timeline 嵌套使用时最后一项样式异常的问题 [#865](https://github.com/DouyinFE/semi-design/issues/865)
@@ -284,7 +300,7 @@ Semi 版本号遵循**Semver**规范(主版本号-次版本号-修订版本号
 - 【Feat】
     - Select 支持autoClearSearchValue,允许选中后保留当前搜索关键字
     - Slider 拖动时添加鼠标 grabbing 样式
-    - 40+ 组件增加无障碍语义化支持 [#205](https://github.com/DouyinFE/semi-design/issues/205)
+    - A11y: 40+ 组件增加无障碍语义化支持 [#205](https://github.com/DouyinFE/semi-design/issues/205)
         - Button 新增 aria-label 属性,禁用时 Button 具有 aria-disabled 属性
         - Checkbox 的 role 为 checkbox,CheckboxGroup 的 role 为 list,它的直接子元素为 listitem,新增 aria-label 属性,用于解释选择框的作用;aria-disabled 表示当前的禁用状态;aria-checked 表示当前的选中状态
         - Empty 插图的 aria-hidden 为 true

+ 39 - 0
cypress/integration/checkbox.spec.js

@@ -0,0 +1,39 @@
+describe('checkbox', () => {
+    it('checkbox tab test', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=checkbox--checkbox-group-demo&args=&viewMode=story');
+        cy.get('.semi-checkbox').eq(0).click();
+        cy.focused().tab();
+        cy.focused().type('{backspace}');
+        cy.get('.semi-checkbox').eq(1).get('.semi-checkbox-checked');
+        cy.focused().type('{backspace}');
+        cy.get('.semi-checkbox').eq(1).get('.semi-checkbox-unChecked');
+    });
+
+    it('checkbox disable', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=checkbox--checkbox-default&args=&viewMode=story');
+        cy.get('.semi-checkbox').eq(0).click();
+        cy.focused().tab();
+        cy.focused().tab();
+        cy.focused().tab();
+        cy.get('.semi-checkbox-inner-display').eq(4).get('.semi-checkbox-focus');
+    });
+
+    it('checkbox card', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=checkbox--checkbox-group-card-style&args=&viewMode=story');
+        cy.get('.semi-checkbox').eq(0).click();
+        cy.focused().tab();
+        cy.get('.semi-checkbox').eq(1).get('.semi-checkbox-focus');
+        cy.get('.semi-checkbox-focus').eq(0).type('{backspace}');
+        cy.get('.semi-checkbox-inner-display').eq(1).get('.semi-icon-checkbox_tick');
+    });
+
+    it('checkbox pureCard', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=checkbox--checkbox-group-pure-card-style&args=&viewMode=story');
+        cy.get('.semi-checkbox').eq(0).click();
+        cy.focused().tab();
+        cy.get('.semi-checkbox').eq(1).get('.semi-checkbox-focus');
+        cy.get('.semi-checkbox-focus').eq(0).type('{backspace}');
+        cy.get('.semi-checkbox-inner-display').eq(1).get('.semi-icon-checkbox_tick');
+    });
+
+});

+ 20 - 1
cypress/integration/inputNumber.spec.js

@@ -12,5 +12,24 @@ describe('inputNumber', () => {
         cy.get('[data-cy=fix-precision-786] .semi-input').type('aaa');
         cy.get('[data-cy=fix-precision-786] .semi-input').blur();
         cy.get('[data-cy=fix-precision-786] .semi-input').should('have.value', '');
-    })
+    });
+
+    it('a11y', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=inputnumber--input-number-a-11-y&args=&viewMode=story');
+        cy.get('input[data-cy=default]').click();
+        cy.get('input[data-cy=default]').type('{upArrow}');
+        cy.get('input[data-cy=default]').should('have.value', '1');
+        cy.get('input[data-cy=default]').trigger('keydown', { eventConstructor: 'KeyboardEvent', key: 'upArrow', keyCode: 38, shiftKey: true });
+        cy.get('input[data-cy=default]').should('have.value', '11');
+        cy.get('input[data-cy=default]').type('{downArrow}');
+        cy.get('input[data-cy=default]').should('have.value', '10');
+
+        cy.get('input[data-cy=step]').click();
+        cy.get('input[data-cy=step]').type('{upArrow}');
+        cy.get('input[data-cy=step]').should('have.value', '5');
+        cy.get('input[data-cy=step]').trigger('keydown', { eventConstructor: 'KeyboardEvent', key: 'upArrow', keyCode: 38, shiftKey: true });
+        cy.get('input[data-cy=step]').should('have.value', '105');
+        cy.get('input[data-cy=step]').trigger('keydown', { eventConstructor: 'KeyboardEvent', key: 'downArrow', keyCode: 40, shiftKey: true });
+        cy.get('input[data-cy=step]').should('have.value', '5');
+    });
 });

+ 16 - 0
cypress/integration/navigation.spec.js

@@ -0,0 +1,16 @@
+describe('navigation', () => {
+    it('danamic change', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=navigation--items-change-demo&viewMode=story');
+
+        cy.get('span').contains('用户管理').should('exist');
+        cy.get('button').contains('change items').click();
+        cy.get('span').contains('用户管理').should('not.exist');
+    });
+
+    it('auto open', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=navigation--auto-open&viewMode=story');
+
+        cy.get('span').contains('人员管理').should('exist');
+    });
+
+});

+ 36 - 1
cypress/integration/table.spec.js

@@ -72,5 +72,40 @@ describe('table', () => {
         cy.visit('http://localhost:6006/iframe.html?id=table--fixed-on-header-row&args=&viewMode=story');
         cy.contains('标题').click();
         cy.contains('header click').should('be.visible');
-    })
+    });
+
+    it('infinite scroll', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=table--infinite-scroll-demo&args=&viewMode=story');
+        // fixed the viewport
+        cy.viewport(1000, 660);
+
+        // the virtualied table should not load all table item
+        cy.get('div[role="row"]').should('have.length.below', 30);
+
+        // test the scroll in virtualized table 
+        cy.get('.semi-table-body').scrollTo('bottom');
+        // Wait for the scroll result to take effect
+        cy.wait(500);
+        cy.get('div[role="gridcell"]').contains('Edward King 14');
+        cy.get('.semi-table-body').scrollTo('bottom');
+        cy.wait(500);
+        cy.get('div[role="row"]').contains('Edward King 34');
+        cy.get('.semi-table-body').scrollTo('top');
+        cy.wait(500);
+        cy.get('div[role="row"]').contains('Edward King 0');
+    });
+
+    it.skip('scrollToFirstRowOnChange', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=table--virtualized&args=&viewMode=story');
+        cy.get('.semi-table-body').scrollTo(0, 150);
+        cy.get('div[role="button"]').click();
+        cy.get('div[role="row"]').contains('Edward King 9983');
+    });
+
+    it('resize', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=table--dynamic-table&args=&viewMode=story');
+        cy.viewport(1000, 660);
+        cy.get('.semi-switch').eq(0).click();
+        cy.get('.semi-table-body').should('have.css', "max-height", "300px");
+    });
 });

+ 46 - 0
cypress/integration/tabs.spec.js

@@ -36,4 +36,50 @@ describe('tabs', () => {
         cy.get('.semi-tabs-tab').contains('Tab-10').click({ force: true });
         cy.get('.semi-tabs-content').eq(0).contains('Content of card tab 10');
     });
+
+    it('keyboard test when the tabs is horizontal', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=tabs--disabled-tab&args=&viewMode=story');
+        cy.get('[id=semiTab2]').click();
+        cy.get('[id=semiTab2]').should('be.focused');
+
+        cy.get('[id=semiTab2]').type('{rightArrow}');
+        cy.get('[id=semiTab5]').should('be.focused');
+
+        cy.get('[id=semiTab5]').type('{home}');
+        cy.get('[id=semiTab1]').should('be.focused');
+
+        cy.get('[id=semiTab1]').type('{leftArrow}');
+        cy.get('[id=semiTab5]').should('be.focused');
+
+        cy.get('[id=semiTab5]').type('{rightArrow}');
+        cy.get('[id=semiTab1]').should('be.focused');
+
+        cy.get('[id=semiTab1]').type('{end}');
+        cy.get('[id=semiTab5]').should('be.focused');
+
+        cy.get('[id=semiTab5]').type('{enter}');
+        cy.get('[id=semiTab5]').tab();
+        cy.get("[id=semiTabPanel5]").should('be.focused');
+    });
+
+    it('keyboard test when the tabs is vertical', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=tabs--vertical-tabs&args=&viewMode=story');
+        cy.get('[id=semiTab1]').click();
+        cy.get('[id=semiTab1]').should('be.focused');
+
+        cy.get('[id=semiTab1]').type('{downArrow}');
+        cy.get('[id=semiTab2]').should('be.focused');
+
+        cy.get('[id=semiTab2]').type('{upArrow}');
+        cy.get('[id=semiTab1]').should('be.focused');
+    });
+
+    it('keyboard test when the tabs is closable', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=tabs--tab-closable&args=&viewMode=story');
+        cy.get('[id=semiTab1]').click();
+        cy.get('[id=semiTab1]').should('be.focused');
+
+        cy.get('[id=semiTab1]').type('{backspace}');
+        cy.get('[id=semiTab1]').should('not.exist');
+    });
 });

+ 33 - 0
cypress/integration/tag.spec.js

@@ -0,0 +1,33 @@
+describe('tag', () => {
+    it('tag group with max count', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=tag--tag-group&args=&viewMode=story');
+
+        // focus and esc
+        cy.get('.semi-tag-content').eq(3).click();
+        cy.get('.semi-portal');
+    });
+
+    it('keyboard test', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=tag--tag-avatar&args=&viewMode=story', {
+            onBeforeLoad(win) {
+                cy.stub(win.console, 'log').as('consoleLog'); // 测试时用到控制台的前置步骤
+            },
+        });
+
+        // focus + esc + enter
+        cy.get('body').tab(); // 按下tab键
+        cy.get('.semi-tag').eq(0).should('be.focused'); // 第一个tag应该被focus
+
+        cy.get('.semi-tag').eq(0).type('{enter}'); // 在第一个tag上按enter
+        cy.get('@consoleLog').should('be.calledWith', '如果能重来,我要做李白'); // 控制台应该打印“如果能重来,我要做李白”
+        cy.get('.semi-tag').eq(0).type('{esc}'); // 在第一个tag上按ESC
+        cy.get('.semi-tag').eq(0).should('not.be.focused'); // 第一个tag应该没有被focus
+
+        // backspace
+        cy.get('.semi-tag').eq(0).tab();
+        cy.get('.semi-tag').eq(4).should('be.focused');
+        cy.get('.semi-tag').eq(4).type('{backspace}'); // 在第5个tag上按backspace
+        cy.get('.semi-tag').eq(4).get('.semi-tag-invisible'); // 第5个tag上应该被隐藏
+
+    });
+});

+ 49 - 0
cypress/integration/upload.spec.js

@@ -18,4 +18,53 @@ describe('upload', () => {
         cy.get('.semi-upload-file-card-info-main-text').contains('README.md');
     });
 
+    it('replace', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=upload--test-replace-func&args=&viewMode=story', {
+            onBeforeLoad(win) {
+                cy.stub(win.console, 'log').as('consoleLog'); // 测试时用到控制台的前置步骤
+            },
+        });
+
+        // not an acceptable type
+        cy.get('input[type=file]').eq(3).selectFile('README.md', { force: true });
+
+        // not an acceptable size
+        cy.get('input[type=file]').eq(5).selectFile('README.md', { force: true });
+        cy.get('div').not('.semi-upload-file-list');
+
+        cy.get('input[type=file]').eq(1).selectFile('README.md', { force: true });
+        
+        // assert
+        cy.get('.semi-upload').eq(1).get('div').not('.semi-upload-file-list');
+        cy.get('@consoleLog').should('be.calledWith', 'onSizeError');
+        cy.get('.semi-upload-file-card-info-main-text').contains('README.md');
+    });
+
+    it('ref method insert', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=upload--insert&args=&viewMode=story',  {
+            onBeforeLoad(win) {
+                cy.stub(win.console, 'log').as('consoleLog'); // 测试时用到控制台的前置步骤
+            },
+        });
+
+        cy.get('input[type=file]').eq(1).selectFile('README.md', { force: true });
+        cy.get('input[type=file]').eq(1).selectFile('README.md', { force: true });
+
+
+        // test file number limit
+        cy.get('span').contains('插入首项上传1').click();
+        cy.get('span').contains('插入首项上传1').click();
+        cy.get('.semi-upload-file-list').get('div[role="listitem"]').should('have.length', 1);
+
+        // test file number limit
+        cy.get('span').contains('插入首项上传2').click();
+        cy.get('span').contains('插入首项上传2').click();
+        cy.get('.semi-upload-file-list').get('div[role="listitem"]').should('have.length', 3);
+
+        // test size limit
+        cy.get('span').contains('插入首项上传3').click();
+        cy.get('@consoleLog').should('be.calledWith', 'onSizeError');
+        
+    });
+
 });

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.12.0"
+    "version": "2.13.0-beta.0"
 }

+ 5 - 4
package.json

@@ -12,10 +12,10 @@
     "bootstrap": "lerna bootstrap",
     "docsite": "npm run develop",
     "pre-develop": "npm run scripts:changelog && node ./scripts/designToken.js ./static/designToken.json",
-    "develop": "npm run pre-develop && gatsby clean && lerna run build:lib --scope=@douyinfe/semi-webpack-plugin && gatsby develop -H 0.0.0.0 --port=3666 --verbose",
+    "develop": "npm run pre-develop && gatsby clean && lerna run build:lib --scope @douyinfe/semi-webpack-plugin --scope eslint-plugin-semi-design && gatsby develop -H 0.0.0.0 --port=3666 --verbose",
     "scripts:changelog": "node scripts/changelog.js",
     "start": "npm run story",
-    "pre-story": "lerna exec --scope=@douyinfe/semi-ui --scope=@douyinfe/semi-foundation -- rm -rf ./lib && lerna run build:lib --scope=@douyinfe/semi-webpack-plugin",
+    "pre-story": "lerna exec --scope=@douyinfe/semi-ui --scope=@douyinfe/semi-foundation -- rm -rf ./lib && lerna run build:lib --scope @douyinfe/semi-webpack-plugin --scope eslint-plugin-semi-design",
     "story": "npm run pre-story && start-storybook -c ./.storybook/js/ -p 6006",
     "story:ts": "npm run pre-story && && start-storybook -c ./.storybook/ts/ -p 6007",
     "story:ani": "npm run pre-story && && start-storybook -c ./.storybook/animation/react -p 6008",
@@ -34,7 +34,7 @@
     "build:js": "lerna run build:js",
     "build:css": "lerna run build:css",
     "build-storybook": "build-storybook  -c ./.storybook/js/",
-    "build:gatsbydoc": "lerna run build:lib --scope=@douyinfe/semi-webpack-plugin && cross-env NODE_ENV=production node --max_old_space_size=16384 ./node_modules/gatsby/cli.js build --prefix-paths --verbose && rm -rf build && mv public build",
+    "build:gatsbydoc": "lerna run build:lib --scope @douyinfe/semi-webpack-plugin --scope eslint-plugin-semi-design && cross-env NODE_ENV=production node --max_old_space_size=16384 ./node_modules/gatsby/cli.js build --prefix-paths --verbose && rm -rf build && mv public build",
     "build:icon": "lerna run build:icon --scope='@douyinfe/semi-{icons,illustrations}'",
     "cypress:coverage": "npx wait-on http://127.0.0.1:6006 && ./node_modules/.bin/cypress run",
     "postcypress:coverage": "yarn coverage:merge",
@@ -166,6 +166,7 @@
     "eslint-plugin-markdown": "^2.2.1",
     "eslint-plugin-react": "^7.24.0",
     "eslint-plugin-react-hooks": "^4.2.0",
+    "eslint-plugin-semi-design": "^0.0.1",
     "fs-extra": "^8.1.0",
     "glob": "^7.1.7",
     "html-webpack-plugin": "^3.2.0",
@@ -196,7 +197,7 @@
     "svgo": "^2.7.0",
     "terser-webpack-plugin": "^4.2.3",
     "ts-loader": "^5.4.5",
-    "typescript": "^4.4.3",
+    "typescript": "4.4.3",
     "webpack": "^4.46.0",
     "webpack-cli": "^3.3.12",
     "webpack-dev-server": "^3.11.2",

+ 1 - 1
packages/semi-animation-react/README.md

@@ -28,7 +28,7 @@ export default function App() {
         enter={{ opacity: 1, scale: 1 }}
         leave={{ opacity: 0, scale: 0 }}
       >
-        {({scale, opacity}: any) => (
+        {({ scale, opacity }: any) => (
           <h2 style={{transform: `scale(${scale})`, opacity}}>
             Toggle to see some animation happen!
           </h2>

+ 2 - 2
packages/semi-animation-react/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-react",
-  "version": "2.12.0",
+  "version": "2.13.0-beta.0",
   "description": "motion library for semi-ui-react",
   "keywords": [
     "motion",
@@ -27,7 +27,7 @@
   "dependencies": {
     "@babel/runtime-corejs3": "^7.15.4",
     "@douyinfe/semi-animation": "2.12.0",
-    "@douyinfe/semi-animation-styled": "2.12.0",
+    "@douyinfe/semi-animation-styled": "2.13.0-beta.0",
     "classnames": "^2.2.6"
   },
   "peerDependencies": {

+ 1 - 1
packages/semi-animation-styled/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-styled",
-  "version": "2.12.0",
+  "version": "2.13.0-beta.0",
   "description": "semi styled animation",
   "keywords": [
     "semi",

+ 1 - 1
packages/semi-animation/README.md

@@ -16,7 +16,7 @@ npm install @douyinfe/semi-animation
 
 `semi-animation` provides a class called  `Animation` . It has a complete life cycle hook and control method to support operating animation like audio and video.
 
--   Use in JS
+-  Use in JS
 
 ```js
 import { Animation } from '@douyinfe/semi-animation';

+ 1 - 1
packages/semi-animation/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation",
-  "version": "2.12.0",
+  "version": "2.13.0-beta.0",
   "description": "animation base library for semi-ui",
   "keywords": [
     "animation",

+ 6 - 0
packages/semi-eslint-plugin/.eslintrc.json

@@ -0,0 +1,6 @@
+{
+    "parserOptions": {
+        "ecmaVersion": "latest",
+        "sourceType": "module"
+    }
+}

+ 55 - 0
packages/semi-eslint-plugin/README-zh_CN.md

@@ -0,0 +1,55 @@
+# eslint-plugin-semi-design
+
+Semi 仓库使用的 eslint 插件
+
+## eslint 规则
+
+### ✅ 不能在 semi-foundation 里引用 semi-ui
+
+semi-ui 不应该作为 semi-foundation 的依赖。
+
+原因:根据 Semi 的 foundation 和 adapter 设计,foundation 不应依赖 adapter。点击查看 [F/A 设计](https://bytedance.feishu.cn/wiki/wikcnOVYexosCS1Rmvb5qCsWT1f)。
+
+### ✅ 不能在 semi-ui 和 semi-foundation 引用 lodash-es
+
+使用 lodash 而不是 lodash-es。
+
+原因:为了兼容 next,而 lodash-es 只提供了 es module 的产物。
+
+![image](https://user-images.githubusercontent.com/26477537/172051379-30b42f31-b677-43be-982f-1e8f5345cfc9.png)
+
+点击查看[详情](https://github.com/vercel/next.js/issues/2259)。
+
+### ✅ 不能在 semi-ui 或 semi-foundation 使用相对路径引用 pacakges 下的包
+
+monorepo 下各个包之间的 import 请使用包名而不是相对路径。
+
+原因:这两个包在用户项目的安装路径可能不在同一文件夹下,使用相对路径会找不到对应的包。
+
+```javascript
+// ❌ 不推荐
+// semi-ui/input/index.tsx
+import inputFoundation from '../semi-foundation/input/foundation';
+
+// ✅ 推荐
+// semi-ui/input/index.tsx
+import inputFoundation from '@douyinfe/semi-foundation/input/foundation';
+```
+
+### ✅ 不推荐在同个包下使用包名加路径引用其他模块
+
+同一个包 import 请使用相对路径而不是引用包名。
+
+```javascript
+// ❌ 不推荐
+// semi-ui/modal/Modal.tsx
+import { Button } from '@douyinfe/semi-ui';
+
+// ✅ 推荐
+// semi-ui/modal/Modal.tsx
+import Button from '../button';
+```
+
+## 相关资料
+
+- eslint plugin 文档:https://eslint.org/docs/developer-guide/working-with-plugins

+ 55 - 0
packages/semi-eslint-plugin/README.md

@@ -0,0 +1,55 @@
+# eslint-plugin-semi-design
+
+eslint plugin for semi design
+
+## Rules
+
+### ✅ Should not reference semi-ui in semi-foundation
+
+semi-ui should not be used as a dependency of semi-foundation.
+
+Why: According to Semi's foundation and adapter design, foundation should not depend on adapter. Click to view the [F/A design](https://bytedance.feishu.cn/wiki/wikcnOVYexosCS1Rmvb5qCsWT1f).
+
+### ✅ Should not import lodash-es in semi-ui and semi-foundation
+
+Use lodash instead of lodash-es.
+
+Why: In order to be compatible with next, lodash-es only provides the product of es module.
+
+![image](https://user-images.githubusercontent.com/26477537/172051379-30b42f31-b677-43be-982f-1e8f5345cfc9.png)
+
+See more [here](https://github.com/vercel/next.js/issues/2259)。
+
+### ✅ Should not use relative paths to import a package under pacakges in semi-ui or semi-foundation
+
+For imports between packages under monorepo, use package names instead of relative paths.
+
+Why: These two packages may not be in the same folder in the installation path of the user project, and the corresponding package cannot be found using the relative path.
+
+```javascript
+// ❌ Not recommend
+// semi-ui/input/index.tsx
+import inputFoundation from '../semi-foundation/input/foundation';
+
+// ✅ Recommend
+// semi-ui/input/index.tsx
+import inputFoundation from '@douyinfe/semi-foundation/input/foundation';
+```
+
+### ✅ Should not use the package name and path to import other modules under the same package
+When importing the same package, use relative paths instead of referencing the package name.
+
+```javascript
+// ❌ Not recommend
+// semi-ui/modal/Modal.tsx
+import { Button } from '@douyinfe/semi-ui';
+
+// ✅ Recommend
+// semi-ui/modal/Modal.tsx
+import Button from '../button';
+
+```
+
+## Related docs
+
+- eslint plugin doc:https://eslint.org/docs/developer-guide/working-with-plugins

+ 44 - 0
packages/semi-eslint-plugin/__tests__/index.js

@@ -0,0 +1,44 @@
+const rule = require('../lib/rules/index').default;
+const RuleTester = require('eslint').RuleTester;
+const eslintConfig = require('../.eslintrc.json');
+
+const ruleTester = new RuleTester({ parserOptions: eslintConfig.parserOptions });
+const { messages } = rule['no-import'].meta;
+
+ruleTester.run('no-import', rule['no-import'], {
+    valid: [
+        {
+            code: 'var invalidVariable = true',
+        }
+    ],
+    invalid: [
+        {
+            code: "import Input from '@douyinfe/semi-ui'",
+            filename: 'packages/semi-foundation/input/foundation.ts',
+            errors: [{ message: messages.unexpected }]
+        },
+        {
+            code: "import { get } from 'lodash-es'",
+            filename: 'packages/semi-foundation/input/foundation.ts',
+            output: "import { get } from 'lodash'",
+            errors: [{ message: messages.unexpectedLodashES }]
+        },
+        {
+            code: "import get from 'lodash-es/get'",
+            filename: 'packages/semi-ui/input/index.tsx',
+            output: "import get from 'lodash/get'",
+            errors: [{ message: messages.unexpectedLodashES }]
+        },
+        {
+            code: "import inputNumberFoundation from '../../semi-foundation/inputNumber/foundation.ts'",
+            filename: 'packages/semi-ui/inputNumber/index.tsx',
+            output: "import inputNumberFoundation from '@douyinfe/semi-foundation/inputNumber/foundation.ts'",
+            errors: [{ message: messages.unexpectedRelativeImport }]
+        },
+        {
+            code: "import Input from '@douyinfe/semi-ui/input/index.tsx'",
+            filename: 'packages/semi-ui/inputNumber/index.tsx',
+            errors: [{ message: messages.unexpectedImportSelf }]
+        },
+    ]
+});

+ 43 - 0
packages/semi-eslint-plugin/package.json

@@ -0,0 +1,43 @@
+{
+  "name": "eslint-plugin-semi-design",
+  "version": "2.13.0-beta.0",
+  "description": "semi ui eslint plugin",
+  "keywords": [
+    "semi",
+    "eslint"
+  ],
+  "author": "shijia.me <[email protected]>",
+  "homepage": "https://semi.design",
+  "license": "MIT",
+  "main": "lib/index.js",
+  "directories": {
+    "lib": "lib",
+    "test": "__tests__"
+  },
+  "files": [
+    "lib",
+    "README.md",
+    "README-zh_CN.md"
+  ],
+  "publishConfig": {
+    "registry": "https://registry.npmjs.org"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/DouyinFE/semi-design.git"
+  },
+  "scripts": {
+    "build:lib": "rm -rf lib && tsc",
+    "prepublishOnly": "npm run build:lib",
+    "test": "node __tests__/index.js"
+  },
+  "devDependencies": {
+    "typescript": "^4"
+  },
+  "peerDependencies": {
+    "eslint": ">=0.8.0"
+  },
+  "bugs": {
+    "url": "https://github.com/DouyinFE/semi-design/issues"
+  }
+}

+ 5 - 0
packages/semi-eslint-plugin/src/index.ts

@@ -0,0 +1,5 @@
+import rules from './rules';
+
+export {
+    rules
+};

+ 5 - 0
packages/semi-eslint-plugin/src/rules/index.ts

@@ -0,0 +1,5 @@
+import noImport from './no-import';
+
+export default {
+    'no-import': noImport,
+};

+ 91 - 0
packages/semi-eslint-plugin/src/rules/no-import.ts

@@ -0,0 +1,91 @@
+import { Rule } from "eslint";
+
+const SEMI_PACKAGE_REG = /(?<=packages\/|@douyinfe\/|\.\.\/)(semi-[\w-]+)/;
+const RELATIVE_PATH_REG = /(..\/)+semi-[\w-]+/;
+
+const rule: Rule.RuleModule = {
+    meta: {
+        type: "problem",
+        docs: {
+            description: "disable import statement",
+            recommended: true,
+            url: "https://github.com/DouyinFE/semi-design"
+        },
+        fixable: "code",
+        messages: {
+            unexpected: "Unexpected import statement, semi ui should not be used as a dependency of semi foundation",
+            unexpectedLodashES: "Unexpected import statement, please use lodash instead of lodash-es.",
+            unexpectedRelativeImport: "Unexpected import statement, please use module name instead of relative path.",
+            unexpectedImportSelf: 'Unexpected import statement, please use relative paths to import modules in the same package.'
+        },
+        schema: [],
+    },
+    create(context) {
+        return {
+            ImportDeclaration: (node) => {
+                const fileName = context.getFilename();
+                const sourceCode = context.getSourceCode();
+                const importName = node.source.raw;
+                const isFoundationFile = fileName.includes('semi-foundation');
+                const isUIFile = fileName.includes('semi-ui');
+                const importText = sourceCode.getText(node);
+
+                if (isFoundationFile) {
+                    if (importName.includes('semi-ui')) {
+                        context.report({ node, messageId: "unexpected" });
+                    }
+                }
+
+                if (isFoundationFile || isUIFile) {
+                    if (importName.includes('lodash-es')) {
+                        const fixedSource = importText.replace('lodash-es', 'lodash');
+                        context.report({
+                            node,
+                            messageId: "unexpectedLodashES",
+                            fix: (fixer) => {
+                                return fixer.replaceText(node, fixedSource);
+                            }
+                        });
+                    } else if (importName.includes('semi-')) {
+                        if (isImportRelativePackage({ path: importName, fileName })) {
+                            const importPackageName = SEMI_PACKAGE_REG.exec(importName)[0];
+                            const fixedSource = importText.replace(RELATIVE_PATH_REG, `@douyinfe/${importPackageName}`);
+                            context.report({
+                                node,
+                                messageId: "unexpectedRelativeImport",
+                                fix: (fixer) => {
+                                    return fixer.replaceText(node, fixedSource);
+                                }
+                            });
+                        } else if (isImportSelf({ path: importName, fileName })) {
+                            context.report({
+                                node,
+                                messageId: "unexpectedImportSelf",
+                            });
+                        }
+                    }
+                }
+            }
+        };
+    }
+};
+
+function isRelativePath(path: string) {
+    return path.includes('../');
+}
+
+function isImportRelativePackage(options: { path: string, fileName: string }) {
+    const { path, fileName } = options;
+    const currentPackageName = SEMI_PACKAGE_REG.exec(fileName)[0];
+    const importPackageName = SEMI_PACKAGE_REG.exec(path)[0];
+    return currentPackageName !== importPackageName && isRelativePath(path);
+}
+
+function isImportSelf(options: { path: string, fileName: string }) {
+    const { path, fileName } = options;
+    const currentPackageName = SEMI_PACKAGE_REG.exec(fileName)[0];
+    const importPackageName = SEMI_PACKAGE_REG.exec(path)[0];
+    return currentPackageName === importPackageName;
+}
+
+export default rule;

+ 12 - 0
packages/semi-eslint-plugin/tsconfig.json

@@ -0,0 +1,12 @@
+{
+    "compilerOptions": {
+        "target": "ES6",
+        "module": "CommonJS",
+        "baseUrl": "./",
+        "outDir": "./lib",
+        "lib": ["ES2015"],
+        "moduleResolution": "node",
+        "skipLibCheck": true
+    },
+    "include": ["src"]
+}

+ 1 - 1
packages/semi-foundation/avatar/mixin.scss

@@ -1,4 +1,4 @@
 @mixin avatar-style($color) {
     background-color: rgba(var(--semi-#{$color}-3), 1);
-    color: white;
+    color: rgba(var(--semi-white), 1);
 }

+ 4 - 4
packages/semi-foundation/button/button.scss

@@ -47,7 +47,7 @@ $module: #{$prefix}-button;
         }
         &.#{$module}-light,
         &.#{$module}-borderless {
-            color: $color-button_danger-bg-default;
+            color: $color-button_danger_borderless-text-default;
         }
         &:not(.#{$module}-borderless):not(.#{$module}-light):focus-visible {
             outline: $width-button-outline solid $color-button_danger-outline-focus;
@@ -64,7 +64,7 @@ $module: #{$prefix}-button;
         }
         &.#{$module}-light,
         &.#{$module}-borderless {
-            color: $color-button_warning-bg-default;
+            color: $color-button_warning_borderless-text-default;
         }
         &:not(.#{$module}-borderless):not(.#{$module}-light):focus-visible {
             outline: $width-button-outline solid $color-button_warning-outline-focus;
@@ -96,7 +96,7 @@ $module: #{$prefix}-button;
 
         &.#{$module}-light,
         &.#{$module}-borderless {
-            color: $color-button_primary-bg-default;
+            color: $color-button_primary_borderless-text-default;
         }
     }
     &-secondary {
@@ -112,7 +112,7 @@ $module: #{$prefix}-button;
 
         &.#{$module}-light,
         &.#{$module}-borderless {
-            color: $color-button_secondary-bg-default;
+            color: $color-button_secondary_borderless-text-default;
         }
     }
     &-disabled {

+ 27 - 19
packages/semi-foundation/button/variables.scss

@@ -1,70 +1,78 @@
 // primary
 $color-button_primary-bg-default: var(--semi-color-primary); // 主要按钮背景颜色
 $color-button_primary-border-default: transparent; // 主要按钮描边颜色
-$color-button_primary-text-default: white; // 主要按钮文字颜色
+$color-button_primary-text-default: rgba(var(--semi-white), 1); // 主要按钮文字颜色
 
 $color-button_primary-bg-hover: var(--semi-color-primary-hover); // 主要按钮背景颜色 - 悬浮
 $color-button_primary-border-hover: var(--semi-color-primary-hover); // 主要按钮描边颜色 - 悬浮
-$color-button_primary-text-hover: white; // 主要按钮文字颜色 - 悬浮
+$color-button_primary-text-hover: rgba(var(--semi-white), 1); // 主要按钮文字颜色 - 悬浮
 
 $color-button_primary-bg-active: var(--semi-color-primary-active); // 主要按钮背景颜色 - 按下
 $color-button_primary-border-active: var(--semi-color-primary-active); // 主要按钮描边颜色 - 按下
-$color-button_primary-text-active: white; // 主要按钮文字颜色 - 按下
+$color-button_primary-text-active: rgba(var(--semi-white), 1); // 主要按钮文字颜色 - 按下
 $color-button_primary-outline-focus: var(--semi-color-primary-light-active); // 主要按钮轮廓 - 聚焦
 
+$color-button_primary_borderless-text-default: var(--semi-color-primary); // 主要按钮无边框文字颜色
+
 // secondary
 $color-button_secondary-bg-default: var(--semi-color-secondary); // 次要按钮背景颜色
 $color-button_secondary-border-default: var(--semi-color-secondary); // 次要按钮描边颜色
-$color-button_secondary-text-default: white; // 次要按钮文字颜色
+$color-button_secondary-text-default: rgba(var(--semi-white), 1); // 次要按钮文字颜色
 
 $color-button_secondary-bg-hover: var(--semi-color-secondary-hover); // 次要按钮背景颜色 - 悬浮
 $color-button_secondary-border-hover: var(--semi-color-secondary-hover); // 次要按钮描边颜色 - 悬浮
-$color-button_secondary-text-hover: white; // 次要按钮文字颜色 - 悬浮
+$color-button_secondary-text-hover: rgba(var(--semi-white), 1); // 次要按钮文字颜色 - 悬浮
 
 $color-button_secondary-bg-active: var(--semi-color-secondary-active); // 次要按钮背景颜色 - 按下
 $color-button_secondary-border-active: var(--semi-color-secondary-active); // 次要按钮描边颜色 - 按下
-$color-button_secondary-text-active: white; // 次要按钮文字颜色 - 按下
+$color-button_secondary-text-active: rgba(var(--semi-white), 1); // 次要按钮文字颜色 - 按下
+
+$color-button_secondary_borderless-text-default: var(--semi-color-secondary); // 次要按钮无边框文字颜色
 
 // danger
 $color-button_danger-bg-default: var(--semi-color-danger); // 危险按钮背景颜色
 $color-button_danger-border-default: var(--semi-color-danger); // 危险按钮描边颜色
-$color-button_danger-text-default: white; // 危险按钮文字颜色
+$color-button_danger-text-default: rgba(var(--semi-white), 1); // 危险按钮文字颜色
 
 $color-button_danger-bg-hover: var(--semi-color-danger-hover); // 危险按钮背景颜色 - 悬浮
 $color-button_danger-border-hover: var(--semi-color-danger); // 危险按钮描边颜色 - 悬浮
-$color-button_danger-text-hover: white; // 危险按钮文字颜色 - 悬浮
+$color-button_danger-text-hover: rgba(var(--semi-white), 1); // 危险按钮文字颜色 - 悬浮
 
 $color-button_danger-bg-active: var(--semi-color-danger-active); // 危险按钮背景颜色 - 按下
 $color-button_danger-border-active: var(--semi-color-danger-active); // 危险按钮描边颜色 - 按下
-$color-button_danger-text-active: white; // 危险按钮文字颜色 - 按下
+$color-button_danger-text-active: rgba(var(--semi-white), 1); // 危险按钮文字颜色 - 按下
 $color-button_danger-outline-focus: var(--semi-color-danger-light-active); // 危险按钮轮廓 - 聚焦
 
+$color-button_danger_borderless-text-default: var(--semi-color-danger); // 危险按钮无边框文字颜色
+
 // warning
 $color-button_warning-bg-default: var(--semi-color-warning); // 警告按钮背景颜色
 $color-button_warning-border-default: var(--semi-color-warning); // 警告按钮描边颜色
-$color-button_warning-text-default: white; // 警告按钮文字颜色
+$color-button_warning-text-default: rgba(var(--semi-white), 1); // 警告按钮文字颜色
 
 $color-button_warning-bg-hover: var(--semi-color-warning-hover); // 警告按钮背景颜色 - 悬浮
 $color-button_warning-border-hover: var(--semi-color-warning-hover); // 警告按钮描边颜色 - 悬浮
-$color-button_warning-text-hover: white; // 警告按钮文字颜色 - 悬浮
+$color-button_warning-text-hover: rgba(var(--semi-white), 1); // 警告按钮文字颜色 - 悬浮
 
 $color-button_warning-bg-active: var(--semi-color-warning-active); // 警告按钮背景颜色 - 按下
 $color-button_warning-border-active: var(--semi-color-warning-active); // 警告按钮描边颜色 - 按下
-$color-button_warning-text-active: white; // 警告按钮文字颜色 - 按下
+$color-button_warning-text-active: rgba(var(--semi-white), 1); // 警告按钮文字颜色 - 按下
 $color-button_warning-outline-focus: var(--semi-color-warning-light-active); // 警告按钮轮廓 - 聚焦
 
+$color-button_warning_borderless-text-default: var(--semi-color-warning); // 警告按钮无边框文字颜色
+
 // tertiary
 $color-button_tertiary-bg-default: var(--semi-color-tertiary); // 第三按钮背景颜色
 $color-button_tertiary-border-default: var(--semi-color-tertiary); // 第三按钮描边颜色
-$color-button_tertiary-text-default: white; // 第三按钮文字颜色
+$color-button_tertiary-text-default: rgba(var(--semi-white), 1); // 第三按钮文字颜色
 
 $color-button_tertiary-bg-hover: var(--semi-color-tertiary-hover); // 第三按钮背景颜色 - 悬浮
 $color-button_tertiary-border-hover: var(--semi-color-tertiary-hover); // 第三按钮描边颜色 - 悬浮
-$color-button_tertiary-text-hover: white; // 第三按钮文字颜色 - 悬浮
+$color-button_tertiary-text-hover: rgba(var(--semi-white), 1); // 第三按钮文字颜色 - 悬浮
 
 $color-button_tertiary-bg-active: var(--semi-color-tertiary-active); // 第三按钮背景颜色 - 按下
 $color-button_tertiary-border-active: var(--semi-color-tertiary-active); // 第三按钮描边颜色 - 按下
-$color-button_tertiary-text-active: white; // 第三按钮文字颜色 - 按下
+$color-button_tertiary-text-active: rgba(var(--semi-white), 1); // 第三按钮文字颜色 - 按下
 
 $color-button_tertiary_solid-text-default: var(--semi-color-text-1); // 浅色第三按钮文字颜色
 
@@ -79,10 +87,10 @@ $color-button_disabled-bg-hover: var(--semi-color-disabled-bg); // 禁用按钮
 $color-button_light-bg-default: var(--semi-color-fill-0); // 浅色按钮背景颜色
 $color-button_light-bg-hover: var(--semi-color-fill-1); // 禁用按钮背景颜色 - 悬浮
 $color-button_light-bg-active: var(--semi-color-fill-2); // 禁用按钮背景颜色 - 按下
-$color-button_light-border-default: transparent; // 浅色按钮描边颜色 
+$color-button_light-border-default: transparent; // 浅色按钮描边颜色
 $color-button_light-border-hover: $color-button_light-border-default; // 浅色按钮描边颜色 - 悬浮
 $color-button_light-border-active: $color-button_light-border-hover; // 浅色按钮描边颜色 - 按下
-$width-button_light-border: 0;  // 浅色按钮描边宽度
+$width-button_light-border: 0; // 浅色按钮描边宽度
 
 // borderless
 $color-button_borderless-text-default: var(--semi-color-primary); // 无背景按钮背景颜色
@@ -126,7 +134,7 @@ $spacing-button_iconOnly_small-paddingTop: $spacing-extra-tight; // 图标按钮
 $spacing-button_iconOnly_small-paddingBottom: $spacing-extra-tight; // 图标按钮底部内边距 - 小尺寸
 
 // margin
-$spacing-button_iconOnly_content-marginLeft: $spacing-tight;  // 按钮左侧图标距离文字间距
+$spacing-button_iconOnly_content-marginLeft: $spacing-tight; // 按钮左侧图标距离文字间距
 $spacing-button_iconOnly_content-marginRight: $spacing-tight; // 按钮右侧图标距离文字间距
 
 $font-button-fontWeight: $font-weight-bold; // 按钮文字字重
@@ -134,7 +142,7 @@ $height-button_large: $height-control-large; // 按钮高度 - 大尺寸
 $height-button_small: $height-control-small; // 按钮高度 - 小尺寸
 $height-button_default: $height-control-default; // 按钮高度 - 默认
 
-$width-button-border: $border-thickness;  // 按钮描边宽度
+$width-button-border: $border-thickness; // 按钮描边宽度
 $radius-button: var(--semi-border-radius-small); // 按钮圆角大小
 $radius-button_group: $radius-button; // 按钮组圆角大小
 $width-button-outline: 2px; // 按钮轮廓宽度

+ 14 - 1
packages/semi-foundation/checkbox/checkbox.scss

@@ -103,7 +103,12 @@ $module: #{$prefix}-checkbox;
         }
 
         &-pureCardType {
-            display: none;
+            // Reasons to use opacity:0 & width: 0 instead of display: none
+            // The a11y keyboard focus event of the checkbox depends on the implementation of the input focus/blur event
+            // input focus/blur cannot take effect when display: none
+            opacity: 0;
+            width: 0;
+            margin-right: 0 !important;
         }
     }
 
@@ -347,6 +352,14 @@ $module: #{$prefix}-checkbox;
         color: $color-checkbox_extra-text-default;
         margin-top: $spacing-checkbox_extra-marginTop;
     }
+
+    &-focus {
+        outline: $width-checkbox-outline solid $color-checkbox_primary-outline-focus;
+        
+        &-border {
+            box-shadow: inset 0 0 0 $size-checkbox_inner-shadow $color-checkbox_default-border-hover;
+        }
+    }
 }
 
 .#{$module}Group {

+ 30 - 0
packages/semi-foundation/checkbox/checkboxFoundation.ts

@@ -23,6 +23,8 @@ export interface CheckboxAdapter<P = Record<string, any>, S = Record<string, any
     notifyChange: (event: BasicCheckboxEvent) => void;
     setAddonId: () => void;
     setExtraId: () => void;
+    setFocusVisible: (focusVisible: boolean) => void;
+    focusCheckboxEntity: () => void;
 }
 
 class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<CheckboxAdapter<P, S>, P, S> {
@@ -31,6 +33,8 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
         super({ ...adapter });
     }
 
+    clickState = false;
+
     init() {
         const { children, extra, extraId, addonId } = this.getProps();
         if (children && !addonId) {
@@ -77,6 +81,12 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
             return;
         }
 
+        if (e?.type === 'click') {
+            this.clickState = true;
+        }
+
+        this._adapter.focusCheckboxEntity();
+
         const isInGroup = this._adapter.getIsInGroup();
 
         if (isInGroup) {
@@ -118,6 +128,26 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
         this._adapter.setNativeControlChecked(checked);
     }
 
+    handleFocusVisible = (event: any) => {
+        const { target } = event;
+        try {
+            if (this.clickState) {
+                this.clickState = false;
+                return;
+            } 
+            if (target.matches(':focus-visible')) {
+                this._adapter.setFocusVisible(true);
+            }
+        } catch (error){
+            console.warn('The current browser does not support the focus-visible');
+        }
+    }
+
+    handleBlur = () => {
+        this.clickState = false;
+        this._adapter.setFocusVisible(false);
+    }
+
     // eslint-disable-next-line @typescript-eslint/no-empty-function
     destroy() {}
 }

+ 2 - 0
packages/semi-foundation/checkbox/variables.scss

@@ -36,12 +36,14 @@ $color-checkbox_disabled-bg-default: var(--semi-color-disabled-fill); // 选框
 $color-checkbox_disabled-border-default: var(--semi-color-border); // 选框禁用态描边颜色 - 默认
 $color-checkbox_checked-bg-disabled: var(--semi-color-primary-disabled); // 选框选中 + 禁用态背景颜色
 $color-checkbox_checked-icon-disabled: var(--semi-color-white); // 选框禁用态对勾颜色
+$color-checkbox_primary-outline-focus: var(--semi-color-primary-light-active); // 复选框轮廓-聚焦颜色
 
 $size-checkbox_inner-shadow: $border-thickness-control; // 选框内描边宽度
 $width-checkbox_inner: $width-icon-medium; // 选框对勾 icon 宽度
 $height-checkbox_inner: 20px; // 选框对勾 icon 高度
 $width-checkbox_cardType_checked-border: 1px; // 卡片类型复选框的边框宽度
 $width-checkbox_cardType_checked_disabled-border: 1px; // 卡片类型复选框选中且禁用的边框宽度
+$width-checkbox-outline: 2px; // 复选框轮廓宽度
 
 $radius-checkbox_cardType: 3px; // 卡片类型复选框的圆角大小
 $radius-checkbox_inner: var(--semi-border-radius-extra-small); // 选框圆角

+ 1 - 1
packages/semi-foundation/inputNumber/constants.ts

@@ -7,7 +7,7 @@ const cssClasses = {
 const numbers = {
     ...inputNumbers,
     DEFAULT_STEP: 1,
-    DEFAULT_SHIFT_STEP: 1,
+    DEFAULT_SHIFT_STEP: 10,
     DEFAULT_PRESS_TIMEOUT: 250,
     DEFAULT_PRESS_INTERVAL: 0,
     MOUSE_BUTTON_LEFT: 0, // left mouse button

+ 2 - 2
packages/semi-foundation/inputNumber/foundation.ts

@@ -215,7 +215,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         if (code === keyCode.UP || code === keyCode.DOWN) {
             this._adapter.setClickUpOrDown(true);
             this._adapter.recordCursorPosition();
-            const formattedVal = code === keyCode.UP ? this.add() : this.minus();
+            const formattedVal = code === keyCode.UP ? this.add(null, event) : this.minus(null, event);
 
             this._doInput(formattedVal, event, () => {
                 this._adapter.restoreCursor();
@@ -382,7 +382,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         this._adapter.setNumber(number);
         this._adapter.setValue(formattedValue);
 
-        if (isString(formattedValue) && formattedValue !== String(propsValue)) {
+        if (isString(formattedValue) && formattedValue !== String(propsValue ?? '')) {
             this.notifyChange(formattedValue, null);
         }
     }

+ 2 - 0
packages/semi-foundation/navigation/foundation.ts

@@ -53,6 +53,7 @@ export default class NavigationFoundation<P = Record<string, any>, S = Record<st
         super({ ...adapter });
     }
 
+    /* istanbul ignore next */
     static getZeroParentKeys(itemKeysMap = {}, ...itemKeys: (string | number)[]) {
         const willAddKeys = [];
         if (itemKeys.length) {
@@ -240,6 +241,7 @@ export default class NavigationFoundation<P = Record<string, any>, S = Record<st
         this._adapter.notifySelect(data);
     }
 
+    /* istanbul ignore next */
     judgeIfOpen(openKeys: (string | number)[], items: NavItemType[]): boolean {
         let shouldBeOpen = false;
 

+ 1 - 1
packages/semi-foundation/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.12.0",
+    "version": "2.13.0-beta.0",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",

+ 1 - 1
packages/semi-foundation/radio/variables.scss

@@ -13,7 +13,7 @@ $color-radio_primary-bg-hover: var(--semi-color-primary-hover); // 选中项悬
 $color-radio_primary-bg-active: var(--semi-color-primary-active); // 选中项按下态背景颜色
 $color-radio_primary-border-default: var(--semi-color-primary); // 选中项描边颜色
 $color-radio_primary-bg-default: var(--semi-color-primary); // 选中项背景颜色
-$color-radio_primary-text-default: white; // 选中项原点颜色
+$color-radio_primary-text-default: rgba(var(--semi-white), 1); // 选中项原点颜色
 
 $color-radio_checked-bg-disabled: var(--semi-color-primary-disabled); // 选中项禁用态背景颜色
 $color-radio_default-border-disabled: var(--semi-color-border); // 禁用态描边颜色

+ 1 - 1
packages/semi-foundation/steps/variables.scss

@@ -4,7 +4,7 @@ $color-steps-bg-default: var(--semi-color-text-2); // 未到达步骤条图标
 $color-steps-bg-hover: var(--semi-color-fill-0); // 步骤条背景 - 悬浮态
 $color-steps-border-default: transparent; // 步骤条描边 - 默认
 $color-steps-icon-default: var(--semi-color-text-2); // 未到达步骤条图标背景
-$color-steps-text-default: white; // 步骤条图标默认文本颜色
+$color-steps-text-default: rgba(var(--semi-white), 1); // 步骤条图标默认文本颜色
 $color-steps_danger-text-active: var(--semi-color-danger-active); // 错误步骤条文本颜色 - 按下态
 $color-steps_danger-text-default: var(--semi-color-danger); // 错误步骤条文本颜色 - 默认态
 $color-steps_danger-text-hover: var(--semi-color-danger-hover); // 错误步骤条文本颜色 - 悬浮态

+ 1 - 1
packages/semi-foundation/switch/variables.scss

@@ -30,7 +30,7 @@ $color-switch_disabled-border-default: var(--semi-color-border); // 禁用态开
 $color-switch_disabled-bg-hover: transparent; // 禁用态开关背景色 - 悬浮
 $color-switch_checked_disabled-bg-default: var(--semi-color-success-disabled); // 禁用开启态开关背景颜色
 $color-switch_checked_disabled-border-default: transparent; // 禁用开启态开关描边颜色
-$color-switch_knob-bg-default: white; // 开关按钮背景颜色
+$color-switch_knob-bg-default: rgba(var(--semi-white), 1); // 开关按钮背景颜色
 $color-switch_knob-border-default: var(--semi-color-border); // 开关按钮描边颜色
 $color-switch_checked-text-default: var(--semi-color-white); // 开启态开关按钮文字颜色
 $color-switch_unchecked-text-default: var(--semi-color-text-2); // 关闭态开关按钮文字颜色

+ 1 - 1
packages/semi-foundation/tabs/foundation.ts

@@ -80,7 +80,7 @@ class TabsFoundation<P = Record<string, any>, S = Record<string, any>> extends B
 
     handleKeyDown = (event: any, itemKey: string, closable: boolean) => {
         const tabs = [...event.target.parentNode.childNodes].filter(item => {
-            return get(item, 'attributes.data-tabkey.value', '').includes('semiTab') && item.ariaDisabled!=="true";
+            return get(item, 'attributes.data-tabkey.value', '').includes('semiTab') && get(item, 'attributes.aria-disabled.value', '') !== "true";
         });
 
         switch (event.key) {

+ 1 - 1
packages/semi-foundation/tag/mixin.scss

@@ -1,7 +1,7 @@
 @mixin tag-style($color, $type) {
     @if $type==solid {
         background-color: unquote("rgba(var(--semi-#{$color}-5), 1)");
-        color: white;
+        color: rgba(var(--semi-white), 1);
     }
 
     @else if $type==ghost {

+ 14 - 0
packages/semi-foundation/tagInput/foundation.ts

@@ -18,6 +18,7 @@ export interface TagInputAdapter extends DefaultAdapter {
     setInputValue: (inputValue: string) => void;
     setTagsArray: (tagsArray: string[]) => void;
     setFocusing: (focusing: boolean) => void;
+    toggleFocusing(focused: boolean): void;
     setHovering: (hovering: boolean) => void;
     notifyBlur: (e: TagInputCursorEvent) => void;
     notifyFocus: (e: TagInputCursorEvent) => void;
@@ -183,6 +184,19 @@ class TagInputFoundation extends BaseFoundation<TagInputAdapter> {
         this._adapter.setHovering(false);
     }
 
+    handleClickPrefixOrSuffix(e: any) {
+        const { disabled } = this._adapter.getProps();
+        const { isFocus } = this._adapter.getStates();
+        if (!disabled && !isFocus) {
+            this._adapter.toggleFocusing(true);
+        }
+    }
+
+    handlePreventMouseDown(e: any) {
+        if (e && isFunction(e.preventDefault)) {
+            e.preventDefault();
+        }
+    }
     /**
      * handler of delete tag
      */

+ 2 - 0
packages/semi-foundation/upload/foundation.ts

@@ -401,6 +401,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
         });
     }
 
+    /* istanbul ignore next */
     manualUpload(): void {
         // find the list of files that have not been uploaded
         const waitToUploadFileList = this.getState('fileList').filter((item: BaseFileItem) => item.status === FILE_STATUS_WAIT_UPLOAD);
@@ -739,6 +740,7 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
         return /(webp|svg|png|gif|jpg|jpeg|bmp|dpg)$/i.test(file.type);
     }
 
+    /* istanbul ignore next */
     isMultiple(): boolean {
         return Boolean(this.getProp('multiple'));
     }

+ 2 - 2
packages/semi-icons/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-icons",
-  "version": "2.12.0",
+  "version": "2.13.0-beta.0",
   "description": "semi icons",
   "keywords": [
     "semi",
@@ -38,7 +38,7 @@
     "@babel/plugin-transform-runtime": "^7.15.8",
     "@babel/preset-env": "^7.15.8",
     "@babel/preset-react": "^7.14.5",
-    "@douyinfe/semi-webpack-plugin": "2.12.0",
+    "@douyinfe/semi-webpack-plugin": "2.13.0-beta.0",
     "babel-loader": "^8.2.2",
     "css-loader": "4.3.0",
     "del": "^6.0.0",

+ 1 - 1
packages/semi-illustrations/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-illustrations",
-  "version": "2.12.0",
+  "version": "2.13.0-beta.0",
   "description": "semi illustrations",
   "keywords": [
     "semi",

+ 2 - 2
packages/semi-next/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-next",
-    "version": "2.12.0",
+    "version": "2.13.0-beta.0",
     "description": "Plugin that support Semi Design in Next.js",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",
@@ -23,7 +23,7 @@
         "typescript": "^4"
     },
     "dependencies": {
-        "@douyinfe/semi-webpack-plugin": "2.12.0"
+        "@douyinfe/semi-webpack-plugin": "2.13.0-beta.0"
     },
     "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 1 - 1
packages/semi-scss-compile/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-scss-compile",
-  "version": "2.12.0",
+  "version": "2.13.0-beta.0",
   "description": "compile semi scss to css",
   "author": "[email protected]",
   "license": "MIT",

+ 1 - 1
packages/semi-theme-default/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-theme-default",
-    "version": "2.12.0",
+    "version": "2.13.0-beta.0",
     "description": "semi-theme-default",
     "keywords": [
         "semi-theme",

+ 26 - 7
packages/semi-ui/checkbox/checkbox.tsx

@@ -36,6 +36,7 @@ interface CheckboxState {
     checked: boolean;
     addonId?: string;
     extraId?: string;
+    focusVisible?: boolean;
 }
 class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
     static contextType = Context;
@@ -99,13 +100,17 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             },
             setExtraId: () => {
                 this.setState({ extraId: getUuidShort({ prefix: 'extra' }) });
-            }
+            },
+            setFocusVisible: (focusVisible: boolean): void => {
+                this.setState({ focusVisible });
+            },
+            focusCheckboxEntity: () => {
+                this.focus();
+            },
         };
     }
 
     foundation: CheckboxFoundation;
-    addonId: string;
-    extraId: string;
     constructor(props: CheckboxProps) {
         super(props);
 
@@ -115,6 +120,7 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             checked: props.checked || props.defaultChecked || checked,
             addonId: props.addonId,
             extraId: props.extraId,
+            focusVisible: false
         };
 
         this.checkboxEntity = null;
@@ -147,6 +153,14 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
 
     handleEnterPress = (e: React.KeyboardEvent<HTMLSpanElement>) => this.foundation.handleEnterPress(e);
 
+    handleFocusVisible = (event: React.FocusEvent) => {
+        this.foundation.handleFocusVisible(event);
+    }
+
+    handleBlur = (event: React.FocusEvent) => {
+        this.foundation.handleBlur();
+    }
+
     render() {
         const {
             disabled,
@@ -163,7 +177,7 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             tabIndex,
             id
         } = this.props;
-        const { checked, addonId, extraId } = this.state;
+        const { checked, addonId, extraId, focusVisible } = this.state;
         const props: Record<string, any> = {
             checked,
             disabled,
@@ -186,6 +200,8 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
 
         const prefix = prefixCls || css.PREFIX;
 
+        const focusOuter = props.isCardType || props.isPureCardType;
+
         const wrapper = classnames(prefix, {
             [`${prefix}-disabled`]: props.disabled,
             [`${prefix}-indeterminate`]: indeterminate,
@@ -197,6 +213,7 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             [`${prefix}-cardType_checked`]: props.isCardType && props.checked && !props.disabled,
             [`${prefix}-cardType_checked_disabled`]: props.isCardType && props.checked && props.disabled,
             [className]: Boolean(className),
+            [`${prefix}-focus`]: focusVisible && focusOuter,
         });
 
         const extraCls = classnames(`${prefix}-extra`, {
@@ -211,7 +228,6 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
         );
         return (
             // label is better than span, however span is here which is to solve gitlab issue #364
-            // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
             <span
                 role={role}
                 tabIndex={tabIndex}
@@ -227,12 +243,15 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
                 <CheckboxInner
                     {...this.props}
                     {...props}
-                    addonId={children && this.addonId}
-                    extraId={extra && this.extraId}
+                    addonId={children && addonId}
+                    extraId={extra && extraId}
                     isPureCardType={props.isPureCardType}
                     ref={ref => {
                         this.checkboxEntity = ref;
                     }}
+                    focusInner={focusVisible && !focusOuter}
+                    onInputFocus={this.handleFocusVisible}
+                    onInputBlur={this.handleBlur}
                 />
                 {
                     props.isCardType ?

+ 11 - 1
packages/semi-ui/checkbox/checkboxInner.tsx

@@ -23,6 +23,9 @@ export interface CheckboxInnerProps {
     addonId?: string;
     extraId?: string;
     'aria-label'?: React.AriaAttributes['aria-label'];
+    focusInner?: boolean;
+    onInputFocus?: (e: any) => void;
+    onInputBlur?: (e: any) => void;
 }
 
 class CheckboxInner extends PureComponent<CheckboxInnerProps> {
@@ -43,6 +46,9 @@ class CheckboxInner extends PureComponent<CheckboxInnerProps> {
         isPureCardType: PropTypes.bool,
         addonId: PropTypes.string,
         extraId: PropTypes.string,
+        focusInner: PropTypes.bool,
+        onInputFocus: PropTypes.func,
+        onInputBlur: PropTypes.func,
     };
 
     static defaultProps = {
@@ -59,7 +65,7 @@ class CheckboxInner extends PureComponent<CheckboxInnerProps> {
     }
 
     render() {
-        const { indeterminate, checked, disabled, prefixCls, name, isPureCardType, addonId, extraId } = this.props;
+        const { indeterminate, checked, disabled, prefixCls, name, isPureCardType, addonId, extraId, focusInner, onInputFocus, onInputBlur } = this.props;
         const prefix = prefixCls || css.PREFIX;
 
         const wrapper = classnames(
@@ -73,6 +79,8 @@ class CheckboxInner extends PureComponent<CheckboxInnerProps> {
 
         const inner = classnames({
             [`${prefix}-inner-display`]: true,
+            [`${prefix}-focus`]: focusInner,
+            [`${prefix}-focus-border`]:  focusInner && !checked,
         });
 
         const icon = checked ? (
@@ -95,6 +103,8 @@ class CheckboxInner extends PureComponent<CheckboxInnerProps> {
             onChange: noop,
             checked: checked,
             disabled: disabled,
+            onFocus: onInputFocus,
+            onBlur: onInputBlur,
         };
         
         name && (inputProps['name'] = name);

+ 2 - 3
packages/semi-ui/inputNumber/__test__/inputNumber.test.js

@@ -412,9 +412,8 @@ describe(`InputNumber`, () => {
             <InputNumber min={1} value={value} onChange={spyChange} />
         );
         inputNumber.setProps({ value: 0 });
-        expect(spyChange.calledTwice).toBe(true);
-        expect(spyChange.getCall(0).args[0]).toEqual('');
-        expect(spyChange.getCall(1).args[0]).toEqual(1);
+        expect(spyChange.calledOnce).toBe(true);
+        expect(spyChange.getCall(0).args[0]).toEqual(1);
     });
 
     it('fix controlled min value form field', () => {

+ 53 - 0
packages/semi-ui/inputNumber/_story/inputNumber.stories.js

@@ -5,6 +5,7 @@ import InputNumber from '../index';
 import Button from '../../button/index';
 import { withField, Form } from '../../index';
 import { useFormApi } from '../../form';
+import { Space } from '../../index';
 
 export default {
   title: 'InputNumber',
@@ -712,3 +713,55 @@ export const FixPrecision786 = () => {
   );
 }
 FixPrecision786.storyName = 'fix precision 删除后输入非法值会显示 0.00';
+
+
+ export const FixFormValidate = () => {
+  return (
+      <div data-cy="fix-precision-786">
+          <Form  >
+              <Form.InputNumber
+                  field="inputnumber"
+                  label='inputnumber' 
+                  rules={[
+                      {
+                          required: true,
+                      },
+                  ]}
+              />
+              <Form.Input
+                  field="input"
+                  label='input'
+                  rules={[
+                      {
+                          required: true,
+                      },
+                  ]}
+              />
+          </Form>
+      </div>
+  );
+}
+FixFormValidate.storyName = 'fix form validate';
+
+export const InputNumberA11y = () => {
+  return (
+    <Space vertical align="start" data-cy="a11y">
+      <label for="default">
+        step=1, shiftStep=10
+      </label>
+      <InputNumber id="default" data-cy="default" />
+      <label for="step">
+        step=5, shiftStep=100
+      </label>
+      <InputNumber id="step" data-cy="step" step={5} shiftStep={100} />
+      <label for="max">
+        step=1, shiftStep=10, max=10
+      </label>
+      <InputNumber id="max" data-cy="max" max={10} />
+      <Form>
+        <Form.InputNumber field="test" label="item number" />
+      </Form>
+    </Space>
+  );
+}
+InputNumberA11y.storyName = "inputNumber a11y";

+ 0 - 4
packages/semi-ui/inputNumber/index.tsx

@@ -397,8 +397,6 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
         return (
             <div className={suffixChildrenCls}>
                 <span
-                    role="button"
-                    tabIndex={-1}
                     className={upClassName}
                     onMouseDown={notAllowedUp ? noop : this.handleUpClick}
                     onMouseUp={this.handleMouseUp}
@@ -407,8 +405,6 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
                     <IconChevronUp size="extra-small" />
                 </span>
                 <span
-                    role="button"
-                    tabIndex={-1}
                     className={downClassName}
                     onMouseDown={notAllowedDown ? noop : this.handleDownClick}
                     onMouseUp={this.handleMouseUp}

+ 7 - 7
packages/semi-ui/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.12.0",
+    "version": "2.13.0-beta.0",
     "description": "",
     "main": "lib/cjs/index.js",
     "module": "lib/es/index.js",
@@ -15,11 +15,11 @@
     "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",
+        "@douyinfe/semi-animation-react": "2.13.0-beta.0",
+        "@douyinfe/semi-foundation": "2.13.0-beta.0",
+        "@douyinfe/semi-icons": "2.13.0-beta.0",
+        "@douyinfe/semi-illustrations": "2.13.0-beta.0",
+        "@douyinfe/semi-theme-default": "2.13.0-beta.0",
         "@types/react-window": "^1.8.2",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
@@ -75,7 +75,7 @@
         "@babel/plugin-transform-runtime": "^7.15.8",
         "@babel/preset-env": "^7.15.8",
         "@babel/preset-react": "^7.14.5",
-        "@douyinfe/semi-scss-compile": "2.12.0",
+        "@douyinfe/semi-scss-compile": "2.13.0-beta.0",
         "@storybook/addon-knobs": "^6.3.1",
         "@types/lodash": "^4.14.176",
         "babel-loader": "^8.2.2",

+ 1 - 1
packages/semi-ui/popover/index.tsx

@@ -44,6 +44,7 @@ export interface PopoverProps extends BaseProps {
     guardFocus?: TooltipProps['guardFocus'];
     returnFocusOnClose?: TooltipProps['returnFocusOnClose'];
     onEscKeyDown?: TooltipProps['onEscKeyDown'];
+    clickToHide?:TooltipProps['clickToHide']
 }
 
 export interface PopoverState {
@@ -175,5 +176,4 @@ class Popover extends React.PureComponent<PopoverProps, PopoverState> {
         );
     }
 }
-
 export default Popover;

+ 36 - 0
packages/semi-ui/tabs/_story/tabs.stories.js

@@ -867,3 +867,39 @@ export const TabListChange = () => <TabListChangeDemo />;
 TabListChange.story = {
   name: 'tablist change',
 };
+
+
+class TabClosableDemo extends React.Component {
+  constructor(props){
+    super(props);
+      this.state = {
+          tabList: [
+              {tab: '文档', itemKey:'1', text:'文档', closable:true},
+              {tab: '快速起步', itemKey:'2', text:'快速起步', closable:true},
+              {tab: '帮助', itemKey:'3', text:'帮助'},
+          ]
+      }
+  }
+  close(key){
+    const newTabList = [...this.state.tabList];
+    const closeIndex = newTabList.findIndex(t=>t.itemKey===key);
+    newTabList.splice(closeIndex, 1);
+    this.setState({tabList:newTabList});
+  }
+
+  render() {
+    return (
+      <Tabs type="card" defaultActiveKey="1" onTabClose={this.close.bind(this)}>
+          {
+            this.state.tabList.map(t=><TabPane closable={t.closable} tab={t.tab} itemKey={t.itemKey} key={t.itemKey}>{t.text}</TabPane>)
+          }
+      </Tabs>
+    );
+  }
+}
+
+export const TabClosable = () => <TabClosableDemo />;
+
+TabClosable.story = {
+  name: 'tab closable',
+};

+ 1 - 1
packages/semi-ui/tag/_story/tag.stories.js

@@ -155,7 +155,7 @@ export const TagAvatar = () => {
     'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/avatarDemo.jpeg';
   return (
     <div style={{ margin: 10 }}>
-      <Tag avatarSrc={avatarSrc} avatarShape={'square'}>
+      <Tag avatarSrc={avatarSrc} avatarShape={'square'} onClick={()=>{console.log('如果能重来,我要做李白')}}>
         李白
       </Tag>
       <br />

+ 38 - 11
packages/semi-ui/tagInput/index.tsx

@@ -13,6 +13,7 @@ import { cssClasses, strings } from '@douyinfe/semi-foundation/tagInput/constant
 import '@douyinfe/semi-foundation/tagInput/tagInput.scss';
 import TagInputFoundation, { TagInputAdapter } from '@douyinfe/semi-foundation/tagInput/foundation';
 import { ArrayElement } from '../_base/base';
+import { isSemiIcon } from '../_utils';
 import BaseComponent from '../_base/baseComponent';
 import Tag from '../tag';
 import Input from '../input';
@@ -47,6 +48,8 @@ export interface TagInputProps {
     onKeyDown?: (e: React.MouseEvent<HTMLInputElement>) => void;
     onRemove?: (removedValue: string, idx: number) => void;
     placeholder?: string;
+    insetLabel?: React.ReactNode;
+    insetLabelId?: string;
     prefix?: React.ReactNode;
     renderTagItem?: (value: string, index: number) => React.ReactNode;
     separator?: string | string[] | null;
@@ -129,6 +132,8 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
     };
 
     inputRef: React.RefObject<HTMLInputElement>;
+    foundation: TagInputFoundation;
+
     constructor(props: TagInputProps) {
         super(props);
         this.foundation = new TagInputFoundation(this.adapter);
@@ -170,6 +175,15 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
             setFocusing: (focusing: boolean) => {
                 this.setState({ focusing });
             },
+            toggleFocusing: (isFocus: boolean) => {
+                const input = this.inputRef && this.inputRef.current;
+                if (isFocus) {
+                    input && input.focus();
+                } else {
+                    input && input.blur();
+                }
+                this.setState({ focusing: isFocus });
+            },
             setHovering: (hovering: boolean) => {
                 this.setState({ hovering });
             },
@@ -240,6 +254,15 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
         this.foundation.handleInputMouseEnter();
     };
 
+    handleClickPrefixOrSuffix = (e: React.MouseEvent<HTMLInputElement>) => {
+        this.foundation.handleClickPrefixOrSuffix(e);
+    };
+
+    handlePreventMouseDown = (e: React.MouseEvent<HTMLInputElement>) => {
+        this.foundation.handlePreventMouseDown(e);
+    };
+
+
     renderClearBtn() {
         const { hovering, tagsArray, inputValue } = this.state;
         const { showClear, disabled } = this.props;
@@ -248,11 +271,11 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
         });
         if (showClear) {
             return (
-                <div 
+                <div
                     role="button"
-                    tabIndex={0} 
-                    aria-label="Clear TagInput value" 
-                    className={clearCls} 
+                    tabIndex={0}
+                    aria-label="Clear TagInput value"
+                    className={clearCls}
                     onClick={e => this.handleClearBtn(e)}
                     onKeyPress={e => this.handleClearEnterPress(e)}
                 >
@@ -264,16 +287,19 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
     }
 
     renderPrefix() {
-        const { prefix } = this.props;
-        if (isNull(prefix) || isUndefined(prefix)) {
+        const { prefix, insetLabel, insetLabelId } = this.props;
+        const labelNode = prefix || insetLabel;
+        if (isNull(labelNode) || isUndefined(labelNode)) {
             return null;
         }
         const prefixWrapperCls = cls(`${prefixCls}-prefix`, {
-            [`${prefixCls}-prefix-text`]: prefix && isString(prefix),
+            [`${prefixCls}-inset-label`]: insetLabel,
+            [`${prefixCls}-prefix-text`]: labelNode && isString(labelNode),
             // eslint-disable-next-line max-len
-            [`${prefixCls}-prefix-icon`]: React.isValidElement(prefix) && !(prefix && isString(prefix)),
+            [`${prefixCls}-prefix-icon`]: isSemiIcon(labelNode),
         });
-        return <div className={prefixWrapperCls}>{prefix}</div>;
+        // eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
+        return <div className={prefixWrapperCls} onMouseDown={this.handlePreventMouseDown} onClick={this.handleClickPrefixOrSuffix} id={insetLabelId}>{labelNode}</div>;
     }
 
     renderSuffix() {
@@ -284,9 +310,10 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
         const suffixWrapperCls = cls(`${prefixCls}-suffix`, {
             [`${prefixCls}-suffix-text`]: suffix && isString(suffix),
             // eslint-disable-next-line max-len
-            [`${prefixCls}-suffix-icon`]: React.isValidElement(suffix) && !(suffix && isString(suffix)),
+            [`${prefixCls}-suffix-icon`]: isSemiIcon(suffix),
         });
-        return <div className={suffixWrapperCls}>{suffix}</div>;
+        // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
+        return <div className={suffixWrapperCls} onMouseDown={this.handlePreventMouseDown} onClick={this.handleClickPrefixOrSuffix}>{suffix}</div>;
     }
 
     renderTags() {

+ 152 - 0
packages/semi-ui/upload/_story/upload.stories.js

@@ -957,3 +957,155 @@ export const CustomListOperation = () => {
 CustomListOperation.story = {
   name: 'custom list operation',
 }
+
+
+export const TestReplaceFunc = () => (
+  <>
+    <Upload
+      {...commonProps}
+      action={action}
+      accept=".md,image/*,video/*"
+      maxSize={mb1}
+      minSize={0}
+      transformFile={(fileInstance)=>{return fileInstance;}}
+    >
+      <Button icon={<IconUpload />} theme="light">
+        点击上传(最小0kB,最大1MB)
+      </Button>
+    </Upload>
+    <Upload
+      {...commonProps}
+      action={action}
+      accept="image/*"
+      maxSize={mb1}
+      minSize={0}
+      transformFile={(fileInstance)=>{return fileInstance;}}
+    >
+      <Button icon={<IconUpload />} theme="light">
+        只接受image点击上传(最小0kB,最大1MB)
+      </Button>
+    </Upload>
+    <Upload
+      {...commonProps}
+      action={action}
+      accept=".md,image/*,video/*"
+      maxSize={mb1}
+      minSize={kb2}
+      transformFile={(fileInstance)=>{return fileInstance;}}
+    >
+      <Button icon={<IconUpload />} theme="light">
+        点击上传(最小200kB,最大1MB)
+      </Button>
+    </Upload>
+  </>
+);
+
+TestReplaceFunc.story = {
+  name: 'test replace func',
+};
+
+
+class InsertUpload extends React.Component {
+   constructor() {
+        super();
+        this.onFileChange = this.onFileChange.bind(this);
+        this.insert1 = this.insert1.bind(this);
+        this.insert2 = this.insert2.bind(this);
+        this.insert3 = this.insert3.bind(this);
+        this.uploadRef1 = React.createRef();
+        this.uploadRef2 = React.createRef();
+        this.uploadRef3 = React.createRef();
+        this.file = null;
+    }
+
+    onFileChange(file) {
+      delete file[0].uid;
+      this.file = file;
+    }
+
+    insert1() {
+      // test file number limit
+      this.uploadRef1.current.insert(this.file, 0);
+    }
+
+    insert2() {
+      this.uploadRef2.current.insert(this.file, 0);
+    }
+
+    insert3() {
+      // test size limit
+      this.uploadRef3.current.insert(this.file, 0);
+    }
+
+    render() {
+        let action = 'https://run.mocky.io/v3/d6ac5c9e-4d39-4309-a747-7ed3b5694859';
+        return (
+            <div>
+                <Upload
+                    action={action}
+                    ref={this.uploadRef1}
+                    accept=".md,image/*,video/*"
+                    onSuccess={(...v) => console.log(...v)}
+                    onError={(...v) => console.log(...v)}
+                    onFileChange={this.onFileChange}
+                    maxSize={mb1}
+                    minSize={0}
+                    limit={1}
+                    transformFile={(fileInstance)=>{return fileInstance;}}
+                >
+                    <Button icon={<IconPlus />} theme="light" style={{ marginRight: 8 }}>
+                        选择文件 limit 1
+                    </Button>
+                </Upload>
+                <Upload
+                    action={action}
+                    ref={this.uploadRef2}
+                    accept=".md,image/*,video/*"
+                    onSuccess={(...v) => console.log(...v)}
+                    onError={(...v) => console.log(...v)}
+                    onFileChange={this.onFileChange}
+                    maxSize={mb1}
+                    minSize={0}
+                    limit={2}
+                    transformFile={(fileInstance)=>{return fileInstance;}}
+                >
+                    <Button icon={<IconPlus />} theme="light" style={{ marginRight: 8 }}>
+                        选择文件 limit 2
+                    </Button>
+                </Upload>
+                <Upload
+                    {...commonProps}
+                    action={action}
+                    ref={this.uploadRef3}
+                    accept=".md,image/*,video/*"
+                    onSuccess={(...v) => console.log(...v)}
+                    onError={(...v) => console.log(...v)}
+                    onFileChange={this.onFileChange}
+                    maxSize={mb1}
+                    minSize={kb2}
+                    limit={1}
+                    transformFile={(fileInstance)=>{return fileInstance;}}
+                >
+                    <Button icon={<IconPlus />} theme="light" style={{ marginRight: 8 }}>
+                        选择文件 size 限制
+                    </Button>
+                </Upload>
+                <Button icon={<IconUpload />} theme="light" onClick={this.insert1}>
+                  插入首项上传1
+                </Button>
+                <Button icon={<IconUpload />} theme="light" onClick={this.insert2}>
+                  插入首项上传2
+                </Button>
+                <Button icon={<IconUpload />} theme="light" onClick={this.insert3}>
+                  插入首项上传3
+                </Button>
+            </div>
+        );
+    }
+}
+
+export const Insert = () => <InsertUpload></InsertUpload>;
+
+Insert.story = {
+  name: 'insert',
+};

+ 1 - 1
packages/semi-webpack/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-webpack-plugin",
-    "version": "2.12.0",
+    "version": "2.13.0-beta.0",
     "description": "",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",

+ 51 - 49
yarn.lock

@@ -1460,14 +1460,14 @@
   resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
   integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
 
-"@douyinfe/[email protected]1.1":
-  version "2.11.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.11.1.tgz#08814b8f16952b47397461983d4b934ecabf1b52"
-  integrity sha512-3SjRJqhv2L/+sT6hV93zA2L77oroFX32zZY7yu6XAX5EVseI2fvVl+xuszq838DR5dzqM77QP0ZUh017cIiv2w==
+"@douyinfe/[email protected]2.0":
+  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.11.1"
-    "@douyinfe/semi-animation-styled" "2.11.1"
+    "@douyinfe/semi-animation" "2.12.0"
+    "@douyinfe/semi-animation-styled" "2.12.0"
     classnames "^2.2.6"
 
 "@douyinfe/[email protected]":
@@ -1480,10 +1480,10 @@
     "@douyinfe/semi-animation-styled" "2.9.1"
     classnames "^2.2.6"
 
-"@douyinfe/[email protected]1.1":
-  version "2.11.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.11.1.tgz#648d2cc62e6328ce0cc5876211c7130c411a9b22"
-  integrity sha512-hDroIJA07S1szU6nomskVIt9lLe1ZC08Ww0Ias6QiEpFX/RW9VRsPr8QBQnzQC9aBOKQn1tq8cLeoPrYdPvqeA==
+"@douyinfe/[email protected]2.0":
+  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"
 
@@ -1494,10 +1494,10 @@
   dependencies:
     "@babel/runtime-corejs3" "^7.15.4"
 
-"@douyinfe/[email protected]1.1":
-  version "2.11.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.11.1.tgz#8de679cc36c41737a7fddcd27d16d599e90ab76a"
-  integrity sha512-oZ2/ytAFfgvt3ey0iAcEJVYz640P1mNuiknJljmvD9pLhUafCF4OHQ2uUvzQEd45D+IcLgoRkoe7F22grTCdRA==
+"@douyinfe/[email protected]2.0":
+  version "2.12.0"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.12.0.tgz#51fe52d3911c2591a80a6e9fe96e6809c1511f13"
+  integrity sha512-OAfL9Nk38ZPqfdKm9k4cvVXXzm16ALI4LxGNZ0qfe2RCLLnYGB/hNzTctoTDjYD35dFv0yroh3qsXtZuP2xNdg==
   dependencies:
     "@babel/runtime-corejs3" "^7.15.4"
     bezier-easing "^2.1.0"
@@ -1510,13 +1510,13 @@
     "@babel/runtime-corejs3" "^7.15.4"
     bezier-easing "^2.1.0"
 
-"@douyinfe/[email protected]1.1":
-  version "2.11.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.11.1.tgz#2c75f37686d90abddc65383a1bc47b7f8966a2ae"
-  integrity sha512-JZZh3snC65OrPLGN9mGbvexWtWyzWFEr/iRvYOAoxbxPd3farJFmbDyg9X6nEyfcc1ShEj1OEHKcIpJ3d3vztw==
+"@douyinfe/[email protected]2.0":
+  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.11.1"
+    "@douyinfe/semi-animation" "2.12.0"
     async-validator "^3.5.0"
     classnames "^2.2.6"
     date-fns "^2.9.0"
@@ -1540,10 +1540,10 @@
     memoize-one "^5.2.1"
     scroll-into-view-if-needed "^2.2.24"
 
-"@douyinfe/[email protected]1.1":
-  version "2.11.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.11.1.tgz#8d8644f1bca329edf2842ab12aa91e1b38e7a3b4"
-  integrity sha512-bou4IPkgjIlUH9MzUvV8wgrA3k8Rf11nHAUTod8fKiJsFslZFSBoQDBUHtb8xmmJrIe+o2rJzNXYX71+23/N8g==
+"@douyinfe/[email protected]2.0", "@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"
@@ -1556,18 +1556,10 @@
     "@babel/runtime-corejs3" "^7.15.4"
     classnames "^2.2.6"
 
-"@douyinfe/semi-icons@^2.0.0":
-  version "2.11.2"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.11.2.tgz#ae7d06a5c573f1a38dd2293391182be268281c52"
-  integrity sha512-aKw/X/BTCLF+Vxpag8XSpLdUGkMy5ZtI8nY49nUtYXLfeLmWaR6PPpiOqsVThDzDmYnmNNwFuheJBzUF6wxpXA==
-  dependencies:
-    "@babel/runtime-corejs3" "^7.15.4"
-    classnames "^2.2.6"
-
-"@douyinfe/[email protected]":
-  version "2.11.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.11.1.tgz#682c7ef43f31f872309e849fd8f487c0722943ce"
-  integrity sha512-QmFtbzV9pMz3r0UBsEmRSdBUMIhoEGNNMCRzbzA7+sgJNY+eAkABR21BlFAnPgN4dIH1fb4EyOmjvmGUxZ3lPA==
+"@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"
 
@@ -1634,10 +1626,10 @@
     monaco-themes "^0.3.3"
     react-live "^2.2.2"
 
-"@douyinfe/[email protected]1.1":
-  version "2.11.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.11.1.tgz#7cc0dcfcc5d0a7936911fa0af7a7804d4e917f47"
-  integrity sha512-jvSydbkzgMi6JXEsaS0kCmY17q0iiPa6um08LZY6OSQ6gBnMyFf/R6ZQuhnm0kAZMjktfdfyuvTJA5qCYtgc1A==
+"@douyinfe/[email protected]2.0":
+  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"
 
@@ -1649,17 +1641,17 @@
     glob "^7.1.6"
 
 "@douyinfe/semi-ui@^2.0.0":
-  version "2.11.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.11.1.tgz#c35e7581e6c199f4e91f2a184cf363dc6edce34a"
-  integrity sha512-6bHzXNsfpoj5OsRC/JNuPKvZ8NURPthHUyHZH+/FadP8eP2Cj+Rmj++WNY8T4xoVRoaZPKdfr1wk9GbsC8CqyQ==
+  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.11.1"
-    "@douyinfe/semi-animation-react" "2.11.1"
-    "@douyinfe/semi-foundation" "2.11.1"
-    "@douyinfe/semi-icons" "2.11.1"
-    "@douyinfe/semi-illustrations" "2.11.1"
-    "@douyinfe/semi-theme-default" "2.11.1"
+    "@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"
@@ -11023,6 +11015,11 @@ eslint-plugin-react@^7.20.6, eslint-plugin-react@^7.24.0:
     semver "^6.3.0"
     string.prototype.matchall "^4.0.6"
 
+eslint-plugin-semi-design@^0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-semi-design/-/eslint-plugin-semi-design-0.0.1.tgz#e918a43e5b633a4352e173086c150e5e59b800e7"
+  integrity sha512-Gtd4rNrb/hPmn+/SimRTjOZ3d3NoR2SzW8t1uS6/uHjf7pcXOISUqY4709qOwF0EeU7FJHRIJyB4mFOUUs3/jw==
+
 eslint-rule-composer@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9"
@@ -24476,7 +24473,12 @@ typeface-inter@^3.18.1:
   resolved "https://registry.yarnpkg.com/typeface-inter/-/typeface-inter-3.18.1.tgz#24cccdf29923f318589783997be20a662cd3ab9c"
   integrity sha512-c+TBanYFCvmg3j5vPk+zxK4ocMZbPxMEmjnwG7rPQoV87xvQ6b07VbAOC0Va0XBbbZCGw6cWNeFuLeg1YQru3Q==
 
-typescript@^4, typescript@^4.4.3, typescript@^4.4.4:
[email protected]:
+  version "4.4.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324"
+  integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==
+
+typescript@^4, typescript@^4.4.4:
   version "4.6.4"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
   integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==