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

Merge branch 'main' into release

pointhalo 3 жил өмнө
parent
commit
26fd88878c
76 өөрчлөгдсөн 1462 нэмэгдсэн , 151 устгасан
  1. 5 2
      .github/PULL_REQUEST_TEMPLATE.md
  2. 5 2
      .github/PULL_REQUEST_TEMPLATE.zh-CN.md
  3. 117 0
      .github/workflows/cypress.yml
  4. 1 0
      .gitignore
  5. 2 1
      .storybook/base/base.js
  6. 90 0
      .storybook/base/preview.tsx
  7. 1 0
      .storybook/js/preview.js
  8. 0 2
      .storybook/preview.js
  9. 1 0
      .storybook/ts/preview.js
  10. 7 1
      README-zh_CN.md
  11. 8 1
      README.md
  12. 4 1
      content/basic/layout/index-en-US.md
  13. 5 1
      content/basic/layout/index.md
  14. 2 0
      content/input/datepicker/index-en-US.md
  15. 2 0
      content/input/datepicker/index.md
  16. 1 1
      content/input/taginput/index-en-US.md
  17. 1 1
      content/input/taginput/index.md
  18. 1 1
      content/navigation/navigation/index.md
  19. 1 1
      content/show/table/index-en-US.md
  20. 1 1
      content/show/table/index.md
  21. 24 0
      content/start/changelog/index-en-US.md
  22. 24 0
      content/start/changelog/index.md
  23. 1 1
      content/start/customize-theme/index-en-US.md
  24. 3 0
      cypress.json
  25. 40 0
      cypress/README.md
  26. 5 0
      cypress/fixtures/example.json
  27. 101 0
      cypress/integration/datePicker.spec.js
  28. 12 0
      cypress/integration/helloworld.spec.js
  29. 14 0
      cypress/integration/table.spec.js
  30. 22 0
      cypress/plugins/index.js
  31. 25 0
      cypress/support/commands.js
  32. 20 0
      cypress/support/index.js
  33. 10 0
      cypress/tsconfig.json
  34. 1 1
      lerna.json
  35. 4 1
      package.json
  36. 3 3
      packages/semi-animation-react/package.json
  37. 1 1
      packages/semi-animation-styled/package.json
  38. 1 1
      packages/semi-animation/package.json
  39. 11 0
      packages/semi-foundation/datePicker/datePicker.scss
  40. 33 15
      packages/semi-foundation/datePicker/foundation.ts
  41. 2 0
      packages/semi-foundation/datePicker/inputFoundation.ts
  42. 10 3
      packages/semi-foundation/datePicker/monthsGridFoundation.ts
  43. 15 1
      packages/semi-foundation/datePicker/rtl.scss
  44. 2 0
      packages/semi-foundation/datePicker/variables.scss
  45. 3 2
      packages/semi-foundation/inputNumber/foundation.ts
  46. 2 2
      packages/semi-foundation/package.json
  47. 0 2
      packages/semi-foundation/table/table.scss
  48. 5 5
      packages/semi-foundation/tooltip/foundation.ts
  49. 2 2
      packages/semi-icons/package.json
  50. 29 0
      packages/semi-icons/src/icons/IconFastForward.tsx
  51. 1 0
      packages/semi-icons/src/icons/index.ts
  52. 1 0
      packages/semi-icons/src/svgs/fast_forward.svg
  53. 1 1
      packages/semi-icons/src/svgs/meta.json
  54. 1 1
      packages/semi-illustrations/package.json
  55. 2 2
      packages/semi-next/package.json
  56. 1 1
      packages/semi-scss-compile/package.json
  57. 1 1
      packages/semi-theme-default/package.json
  58. 108 0
      packages/semi-ui/datePicker/__test__/datePicker.test.js
  59. 146 2
      packages/semi-ui/datePicker/_story/datePicker.stories.js
  60. 5 0
      packages/semi-ui/datePicker/datePicker.tsx
  61. 2 1
      packages/semi-ui/datePicker/monthsGrid.tsx
  62. 0 2
      packages/semi-ui/form/index.tsx
  63. 12 0
      packages/semi-ui/inputNumber/_story/inputNumber.stories.js
  64. 5 1
      packages/semi-ui/inputNumber/index.tsx
  65. 7 7
      packages/semi-ui/package.json
  66. 24 13
      packages/semi-ui/table/Table.tsx
  67. 46 0
      packages/semi-ui/table/__test__/table.test.js
  68. 1 1
      packages/semi-ui/tabs/index.tsx
  69. 46 0
      packages/semi-ui/tagInput/__test__/tagInput.test.js
  70. 12 6
      packages/semi-ui/tagInput/index.tsx
  71. 45 1
      packages/semi-ui/tooltip/_story/tooltip.stories.js
  72. 20 23
      packages/semi-ui/tooltip/index.tsx
  73. 3 3
      packages/semi-ui/transfer/index.tsx
  74. 1 1
      packages/semi-webpack/package.json
  75. 1 1
      src/components/PageAnchor/index.scss
  76. 290 27
      yarn.lock

+ 5 - 2
.github/PULL_REQUEST_TEMPLATE.md

@@ -26,12 +26,12 @@ Fixes #
 
 ### Changelog
 🇨🇳 Chinese
-- 修复 ...
+- Fix: 修复 ...
 
 ---
 
 🇺🇸 English
-- Fix ...
+- Fix: fix ...
 
 
 ### Checklist
@@ -39,5 +39,8 @@ Fixes #
 - [ ] Document or no need
 - [ ] Changelog or no need
 
+### Other
+- [ ] Skip Changelog
+
 ### Additional information
 <!-- You can provide screenshot/video or some additional information -->

+ 5 - 2
.github/PULL_REQUEST_TEMPLATE.zh-CN.md

@@ -25,12 +25,12 @@ Fixes #
 
 ### 更新日志
 🇨🇳 Chinese
-- 修复 ...
+- Fix: 修复 ...
 
 ---
 
 🇺🇸 English
-- Fix ...
+- Fix: fix ...
 
 
 ### 检查清单
@@ -38,5 +38,8 @@ Fixes #
 - [ ] 已补充文档或无须补充
 - [ ] Changelog已提供或无须提供
 
+### 其他要求
+- [ ] 本条 PR 不需要纳入 Changelog
+
 ### 附加信息
 <!-- 你可以提供一些 截图/录屏 或者其他的信息 -->

+ 117 - 0
.github/workflows/cypress.yml

@@ -0,0 +1,117 @@
+name: 'test:cypress'
+
+on:
+  pull_request:
+    branches: [ main, release, milestone**, test-cypress ]
+    paths:
+      - 'cypress/**'
+      - 'packages/**'
+      - '!packages/**/__test__/**'
+      - '!packages/**/*.test.[tj]sx?'
+      - '!packages/**/*.md'
+  push:
+    branches: [ main, release, milestone**, test-cypress ]
+    paths:
+      - 'cypress/**'
+      - 'packages/**'
+      - '!packages/**/__test__/**'
+      - '!packages/**/*.test.[tj]sx?'
+      - '!packages/**/*.md'
+
+jobs:
+  install:
+    runs-on: ubuntu-latest
+    container: cypress/browsers:node14.16.0-chrome90-ff88
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Install global packages
+        run: npm i -g lerna yarn
+      - name: Build storybook
+        run: |
+          yarn bootstrap
+          yarn build:lib
+          yarn build-storybook
+      - name: Save build folder
+        uses: actions/upload-artifact@v2
+        with:
+          name: storybook-static
+          if-no-files-found: error
+          path: storybook-static
+      - name: Cypress install
+        uses: cypress-io/github-action@v2
+        with:
+          # Disable running of tests within install job
+          runTests: false
+  chrome-tests:
+    runs-on: ubuntu-latest
+    container: cypress/browsers:node14.16.0-chrome90-ff88
+    needs: install
+    strategy:
+      fail-fast: false
+      matrix:
+        # run copies of the current job in parallel
+        containers: [1, 2, 3, 4, 5]
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Download the build folders
+        uses: actions/download-artifact@v2
+        with:
+          name: storybook-static
+          path: storybook-static
+      - name: 'Cypress Tests - Chrome'
+        uses: cypress-io/github-action@v2
+        with:
+          # we have already installed all dependencies above
+          install: true
+          start: npx http-server -p 6009 storybook-static
+          wait-on: 'http://localhost:6009'
+          wait-on-timeout: 120
+          browser: chrome
+          record: true
+          parallel: true
+          group: 'Cypress - Chrome'
+          spec: cypress/integration/*
+        env:
+          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
+          # Recommended: pass the GitHub token lets this action correctly
+          # determine the unique run id necessary to re-run the checks
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  firefox-tests:
+    runs-on: ubuntu-latest
+    container: 
+      image: cypress/browsers:node14.16.0-chrome90-ff88
+      options: --user 1001
+    needs: install
+    strategy:
+      fail-fast: false
+      matrix:
+        # run copies of the current job in parallel
+        containers: [1, 2, 3, 4, 5]
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Download the build folders
+        uses: actions/download-artifact@v2
+        with:
+          name: storybook-static
+          path: storybook-static
+      - name: 'Cypress Tests - Firefox'
+        uses: cypress-io/github-action@v2
+        with:
+          # we have already installed all dependencies above
+          install: true
+          start: npx http-server -p 6009 storybook-static
+          wait-on: 'http://localhost:6009'
+          wait-on-timeout: 120
+          browser: firefox
+          record: true
+          parallel: true
+          group: 'Cypress - Firefox'
+          spec: cypress/integration/*
+        env:
+          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
+          # Recommended: pass the GitHub token lets this action correctly
+          # determine the unique run id necessary to re-run the checks
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 1 - 0
.gitignore

@@ -26,6 +26,7 @@ packages/semi-theme-default/semi.scss
 public
 storybook-static/
 *.zip
+cypress/videos/
 
 # misc
 .env.local

+ 2 - 1
.storybook/base/base.js

@@ -14,7 +14,8 @@ module.exports = {
         optimizationLevel: 3,
       },
     },
-    '@storybook/addon-a11y'
+    '@storybook/addon-a11y',
+    '@storybook/addon-toolbars',
   ],
   webpackFinal: async (config) => {
     const rules =

+ 90 - 0
.storybook/base/preview.tsx

@@ -0,0 +1,90 @@
+import 'reset-css';
+import 'normalize.css';
+import React from 'react';
+import { StoryContext } from '@storybook/react';
+
+import { ConfigProvider } from '../../packages/semi-ui/index';
+import zh_CN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
+import en_GB from '@douyinfe/semi-ui/lib/es/locale/source/en_GB';
+import ko_KR from '@douyinfe/semi-ui/lib/es/locale/source/ko_KR';
+import ja_JP from '@douyinfe/semi-ui/lib/es/locale/source/ja_JP';
+import ar from '@douyinfe/semi-ui/lib/es/locale/source/ar';
+import vi_VN from '@douyinfe/semi-ui/lib/es/locale/source/vi_VN';
+import ru_RU from '@douyinfe/semi-ui/lib/es/locale/source/ru_RU';
+import id_ID from '@douyinfe/semi-ui/lib/es/locale/source/id_ID';
+import ms_MY from '@douyinfe/semi-ui/lib/es/locale/source/ms_MY';
+import th_TH from '@douyinfe/semi-ui/lib/es/locale/source/th_TH';
+import tr_TR from '@douyinfe/semi-ui/lib/es/locale/source/tr_TR';
+import pt_BR from '@douyinfe/semi-ui/lib/es/locale/source/pt_BR';
+import zh_TW from '@douyinfe/semi-ui/lib/es/locale/source/zh_TW';
+import es from '@douyinfe/semi-ui/lib/es/locale/source/es';
+
+export const globalTypes = {
+    direction: {
+        name: 'Direction',
+        description: 'RTL direction',
+        defaultValue: 'ltr',
+        toolbar: {
+            icon: 'globe',
+            items: ['ltr', 'rtl'],
+        },
+    },
+    theme: {
+        name: 'Theme',
+        description: 'Theme mode',
+        defaultValue: 'light',
+        toolbar: {
+            icon: 'circle',
+            items: ['light', 'dark'],
+        },
+    },
+    language: {
+      name: 'Locale',
+      description: 'Locale language',
+      defaultValue: 'zh_CN',
+      toolbar: {
+          icon: 'google',
+          items:  ['zh_CN', 'en_GB', 'ko_KR', 'ja_JP', 'ar', 'vi_VN', 'ru_RU', 'id_ID', 'ms_MY', 'th_TH', 'tr_TR', 'pt_BR', 'zh_TW', 'es'],
+      },
+    }
+};
+
+const switchMode = (theme: 'light' | 'dark') => {
+    const body = document.body;
+    body.setAttribute('theme-mode', theme);
+};
+
+const getLocale = code => {
+  let language = {
+      'zh_CN': zh_CN,
+      'en_GB': en_GB,
+      'ko_KR': ko_KR,
+      'ja_JP': ja_JP,
+      'ar': ar,
+      'vi_VN': vi_VN,
+      'ru_RU': ru_RU,
+      'id_ID': id_ID,
+      'ms_MY': ms_MY,
+      'th_TH': th_TH,
+      'tr_TR': tr_TR,
+      'pt_BR': pt_BR,
+      'zh_TW': zh_TW,
+      'es': es,
+  };
+
+  return language[code];
+};
+
+const withConfigProvider = (StoryFn: Function, context: StoryContext) => {
+    const { direction, theme, language } = context.globals;
+    switchMode(theme);
+    const locale = getLocale(language);
+
+    return (
+        <ConfigProvider direction={direction} locale={locale}>
+            <StoryFn />
+        </ConfigProvider>
+    );
+};
+
+export const decorators = [withConfigProvider];

+ 1 - 0
.storybook/js/preview.js

@@ -0,0 +1 @@
+export * from '../base/preview';

+ 0 - 2
.storybook/preview.js

@@ -1,2 +0,0 @@
-import 'reset-css';
-import 'normalize.css';

+ 1 - 0
.storybook/ts/preview.js

@@ -0,0 +1 @@
+export * from '../base/preview';

+ 7 - 1
README-zh_CN.md

@@ -10,7 +10,7 @@
 <div align="center">
 
 [![NPM][npm-badge]][npm-url] [![FIGMA][figma-badge]][figma-url] [![LICENSE][license-badge]][license-url] 
-[![BUILD-JS][build-js-badge]][build-js-url] [![BUILD-CSS][build-css-badge]][build-css-url] [![CODECOV][codecov-badge]][codecov-url] [![Chromatic][chromatic-badge]][chromatic-url]
+[![BUILD-JS][build-js-badge]][build-js-url] [![BUILD-CSS][build-css-badge]][build-css-url] [![CODECOV][codecov-badge]][codecov-url] [![Chromatic][chromatic-badge]][chromatic-url] [![Cypress][cypress-badge]][cypress-url]
 
 
 [npm-badge]: https://img.shields.io/npm/v/@douyinfe/semi-ui.svg
@@ -24,6 +24,8 @@
 [codecov-url]: https://app.codecov.io/gh/DouyinFE/semi-design
 [chromatic-badge]: https://img.shields.io/badge/test-chromatic-f52
 [chromatic-url]: https://www.chromatic.com/
+[cypress-badge]: https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/k83u7j&style=flat&logo=cypress
+[cypress-url]: https://dashboard.cypress.io/projects/k83u7j/runs
 
 [build-js-badge]: https://img.badgesize.io/https:/unpkg.com/@douyinfe/semi-ui/dist/umd/semi-ui.min.js?label=semi.min.js&compression=gzip
 [build-js-url]: https://unpkg.com/browse/@douyinfe/[email protected]/dist/umd/semi-ui.min.js
@@ -120,6 +122,10 @@ Semi UI 支持所有主流浏览器。
 
 感谢 [Chromatic](https://www.chromatic.com/) 提供可视化测试平台,帮助我们审查 UI 更改和提供视觉回归测试。
 
+<a href="https://www.cypress.io/"><img src="https://user-images.githubusercontent.com/26477537/147624641-1274a91d-bc4c-463e-af1a-dbf15de54c49.png" width="90" height="30" alt="Cypress" /></a>
+
+感谢 [Cypress](https://www.cypress.io/) 提供 E2E 测试。
+
 # 🎈 协议
 
 Semi UI 使用 [MIT 协议](LICENSE)

+ 8 - 1
README.md

@@ -10,7 +10,7 @@
 <div align="center">
 
 [![NPM][npm-badge]][npm-url] [![FIGMA][figma-badge]][figma-url] [![LICENSE][license-badge]][license-url]
-[![BUILD-JS][build-js-badge]][build-js-url] [![BUILD-CSS][build-css-badge]][build-css-url] [![CODECOV][codecov-badge]][codecov-url] [![Chromatic][chromatic-badge]][chromatic-url]
+[![BUILD-JS][build-js-badge]][build-js-url] [![BUILD-CSS][build-css-badge]][build-css-url] [![CODECOV][codecov-badge]][codecov-url] [![Chromatic][chromatic-badge]][chromatic-url] [![Cypress][cypress-badge]][cypress-url]
 
 
 [npm-badge]: https://img.shields.io/npm/v/@douyinfe/semi-ui.svg
@@ -24,6 +24,9 @@
 [codecov-url]: https://app.codecov.io/gh/DouyinFE/semi-design
 [chromatic-badge]: https://img.shields.io/badge/test-chromatic-f52
 [chromatic-url]: https://www.chromatic.com/
+[cypress-badge]: https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/k83u7j&style=flat&logo=cypress
+[cypress-url]: https://dashboard.cypress.io/projects/k83u7j/runs
+
 
 [build-js-badge]: https://img.badgesize.io/https:/unpkg.com/@douyinfe/semi-ui/dist/umd/semi-ui.min.js?label=semi.min.js&compression=gzip
 [build-js-url]: https://unpkg.com/browse/@douyinfe/[email protected]/dist/umd/semi-ui.min.js
@@ -118,6 +121,10 @@ Join [User Group](https://bytedance.feishu.cn/docs/doccnw93Dujm3UCkHRDTMTm1qwe#)
 
 Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
 
+<a href="https://www.cypress.io/"><img src="https://user-images.githubusercontent.com/26477537/147624641-1274a91d-bc4c-463e-af1a-dbf15de54c49.png" width="90" height="30" alt="Cypress" /></a>
+
+Thanks to [Cypress](https://www.cypress.io/) for providing E2E testing.
+
 # 🎈 License
 
 Semi UI is [MIT Licensed](LICENSE)

+ 4 - 1
content/basic/layout/index-en-US.md

@@ -18,7 +18,10 @@ brief: Assist in the overall layout of a page.
 -   `Content`: Content component, can only be used inside `Layout`.
 -   `Footer`: Footer component, can only be used inside `Layout`.
 
-> Note: Layout components are implemented with Flex layout. Browser compatibility may need to be considered.
+<Notice title='Notice'>
+1、Layout components are implemented with Flex layout. Browser compatibility may need to be considered.  <br/>
+2、The Layout component will only help you implement the layout, but will not include styles such as background color, text color, width and height. You can pass in style according to your actual needs or write a separate css implementation given a specific className
+</Notice>
 
 ## Demos
 

+ 5 - 1
content/basic/layout/index.md

@@ -16,7 +16,11 @@ brief: 协助进行页面级整体布局。
 -   `Content`:内容部分,其下可嵌套任何元素,只能放在 `Layout` 中。
 -   `Footer`:底部布局,其下可嵌套任何元素,只能放在 `Layout` 中。
 
-> 注意:布局组件采用 Flex 布局实现,需要考虑浏览器兼容性问题。
+<Notice title='注意事项'>
+1、布局组件采用 Flex 布局实现,无法在非现代浏览器中工作  <br/>
+2、Layout 组件仅会帮你实现布局,但不会附带背景色、文本色、宽高度等样式。你可以根据自己实际需求传入 style 或给定特定 className 另行编写css实现
+</Notice>
+
 
 ## 代码演示
 

+ 2 - 0
content/input/datepicker/index-en-US.md

@@ -265,6 +265,8 @@ At the same time, the click callbacks of the "onConfirm" and "onCancel" buttons
 
 The following example binds three callbacks: `onChange`, `onConfirm` and `onCancel`, and you can open the console to see the difference in print information.
 
+> Note: When opening `needConfirm`, you need to click the cancel button to close the panel, and clicking the blank area will no longer close the panel (v2.2.0)
+
 ```jsx live=true
 import React from 'react';
 import { DatePicker } from '@douyinfe/semi-ui';

+ 2 - 0
content/input/datepicker/index.md

@@ -240,6 +240,8 @@ import { DatePicker } from '@douyinfe/semi-ui';
 
 下面这个例子绑定了 onChange、onConfirm、onCancel 三种回调,你可以打开控制台查看打印信息的区别。
 
+> 注意:开启确认选择时,需要点击取消按钮关闭面板,点击空白区域不再关闭面板(v2.2.0)
+
 ```jsx live=true
 import React from 'react';
 import { DatePicker } from '@douyinfe/semi-ui';

+ 1 - 1
content/input/taginput/index-en-US.md

@@ -434,7 +434,7 @@ class CustomRender extends React.Component {
 |style         |Inline style                                     |React.CSSProperties                                               | -        |1.19.0|
 |suffix        |Suffix                                            |ReactNode                                                        |-         |1.19.0|
 |validateStatus|Validate status for styling only, one of  `default`、`warning`、`error`|string                                       |`default` |1.19.0|
-|value         |Controlled tag value                              |string[]                                                         | -        |1.19.0|
+|value         |Controlled tag value                              |string[] \| undefined                                                         | -        |1.19.0|
 |onAdd         |Callback invoked when tags are added             |(addedValue: string[]) => void                                   | -        |1.19.0|
 |onBlur        |Callback invoked when input loses focus          |(e:React.MouseEvent<HTMLInputElement\>) => void                  | -        |1.19.0|
 |onChange      |Callback invoked when tags changes               |(value:string[]) => void                                         | -        |1.19.0|

+ 1 - 1
content/input/taginput/index.md

@@ -434,7 +434,7 @@ class CustomRender extends React.Component {
 |style        |内联样式                                          |React.CSSProperties                         | -        |1.19.0|
 |suffix       |后缀标签                                           |ReactNode                      |-         |1.19.0|
 |validateStatus|设置校验状态样式,可选: `default`、`warning`、`error` |string                          |`default` |1.19.0|
-|value        |当前标签,配合 onChange 实现受控                     |string[]                       | -        |1.19.0|
+|value        |当前标签,配合 onChange 实现受控                     |string[] \| undefined                       | -        |1.19.0|
 |onAdd        |添加标签时的回调                                     |(addedValue: string[]) => void     | -        |1.19.0|
 |onBlur       |输入框失去焦点时的回调           |(e:React.MouseEvent<HTMLInputElement\>) => void                 | -        |1.19.0|
 |onChange     |标签变化时的回调                                     |(value:string[]) => void | -        |1.19.0|

+ 1 - 1
content/navigation/navigation/index.md

@@ -761,7 +761,7 @@ function NavApp (props = {}) {
 |-------------|--------------------------------------------------|-------------------|--------|---------------|
 | children    | 子元素                                             | ReactNode         |        |               |
 | className   | 最外层样式名                                       | string            |        |               |
-| link        | 导航 href 链接,传入时导航项整体会包裹一个 a 标签 | string            | -      | 1.0.0 |
+| link        | 导航 href 链接,传入时导航项整体会包裹一个 a 标签 | string            | -      | 1.0.0 |
 | linkOptions | 透传给 a 标签的参数                                | object            | -      | 1.0.0 |
 | logo        | Logo                         | ReactNode |        |               |
 | style       | 最外层样式                                         | CSSProperties            |        |               |

+ 1 - 1
content/show/table/index-en-US.md

@@ -4407,7 +4407,7 @@ render(App);
 | title                   | Table Title                                                                                                               | string<br/>\|ReactNode<br/>\|(pageData: RecordType[]) => string\|ReactNode                                            |            |
 | virtualized             | Virtualization settings                                                                                                   | Virtualized                                                                                                 | false      | **0.33.0**                                                 |
 | virtualized.itemSize    | Row height                                                                                                                | number\|(index: number) => number                                                                               | 56         | **0.33.0**                                                 |
-| virtualized.onScroll    | Virtualization scroll callback method                                                                                     | ( scrollDirection?: 'foward' \| 'backward', scrollOffset?: number, scrollUpdateWasRequested?: boolean ) => void |            | **0.33.0**                                                 |
+| virtualized.onScroll    | Virtualization scroll callback method                                                                                     | ( scrollDirection?: 'forward' \| 'backward', scrollOffset?: number, scrollUpdateWasRequested?: boolean ) => void |            | **0.33.0**                                                 |
 | onChange                | Trigger when paging, sorting, filtering changes                                                                           | ({ pagination: TablePaginationProps, <br/>filters: Array<\*>, sorter: object, extra: any }) => void                           |            |
 | onExpand                | Trigger when clicking on the row expansion icon                                                                           | (expanded: boolean, record: RecordType, DOMEvent: MouseEvent) => void                                               |            | The third parameter DOMEvent requires version **>=0.28.0** |
 | onExpandedRowsChange    | Triggers when unfolding row changes                                                                                       | (rows: RecordType[]) => void                                                                                        |            |

+ 1 - 1
content/show/table/index.md

@@ -4415,7 +4415,7 @@ render(App);
 | title                     | 表格标题                                                                                                       | ReactNode<br/>\|(pageData: RecordType[]) => ReactNode                                                           |            |
 | virtualized               | 虚拟化配置                                                                                                     | Virtualized                                                                                                     | false      | **0.33.0**                              |
 | virtualized.itemSize      | 每行的高度                                                                                                     | number\|(index: number) => number                                                                               | 56         | **0.33.0**                              |
-| virtualized.onScroll      | 虚拟化滚动回调方法                                                                                             | ( scrollDirection?: 'foward' \| 'backward', scrollOffset?: number, scrollUpdateWasRequested?: boolean ) => void |            | **0.33.0**                              |
+| virtualized.onScroll      | 虚拟化滚动回调方法                                                                                             | ( scrollDirection?: 'forward' \| 'backward', scrollOffset?: number, scrollUpdateWasRequested?: boolean ) => void |            | **0.33.0**                              |
 | onChange                  | 分页、排序、筛选变化时触发                                                                                     | ({ pagination: TablePaginationProps, <br/>filters: Array<\*>, sorter: object, extra: any }) => void             |            |
 | onExpand                  | 点击行展开图标时进行触发                                                                                       | (expanded: boolean, record: RecordType, DOMEvent: MouseEvent) => void                                           |            | 第三个参数 DOMEvent 需版本 **>=0.28.0** |
 | onExpandedRowsChange      | 展开的行变化时触发                                                                                             | (rows: RecordType[]) => void                                                                                    |            |

+ 24 - 0
content/start/changelog/index-en-US.md

@@ -16,6 +16,30 @@ Version:Major.Minor.Patch
 
 ---
 
+#### 🎉 2.2.2 (2021-12-31)
+- 【Fix】
+    - Fix Transfer In the groupList scenario, the title attribute is passed into the reactElement node, resulting in key-warning [@JontyyYang](https://github.com/JontyyYang)
+    - Fixed DatePicker range selection preset date is set to `null` or `undefined`, the panel does not close after selecting the date  [#338](https://github.com/DouyinFE/semi-design/issues/338)
+    - Fix the issue that the dateRange type DatePicker, when triggerRender is passed in, the panel does not close after selecting the date  [#422](https://github.com/DouyinFE/semi-design/issues/422)
+    - Fixed InputNumber precision format bug in controlled mode
+    - Fix spelling errors in IconFastForward [@clark-cui](https://github.com/clark-cui)
+
+#### 🎉 2.2.1 (2021-12-29)
+
+- 【Fix】
+    - Fixed DatePicker input value is back to confirmed value bug when `needConfirm` is true [#457](https://github.com/DouyinFE/semi-design/issues/457)
+    - **Optimize DatePicker interaction details, `needConfirm` mode click outside will no longer close the panel, you need to click cancel to close the panel** [#457](https://github.com/DouyinFE/semi-design/issues/457)
+    - Fixed DatePicker `needConfirm` button margin bug in footer [#457](https://github.com/DouyinFE/semi-design/issues/457)
+    - Fixed DatePicker year button direction bug when `direction='rtl'` [#457](https://github.com/DouyinFE/semi-design/issues/457)
+    - Fixed Table head row paddingY not same with design draft bug and updated to 8px [#460](https://github.com/DouyinFE/semi-design/issues/460)
+
+#### 🎉 2.2.0 (2021-12-24)
+
+- 【Fix】
+    - Fix the problem that tabPlane tab props does not accept dynamic updates when Tabs are used in umd mode
+- 【Docs】
+    - Improve Navigation API documentation [#451](https://github.com/DouyinFE/semi-design/pull/451) [@linjunc](https://github.com/linjunc)
+
 #### 🎉 2.2.0-beta.1 (2021-12-23)
 
 - 【Fix】

+ 24 - 0
content/start/changelog/index.md

@@ -15,6 +15,30 @@ Semi 版本号遵循**Semver**规范(主版本号-次版本号-修订版本号
 
 ---
 
+#### 🎉 2.2.2 (2021-12-31)
+- 【Fix】
+    - 修复 Transfer 在 type 是 groupList 场景下, title 属性传入 ReactElement 类型导致key-warning [@JontyyYang](https://github.com/JontyyYang)
+    - 修复 DatePicker 范围选择 preset 日期设置为 null 或 undefined,选择日期后面板没有关闭问题  [#338](https://github.com/DouyinFE/semi-design/issues/338)
+    - 修复 dateRange 类型 DatePicker,triggerRender 传入时选择完日期面板没有关闭问题  [#422](https://github.com/DouyinFE/semi-design/issues/422)
+    - 修复 InputNumber 精度格式化在受控模式下不正确问题
+    - 修复 IconFastForward 拼写错误 [@clark-cui](https://github.com/clark-cui)
+
+#### 🎉 2.2.1 (2021-12-29)
+
+- 【Fix】
+    - 修复 DatePicker 在 needConfirm 模式时,点击取消按钮输入框日期未返回到已选中日期问题 [#457](https://github.com/DouyinFE/semi-design/issues/457)
+    - **优化 DatePicker 交互细节,确认选择模式 click outside 不再关闭面板,需通过点击取消关闭面板** [#457](https://github.com/DouyinFE/semi-design/issues/457)
+    - 修复 DatePicker 确认选择模式 footer 按钮间距不正确问题 [#457](https://github.com/DouyinFE/semi-design/issues/457)
+    - 修复 DatePicker RTL 模式下,年切换按钮方向错误问题 [#457](https://github.com/DouyinFE/semi-design/issues/457)
+    - 修复 Table head row paddingY 与设计稿不符问题,统一调整为 8px [#460](https://github.com/DouyinFE/semi-design/issues/460)
+
+#### 🎉 2.2.0 (2021-12-24)
+
+- 【Fix】
+    - 修复 Tabs 在 umd 方式使用时,tabPlane tab props 不接受动态更新的问题
+- 【Docs】
+    - 完善 Navigation  API 文档 [#451](https://github.com/DouyinFE/semi-design/pull/451) [@linjunc](https://github.com/linjunc)
+
 #### 🎉 2.2.0-beta.1 (2021-12-23)
 
 - 【Fix】

+ 1 - 1
content/start/customize-theme/index-en-US.md

@@ -6,7 +6,7 @@ localeCode: en-US
 order: 3
 ---
 
-## 定制方式
+## Build your own design system
 
 Semi provides a complete theme configuration process, which not only maintains the uniformity and coherence of colors, fonts, rounded corners, shadows, layouts, etc. in the visual language, but also meets the diversified visual needs of the business and the brand.  
 You can go to [Semi Design System Management Site](https://semi.design/dsm/) (also known as DSM) to choose or create a theme style that meets your needs.

+ 3 - 0
cypress.json

@@ -0,0 +1,3 @@
+{
+  "projectId": "k83u7j"
+}

+ 40 - 0
cypress/README.md

@@ -0,0 +1,40 @@
+## Name
+Semi Cypress E2E(End to End) test
+
+## Usage
+
+Run cypress locally
+
+```bash
+yarn test:cy
+```
+
+## Directory
+
+```
+├── fixtures                # mock data
+│   └── example.json
+├── integration             # test cases
+│   ├── *.spec.js
+├── plugins                 # custom plugins
+│   └── index.js
+├── support                 # custom commands
+│   ├── commands.js        
+│   └── index.js
+├── tsconfig.json           # cypress syntax
+```
+
+## Contributing
+
+1. Write a test case in the `integration` folder;
+
+2. Debug locally with `yarn test:cy` until the use case is passed.
+
+## Related URLs
+
+- [Semi Cypress Dashboard](https://dashboard.cypress.io/projects/k83u7j)
+- [Writing Your First Test](https://docs.cypress.io/guides/getting-started/writing-your-first-test)
+
+## Thanks
+
+[![Cypress](https://www.cypress.io/static/33498b5f95008093f5f94467c61d20ab/c0bf4/cypress-logo.webp)](https://www.cypress.io)

+ 5 - 0
cypress/fixtures/example.json

@@ -0,0 +1,5 @@
+{
+  "name": "Using fixtures to represent data",
+  "email": "[email protected]",
+  "body": "Fixtures are a great way to mock data for responses to routes"
+}

+ 101 - 0
cypress/integration/datePicker.spec.js

@@ -0,0 +1,101 @@
+// datePicker.spec.js created with Cypress
+//
+// Start writing your Cypress tests below!
+// If you're unfamiliar with how Cypress works,
+// check out the link below and learn how to write your first test:
+// https://on.cypress.io/writing-first-test
+
+/**
+ * why use `.then`?
+ * @see https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Return-Values
+ */
+describe('DatePicker', () => {
+    it('dateTime needConfirm cancel', () => {
+        cy.visit('http://127.0.0.1:6009/iframe.html?id=datepicker--fix-need-confirm&args=&viewMode=story');
+        cy.get('[data-cy=1] .semi-input-wrapper').click();
+        cy.get('.semi-datepicker-footer > .semi-button-borderless')
+            .then(($btn) => {
+                console.log('then');
+                $btn.trigger('click');
+            });
+        cy.get('body').find('.semi-popover .semi-datepicker').should('have.length', 0);
+    });
+
+    it('dateTime needConfirm confirm', () => {
+        cy.visit('http://127.0.0.1:6009/iframe.html?id=datepicker--fix-need-confirm&args=&viewMode=story');
+        cy.get('[data-cy=1] .semi-input-wrapper').click();
+        cy.get('.semi-datepicker-day').contains('15')
+            .then($day => {
+                $day.trigger('click');
+            });
+        cy.get('.semi-datepicker-footer > button:nth-child(2)').then($btn => {
+            $btn.trigger('click');
+        });
+        cy.get('body').find('.semi-popover .semi-datepicker').should('have.length', 0);
+        cy.get('[data-cy=1] .semi-input').should('have.value', '2021-12-15 10:37:13');
+    });
+
+    it('dateTime needConfirm select+cancel', () => {
+        cy.visit('http://127.0.0.1:6009/iframe.html?id=datepicker--fix-need-confirm&args=&viewMode=story');
+        cy.get('[data-cy=1] .semi-input-wrapper').click();
+        cy.get('.semi-datepicker-day').contains('15')
+            .then($day => {
+                $day.trigger('click');
+            });
+        cy.get('.semi-datepicker-footer > button:nth-child(1)')
+            .then($btn => {
+                $btn.trigger('click');
+            });
+        cy.get('body').find('.semi-popover .semi-datepicker').should('have.length', 0);
+        cy.get('[data-cy=1] .semi-input').should('have.value', '2021-12-27 10:37:13');
+    });
+
+    it('dateTimeRange needConfirm cancel', () => {
+        cy.visit('http://127.0.0.1:6009/iframe.html?id=datepicker--fix-need-confirm&args=&viewMode=story');
+        cy.get('[data-cy=3] .semi-datepicker-range-input-wrapper-start .semi-input-wrapper').click();
+        cy.get('.semi-datepicker-footer > .semi-button-borderless')
+            .then($btn => {
+                $btn.trigger('click');
+            });
+        cy.get('body').find('.semi-popover .semi-datepicker').should('have.length', 0);
+    });
+
+    it('dateTimeRange needConfirm select+cancel', () => {
+        cy.visit('http://127.0.0.1:6009/iframe.html?id=datepicker--fix-need-confirm&args=&viewMode=story');
+        cy.get('[data-cy=3] .semi-datepicker-range-input-wrapper-start .semi-input-wrapper').click();
+        cy.get('.semi-datepicker-month-grid-left .semi-datepicker-day').contains('15')
+            .then($day => {
+                $day.trigger('click');
+            });
+        cy.get('.semi-datepicker-month-grid-right .semi-datepicker-day').contains('20')
+            .then($day => {
+                $day.trigger('click');
+            });
+        cy.get('.semi-datepicker-footer > button:nth-child(1)').then($cancelBtn => {
+            $cancelBtn.trigger('click');
+        });
+        cy.get('body').find('.semi-popover .semi-datepicker').should('have.length', 0);
+        cy.get('[data-cy=3] .semi-datepicker-range-input-wrapper-start .semi-input').should('have.value', '2021-12-27 10:37:13');
+        cy.get('[data-cy=3] .semi-datepicker-range-input-wrapper-end .semi-input').should('have.value', '2022-01-28 10:37:13');
+    });
+
+    it('dateTimeRange needConfirm confirm', () => {
+        cy.visit('http://127.0.0.1:6009/iframe.html?id=datepicker--fix-need-confirm&args=&viewMode=story');
+        cy.get('[data-cy=3] .semi-datepicker-range-input-wrapper-start .semi-input-wrapper').click();
+        cy.get('.semi-datepicker-month-grid-left .semi-datepicker-day').contains('15')
+            .then($day => {
+                $day.trigger('click');
+            });
+        cy.get('.semi-datepicker-month-grid-right .semi-datepicker-day').contains('20')
+            .then($day => {
+                $day.trigger('click');
+            });
+        cy.get('.semi-datepicker-footer > button:nth-child(2)')
+            .then($confirmBtn => {
+                $confirmBtn.trigger('click');
+            });
+        cy.get('body').find('.semi-popover .semi-datepicker').should('have.length', 0);
+        cy.get('[data-cy=3] .semi-datepicker-range-input-wrapper-start .semi-input').should('have.value', '2021-12-15 10:37:13');
+        cy.get('[data-cy=3] .semi-datepicker-range-input-wrapper-end .semi-input').should('have.value', '2022-01-20 10:37:13');
+    });
+});

+ 12 - 0
cypress/integration/helloworld.spec.js

@@ -0,0 +1,12 @@
+// helloworld.spec.js created with Cypress
+//
+// Start writing your Cypress tests below!
+// If you're unfamiliar with how Cypress works,
+// check out the link below and learn how to write your first test:
+// https://on.cypress.io/writing-first-test
+
+describe('hello world', () => {
+    it('Does not do much!', () => {
+        expect(true).to.equal(true);
+    });
+});

+ 14 - 0
cypress/integration/table.spec.js

@@ -0,0 +1,14 @@
+// table.spec.js created with Cypress
+//
+// Start writing your Cypress tests below!
+// If you're unfamiliar with how Cypress works,
+// check out the link below and learn how to write your first test:
+// https://on.cypress.io/writing-first-test
+
+describe('table', () => {
+    it('row selection', () => {
+        cy.visit('http://127.0.0.1:6009/iframe.html?id=table--selection-table&args=&viewMode=story');
+        cy.get('.semi-table-row-head .semi-checkbox-inner-display').click();
+        cy.get('.semi-checkbox-checked').should('have.length', 4);
+    });
+});

+ 22 - 0
cypress/plugins/index.js

@@ -0,0 +1,22 @@
+/// <reference types="cypress" />
+// ***********************************************************
+// This example plugins/index.js can be used to load plugins
+//
+// You can change the location of this file or turn off loading
+// the plugins file with the 'pluginsFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/plugins-guide
+// ***********************************************************
+
+// This function is called when a project is opened or re-opened (e.g. due to
+// the project's config changing)
+
+/**
+ * @type {Cypress.PluginConfig}
+ */
+// eslint-disable-next-line no-unused-vars
+module.exports = (on, config) => {
+  // `on` is used to hook into various events Cypress emits
+  // `config` is the resolved Cypress config
+}

+ 25 - 0
cypress/support/commands.js

@@ -0,0 +1,25 @@
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add('login', (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

+ 20 - 0
cypress/support/index.js

@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/index.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')

+ 10 - 0
cypress/tsconfig.json

@@ -0,0 +1,10 @@
+{
+    "compilerOptions": {
+      "allowJs": true,
+      "types": ["cypress"],
+      "baseUrl": "./",
+      "outDir": "dist",
+    },
+    "include": ["**/*.spec.js"],
+    "exclude": ["node_modules"]
+}

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.2.0-beta.1"
+    "version": "2.2.2"
 }

+ 4 - 1
package.json

@@ -28,6 +28,7 @@
     "test:story": "cross-env NODE_ENV=test type=story ./node_modules/.bin/jest --silent --notify --maxWorkers=4",
     "test:coverage": "cross-env TZ=Asia/Shanghai NODE_ENV=test type=unit ./node_modules/.bin/jest --silent --coverage --notify",
     "test:storyUpdate": "cross-env NODE_ENV=test type=story ./node_modules/.bin/jest --silent -u --notify --maxWorkers=4",
+    "test:cy": "npx http-server storybook-static -p 6009 && npx wait-on http://127.0.0.1:6009 && cypress open",
     "build:lib": "lerna run build:lib",
     "build:js": "lerna run build:js",
     "build:css": "lerna run build:css",
@@ -111,6 +112,7 @@
     "@storybook/addon-a11y": "^6.3.12",
     "@storybook/addon-actions": "^6.3.7",
     "@storybook/addon-knobs": "^6.3.1",
+    "@storybook/addon-toolbars": "^6.4.9",
     "@storybook/builder-webpack5": "^6.4.0-alpha.29",
     "@storybook/cli": "^5.3.21",
     "@storybook/manager-webpack5": "^6.4.0-alpha.29",
@@ -141,6 +143,7 @@
     "chromatic": "^6.0.6",
     "crypto": "^1.0.1",
     "css-loader": "^3.6.0",
+    "cypress": "^9.2.0",
     "enzyme": "^3.11.0",
     "enzyme-adapter-react-16": "^1.15.6",
     "enzyme-to-json": "^3.6.2",
@@ -150,8 +153,8 @@
     "eslint-config-jest-enzyme": "^7.1.2",
     "eslint-plugin-import": "^2.24.0",
     "eslint-plugin-jest": "^24.4.0",
-    "eslint-plugin-markdown": "^2.2.1",
     "eslint-plugin-jsx-a11y": "^6.5.1",
+    "eslint-plugin-markdown": "^2.2.1",
     "eslint-plugin-react": "^7.24.0",
     "eslint-plugin-react-hooks": "^4.2.0",
     "fs-extra": "^8.1.0",

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

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

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

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

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

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

+ 11 - 0
packages/semi-foundation/datePicker/datePicker.scss

@@ -140,6 +140,17 @@ $module: #{$prefix}-datepicker;
         padding-bottom: $spacing-datepicker_footer-paddingBottom;
         text-align: right;
         background-color: $color-datepicker_footer-bg-default;
+
+        .#{$prefix}-button {
+            // cancel button
+            &:first-of-type {
+                margin-right: $spacing-datepicker_footer_cancel_button-marginRight;
+            }
+            // confirm button
+            &:nth-of-type(2) {
+                margin-right: $spacing-datepicker_footer_confirm_button-marginRight;
+            }
+        }
     }
 
     // 年月选择

+ 33 - 15
packages/semi-foundation/datePicker/foundation.ts

@@ -323,10 +323,8 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
      *   2. set cachedSelectedValue using given dates(in needConfirm mode)
      *      - directly closePanel without click confirm will set cachedSelectedValue to state value
      *      - select one date(which means that the selection value is incomplete) and click confirm also set cachedSelectedValue to state value
-     * @param {String} inputValue
-     * @param {Date[]} dates
      */
-    rangeTypeSideEffectsWhenClosePanel(inputValue: string, dates: Date[]) {
+    rangeTypeSideEffectsWhenClosePanel(inputValue: string, willUpdateDates: Date[]) {
         if (this._isRangeType()) {
             this._adapter.setRangeInputFocus(false);
             /**
@@ -334,11 +332,29 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
              * when inputValue is null, picker value will back to last selected value
              */
             this.handleInputBlur(inputValue);
-            const { value, cachedSelectedValue } = this._adapter.getStates();
-            const newCachedSelectedValue = Array.isArray(dates) && dates.length ? dates : value;
-            if (!isEqual(newCachedSelectedValue, cachedSelectedValue)) {
-                this._adapter.updateCachedSelectedValue(newCachedSelectedValue);
-            }
+            this.resetCachedSelectedValue(willUpdateDates);
+        }
+    }
+
+    /**
+     * clear input value when selected date is not confirmed
+     */
+    needConfirmSideEffectsWhenClosePanel(willUpdateDates: Date[] | null | undefined) {
+        if (this._adapter.needConfirm() && !this._isRangeType()) {
+            /**
+             * if `null` input element will show `cachedSelectedValue` formatted value(format in DateInput render)
+             * if `` input element will show `` directly
+             */
+            this._adapter.updateInputValue(null);
+            this.resetCachedSelectedValue(willUpdateDates);
+        }
+    }
+
+    resetCachedSelectedValue(willUpdateDates?: Date[]) {
+        const { value, cachedSelectedValue } = this._adapter.getStates();
+        const newCachedSelectedValue = Array.isArray(willUpdateDates) ? willUpdateDates : value;
+        if (!isEqual(newCachedSelectedValue, cachedSelectedValue)) {
+            this._adapter.updateCachedSelectedValue(newCachedSelectedValue);
         }
     }
 
@@ -354,13 +370,16 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
      * @param {String} inputValue
      * @param {Date[]} dates
      */
-    closePanel(e?: any, inputValue: string = null, dates: Date[] = []) {
+    closePanel(e?: any, inputValue: string = null, dates?: Date[]) {
+        const { value, cachedSelectedValue } = this._adapter.getStates();
+        const willUpdateDates = isNullOrUndefined(dates) ? this._adapter.needConfirm() ? value : cachedSelectedValue : dates;
         if (!this._isControlledComponent('open')) {
             this._adapter.togglePanel(false);
             this._adapter.unregisterClickOutSide();
         }
         // range type picker, closing panel requires the following side effects
-        this.rangeTypeSideEffectsWhenClosePanel(inputValue, dates);
+        this.rangeTypeSideEffectsWhenClosePanel(inputValue, willUpdateDates as Date[]);
+        this.needConfirmSideEffectsWhenClosePanel(willUpdateDates as Date[]);
         this._adapter.notifyOpenChange(false);
         this._adapter.notifyBlur(e);
     }
@@ -416,7 +435,8 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
         if (parsedResult && parsedResult.length) {
             this._updateValueAndInput(parsedResult, input === '');
         } else if (input === '') {
-            this._updateValueAndInput('' as any, true);
+            // if clear input, set input to `''`
+            this._updateValueAndInput('' as any, true, '');
         } else {
             this._updateValueAndInput(stateValue);
         }
@@ -725,7 +745,7 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
         let inputValue;
         if (!this._someDateDisabled(changedDates)) {
             inputValue = this._isMultiple() ? this.formatMultipleDates(dates) : this.formatDates(dates);
-            const isRangeTypeAndInputIncomplete = this._isRangeType() && (isNullOrUndefined(dates[0]) || isNullOrUndefined(dates[1]));
+            const isRangeTypeAndInputIncomplete = this._isRangeType() && !this._isRangeValueComplete(dates);
             /**
              * If the input is incomplete when under control, the notifyChange is not triggered because
              * You need to update the value of the input box, otherwise there will be a problem that a date is selected but the input box does not show the date #1357
@@ -1004,11 +1024,9 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
     };
 
     _isRangeValueComplete = (value: Date[] | Date) => {
-        let result = true;
+        let result = false;
         if (Array.isArray(value)) {
             result = !value.some(date => isNullOrUndefined(date));
-        } else {
-            result = false;
         }
         return result;
     };

+ 2 - 0
packages/semi-foundation/datePicker/inputFoundation.ts

@@ -89,6 +89,8 @@ export default class InputFoundation extends BaseFoundation<DateInputAdapter> {
     }
 
     handleRangeInputClear(e: any) {
+        // prevent trigger click outside
+        this.stopPropagation(e);
         this._adapter.notifyRangeInputClear(e);
     }
 

+ 10 - 3
packages/semi-foundation/datePicker/monthsGridFoundation.ts

@@ -16,7 +16,7 @@ import {
 import { isBefore, isValidDate, getDefaultFormatToken, getFullDateOffset } from './_utils/index';
 import { formatFullDate, WeekStartNumber } from './_utils/getMonthTable';
 import { compatiableParse } from './_utils/parser';
-import { includes, isSet, isEqual } from 'lodash';
+import { includes, isSet, isEqual, isFunction } from 'lodash';
 import { zonedTimeToUtc } from '../utils/date-fns-extra';
 import { getDefaultFormatTokenByType } from './_utils/getDefaultFormatToken';
 import isNullOrUndefined from '../utils/isNullOrUndefined';
@@ -85,6 +85,7 @@ export interface MonthsGridFoundationProps extends MonthsGridElementProps {
     setRangeInputFocus?: (rangeInputFocus: 'rangeStart' | 'rangeEnd') => void;
     isAnotherPanelHasOpened?: (currentRangeInput: 'rangeStart' | 'rangeEnd') => boolean;
     focusRecordsRef?: any;
+    triggerRender?: (props: Record<string, any>) => any;
 }
 
 export interface MonthInfo {
@@ -636,7 +637,7 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
 
     handleRangeSelected(day: MonthDayInfo) {
         let { rangeStart, rangeEnd } = this.getStates();
-        const { startDateOffset, endDateOffset, type, dateFnsLocale, rangeInputFocus } = this.getProps();
+        const { startDateOffset, endDateOffset, type, dateFnsLocale, rangeInputFocus, triggerRender } = this._adapter.getProps();
         const { fullDate } = day;
         let rangeStartReset = false;
         let rangeEndReset = false;
@@ -712,7 +713,13 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
                     date = [start, end];
                 }
             }
-            this._adapter.notifySelectedChange(date, { needCheckFocusRecord: !(type === 'dateRange' && isDateRangeAndHasOffset) });
+            /**
+             * no need to check focus then
+             *  - dateRange and isDateRangeAndHasOffset
+             *  - dateRange and triggerRender
+             */
+            const needCheckFocusRecord = !(type === 'dateRange' && (isDateRangeAndHasOffset || isFunction(triggerRender)));
+            this._adapter.notifySelectedChange(date, { needCheckFocusRecord });
         }
     }
 

+ 15 - 1
packages/semi-foundation/datePicker/rtl.scss

@@ -9,6 +9,18 @@ $module: #{$prefix}-datepicker;
             padding-right: 0;
             padding-left: $spacing-datepicker_footer-paddingRight;
             text-align: left;
+
+            .#{$prefix}-button {
+                &:first-of-type {
+                    margin-left: 0;
+                    margin-right: 0;
+                }
+                // confirm button
+                &:nth-of-type(2) {
+                    margin-right: $spacing-datepicker_footer_cancel_button-marginRight;
+                    margin-left: 0;
+                }
+            }
         }
 
         &-day {
@@ -66,7 +78,9 @@ $module: #{$prefix}-datepicker;
         &-yam {
             // rtl 对箭头进行翻转
             .#{$prefix}-icon-chevron_left,
-            .#{$prefix}-icon-chevron_right {
+            .#{$prefix}-icon-chevron_right,
+            .#{$prefix}-icon-double_chevron_left,
+            .#{$prefix}-icon-double_chevron_right {
                 transform: scaleX(-1);
             }
         }

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

@@ -33,6 +33,8 @@ $spacing-datepicker_scrolllist_body-padding: 0; // 时间选择滚动菜单内
 $spacing-datepicker_footer-paddingTop: 10px; // 确认选择 footer 顶部内边距
 $spacing-datepicker_footer-paddingBottom: 10px; // 确认选择 footer 底部内边距
 $spacing-datepicker_footer-paddingRight: 8px; // 确认选择 footer 右侧内边距
+$spacing-datepicker_footer_cancel_button-marginRight: 12px; // 确认选择 footer 取消按钮右外边距
+$spacing-datepicker_footer_confirm_button-marginRight: 8px; // 确认选择 footer 确认按钮右外边距
 $spacing-datepicker_navigation-paddingY: $spacing-base-tight; // 年月切换 header 垂直内边距
 $spacing-datepicker_navigation-paddingX: $spacing-base; // 年月切换 header 水平内边距
 $spacing-datepicker_month-padding: $spacing-base; 

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

@@ -126,6 +126,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         }
         this._adapter.recordCursorPosition();
         this._adapter.setFocusing(true, null);
+        this._adapter.setClickUpOrDown(false);
         this._adapter.notifyFocus(e);
     }
 
@@ -425,11 +426,11 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
 
     /**
      * format number to string
-     * @param {number} value
+     * @param {string|number} value
      * @param {boolean} needAdjustPrec
      * @returns {string}
      */
-    doFormat(value = 0, needAdjustPrec = true): string {
+    doFormat(value: string | number = 0, needAdjustPrec = true): string {
         // if (typeof value === 'string') {
         //     return value;
         // }

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.2.0-beta.1",
+    "version": "2.2.2",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",
@@ -8,7 +8,7 @@
     },
     "dependencies": {
         "@babel/runtime-corejs3": "^7.15.4",
-        "@douyinfe/semi-animation": "2.2.0-beta.1",
+        "@douyinfe/semi-animation": "2.2.2",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
         "date-fns": "^2.9.0",

+ 0 - 2
packages/semi-foundation/table/table.scss

@@ -25,7 +25,6 @@ $module: #{$prefix}-table;
     }
 
     &-middle {
-        .#{$module}-thead > .#{$module}-row > .#{$module}-row-head,
         .#{$module}-tbody > .#{$module}-row > .#{$module}-row-cell {
             padding-top: $spacing-table_middle-paddingY;
             padding-bottom: $spacing-table_middle-paddingY;
@@ -33,7 +32,6 @@ $module: #{$prefix}-table;
     }
 
     &-small {
-        .#{$module}-thead > .#{$module}-row > .#{$module}-row-head,
         .#{$module}-tbody > .#{$module}-row > .#{$module}-row-cell {
             padding-top: $spacing-table_small-paddingY;
             padding-bottom: $spacing-table_small-paddingY;

+ 5 - 5
packages/semi-foundation/tooltip/foundation.ts

@@ -270,7 +270,6 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
         if (trigger === 'custom') {
             // eslint-disable-next-line
             this._adapter.registerClickOutsideHandler(() => {});
-            this._togglePortalVisible(true);
         }
 
         /**
@@ -571,6 +570,7 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
     // place the dom correctly
     adjustPosIfNeed(position: Position | string, style: Record<string, any>, triggerRect: DOMRect, wrapperRect: DOMRect, containerRect: PopupContainerDOMRect) {
         const { innerWidth, innerHeight } = window;
+        const { spacing } = this.getProps();
 
         if (wrapperRect.width > 0 && wrapperRect.height > 0) {
             // let clientLeft = left + translateX * wrapperRect.width - containerRect.scrollLeft;
@@ -600,10 +600,10 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
 
             // The wrapperR ect.top|bottom equivalent cannot be directly used here for comparison, which is easy to cause jitter
 
-            const shouldReverseTop = clientTop < wrapperRect.height && restClientBottom > wrapperRect.height;
-            const shouldReverseLeft = clientLeft < wrapperRect.width && restClientRight > wrapperRect.width;
-            const sholdReverseBottom = restClientBottom < wrapperRect.height && clientTop > wrapperRect.height;
-            const shouldReverseRight = restClientRight < wrapperRect.width && clientLeft > wrapperRect.width;
+            const shouldReverseTop = clientTop < wrapperRect.height + spacing && restClientBottom > wrapperRect.height + spacing;
+            const shouldReverseLeft = clientLeft < wrapperRect.width + spacing && restClientRight > wrapperRect.width + spacing;
+            const sholdReverseBottom = restClientBottom < wrapperRect.height + spacing && clientTop > wrapperRect.height + spacing;
+            const shouldReverseRight = restClientRight < wrapperRect.width + spacing && clientLeft > wrapperRect.width + spacing;
 
             const shouldReverseTopSide = restClientTop < wrapperRect.height && clientBottom > wrapperRect.height;
             const shouldReverseBottomSide = clientBottom < wrapperRect.height && restClientTop > wrapperRect.height;

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-icons",
-  "version": "2.2.0-beta.1",
+  "version": "2.2.2",
   "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.2.0-beta.1",
+    "@douyinfe/semi-webpack-plugin": "2.2.2",
     "babel-loader": "^8.2.2",
     "css-loader": "4.3.0",
     "del": "^6.0.0",

+ 29 - 0
packages/semi-icons/src/icons/IconFastForward.tsx

@@ -0,0 +1,29 @@
+import * as React from 'react';
+import { convertIcon } from '../components/Icon';
+
+function SvgComponent(props: React.SVGProps<SVGSVGElement>) {
+    return (
+        <svg
+            viewBox="0 0 24 24"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+            width="1em"
+            height="1em"
+            focusable={false}
+            aria-hidden={true}
+            {...props}
+        >
+            <path
+                d="M1 5.96376C1 5.14647 1.9272 4.67432 2.58817 5.15502L10.888 11.1913C11.4371 11.5906 11.4371 12.4094 10.888 12.8087L2.58817 18.845C1.9272 19.3257 1 18.8535 1 18.0362V5.96376Z"
+                fill="currentColor"
+            />
+            <path
+                d="M12 5.96376C12 5.14647 12.9272 4.67432 13.5882 5.15502L21.888 11.1913C22.4371 11.5906 22.4371 12.4094 21.888 12.8087L13.5882 18.845C12.9272 19.3257 12 18.8535 12 18.0362V5.96376Z"
+                fill="currentColor"
+            />
+        </svg>
+    );
+}
+
+const IconComponent = convertIcon(SvgComponent, 'fast_forward');
+export default IconComponent;

+ 1 - 0
packages/semi-icons/src/icons/index.ts

@@ -143,6 +143,7 @@ export { default as IconEyeClosedSolid } from './IconEyeClosedSolid';
 export { default as IconEyeOpened } from './IconEyeOpened';
 export { default as IconFacebook } from './IconFacebook';
 export { default as IconFaceuLogo } from './IconFaceuLogo';
+export { default as IconFastForward } from './IconFastForward';
 export { default as IconFastFoward } from './IconFastFoward';
 export { default as IconFavoriteList } from './IconFavoriteList';
 export { default as IconFeishuLogo } from './IconFeishuLogo';

+ 1 - 0
packages/semi-icons/src/svgs/fast_forward.svg

@@ -0,0 +1 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 5.96376C1 5.14647 1.9272 4.67432 2.58817 5.15502L10.888 11.1913C11.4371 11.5906 11.4371 12.4094 10.888 12.8087L2.58817 18.845C1.9272 19.3257 1 18.8535 1 18.0362V5.96376Z" fill="black"/><path d="M12 5.96376C12 5.14647 12.9272 4.67432 13.5882 5.15502L21.888 11.1913C22.4371 11.5906 22.4371 12.4094 21.888 12.8087L13.5882 18.845C12.9272 19.3257 12 18.8535 12 18.0362V5.96376Z" fill="black"/></svg>

+ 1 - 1
packages/semi-icons/src/svgs/meta.json

@@ -1080,7 +1080,7 @@
         "category": "Writing"
     },
     {
-        "name": "fast_foward",
+        "name": "fast_forward",
         "category": "Music"
     },
     {

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

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

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-next",
-    "version": "2.2.0-beta.1",
+    "version": "2.2.2",
     "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.2.0-beta.1"
+        "@douyinfe/semi-webpack-plugin": "2.2.2"
     },
     "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-scss-compile",
-  "version": "2.2.0-beta.1",
+  "version": "2.2.2",
   "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.2.0-beta.1",
+    "version": "2.2.2",
     "description": "semi-theme-default",
     "keywords": [
         "semi-theme",

+ 108 - 0
packages/semi-ui/datePicker/__test__/datePicker.test.js

@@ -191,12 +191,27 @@ describe(`DatePicker`, () => {
         btns[0].click();
         await sleep();
         expect(_.first(elem.state('value')).getDate() === currentValue.getDate()).toBeTruthy();
+        expect(_.isEqual(elem.state('cachedSelectedValue'), [currentValue])).toBe(true);
 
         /**
          * click ensure button
          */
         btns[1].click();
         await sleep();
+        expect(_.first(elem.state('value')).getDate() === currentValue.getDate()).toBe(true);
+
+        /**
+         * re click next day
+         */
+        nextOffsetDayElem.click();
+        await sleep();
+        expect(_.first(elem.state('value')).getDate() === currentValue.getDate()).toBeTruthy();
+
+        /**
+         * re click ensure button
+         */
+        btns[1].click();
+        await sleep();
         expect(_.first(elem.state('value')).getDate() - currentValue.getDate()).toBe(dayOffset);
 
         demo.unmount();
@@ -953,4 +968,97 @@ describe(`DatePicker`, () => {
 
     it('test month sync change dateRange type', () => { testMonthSyncChange('dateRange') });
     it('test month sync change dateTimeRange type', () => { testMonthSyncChange('dateTimeRange')});
+
+    it(`test preset given null`, async () => {
+        const props = {
+            presets: [
+                {
+                    text: 'Today',
+                    start: null,
+                    end: null,
+                }
+            ],
+            defaultValue: baseDate,
+            defaultOpen: true,
+            motion: false,
+            type: 'dateRange'
+        }
+        const handleChange = sinon.spy();
+        const demo = mount(<DatePicker {...props} onChange={handleChange} />);
+        const elem = demo.find(BaseDatePicker);
+
+        const btns = document.querySelectorAll('.semi-datepicker-quick-control-item');
+
+        btns[0].click();
+        expect(handleChange.called).toBeTruthy();
+        const args = handleChange.getCall(0).args;
+        expect(args[0].length).toEqual(0);
+        expect(elem.state('panelShow')).toBeFalsy();
+    });
+
+    it(`test preset given null + needConfirm`, async () => {
+        const props = {
+            presets: [
+                {
+                    text: 'Today',
+                    start: null,
+                    end: null,
+                }
+            ],
+            defaultValue: baseDate,
+            defaultOpen: true,
+            motion: false,
+            type: 'dateTimeRange',
+            needConfirm: true,
+        }
+        const handleChange = sinon.spy();
+        const handleConfirm = sinon.spy();
+        const demo = mount(<DatePicker {...props} onChange={handleChange} onConfirm={handleConfirm} />);
+        const elem = demo.find(BaseDatePicker);
+
+        const btns = document.querySelectorAll('.semi-datepicker-quick-control-item');
+
+        // 点击 preset
+        btns[0].click();
+        expect(handleChange.called).toBe(true);
+        const argsChange = handleChange.getCall(0).args;
+        expect(argsChange[0].length).toBe(0);
+        expect(elem.state('panelShow')).toBe(true);
+        // 点击确定
+        const footerBtns = document.querySelectorAll('.semi-datepicker-footer .semi-button');
+        footerBtns[1].click();
+        expect(handleConfirm.called).toBe(true);
+        const argsConfirm = handleConfirm.getCall(0).args;
+        expect(argsConfirm[0].length).toBe(0);
+        expect(elem.state('panelShow')).toBe(false);
+    });
+    
+    it('test dateRange triggerRender', async () => {
+        const elem = mount(
+            <DatePicker
+                motion={false}
+                // defaultOpen
+                type="dateRange"
+                triggerRender={({ placeholder }) => (
+                    <button>
+                        {placeholder}
+                    </button>
+                )}
+            />
+        );
+        const trigger = document.querySelector('button');
+        trigger.click();
+        await sleep();
+        const leftPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-left`);
+        const leftSecondWeek = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
+        const leftSecondWeekDays = leftSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
+        const rightPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-right`);
+        const rightSecondWeek = rightPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
+        const rightSecondWeekDays = rightSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
+        leftSecondWeekDays[0].click();
+        rightSecondWeekDays[0].click();
+
+        const baseElem = elem.find(BaseDatePicker);
+        expect(baseElem.state('panelShow')).toBeFalsy();
+    });
 });

+ 146 - 2
packages/semi-ui/datePicker/_story/datePicker.stories.js

@@ -10,7 +10,7 @@ import {
   startOfWeek,
   endOfWeek,
 } from 'date-fns';
-import { Space, ConfigProvider, InputGroup, InputNumber, Form, withField } from '../../index';
+import { Space, ConfigProvider, InputGroup, InputNumber, Form, withField, Button } from '../../index';
 
 // stores
 import NeedConfirmDemo from './NeedConfirm';
@@ -661,4 +661,148 @@ export const FixParseISOBug = () => (
 FixParseISOBug.storyName = '修复 parseISO bug';
 FixParseISOBug.parameters = {
   chromatic: { disableSnapshot: false },
-};
+};
+
+export const FixNeedConfirm = () => {
+  const defaultDate = '2021-12-27 10:37:13';
+  const defaultDateRange = ['2021-12-27 10:37:13', '2022-01-28 10:37:13' ];
+  const props = {
+    needConfirm: true,
+    onConfirm: (...args) => {
+      console.log('Confirmed: ', ...args);
+    },
+    onChange: (...args) => {
+      console.log('Changed: ', ...args);
+    },
+    onCancel: (...args) => {
+      console.log('Canceled: ', ...args);
+    },
+  };
+
+  return (
+    <div>
+      <div data-cy="1">
+        <span>dateTime + needConfirm + defaultValue</span>
+        <div>
+          <DatePicker
+            type="dateTime"
+            defaultValue={defaultDate}
+            {...props}
+          />
+        </div>
+      </div>
+      <div data-cy="2">
+        <span>dateTime + needConfirm</span>
+        <div>
+          <DatePicker
+            type="dateTime"
+            {...props}
+          />
+        </div>
+      </div>
+      <div data-cy="3">
+        <span>dateTimeRange + needConfirm + defaultValue</span>
+        <div>
+          <DatePicker
+            type="dateTimeRange"
+            defaultValue={defaultDateRange}
+            {...props}
+          />
+        </div>
+      </div>
+      <div data-cy="4">
+        <span>dateTimeRange + needConfirm</span>
+        <div>
+          <DatePicker
+            type="dateTimeRange"
+            {...props}
+          />
+        </div>
+      </div>
+    </div>
+  )
+}
+FixNeedConfirm.storyName = '修复 needConfirm 取消后输入框显示错误';
+
+/**
+ * fix https://github.com/DouyinFE/semi-design/issues/388
+ */
+export const FixPresetsClick = () => {
+  const presets = [
+    {
+      text: '清空',
+      start: '',
+      end: '',
+    },
+    {
+      text: 'Tomorrow',
+      start: new Date(new Date().valueOf() + 1000 * 3600 * 24),
+      end: new Date(new Date().valueOf() + 1000 * 3600 * 24),
+    },
+  ];
+
+  const handleChange = v => {
+    console.log('change', v);
+  };
+
+  const handleConfirm = v => {
+    console.log('confirm', v);
+  }
+
+  return (
+    <div>
+      <div>
+        <label>
+          <span>不设置 needConfirm</span>
+          <DatePicker onChange={console.log} type="dateRange" presets={presets} />
+        </label>
+      </div>
+      <div>
+        <label>
+          <span>设置 needConfirm</span>
+          <DatePicker needConfirm onChange={handleChange} onConfirm={handleConfirm} type="dateTimeRange" presets={presets} />
+        </label>
+      </div>
+    </div>
+  );
+};
+FixPresetsClick.storyName = '修复 presets 点击后不收起问题';
+
+/**
+ * fix https://github.com/DouyinFE/semi-design/issues/410
+ */
+export const FixTriggerRenderClosePanel = () => {
+  const [value, setValue] = useState([]);
+
+  const handleChange = v => {
+    console.log('change', v);
+    setValue(v);
+  };
+
+  const formatValue = (dates) => {
+    const dateStrs = dates.map(v => String(v));
+    return dateStrs.join(' ~ ');
+  };
+
+  const showClear = Array.isArray(value) && value.length > 1;
+
+  return (
+    <Space>
+      <DatePicker
+        value={value}
+        type="dateRange"
+        onChange={handleChange}
+        motion={false}
+        triggerRender={({ placeholder }) => (
+            <Button>
+                {(value && formatValue(value)) || placeholder}
+            </Button>
+        )}
+      />
+      {showClear && (
+        <Button onClick={() => setValue([])}>清除</Button>
+      )}
+    </Space>
+  );
+};
+FixTriggerRenderClosePanel.storyName = "fix triggerRender close bug"

+ 5 - 0
packages/semi-ui/datePicker/datePicker.tsx

@@ -206,6 +206,9 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
                     this.clickOutSideHandler = null;
                 }
                 this.clickOutSideHandler = e => {
+                    if (this.adapter.needConfirm()) {
+                        return;
+                    }
                     const triggerEl = this.triggerElRef && this.triggerElRef.current;
                     const panelEl = this.panelRef && this.panelRef.current;
                     const isInTrigger = triggerEl && triggerEl.contains(e.target as Node);
@@ -360,6 +363,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
             syncSwitchMonth,
             onPanelChange,
             timeZone,
+            triggerRender
         } = this.props;
         const { value, cachedSelectedValue, motionEnd, rangeInputFocus } = this.state;
 
@@ -405,6 +409,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
                 onPanelChange={onPanelChange}
                 timeZone={timeZone}
                 focusRecordsRef={this.focusRecordsRef}
+                triggerRender={triggerRender}
             />
         );
     }

+ 2 - 1
packages/semi-ui/datePicker/monthsGrid.tsx

@@ -71,7 +71,8 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
         syncSwitchMonth: PropTypes.bool,
         // Callback function for panel date switching
         onPanelChange: PropTypes.func,
-        focusRecordsRef: PropTypes.object
+        focusRecordsRef: PropTypes.object,
+        triggerRender: PropTypes.func,
     };
 
     static defaultProps = {

+ 0 - 2
packages/semi-ui/form/index.tsx

@@ -19,8 +19,6 @@ import withField from './hoc/withField';
 import withFormState from './hoc/withFormState';
 import withFormApi from './hoc/withFormApi';
 
-export * from './interface';
-
 export {
     Form,
     ArrayField,

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

@@ -641,3 +641,15 @@ export const FormCustomInput = () => {
 FormCustomInput.story = {
   name: 'Form.CustomInput',
 };
+
+
+export const FixPrecision = () => {
+  const [value, setValue] = useState(5.12);
+  const [value2, setValue2] = useState(5.12);
+  return (
+    <div>
+        <InputNumber onChange={v => setValue(v)} value={value} style={{ width: 190 }} precision={2} />
+        <InputNumber keepFocus onBlur={() => console.log('blur')} onChange={v => setValue2(v)} value={value2} style={{ width: 190 }} precision={2} />
+    </div>
+  );
+}

+ 5 - 1
packages/semi-ui/inputNumber/index.tsx

@@ -225,6 +225,7 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
     currentValue!: number | string;
     cursorBefore!: string;
     cursorAfter!: string;
+    foundation: InputNumberFoundation;
     constructor(props: InputNumberProps) {
         super(props);
         this.state = {
@@ -291,7 +292,10 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
                 if (focusing) {
                     if (this.foundation.isValidNumber(parsedNum) && parsedNum !== this.state.number) {
                         const obj: { number?: number; value?: string } = { number: parsedNum };
-                        // Updates input when a button is clicked
+                        /**
+                         * If you are clicking the button, it will automatically format once
+                         * We need to set the status to false after trigger focus event
+                         */
                         if (this.clickUpOrDown) {
                             obj.value = this.foundation.doFormat(valueStr, true);
                         }

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.2.0-beta.1",
+    "version": "2.2.2",
     "description": "",
     "main": "lib/cjs/index.js",
     "module": "lib/es/index.js",
@@ -14,11 +14,11 @@
     },
     "dependencies": {
         "@babel/runtime-corejs3": "^7.15.4",
-        "@douyinfe/semi-animation-react": "2.2.0-beta.1",
-        "@douyinfe/semi-foundation": "2.2.0-beta.1",
-        "@douyinfe/semi-icons": "2.2.0-beta.1",
-        "@douyinfe/semi-illustrations": "2.2.0-beta.1",
-        "@douyinfe/semi-theme-default": "2.2.0-beta.1",
+        "@douyinfe/semi-animation-react": "2.2.2",
+        "@douyinfe/semi-foundation": "2.2.2",
+        "@douyinfe/semi-icons": "2.2.2",
+        "@douyinfe/semi-illustrations": "2.2.2",
+        "@douyinfe/semi-theme-default": "2.2.2",
         "@types/react-window": "^1.8.2",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
@@ -74,7 +74,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.2.0-beta.1",
+        "@douyinfe/semi-scss-compile": "2.2.2",
         "@storybook/addon-knobs": "^6.3.1",
         "@types/lodash": "^4.14.176",
         "babel-loader": "^8.2.2",

+ 24 - 13
packages/semi-ui/table/Table.tsx

@@ -95,6 +95,7 @@ export interface NormalTableState<RecordType extends Record<string, any> = Data>
     bodyHasScrollBar?: boolean;
     prePropRowSelection?: TableStateRowSelection<RecordType>;
     tableWidth?: number;
+    prePagination?: Pagination;
 }
 
 export type TableStateRowSelection<RecordType extends Record<string, any> = Data> = (RowSelectionProps<RecordType> & { selectedRowKeysSet?: Set<(string | number)> }) | boolean;
@@ -390,6 +391,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
             headWidths: [], // header cell width
             bodyHasScrollBar: false,
             prePropRowSelection: undefined,
+            prePagination: undefined
         };
 
         this.rootWrapRef = createRef();
@@ -412,7 +414,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
 
     static getDerivedStateFromProps(props: NormalTableProps, state: NormalTableState) {
         const willUpdateStates: Partial<NormalTableState> = {};
-        const { rowSelection, dataSource, childrenRecordName, rowKey } = props;
+        const { rowSelection, dataSource, childrenRecordName, rowKey, pagination } = props;
         props.columns && props.children && logger.warn('columns should not given by object and children at the same time');
 
         if (props.columns && props.columns !== state.cachedColumns) {
@@ -453,6 +455,17 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
             willUpdateStates.rowSelection = newSelectionStates;
             willUpdateStates.prePropRowSelection = rowSelection;
         }
+        if (pagination !== state.prePagination) {
+            let newPagination: Pagination = {};
+            if (isObject(state.pagination)) {
+                newPagination = { ...newPagination, ...state.pagination };
+            }
+            if (isObject(pagination)) {
+                newPagination = { ...newPagination, ...pagination };
+            }
+            willUpdateStates.pagination = newPagination;
+            willUpdateStates.prePagination = pagination;
+        }
         return willUpdateStates;
     }
 
@@ -468,16 +481,17 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
     // TODO: Extract the setState operation to the adapter or getDerivedStateFromProps function
     componentDidUpdate(prevProps: NormalTableProps<RecordType>, prevState: NormalTableState<RecordType>) {
         const {
-            pagination,
             dataSource,
             expandedRowKeys,
             expandAllRows,
             expandAllGroupRows,
             virtualized,
             components,
+            pagination: propsPagination
         } = this.props;
 
         const {
+            pagination: statePagination,
             queries: stateQueries,
             cachedColumns: stateCachedColumns,
             cachedChildren: stateCachedChildren,
@@ -521,11 +535,6 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
         }
 
 
-        // Update pagination
-        if (pagination !== prevProps.pagination) {
-            states.pagination = isObject(pagination) ? { ...pagination } : pagination;
-        }
-
         /**
          * After dataSource is updated || (cachedColumns || cachedChildren updated)
          * 1. Cache filtered sorted data and a collection of data rows, stored in this
@@ -538,10 +547,11 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
             const filteredSortedDataSource = this.foundation.getFilteredSortedDataSource(_dataSource, stateQueries);
             this.foundation.setCachedFilteredSortedDataSource(filteredSortedDataSource);
             states.dataSource = filteredSortedDataSource;
-
-            if (pagination === prevProps.pagination) {
-                states.pagination = isObject(pagination) ? { ...pagination } : pagination;
-            }
+            // when dataSource has change, should reset currentPage
+            states.pagination = isObject(statePagination) ? {
+                ...statePagination,
+                currentPage: isObject(propsPagination) && propsPagination.currentPage ? propsPagination.currentPage : 1,
+            } : statePagination;
 
             if (this.props.groupBy) {
                 states.groups = null;
@@ -551,11 +561,11 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
         if (Object.keys(states).length) {
             const {
                 // eslint-disable-next-line @typescript-eslint/no-shadow
+                pagination: mergedStatePagination = null,
                 queries: stateQueries = null,
-                pagination: statePagination = null,
                 dataSource: stateDataSource = null,
             } = states;
-            const handledProps: Partial<NormalTableState<RecordType>> = this.foundation.getCurrentPageData(stateDataSource, statePagination as TablePaginationProps, stateQueries);
+            const handledProps: Partial<NormalTableState<RecordType>> = this.foundation.getCurrentPageData(stateDataSource, mergedStatePagination as TablePaginationProps, stateQueries);
 
             // After the pager is updated, reset allRowKeys of the current page
             this.adapter.setAllRowKeys(handledProps.allRowKeys);
@@ -966,6 +976,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
     /**
      * render pagination
      * @param {object} pagination
+     * @param {object} propRenderPagination
      */
     renderPagination = (pagination: TablePaginationProps, propRenderPagination: RenderPagination) => {
         if (!pagination) {

+ 46 - 0
packages/semi-ui/table/__test__/table.test.js

@@ -1452,6 +1452,52 @@ describe(`Table`, () => {
         });
         expect(demo.find(BaseTable).state('disabledRowKeys').length).toEqual(2);
     });
+    /**
+     * 分页受控场景,更新数据后查看分页器是否保持当前页
+     */
+    it('test controlled pagination reset when dataSource change', async () => {
+        const total = 100;
+        const pagination = {
+            pageSize: 10,
+            currentPage: 2,
+        };
+        const columns = getColumns();
+        const demo = mount(<Table dataSource={getData(total)} columns={columns} pagination={pagination}/>);
+
+        const dataNum = getRandomNumber(100, 40);
+        const newData = getData(dataNum);
+        demo.setProps({
+            dataSource: newData,
+        });
+        await sleep(2000);
+        expect(
+          demo
+            .find(`.${BASE_CLASS_PREFIX}-page .${BASE_CLASS_PREFIX}-page-item-active`)
+            .getDOMNode().innerHTML
+        ).toBe('2');
+    });
+
+    /**
+     * 分页非受控场景,更新数据后查看分页器是否重置
+     */
+    it('test uncontrolled pagination reset when dataSource change', async () => {
+        const total = 100;
+        const columns = getColumns();
+        const demo = mount(<Table dataSource={getData(total)} columns={columns}/>);
+        demo.find(`.${BASE_CLASS_PREFIX}-page .${BASE_CLASS_PREFIX}-page-item`)
+          .at(2)
+          .simulate('click');
+        const dataNum = getRandomNumber(100, 40);
+        const newData = getData(dataNum);
+        demo.setProps({
+            dataSource: newData,
+        });
+        await sleep(2000);
+        demo.update();
+        expect(demo.find(`.semi-page .semi-page-item-active`).getDOMNode().innerHTML).toBe('1');
+        expect(demo.find(BaseTable).state('pagination')).toHaveProperty('currentPage', 1);
+    });
+
     it('test pagination changed', async () => {
         const total = 100;
         const pagination = {

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

@@ -15,7 +15,7 @@ import TabPane from './TabPane';
 import TabsContext from './tabs-context';
 import { TabsProps, PlainTab, TabBarProps } from './interface';
 
-const panePickKeys = Object.keys(omit(TabPane.propTypes, ['children']));
+const panePickKeys = ['className', 'style', 'disabled', 'itemKey', 'tab', 'icon'];
 
 export * from './interface';
 

+ 46 - 0
packages/semi-ui/tagInput/__test__/tagInput.test.js

@@ -76,6 +76,28 @@ describe('TagInput', () => {
         tagInput.unmount();
     });
 
+    it('TagInput with defaultValue and value is undefined', () => {
+        const props = {
+            defaultValue: ['tiktok', 'hotsoon'],
+            value: undefined,
+        };
+        const tagInput = getTagInput(props);
+        const tags = tagInput.find(`.${BASE_CLASS_PREFIX}-tagInput-wrapper .${BASE_CLASS_PREFIX}-tag-content`);
+        expect(tags.length).toEqual(0);
+        tagInput.unmount();
+    });
+
+    it('TagInput with defaultValue and value is null', () => {
+        const props = {
+            defaultValue: ['tiktok', 'hotsoon'],
+            value: null,
+        };
+        const tagInput = getTagInput(props);
+        const tags = tagInput.find(`.${BASE_CLASS_PREFIX}-tagInput-wrapper .${BASE_CLASS_PREFIX}-tag-content`);
+        expect(tags.length).toEqual(0);
+        tagInput.unmount();
+    });
+
     it('TagInput with disabled', () => {
         const disabledTagInput = mount(<TagInput disabled />);
         expect(disabledTagInput.exists(`.${BASE_CLASS_PREFIX}-tagInput-disabled`)).toEqual(true);
@@ -328,6 +350,30 @@ describe('TagInput', () => {
         expect(tagInput.find(`.${BASE_CLASS_PREFIX}-tagInput-wrapper .${BASE_CLASS_PREFIX}-tag-content`).getDOMNode().textContent).toEqual('hotsoon');
     });
 
+    it('tagInput with set value to null  ', () => {
+        const props = {
+            value: ['tiktok']
+        };
+        const tagInput = getTagInput(props);
+        expect(tagInput.find(`.${BASE_CLASS_PREFIX}-tagInput-wrapper .${BASE_CLASS_PREFIX}-tag-content`).getDOMNode().textContent).toEqual('tiktok');
+        tagInput.setProps({ value: null });
+        tagInput.update();
+        const tags = tagInput.find(`.${BASE_CLASS_PREFIX}-tagInput-wrapper .${BASE_CLASS_PREFIX}-tag-content`);
+        expect(tags.length).toEqual(0);
+    });
+
+    it('tagInput with set value to null  ', () => {
+        const props = {
+            value: ['tiktok']
+        };
+        const tagInput = getTagInput(props);
+        expect(tagInput.find(`.${BASE_CLASS_PREFIX}-tagInput-wrapper .${BASE_CLASS_PREFIX}-tag-content`).getDOMNode().textContent).toEqual('tiktok');
+        tagInput.setProps({ value: undefined });
+        tagInput.update();
+        const tags = tagInput.find(`.${BASE_CLASS_PREFIX}-tagInput-wrapper .${BASE_CLASS_PREFIX}-tag-content`);
+        expect(tags.length).toEqual(0);
+    });
+
     it('tagInput with inputValue controlled mode ', () => {
         const props = {
             inputValue: 'abc'

+ 12 - 6
packages/semi-ui/tagInput/index.tsx

@@ -55,7 +55,7 @@ export interface TagInputProps {
     style?: React.CSSProperties;
     suffix?: React.ReactNode;
     validateStatus?: ValidateStatus;
-    value?: string[];
+    value?: string[] | undefined;
     autoFocus?: boolean;
 }
 
@@ -140,12 +140,18 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
     }
 
     static getDerivedStateFromProps(nextProps: TagInputProps, prevState: TagInputState) {
-        const {
-            value,
-            inputValue,
-        } = nextProps;
+        const { value, inputValue } = nextProps;
+        const { tagsArray: prevTagsArray } = prevState;
+        let tagsArray: string[];
+        if (isArray(value)) {
+            tagsArray = value;
+        } else if ('value' in nextProps && !value) {
+            tagsArray = [];
+        } else {
+            tagsArray = prevTagsArray;
+        }
         return {
-            tagsArray: isArray(value) ? value : prevState.tagsArray,
+            tagsArray,
             inputValue: isString(inputValue) ? inputValue : prevState.inputValue
         };
     }

+ 45 - 1
packages/semi-ui/tooltip/_story/tooltip.stories.js

@@ -1,7 +1,7 @@
 import React, { useState, useMemo } from 'react';
 import Tooltip from '../index';
 import './story.scss';
-import { Tag, Icon, IconButton, Switch, Checkbox, Radio, Button, Select } from '@douyinfe/semi-ui';
+import { Tag, Icon, IconButton, Switch, Checkbox, Radio, Button, Select, InputNumber } from '@douyinfe/semi-ui';
 
 import InTableDemo from './InTable';
 import EdgeDemo from './Edge';
@@ -687,4 +687,48 @@ export const OnClickOutSideDemo = () => {
 }
 OnClickOutSideDemo.story = {
   name: 'OnClickOutSide',
+};
+
+export const AutoAdjustWithSpacing = () => {
+    const [height, setHeight] = useState(84);
+    const [key, setKey] = useState(1);
+    const initSpacing = 8;
+    const [spacing, setSpacing] = useState(initSpacing);
+
+    const change = (height, hasSpace) => {
+        setHeight(height);
+        hasSpace ? setSpacing(initSpacing) : setSpacing(0);
+        setKey(Math.random());
+    };
+
+    return (
+        <div className="demo1">
+            <div>
+                <Tooltip
+                    motion={false}
+                    rePosKey={key}
+                    // spacing={spacing}
+                    content={
+                        <article style={{ boxSizing: 'border-box', height: height }}>
+                            <p>hi bytedance, + padding 20</p>
+                            <p>hi bytedance</p>
+                        </article>
+                    }
+                    position="top"
+                    trigger="custom"
+                    visible={true}
+                >
+                    <Tag>demo</Tag>
+                </Tooltip>
+            </div>
+            <div style={{ marginTop: 200 }}>
+                <Switch onChange={hasSpace => change(height, hasSpace)} checked={spacing === initSpacing ? true : false}></Switch>
+                <InputNumber onChange={height => change(Number(height))} value={height} style={{ width: 200 }} />
+            </div>
+        </div>
+    )
+};
+
+AutoAdjustWithSpacing.story = {
+  name: 'AutoAdjustWithSpacing',
 };

+ 20 - 23
packages/semi-ui/tooltip/index.tsx

@@ -77,6 +77,7 @@ interface TooltipState {
     isInsert: boolean;
     placement: Position;
     transitionStyle: Record<string, any>;
+    isPositionUpdated: boolean;
 }
 
 const prefix = cssClasses.PREFIX;
@@ -167,6 +168,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
             isInsert: false,
             placement: props.position || 'top',
             transitionStyle: {},
+            isPositionUpdated: false,
         };
         this.foundation = new TooltipFoundation(this.adapter);
         this.eventManager = new Event();
@@ -197,18 +199,15 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
                         containerStyle: { ...this.state.containerStyle, ...containerStyle },
                     },
                     () => {
-                        /**
-                         * Dangerous: remove setTimeout from here fix #1301
-                         * setTimeout may emit portalInserted event after hiding portal
-                         * Hiding portal will remove portalInserted event listener(normal process)
-                         * then portal can't hide because _togglePortalVisible(false) will found isVisible=false and nowVisible=false(bug here)
-                         */
-                        this.eventManager.emit('portalInserted');
+                        setTimeout(() => {
+                            // waiting child component mounted
+                            this.eventManager.emit('portalInserted');
+                        }, 0);
                     }
                 );
             },
             removePortal: () => {
-                this.setState({ isInsert: false });
+                this.setState({ isInsert: false, isPositionUpdated: false });
             },
             getEventName: () => ({
                 mouseEnter: 'onMouseEnter',
@@ -275,7 +274,11 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
             getDocumentElementBounding: () => document.documentElement.getBoundingClientRect(),
             setPosition: ({ position, ...style }: { position: Position }) => {
                 this.setState(
-                    { containerStyle: { ...this.state.containerStyle, ...style }, placement: position },
+                    { 
+                        containerStyle: { ...this.state.containerStyle, ...style },
+                        placement: position,
+                        isPositionUpdated: true
+                    },
                     () => {
                         this.eventManager.emit('positionUpdated');
                     }
@@ -422,16 +425,10 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         return false;
     };
 
-    willEnter = () => {
-        this.foundation.calcPosition();
-        /**
-         * Dangerous: remove setState in motion fix #1379
-         * because togglePortalVisible callback function will use visible state to notifyVisibleChange
-         * if visible state is old value, then notifyVisibleChange function will not be called
-         * we should ensure that after calling togglePortalVisible, callback function can get right visible value
-         */
-        // this.setState({ visible: true });
-    };
+    // willEnter = () => {
+    // this.foundation.calcPosition();
+    // this.setState({ visible: true });
+    // };
 
     didLeave = () => {
         this.adapter.unregisterClickOutsideHandler();
@@ -489,7 +486,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
     };
 
     renderPortal = () => {
-        const { containerStyle = {}, visible, portalEventSet, placement, transitionState } = this.state;
+        const { containerStyle = {}, visible, portalEventSet, placement, transitionState, isPositionUpdated } = this.state;
         const { prefixCls, content, showArrow, style, motion, zIndex } = this.props;
         const { className: propClassName } = this.props;
         const direction = this.context.direction;
@@ -502,15 +499,15 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         const icon = this.renderIcon();
         const portalInnerStyle = omit(containerStyle, motion ? ['transformOrigin'] : undefined);
         const transformOrigin = get(containerStyle, 'transformOrigin');
-        const inner = motion ? (
-            <TooltipTransition position={placement} willEnter={this.willEnter} didLeave={this.didLeave} motion={motion}>
+        const inner = motion && isPositionUpdated ? (
+            <TooltipTransition position={placement} didLeave={this.didLeave} motion={motion}>
                 {
                     transitionState === 'enter' ?
                         ({ animateCls, animateStyle, animateEvents }) => (
                             <div
                                 className={classNames(className, animateCls)}
                                 style={{
-                                    visibility: 'visible',
+                                    // visibility: 'visible',
                                     ...animateStyle,
                                     transformOrigin,
                                     ...style,

+ 3 - 3
packages/semi-ui/transfer/index.tsx

@@ -445,10 +445,10 @@ class Transfer extends BaseComponent<TransferProps, TransferState> {
         );
     }
 
-    renderGroupTitle(group: GroupItem) {
+    renderGroupTitle(group: GroupItem, index: number) {
         const groupCls = cls(`${prefixcls }-group-title`);
         return (
-            <div className={groupCls} key={group.title}>
+            <div className={groupCls} key={`title-${index}`}>
                 {group.title}
             </div>
         );
@@ -493,7 +493,7 @@ class Transfer extends BaseComponent<TransferProps, TransferState> {
                 // group content already insert
                 content.push(optionContent);
             } else if (parentGroup) {
-                const groupContent = this.renderGroupTitle(parentGroup);
+                const groupContent = this.renderGroupTitle(parentGroup, index);
                 groupStatus.set(parentGroup.title, true);
                 content.push(groupContent);
                 content.push(optionContent);

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

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

+ 1 - 1
src/components/PageAnchor/index.scss

@@ -1,6 +1,6 @@
 .category-anchor {
     width: 150px;
-    position: fixed;
+    position: fixed !important;
     right: 0vw;
     top: 12vh;
     display: flex;

+ 290 - 27
yarn.lock

@@ -1403,6 +1403,38 @@
   resolved "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz#d05f66db03e3a3638a654e8badf2deb489eb220d"
   integrity sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==
 
+"@cypress/request@^2.88.10":
+  version "2.88.10"
+  resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
+  integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==
+  dependencies:
+    aws-sign2 "~0.7.0"
+    aws4 "^1.8.0"
+    caseless "~0.12.0"
+    combined-stream "~1.0.6"
+    extend "~3.0.2"
+    forever-agent "~0.6.1"
+    form-data "~2.3.2"
+    http-signature "~1.3.6"
+    is-typedarray "~1.0.0"
+    isstream "~0.1.2"
+    json-stringify-safe "~5.0.1"
+    mime-types "~2.1.19"
+    performance-now "^2.1.0"
+    qs "~6.5.2"
+    safe-buffer "^5.1.2"
+    tough-cookie "~2.5.0"
+    tunnel-agent "^0.6.0"
+    uuid "^8.3.2"
+
+"@cypress/xvfb@^1.2.4":
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a"
+  integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==
+  dependencies:
+    debug "^3.1.0"
+    lodash.once "^4.1.1"
+
 "@discoveryjs/json-ext@^0.5.3":
   version "0.5.6"
   resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f"
@@ -3491,6 +3523,18 @@
     react-lifecycles-compat "^3.0.4"
     react-select "^3.2.0"
 
+"@storybook/addon-toolbars@^6.4.9":
+  version "6.4.9"
+  resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.4.9.tgz#147534d0b185a1782f3381a47c627b4a4193297d"
+  integrity sha512-fep1lLDcyaQJdR8rC/lJTamiiJ8Ilio580d9aXDM651b7uHqhxM0dJvM9hObBU8dOj/R3hIAszgTvdTzYlL2cQ==
+  dependencies:
+    "@storybook/addons" "6.4.9"
+    "@storybook/api" "6.4.9"
+    "@storybook/components" "6.4.9"
+    "@storybook/theming" "6.4.9"
+    core-js "^3.8.2"
+    regenerator-runtime "^0.13.7"
+
 "@storybook/[email protected]":
   version "6.4.9"
   resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.4.9.tgz#43b5dabf6781d863fcec0a0b293c236b4d5d4433"
@@ -4768,6 +4812,11 @@
   resolved "https://registry.npmjs.org/@types/node/-/node-14.18.1.tgz#459886b51f52aa923dc06b9ea81cb8b1d733e9d3"
   integrity sha512-fTFWOFrgAkj737w1o0HLTIgisgYHnsZfeiqhG1Ltrf/iJjudEbUwetQAsfrtVE49JGwvpEzQR+EbMkIqG4227g==
 
+"@types/node@^14.14.31":
+  version "14.18.2"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.2.tgz#00fe4d1686d5f6cf3a2f2e9a0eef42594d06abfc"
+  integrity sha512-fqtSN5xn/bBzDxMT77C1rJg6CsH/R49E7qsGuvdPJa20HtV5zSTuLJPNfnlyVH3wauKnkHdLggTVkOW/xP9oQg==
+
 "@types/node@^8.5.7":
   version "8.10.66"
   resolved "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3"
@@ -4889,6 +4938,16 @@
   resolved "https://registry.npmjs.org/@types/shallowequal/-/shallowequal-1.1.1.tgz#aad262bb3f2b1257d94c71d545268d592575c9b1"
   integrity sha512-Lhni3aX80zbpdxRuWhnuYPm8j8UQaa571lHP/xI4W+7BAFhSIhRReXnqjEgT/XzPoXZTJkCqstFMJ8CZTK6IlQ==
 
+"@types/sinonjs__fake-timers@^6.0.2":
+  version "6.0.4"
+  resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz#0ecc1b9259b76598ef01942f547904ce61a6a77d"
+  integrity sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A==
+
+"@types/sizzle@^2.3.2":
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef"
+  integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==
+
 "@types/source-list-map@*":
   version "0.1.2"
   resolved "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
@@ -5009,6 +5068,13 @@
   dependencies:
     "@types/yargs-parser" "*"
 
+"@types/yauzl@^2.9.1":
+  version "2.9.2"
+  resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a"
+  integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==
+  dependencies:
+    "@types/node" "*"
+
 "@types/[email protected]":
   version "1.9.2"
   resolved "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz#efaf9e991a7390dc081a0b679185979a83a9639a"
@@ -5791,7 +5857,7 @@ aproba@^1.0.3, aproba@^1.1.1, aproba@^1.1.2:
   resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
   integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
 
-arch@^2.1.1:
+arch@^2.1.1, arch@^2.2.0:
   version "2.2.0"
   resolved "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
   integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
@@ -6190,6 +6256,11 @@ async@^2.6.2:
   dependencies:
     lodash "^4.17.14"
 
+async@^3.2.0:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/async/-/async-3.2.2.tgz#2eb7671034bb2194d45d30e31e24ec7e7f9670cd"
+  integrity sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==
+
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -6688,7 +6759,12 @@ bl@^4.0.0:
     inherits "^2.0.4"
     readable-stream "^3.4.0"
 
-bluebird@^3.0.5, bluebird@^3.3.5, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2:
+blob-util@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb"
+  integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==
+
[email protected], bluebird@^3.0.5, bluebird@^3.3.5, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2:
   version "3.7.2"
   resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
   integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
@@ -6956,6 +7032,11 @@ [email protected]:
     regexpu-core "^4.2.0"
     vlq "^1.0.0"
 
+buffer-crc32@~0.2.3:
+  version "0.2.13"
+  resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
+  integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
+
 buffer-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
@@ -7193,6 +7274,11 @@ cacheable-request@^6.0.0:
     normalize-url "^4.1.0"
     responselike "^1.0.2"
 
+cachedir@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"
+  integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==
+
 call-bind@^1.0.0, call-bind@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@@ -7475,6 +7561,11 @@ chardet@^0.7.0:
   resolved "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
   integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
 
+check-more-types@^2.24.0:
+  version "2.24.0"
+  resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
+  integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=
+
 cheerio-select@^1.5.0:
   version "1.5.0"
   resolved "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz#faf3daeb31b17c5e1a9dabcee288aaf8aafa5823"
@@ -7607,6 +7698,11 @@ ci-info@^1.5.0:
   resolved "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
   integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
 
+ci-info@^3.2.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2"
+  integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==
+
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
   version "1.0.4"
   resolved "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -7688,7 +7784,7 @@ cli-cursor@^3.1.0:
   dependencies:
     restore-cursor "^3.1.0"
 
[email protected]:
[email protected], cli-table3@~0.6.0:
   version "0.6.0"
   resolved "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee"
   integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==
@@ -7976,6 +8072,11 @@ commander@^4.0.1, commander@^4.1.1:
   resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
   integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
 
+commander@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
+  integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
+
 commander@^6.2.0, commander@^6.2.1:
   version "6.2.1"
   resolved "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
@@ -8940,6 +9041,53 @@ cyclist@^1.0.1:
   resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
   integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
 
+cypress@^9.2.0:
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.2.0.tgz#727c20b4662167890db81d5f6ba615231835b17d"
+  integrity sha512-Jn26Tprhfzh/a66Sdj9SoaYlnNX6Mjfmj5PHu2a7l3YHXhrgmavM368wjCmgrxC6KHTOv9SpMQGhAJn+upDViA==
+  dependencies:
+    "@cypress/request" "^2.88.10"
+    "@cypress/xvfb" "^1.2.4"
+    "@types/node" "^14.14.31"
+    "@types/sinonjs__fake-timers" "^6.0.2"
+    "@types/sizzle" "^2.3.2"
+    arch "^2.2.0"
+    blob-util "^2.0.2"
+    bluebird "3.7.2"
+    cachedir "^2.3.0"
+    chalk "^4.1.0"
+    check-more-types "^2.24.0"
+    cli-cursor "^3.1.0"
+    cli-table3 "~0.6.0"
+    commander "^5.1.0"
+    common-tags "^1.8.0"
+    dayjs "^1.10.4"
+    debug "^4.3.2"
+    enquirer "^2.3.6"
+    eventemitter2 "^6.4.3"
+    execa "4.1.0"
+    executable "^4.1.1"
+    extract-zip "2.0.1"
+    figures "^3.2.0"
+    fs-extra "^9.1.0"
+    getos "^3.2.1"
+    is-ci "^3.0.0"
+    is-installed-globally "~0.4.0"
+    lazy-ass "^1.6.0"
+    listr2 "^3.8.3"
+    lodash "^4.17.21"
+    log-symbols "^4.0.0"
+    minimist "^1.2.5"
+    ospath "^1.2.2"
+    pretty-bytes "^5.6.0"
+    proxy-from-env "1.0.0"
+    request-progress "^3.0.0"
+    supports-color "^8.1.1"
+    tmp "~0.2.1"
+    untildify "^4.0.0"
+    url "^0.11.0"
+    yauzl "^2.10.0"
+
 d3-array@^1.2.0:
   version "1.2.4"
   resolved "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
@@ -9021,6 +9169,11 @@ dateformat@^3.0.0:
   resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
   integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
 
+dayjs@^1.10.4:
+  version "1.10.7"
+  resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
+  integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
+
 debug@2, [email protected], debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.6, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -10655,6 +10808,11 @@ event-target-shim@^5.0.0:
   resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
   integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
 
+eventemitter2@^6.4.3:
+  version "6.4.5"
+  resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.5.tgz#97380f758ae24ac15df8353e0cc27f8b95644655"
+  integrity sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw==
+
 eventemitter3@^3.1.0:
   version "3.1.2"
   resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
@@ -10697,6 +10855,21 @@ exec-sh@^0.3.2:
   resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc"
   integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==
 
[email protected], execa@^4.0.2, execa@^4.0.3, execa@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
+  integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
+  dependencies:
+    cross-spawn "^7.0.0"
+    get-stream "^5.0.0"
+    human-signals "^1.1.1"
+    is-stream "^2.0.0"
+    merge-stream "^2.0.0"
+    npm-run-path "^4.0.0"
+    onetime "^5.1.0"
+    signal-exit "^3.0.2"
+    strip-final-newline "^2.0.0"
+
 execa@^0.7.0:
   version "0.7.0"
   resolved "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
@@ -10739,21 +10912,6 @@ execa@^3.4.0:
     signal-exit "^3.0.2"
     strip-final-newline "^2.0.0"
 
-execa@^4.0.2, execa@^4.0.3, execa@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
-  integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
-  dependencies:
-    cross-spawn "^7.0.0"
-    get-stream "^5.0.0"
-    human-signals "^1.1.1"
-    is-stream "^2.0.0"
-    merge-stream "^2.0.0"
-    npm-run-path "^4.0.0"
-    onetime "^5.1.0"
-    signal-exit "^3.0.2"
-    strip-final-newline "^2.0.0"
-
 execall@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45"
@@ -10761,6 +10919,13 @@ execall@^2.0.0:
   dependencies:
     clone-regexp "^2.1.0"
 
+executable@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c"
+  integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==
+  dependencies:
+    pify "^2.2.0"
+
 exenv@^1.2.2:
   version "1.2.2"
   resolved "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
@@ -10913,6 +11078,17 @@ [email protected]:
   resolved "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a"
   integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==
 
[email protected]:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
+  integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
+  dependencies:
+    debug "^4.1.1"
+    get-stream "^5.1.0"
+    yauzl "^2.10.0"
+  optionalDependencies:
+    "@types/yauzl" "^2.9.1"
+
 [email protected]:
   version "1.3.0"
   resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
@@ -11034,6 +11210,13 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "2.1.1"
 
+fd-slicer@~1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
+  integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
+  dependencies:
+    pend "~1.2.0"
+
 fd@~0.0.2:
   version "0.0.3"
   resolved "https://registry.npmjs.org/fd/-/fd-0.0.3.tgz#b3240de86dbf5a345baae7382a07d4713566ff0c"
@@ -11067,7 +11250,7 @@ figures@^2.0.0:
   dependencies:
     escape-string-regexp "^1.0.5"
 
-figures@^3.0.0:
+figures@^3.0.0, figures@^3.2.0:
   version "3.2.0"
   resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
   integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
@@ -11505,7 +11688,7 @@ fs-extra@^8.0.1, fs-extra@^8.1.0:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
-fs-extra@^9.0.0, fs-extra@^9.0.1:
+fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0:
   version "9.1.0"
   resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
   integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
@@ -12240,6 +12423,13 @@ get-value@^2.0.3, get-value@^2.0.6:
   resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
   integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
 
+getos@^3.2.1:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5"
+  integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==
+  dependencies:
+    async "^3.2.0"
+
 getpass@^0.1.1:
   version "0.1.7"
   resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
@@ -13410,6 +13600,15 @@ http-signature@~1.2.0:
     jsprim "^1.2.2"
     sshpk "^1.7.0"
 
+http-signature@~1.3.6:
+  version "1.3.6"
+  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9"
+  integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==
+  dependencies:
+    assert-plus "^1.0.0"
+    jsprim "^2.0.2"
+    sshpk "^1.14.1"
+
 [email protected], https-browserify@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
@@ -13963,6 +14162,13 @@ is-ci@^2.0.0:
   dependencies:
     ci-info "^2.0.0"
 
+is-ci@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867"
+  integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==
+  dependencies:
+    ci-info "^3.2.0"
+
 is-color-stop@^1.0.0:
   version "1.1.0"
   resolved "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345"
@@ -14156,7 +14362,7 @@ is-installed-globally@^0.1.0:
     global-dirs "^0.1.0"
     is-path-inside "^1.0.0"
 
-is-installed-globally@^0.4.0:
+is-installed-globally@^0.4.0, is-installed-globally@~0.4.0:
   version "0.4.0"
   resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
   integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==
@@ -15397,6 +15603,16 @@ jsprim@^1.2.2:
     json-schema "0.4.0"
     verror "1.10.0"
 
+jsprim@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d"
+  integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==
+  dependencies:
+    assert-plus "1.0.0"
+    extsprintf "1.3.0"
+    json-schema "0.4.0"
+    verror "1.10.0"
+
 "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1:
   version "3.2.1"
   resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b"
@@ -15532,6 +15748,11 @@ latest-version@^3.0.0:
   dependencies:
     package-json "^4.0.0"
 
+lazy-ass@^1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
+  integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM=
+
 lazy-universal-dotenv@^3.0.1:
   version "3.0.1"
   resolved "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz#a6c8938414bca426ab8c9463940da451a911db38"
@@ -15753,7 +15974,7 @@ lint-staged@^10.5.4:
     string-argv "0.3.1"
     stringify-object "^3.3.0"
 
-listr2@^3.2.2:
+listr2@^3.2.2, listr2@^3.8.3:
   version "3.13.5"
   resolved "https://registry.npmjs.org/listr2/-/listr2-3.13.5.tgz#105a813f2eb2329c4aae27373a281d610ee4985f"
   integrity sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA==
@@ -16083,6 +16304,11 @@ lodash.merge@^4.4.0, lodash.merge@^4.6.2:
   resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
   integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
 
+lodash.once@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+  integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
+
 lodash.pick@^4.2.1:
   version "4.4.0"
   resolved "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
@@ -18416,6 +18642,11 @@ osenv@0, osenv@^0.1.4, osenv@^0.1.5:
     os-homedir "^1.0.0"
     os-tmpdir "^1.0.0"
 
+ospath@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b"
+  integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=
+
 overlayscrollbars@^1.13.1:
   version "1.13.1"
   resolved "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-1.13.1.tgz#0b840a88737f43a946b9d87875a2f9e421d0338a"
@@ -19067,6 +19298,11 @@ peek-readable@^4.0.1:
   resolved "https://registry.npmjs.org/peek-readable/-/peek-readable-4.0.2.tgz#a5cb847e347d3eccdc37642c82d2b4155c1ab8af"
   integrity sha512-9fMaz6zoxw9ypO1KZy5RDJgSupEtu0Q+g/OqqsVHX3rKGR8qehRLYzsFARZ4bVvdvfknKiXvuDbkMnO1g6cRpQ==
 
+pend@~1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
+  integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
+
 performance-now@^2.1.0:
   version "2.1.0"
   resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -19092,7 +19328,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0:
   resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
   integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
 
-pify@^2.0.0, pify@^2.3.0:
+pify@^2.0.0, pify@^2.2.0, pify@^2.3.0:
   version "2.3.0"
   resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
   integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
@@ -19765,7 +20001,7 @@ prettier@^2.0.5, prettier@^2.2.1:
   resolved "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
   integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
 
-pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
+pretty-bytes@^5.3.0, pretty-bytes@^5.4.1, pretty-bytes@^5.6.0:
   version "5.6.0"
   resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
   integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
@@ -19991,6 +20227,11 @@ proxy-addr@~2.0.7:
     forwarded "0.2.0"
     ipaddr.js "1.9.1"
 
[email protected]:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee"
+  integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=
+
 prr@~1.0.1:
   version "1.0.1"
   resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
@@ -21400,6 +21641,13 @@ replacestream@^4.0.3:
     object-assign "^4.0.1"
     readable-stream "^2.0.2"
 
+request-progress@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe"
+  integrity sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=
+  dependencies:
+    throttleit "^1.0.0"
+
 [email protected]:
   version "1.1.4"
   resolved "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
@@ -22598,7 +22846,7 @@ sprintf-js@~1.0.2:
   resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
   integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
 
-sshpk@^1.7.0:
+sshpk@^1.14.1, sshpk@^1.7.0:
   version "1.16.1"
   resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
   integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
@@ -23271,7 +23519,7 @@ supports-color@^7.0.0, supports-color@^7.1.0:
   dependencies:
     has-flag "^4.0.0"
 
-supports-color@^8.0.0:
+supports-color@^8.0.0, supports-color@^8.1.1:
   version "8.1.1"
   resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
   integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
@@ -23617,6 +23865,11 @@ throttle-debounce@^3.0.1:
   resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb"
   integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==
 
+throttleit@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c"
+  integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=
+
 through2-filter@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
@@ -23708,7 +23961,7 @@ tmp@^0.0.33:
   dependencies:
     os-tmpdir "~1.0.2"
 
-tmp@^0.2.1:
+tmp@^0.2.1, tmp@~0.2.1:
   version "0.2.1"
   resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
   integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
@@ -25080,8 +25333,10 @@ watchpack@^1.7.4:
   resolved "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453"
   integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==
   dependencies:
+    chokidar "^3.4.1"
     graceful-fs "^4.1.2"
     neo-async "^2.5.0"
+    watchpack-chokidar2 "^2.0.1"
   optionalDependencies:
     chokidar "^3.4.1"
     watchpack-chokidar2 "^2.0.1"
@@ -25895,6 +26150,14 @@ yargs@^8.0.2:
     y18n "^3.2.1"
     yargs-parser "^7.0.0"
 
+yauzl@^2.10.0:
+  version "2.10.0"
+  resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
+  integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
+  dependencies:
+    buffer-crc32 "~0.2.3"
+    fd-slicer "~1.1.0"
+
 [email protected]:
   version "0.1.2"
   resolved "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"