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

Merge branch 'main' into css-way

DaiQiangReal 7 сар өмнө
parent
commit
3f67adcac9
100 өөрчлөгдсөн 3292 нэмэгдсэн , 407 устгасан
  1. 1 0
      .gitignore
  2. 20 2
      .storybook/base/base.js
  3. 1 1
      CONTRIBUTING-en-US.md
  4. 1 1
      CONTRIBUTING.md
  5. 12 1
      README-zh_CN.md
  6. 49 33
      README.md
  7. 1 1
      content/feedback/banner/index-en-US.md
  8. 1 1
      content/feedback/banner/index.md
  9. 1 1
      content/feedback/notification/index-en-US.md
  10. 1 1
      content/feedback/notification/index.md
  11. 1 1
      content/feedback/popconfirm/index-en-US.md
  12. 1 1
      content/feedback/popconfirm/index.md
  13. 1 1
      content/feedback/progress/index-en-US.md
  14. 1 1
      content/feedback/progress/index.md
  15. 1 1
      content/feedback/skeleton/index-en-US.md
  16. 1 1
      content/feedback/skeleton/index.md
  17. 1 1
      content/feedback/spin/index-en-US.md
  18. 1 1
      content/feedback/spin/index.md
  19. 1 1
      content/feedback/toast/index-en-US.md
  20. 1 1
      content/feedback/toast/index.md
  21. 15 13
      content/input/select/index-en-US.md
  22. 15 13
      content/input/select/index.md
  23. 2 1
      content/input/treeselect/index-en-US.md
  24. 2 1
      content/input/treeselect/index.md
  25. 1 0
      content/input/upload/index-en-US.md
  26. 1 0
      content/input/upload/index.md
  27. 1 1
      content/navigation/tabs/index-en-US.md
  28. 1 1
      content/navigation/tabs/index.md
  29. 85 1
      content/navigation/tree/index-en-US.md
  30. 88 1
      content/navigation/tree/index.md
  31. 2 0
      content/order.js
  32. 1 1
      content/other/configprovider/index-en-US.md
  33. 1 1
      content/other/configprovider/index.md
  34. 2 2
      content/other/locale/index-en-US.md
  35. 5 3
      content/other/locale/index.md
  36. 171 0
      content/plus/audioPlayer/index-en-US.md
  37. 178 0
      content/plus/audioPlayer/index.md
  38. 4 3
      content/plus/chat/index-en-US.md
  39. 2 1
      content/plus/chat/index.md
  40. 1 1
      content/plus/dragMove/index-en-US.md
  41. 1 1
      content/plus/dragMove/index.md
  42. 3 0
      content/plus/jsonviewer/index-en-US.md
  43. 4 1
      content/plus/jsonviewer/index.md
  44. 1 0
      content/plus/markdownrender/index-en-US.md
  45. 2 0
      content/plus/markdownrender/index.md
  46. 1 1
      content/show/chart/index-en-US.md
  47. 1 1
      content/show/chart/index.md
  48. 281 0
      content/show/cropper/index-en-US.md
  49. 286 0
      content/show/cropper/index.md
  50. 1 0
      content/show/dropdown/index-en-US.md
  51. 1 0
      content/show/dropdown/index.md
  52. 104 133
      content/show/list/index-en-US.md
  53. 101 131
      content/show/list/index.md
  54. 1 1
      content/show/modal/index-en-US.md
  55. 1 1
      content/show/modal/index.md
  56. 1 1
      content/show/overflowlist/index-en-US.md
  57. 1 1
      content/show/overflowlist/index.md
  58. 1 1
      content/show/popover/index-en-US.md
  59. 1 1
      content/show/popover/index.md
  60. 1 1
      content/show/scrolllist/index-en-US.md
  61. 1 1
      content/show/scrolllist/index.md
  62. 1 1
      content/show/sidesheet/index-en-US.md
  63. 1 1
      content/show/sidesheet/index.md
  64. 2 2
      content/show/table/index-en-US.md
  65. 2 2
      content/show/table/index.md
  66. 1 1
      content/show/tag/index-en-US.md
  67. 1 1
      content/show/tag/index.md
  68. 1 1
      content/show/timeline/index-en-US.md
  69. 1 1
      content/show/timeline/index.md
  70. 2 2
      content/show/tooltip/index-en-US.md
  71. 4 4
      content/show/tooltip/index.md
  72. 102 0
      content/start/changelog/index-en-US.md
  73. 101 0
      content/start/changelog/index.md
  74. 3 1
      content/start/overview/index-en-US.md
  75. 6 2
      content/start/overview/index.md
  76. 164 0
      cypress/e2e/jsonViewer.spec.js
  77. 4 2
      cypress/e2e/scrollList.spec.js
  78. 18 3
      gatsby-node.js
  79. 1 1
      lerna.json
  80. 1 0
      package.json
  81. 3 3
      packages/semi-animation-react/package.json
  82. 1 1
      packages/semi-animation-styled/package.json
  83. 1 1
      packages/semi-animation/package.json
  84. 1 1
      packages/semi-eslint-plugin/package.json
  85. 217 0
      packages/semi-foundation/audioPlayer/audioPlayer.scss
  86. 7 0
      packages/semi-foundation/audioPlayer/constants.ts
  87. 103 0
      packages/semi-foundation/audioPlayer/foundation.ts
  88. 55 0
      packages/semi-foundation/audioPlayer/variables.scss
  89. 8 0
      packages/semi-foundation/button/iconButton.scss
  90. 4 3
      packages/semi-foundation/carousel/foundation.ts
  91. 3 0
      packages/semi-foundation/cascader/cascader.scss
  92. 2 0
      packages/semi-foundation/cascader/variables.scss
  93. 26 0
      packages/semi-foundation/cropper/constants.ts
  94. 116 0
      packages/semi-foundation/cropper/cropper.scss
  95. 821 0
      packages/semi-foundation/cropper/foundation.ts
  96. 12 0
      packages/semi-foundation/cropper/utils.ts
  97. 6 0
      packages/semi-foundation/cropper/variables.scss
  98. 12 0
      packages/semi-foundation/dragMove/foundation.ts
  99. 1 1
      packages/semi-foundation/form/interface.ts
  100. 6 0
      packages/semi-foundation/image/image.scss

+ 1 - 0
.gitignore

@@ -19,6 +19,7 @@ dist
 build
 _site
 packages/**/lib
+packages/**/workerLib
 packages/semi-theme-default/css/semi.css
 packages/semi-theme-default/semi.scss
 public

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

@@ -3,7 +3,7 @@ const path = require('path');
 const _ = require('lodash');
 const chalk = require('chalk').default;
 const utils = require('./utils');
-
+const fs = require('fs');
 let AnalyzePlugin = null
 if(process.env.__ENABLE_ANALYZE__ === 'true') {
     AnalyzePlugin = require("@ies/semi-page-analyze-inject/src/AnalyzePlugin")
@@ -79,6 +79,23 @@ module.exports = {
                 },
             ]
         })
+        rules.push({
+            test: /jsonWorkerManager\.ts$/,
+            use: [
+                {
+                    loader: 'webpack-replace-loader',
+                    options: {
+                        search: '%WORKER_RAW%',
+                        replace: () => {
+                            const workFilePath = resolve('packages/semi-json-viewer-core/workerLib/worker.js');
+                            const result = fs.readFileSync(workFilePath, 'utf-8');
+                            const encodedResult = encodeURIComponent(result);
+                            return encodedResult;
+                        }
+                    }
+                }
+            ]
+        });
         config.module.rules = rules;
         config.resolve.extensions.push('.js', '.jsx', '.ts', '.tsx');
         config.resolve.symlinks = false;
@@ -92,7 +109,8 @@ module.exports = {
             '@douyinfe/semi-illustrations': resolve('packages/semi-illustrations/src'),
             '@douyinfe/semi-animation': resolve('packages/semi-animation'),
             '@douyinfe/semi-animation-react': resolve('packages/semi-animation-react'),
-            '@douyinfe/semi-animation-styled': resolve('packages/semi-animation-styled')
+            '@douyinfe/semi-animation-styled': resolve('packages/semi-animation-styled'),
+            '@douyinfe/semi-json-viewer-core': resolve('packages/semi-json-viewer-core/src'),
         };
         config.devtool = 'source-map';
         // config.output.publicPath = "/storybook/"

+ 1 - 1
CONTRIBUTING-en-US.md

@@ -27,7 +27,7 @@ corepack enable
 ```
 
 ```bash
-npm install --global lerna
+npm install --global lerna@6
 ```
  - Install the dependencies
 ```bash

+ 1 - 1
CONTRIBUTING.md

@@ -28,7 +28,7 @@ git checkout -b <TOPIC_BRANCH_NAME>
 >安装环境前确保本地有 `lerna` 和 `yarn` 的依赖,如果没有则运行:
 ```bash
 corepack enable
-npm install --global lerna
+npm install --global lerna@6
 ```
  - 完成项目依赖安装
 ```bash

+ 12 - 1
README-zh_CN.md

@@ -45,7 +45,7 @@
 
 # 🎉 特性
 
-- 💪 60+高质量组件
+- 💪 70+高质量组件
 - 💅 Code2Design,根据不同主题自动生成 Figma UI Kit,保持代码与设计同源
 - 🚀 强大的 D2C (Design2Code)支持,Figma 设计稿一键转出真实代码,快速构建应用
 - 💕 完善的无障碍支持,为所有组件提供遵循 W3C 标准的键盘交互、焦点管理和语义化
@@ -149,6 +149,17 @@ Semi UI 支持所有主流浏览器。
 
 感谢 [Cypress](https://www.cypress.io/) 提供 E2E 测试。
 
+<div>
+  <a href="https://github.com/VisActor#gh-light-mode-only" target="_blank">
+    <img alt="VisActor Logo" height="60" src="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/visactor/logo_500_200_light.svg"/>
+  </a>
+  <a href="https://github.com/VisActor#gh-dark-mode-only" target="_blank">
+    <img alt="VisActor Logo" height="60" src="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/visactor/logo_500_200_dark.svg"/>
+  </a>
+</div>
+
+感谢 [VisActor](https://www.visactor.com/) 提供数据可视化解决方案。
+
 ## 👐 参与共建
 Semi Design 欢迎社区开发者参与共建,衷心感谢每一位协作者的付出
 

+ 49 - 33
README.md

@@ -6,15 +6,14 @@
         A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps.
     </p>
 </article>
-    
+
 <div align="center">
 
 [![Twitter Follow](https://img.shields.io/twitter/follow/SemiDesignUI?style=social)](https://twitter.com/SemiDesignUI)
 
-[![LICENSE][license-badge]][license-url] [![NPM][npm-badge]][npm-url] [![CONTRIBUTORS][contributors-badge]][contributors-url]  ![Design Token][Design Token] [![FIGMA][figma-badge]][figma-url] 
+[![LICENSE][license-badge]][license-url] [![NPM][npm-badge]][npm-url] [![CONTRIBUTORS][contributors-badge]][contributors-url]  ![Design Token][Design Token] [![FIGMA][figma-badge]][figma-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
 [contributors-badge]: https://img.shields.io/github/contributors/DouyinFE/semi-design
 [contributors-url]: https://github.com/DouyinFE/semi-design/graphs/contributors
@@ -32,13 +31,11 @@
 [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/semi-ui/dist/umd/semi-ui.min.js
 [build-css-badge]: https://img.badgesize.io/https:/unpkg.com/@douyinfe/semi-ui/dist/css/semi.min.css?label=semi.min.css&compression=gzip
 [build-css-url]: https://unpkg.com/browse/@douyinfe/semi-ui/dist/css/semi.min.css
 
-
 </div>
 
 <p>
@@ -51,15 +48,15 @@ English | [简体中文](./README-zh_CN.md)
 
 # 🎉 Features
 
-- 💪 Up to 60+ high-quality Components. Stable update since 2019
-- 🚀 Official Design to Code (D2C) support, convert figma draft to real code in a few seconds
-- 💅 Code to Design (C2D), automatically generate Figma UI Kit according to different themes, keep same between design and code
+- 💪 Up to 70+ high-quality Components. Stable updates since 2019
+- 🚀 Official Design to Code (D2C) support, convert Figma draft to real code in a few seconds
+- 💅 Code to Design (C2D), automatically generate Figma UI Kit according to different themes, keep consistency  between design and code
 - 💕 Complete A11y support, follows W3C standards to provide keyboard interaction, focus management and ARIA for all components
 - 💅 Design system management Semi DSM, up to 3000+ Design Tokens, make Semi Design to Any Design quickly.
-- 🌍 Internationalization Support for Dozens of Languages, timezone, RTL support
-- ⚙️ Strict quality assurance, covering unit testing, E2E testing, visual testing.
+- 🌍 Internationalization Support for Dozens of Languages, timezone, and RTL support
+- ⚙️ Strict quality assurance, covering unit testing, E2E testing, and visual testing.
 - 👏 Written in Typescript, friendly Static Type Support. Based on Foundation/Adapter architecture, easy to read and contribute
-- 🥳 SSR (Server Side Rendering) Compatible. 
+- 🥳 SSR (Server Side Rendering) Compatible.
 - 📦 Easily compatible with web components, providing a complete adaptation solution, more suitable for building SDKs, browser plugins and other scenarios that require DOM isolation.
 
 # 🔥 Install
@@ -68,6 +65,14 @@ English | [简体中文](./README-zh_CN.md)
 npm install @douyinfe/semi-ui
 ```
 
+```sh
+yarn add @douyinfe/semi-ui
+```
+
+```sh
+pnpm add @douyinfe/semi-ui
+```
+
 # 👍 Component Usage
 
 Here is a quick example to get you started, it's all you need:
@@ -95,7 +100,7 @@ root.render(<App />);
 
 Install [Semi Figma Plugin](https://www.figma.com/community/plugin/1166339852662786534/Semi-Design-%E8%AE%BE%E8%AE%A1%E8%BD%AC%E4%BB%A3%E7%A0%81). Translate Figma to real code in seconds. Support multiple output formats: JSX + SCSS / Emotion/Tailwind, or JSON Schema DSL
 
-- Support figma devmode, selecting a layer, directly get corresponding code on the right
+- Support Figma devmode, selecting a layer, directly get corresponding code on the right
   
 ![design2code](https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/github/devmode.gif)
 
@@ -103,43 +108,42 @@ Install [Semi Figma Plugin](https://www.figma.com/community/plugin/1166339852662
   
 ![codesandboxdemo](https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/github/1080p-fps5.gif)
 
-
 # 🎨 DSM Usage
 
-Define your own design system base on Semi UI with DSM in one click, Provide more than 3000 tokens for you to configure every detail. Sync between Figma and Code at all times.
+Define your own design system based on Semi UI with DSM in one click, Provide more than 3000 tokens for you to configure every detail. Sync between Figma and Code always.
 
 ![dsmintro](https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/github/dsmintro.png)
 
-
 # 📰 News about Semi UI
-* [Follow on Twitter](https://twitter.com/SemiDesignUI)
-* [Follow on Medium](https://medium.com/@semi-design)
-* [Follow on Dev.to](https://dev.to/semidesign)
 
+- [Follow on Twitter](https://twitter.com/SemiDesignUI)
+- [Follow on Medium](https://medium.com/@semi-design)
+- [Follow on Dev.to](https://dev.to/semidesign)
 
 # 📌 Documentation
-* [Semi DSM](https://semi.design/dsm)
-* [Design to Code](https://semi.design/code/en-US)
-* [Semi Figma Plugin](https://www.figma.com/community/plugin/1166339852662786534/Semi-Design-%E8%AE%BE%E8%AE%A1%E8%BD%AC%E4%BB%A3%E7%A0%81)
-* [Quick Start](https://semi.design/en-US/start/getting-started)
-* [Components Overview](https://semi.design/en-US/start/overview)
-* [Customizing Themes](https://semi.design/en-US/start/customize-theme)
-* [Design Tokens](https://semi.design/en-US/basic/tokens)
-* [Dark Mode](https://semi.design/en-US/start/dark-mode)
-* [Semi Icons](https://semi.design/en-US/basic/icon)
-* [Global Config](https://semi.design/en-US/other/configprovider)
-* [Internationalization](https://semi.design/en-US/other/locale)
-* [FAQ](https://semi.design/en-US/start/faq)
-* [CHANGELOG](https://semi.design/en-US/start/changelog)
+
+- [Semi DSM](https://semi.design/dsm)
+- [Design to Code](https://semi.design/code/en-US)
+- [Semi Figma Plugin](https://www.figma.com/community/plugin/1166339852662786534/Semi-Design-%E8%AE%BE%E8%AE%A1%E8%BD%AC%E4%BB%A3%E7%A0%81)
+- [Quick Start](https://semi.design/en-US/start/getting-started)
+- [Components Overview](https://semi.design/en-US/start/overview)
+- [Customizing Themes](https://semi.design/en-US/start/customize-theme)
+- [Design Tokens](https://semi.design/en-US/basic/tokens)
+- [Dark Mode](https://semi.design/en-US/start/dark-mode)
+- [Semi Icons](https://semi.design/en-US/basic/icon)
+- [Global Config](https://semi.design/en-US/other/configprovider)
+- [Internationalization](https://semi.design/en-US/other/locale)
+- [FAQ](https://semi.design/en-US/start/faq)
+- [CHANGELOG](https://semi.design/en-US/start/changelog)
 
 # 📝 Blogs
+
 - [The Evolution of Semi D2C Design to Code](https://juejin.cn/post/7267418854124699702)
 - [How to design component library architecture to adapt to different mvvm frameworks](https://bytedance.feishu.cn/wiki/wikcnOVYexosCS1Rmvb5qCsWT1f)
 - [How we test semi ui](https://medium.com/front-end-weekly/how-we-test-semi-design-component-libraries-64b854f63b65)
 - [In-depth explanation of Semi theme](https://mp.weixin.qq.com/s/noHoWRuA25PgqFNcurhIUA)
 - [Accessibility in Semi Design](https://mp.weixin.qq.com/s/O3js-SZDNPEOjGxh-aAkbw)
 
-
 # 👌 Platform Support
 
 Semi UI supports all major modern browsers.
@@ -162,14 +166,26 @@ Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testi
 
 Thanks to [Cypress](https://www.cypress.io/) for providing E2E testing.
 
+<div>
+  <a href="https://www.visactor.com#gh-light-mode-only" target="_blank">
+    <img alt="VisActor Logo" height="30" src="https://lf-dp.bytetos.com/obj/dp-open-internet-cn/visactor-site/bytedance/client/img/visactor/navigator-logo.svg"/>
+  </a>
+  <a href="https://www.visactor.com#gh-dark-mode-only" target="_blank">
+    <img alt="VisActor Logo" height="30" src="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/visactor/logo_500_200_dark.svg"/>
+  </a>
+</div>
+
+Thanks to [VisActor](https://www.visactor.com/) for providing the data visualization solution.
+
 ## 👐 Contributing
+
 Thanks to all the people who already contributed!
 
 <a href="https://github.com/DouyinFE/semi-design/graphs/contributors">
   <img src="https://contrib.rocks/image?repo=DouyinFE/semi-design" />
 </a>
 
-Read the contributing guide to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to Semi UI.
+Read the contributing guide to learn about our development process, how to propose bug fixes and improvements, and how to build and test your changes to Semi UI.
 
 See [CONTRIBUTING](CONTRIBUTING-en-US.md) documentation.
 

+ 1 - 1
content/feedback/banner/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 80
+order: 81
 category: Feedback
 title:  Banner
 subTitle: Banner

+ 1 - 1
content/feedback/banner/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 80
+order: 81
 category: 反馈类
 title:  Banner 通知横幅
 icon: doc-banner

+ 1 - 1
content/feedback/notification/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 81
+order: 82
 category: Feedback
 title:  Notification
 subTitle: Notification

+ 1 - 1
content/feedback/notification/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 81
+order: 82
 category: 反馈类
 title: Notification 通知
 icon: doc-notification

+ 1 - 1
content/feedback/popconfirm/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 82
+order: 83
 category: Feedback
 title:  Popconfirm
 subTitle: Popconfirm

+ 1 - 1
content/feedback/popconfirm/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 82
+order: 83
 category: 反馈类
 title:  Popconfirm 气泡确认框
 icon: doc-popconfirm

+ 1 - 1
content/feedback/progress/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 83
+order: 84
 category: Feedback
 title: Progress
 subTitle: Progress

+ 1 - 1
content/feedback/progress/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 83
+order: 84
 category: 反馈类
 title: Progress 进度条
 icon: doc-progress

+ 1 - 1
content/feedback/skeleton/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 84
+order: 85
 category: Feedback
 title: Skeleton
 subTitle: Skeleton

+ 1 - 1
content/feedback/skeleton/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 84
+order: 85
 category: 反馈类
 title: Skeleton 骨架屏
 icon: doc-skeleton

+ 1 - 1
content/feedback/spin/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 85
+order: 86
 category: Feedback
 title: Spin
 subTitle: Spin

+ 1 - 1
content/feedback/spin/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 85
+order: 86
 category: 反馈类
 title: Spin 加载器
 icon: doc-spin

+ 1 - 1
content/feedback/toast/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 86
+order: 87
 category: Feedback
 title: Toast
 subTitle: Toast

+ 1 - 1
content/feedback/toast/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 86
+order: 87
 category: 反馈类
 title: Toast 提示
 icon: doc-toast

+ 15 - 13
content/input/select/index-en-US.md

@@ -1246,19 +1246,21 @@ import { Select, TagInput } from '@douyinfe/semi-ui';
 
     const triggerRender = ({ value, onSearch, onClear }) => {
         return (
-            <TagInput
-                draggable
-                allowDuplicates={false}
-                value={value.map(item => item.label)}
-                inputValue={inputVal}
-                onInputChange={(word) => {
-                    onSearch(word);
-                    setInputVal(word);
-                }}
-                onChange={handleSort}
-                onClear={() => onClear()}
-                showClear
-            />
+            <div onKeyDown={e=>e.stopPropagation()}>
+                <TagInput
+                    draggable
+                    allowDuplicates={false}
+                    value={value.map(item => item.label)}
+                    inputValue={inputVal}
+                    onInputChange={(word) => {
+                        onSearch(word);
+                        setInputVal(word);
+                    }}
+                    onChange={handleSort}
+                    onClear={() => onClear()}
+                    showClear
+                />
+            </div>
         );
     }; 
   

+ 15 - 13
content/input/select/index.md

@@ -1300,19 +1300,21 @@ import { Select, TagInput } from '@douyinfe/semi-ui';
 
     const triggerRender = ({ value, onSearch, onClear }) => {
         return (
-            <TagInput
-                draggable
-                allowDuplicates={false}
-                value={value.map(item => item.label)}
-                inputValue={inputVal}
-                onInputChange={(word) => {
-                    onSearch(word);
-                    setInputVal(word);
-                }}
-                onChange={handleSort}
-                onClear={() => onClear()}
-                showClear
-            />
+            <div onKeyDown={e=>e.stopPropagation()}>
+                <TagInput
+                    draggable
+                    allowDuplicates={false}
+                    value={value.map(item => item.label)}
+                    inputValue={inputVal}
+                    onInputChange={(word) => {
+                        onSearch(word);
+                        setInputVal(word);
+                    }}
+                    onChange={handleSort}
+                    onClear={() => onClear()}
+                    showClear
+                />
+            </div>
         );
     }; 
   

+ 2 - 1
content/input/treeselect/index-en-US.md

@@ -1408,7 +1408,7 @@ function Demo() {
 | arrowIcon|Customize the right drop-down arrow Icon, when the showClear switch is turned on and there is currently a selected value, hover will give priority to the clear icon| ReactNode | | - |
 |autoAdjustOverflow|Whether the pop-up layer automatically adjusts the direction when it is obscured (only vertical direction is supported for the time being, and the inserted parent is body)|boolean | true| - |
 | autoExpandParent | Toggle whether to expand parent nodes automatically | boolean | false | - |
-| autoMergeValue | Sets the automerge value. Specifically, when enabled, when a parent node is selected, value will include that node and its children. (Works if leafOnly is false)| boolean | true | 2.61.0 | 
+| autoMergeValue | Sets the automerge value. Specifically, when enabled, when a parent node is selected, the value will not include the descendants of the node. (Works if leafOnly is false)| boolean | true | 2.61.0 | 
 | borderless        | borderless mode  >=2.33.0                                                                                                                                                                     | boolean                         |           |
 | checkRelation | In multiple, the relationship between the checked states of the nodes, optional: 'related'、'unRelated' | string | 'related' | 2.5.0 |
 | className                | Class name                                                                          | string                                                            | -           | -       |
@@ -1429,6 +1429,7 @@ function Demo() {
 | expandAction             | Expand logic, one of false, 'click', 'doubleClick'. Default is set to false, which means item will not be expanded on clicking except on expand icon    | boolean \| string   | false | - |
 | expandAll | Set whether to expand all nodes by default. If the data (`treeData`) changes, the default expansion will still be affected by this api | boolean | false |- |
 | expandedKeys        | (Controlled)Keys of expanded nodes. Direct child nodes will be displayed.  | string[]                    | -       | - |
+| expandIcon | Custom expand icon, [example](/en-US/navigation/tree#Custom%20expansion%20icon) | ReactNode \| (props: expandProps)=>ReactNode | - | 2.75.0 |
 | keyMaps | Customize the key, label, and value fields in the node | object |  - | 2.47.0 |
 | filterTreeNode           | Toggle whether searchable or pass in a function to customize search behavior, data parameter provided since v2.28.0 | boolean\| <ApiType detail='(inputValue: string, treeNodeString: string, data?: TreeNodeData) => boolean'>Function</ApiType> | false       | -       |
 | getPopupContainer        | Container to render pop-up, you need to set 'position: relative`  This will change the DOM tree position, but not the view's rendering position.                                                    | function():HTMLElement                                            | -           | -       |

+ 2 - 1
content/input/treeselect/index.md

@@ -1391,7 +1391,7 @@ function Demo() {
 | arrowIcon | 自定义右侧下拉箭头Icon,当showClear开关打开且当前有选中值时,hover会优先显示clear icon                                                            |  ReactNode |       | 
 | autoAdjustOverflow| 浮层被遮挡时是否自动调整方向(暂时仅支持竖直方向,且插入的父级为 body)                                                                    | boolean | true| 
 | autoExpandParent | 是否自动展开父节点                                                                                                                  | boolean | false | 
-| autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将包括该节点以及该子孙节点。(在leafOnly为false的情况下生效)。v2.61.0 后提供     | boolean | true |
+| autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将不包括该节点的子孙节点。(在leafOnly为false的情况下生效)。v2.61.0 后提供     | boolean | true |
 | borderless        | 无边框模式,v2.33.0后提供                                                                                                          | boolean | false |
 | checkRelation | 多选时,节点之间选中状态的关系,可选:'related'、'unRelated'。v2.5.0后提供                                                                  | string | 'related' |
 | className | 选择框的 `className` 属性                                                                                                                 | string | - | - |
@@ -1412,6 +1412,7 @@ function Demo() {
 | expandAction | 展开逻辑,可选 false, 'click', 'doubleClick'。默认值为 false,即仅当点击展开按钮时才会展开                                                        | boolean \| string | false |
 | expandAll | 设置是否默认展开所有节点,若后续数据(`treeData`)发生改变,默认的展开情况也是会受到这个 api 影响的                                                         | boolean | false |
 | expandedKeys | (受控)展开的节点,默认展开节点显示其直接子级                                                                                                   | string[] | - |
+| expandIcon | 自定义展开图标,使用[示例](/zh-CN/navigation/tree#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%B1%95%E5%BC%80%20Icon) | ReactNode \| (props: expandProps)=>ReactNode | - | 2.75.0 |
 | keyMaps | 自定义节点中 key、label、value 的字段。v2.47.0后提供                                                                                                | object |  - |
 | filterTreeNode | 是否根据输入项进行筛选,默认用 `treeNodeFilterProp` 的值作为要筛选的 `TreeNodeData` 的属性值, data 参数自 v2.28.0 开始提供                         | boolean\| <ApiType detail='(inputValue: string, treeNodeString: string, data?: TreeNodeData) => boolean'>Function</ApiType> | false |
 | getPopupContainer  | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative` 这会改变浮层 DOM 树位置,但不会改变视图渲染位置。                                                                                       | function():HTMLElement | - |

+ 1 - 0
content/input/upload/index-en-US.md

@@ -1396,6 +1396,7 @@ import { IconUpload } from '@douyinfe/semi-icons';
 |promptPosition | The position of the prompt text. When the listType is list, the reference object is the children element; when the listType is picture, the reference object is the picture list. Optional values ​​`left`, `right`, `bottom`<br/> (In picture wall mode, promptPosition is only supported after v1.3.0) | string |'right' | |
 |renderFileItem | Custom rendering of fileCard | (renderProps: RenderFileItemProps) => ReactNode | | 1.0.0 |
 |renderFileOperation | Custom list item operation area | (renderProps: RenderFileItemProps)=>ReactNode | | 2.5.0 |
+|renderPicClose| Customize the photo wall close button, only valid in photo wall mode| ({className: string, remove: (e: MouseEvent) => void})=>ReactNode | | 2.75.0 |
 |renderPicInfo| Custom photo wall information, only valid in photo wall mode| (renderProps: RenderFileItemProps)=>ReactNode | | 2.2.0 |
 |renderPicPreviewIcon| The preview icon displayed when customizing the photo wall hover, only valid in photo wall mode | (renderProps: RenderFileItemProps)=>ReactNode | | 2.5.0 |
 |renderThumbnail| Custom picture wall thumb, only valid in photo wall mode| (renderProps: RenderFileItemProps)=>ReactNode | | 2.2.0 |

+ 1 - 0
content/input/upload/index.md

@@ -1405,6 +1405,7 @@ import { IconUpload } from '@douyinfe/semi-icons';
 |promptPosition | 提示文本的位置,当 listType 为 list 时,参照物为 children 元素;当 listType 为 picture 时,参照物为图片列表。可选值 `left`、`right`、`bottom`<br/>(图片墙模式下,v1.3.0 后才支持使用 promptPosition) | string | 'right' |  |
 |renderFileItem | fileCard 的自定义渲染 | (renderProps: RenderFileItemProps) => ReactNode |  |  |
 |renderFileOperation | 自定义列表项操作区 | (renderProps: RenderFileItemProps)=>ReactNode | | 2.5.0 |
+|renderPicClose| 自定义照片墙 close 按钮,只在照片墙模式下有效| ({className: string, remove: (e: MouseEvent) => void})=>ReactNode | | 2.75.0 |
 |renderPicInfo| 自定义照片墙信息,只在照片墙模式下有效| (renderProps: RenderFileItemProps)=>ReactNode | | 2.2.0 |
 |renderPicPreviewIcon| 自定义照片墙hover时展示的预览图标,只在照片墙模式下有效 | (renderProps: RenderFileItemProps)=>ReactNode | | 2.5.0 |
 |renderThumbnail| 自定义图片墙缩略图,只在照片墙模式下有效| (renderProps: RenderFileItemProps)=>ReactNode | | 2.2.0 |

+ 1 - 1
content/navigation/tabs/index-en-US.md

@@ -824,7 +824,7 @@ WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/
 
 -   **Why typography with ellipses in Tabs doesn't work?**
 
-    Because when Tabs renders TabPane, the default is to render display: none. At this point these components cannot get the correct width or height values. It is recommended to enable lazyRender in version 1.x, or disable keepDOM. Version 0.x needs to use tabList notation.
+    Because when Tabs renders TabPane, the default is to render display: none. At this point these components cannot get the correct width or height values. It is recommended to enable lazyRender in or disable keepDOM.
 
 -   **Why are the height or width values ​​wrong when using components such as Collapse/Collapsible/Resizable Table in Tabs?**
 

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

@@ -840,7 +840,7 @@ WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/
 
 -   **为什么在 Tabs 中使用 Typography 的省略 ellipsis 失效?**
 
-    因为 Tabs 渲染 TabPane 时,默认是全部渲染 display: none。此时这些组件无法获取到正确的宽度或高度值。建议 1.x 的版本开启 lazyRender,或者关闭 keepDOM。0.x 的版本需要使用 tabList 的写法。
+    因为 Tabs 渲染 TabPane 时,默认是全部渲染 display: none。此时这些组件无法获取到正确的宽度或高度值。建议开启 lazyRender,或者关闭 keepDOM。
 
 -   **为什么在 Tabs 中使用 Collapse/Collapsible/Resizable Table 等组件的高度或宽度值不对?**
 

+ 85 - 1
content/navigation/tree/index-en-US.md

@@ -1213,6 +1213,89 @@ class Demo extends React.Component {
 }
 ```
 
+### Custom expansion icon
+
+The expansion Icon can be customized through `expandIcon`. Supports passing in ReactNode or functions. `expandIcon` is supported since 2.75.0.
+
+```ts
+expandIcon: ReactNode | ((props: {
+    onClick: (e: MouseEvent) => void;
+    className: string;
+    expanded: boolean;
+}))
+```
+
+Examples are as follows:
+
+```jsx live=true 
+() => {
+   const treeData = [
+            {
+                label: 'Asia',
+                key: 'Asia',
+                children: [
+                    {
+                        label: 'China',
+                        key: 'China',
+                        children: [
+                            {
+                                label: 'Beijing',
+                                key: 'Beijing',
+                            },
+                            {
+                                label: 'Shanghai',
+                                key: 'Shanghai',
+                            },
+                        ],
+                    },
+                ],
+            },
+            {
+                label: 'North America',
+                value: 'North America',
+            }
+        ];
+    const expandIconFunc = useCallback((props) => {
+        const { expanded, onClick, className } = props;
+        if (expanded) {
+        return <IconMinus size="small" className={className} onClick={onClick}/>
+        } else {
+        return <IconPlus size="small" className={className} onClick={onClick}/>
+        }
+    });
+    const style = {
+        width: 260,
+        height: 200,
+        border: '1px solid var(--semi-color-border)'
+    };
+
+  return (
+    <>
+      <p>ReactNode type</p>
+      <Tree
+        style={{ width: 300}}
+        expandIcon={<IconChevronDown size="small" className='testCls'/>}
+        multiple
+        defaultExpandedKeys={['Asia']}
+        treeData={treeData}
+        style={style}
+      />
+      <br />
+      <p>Function type</p>
+      <Tree
+        style={{ width: 300}}
+        multiple
+        expandIcon={expandIconFunc}
+        defaultExpandedKeys={['Asia']}
+        treeData={treeData}
+        style={style}
+      />
+    </>
+  );
+}
+```
+
+
 ### Tree with line
 
 Set the line between nodes through `showLine`, the default is false, supported starting from 2.50.0
@@ -2271,7 +2354,7 @@ import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSec
 | ------------------- | --------------------- | ------------------------------------------------- | ------- | ------ |
 | autoExpandParent | Toggle whether to expand parent node automatically | boolean | false | 0.34.0 |
 | autoExpandWhenDragEnter | Toggle whether allow autoExpand when drag enter node | boolean | true | 1.8.0 | 
-| autoMergeValue | Sets the automerge value. Specifically, when enabled, when a parent node is selected, value will include that node and its children. (Works if leafOnly is false)| boolean | true | 2.61.0 | 
+| autoMergeValue | Sets the automerge value. Specifically, when enabled, when a parent node is selected, the value will not include the descendants of the node. (Works if leafOnly is false)| boolean | true | 2.61.0 | 
 | blockNode           | Toggle whether to display node as row     | boolean                     | true    | - |
 | checkRelation | In multiple, the relationship between the checked states of the nodes, optional: 'related'、'unRelated' | string | 'related' | 2.5.0 |
 | className           | Class name| string                      | -       | - |
@@ -2286,6 +2369,7 @@ import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSec
 | expandAction             | Expand logic, one of false, 'click', 'doubleClick'. Default is set to false, which means item will not be expanded on clicking except on expand icon    | boolean \| string   | false | 0.35.0       |
 | expandAll | Set whether to expand all nodes by default. If the subsequent data (`treeData`/`treeDataSimpleJson`) changes, the default expansion will also be affected by this api | boolean | false | 1.30.0 |
 | expandedKeys        | (Controlled)Keys of expanded nodes. Direct child nodes will be displayed.  | string[]                    | -       | - |
+| expandIcon | Custom expand icon | ReactNode \| (props: expandProps)=>ReactNode | - | 2.75.0 |
 | keyMaps | Customize the key, label, and value fields in the node | object |  - | 2.47.0 |
 | filterTreeNode      | Toggle whether searchable or pass in a function to customize search behavior, data parameter provided since v2.28.0 | boolean \| ((inputValue: string, treeNodeString: string, data?: TreeNodeData) => boolean)  | false   | - |
 | hideDraggingNode | Toggle whether to hide dragImg of dragging node | boolean | false | 1.8.0 | 

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

@@ -1241,6 +1241,92 @@ class Demo extends React.Component {
 }
 ```
 
+### 自定义展开 Icon
+
+可以通过 `expandIcon` 自定义展开 Icon。 支持传入 ReactNode 或者函数。`expandIcon` 自 2.75.0 开始支持。
+
+```ts
+expandIcon: ReactNode | ((props: {
+    onClick: (e: MouseEvent) => void;
+    className: string;
+    expanded: boolean;
+}))
+```
+
+示例如下:
+
+```jsx live=true 
+() => {
+    const treeData = [
+        {
+            label: '亚洲',
+            key: 'yazhou',
+            children: [
+                {
+                    label: '中国',
+                    key: 'zhongguo',
+                    children: [
+                        {
+                            label: '北京',
+                            key: 'beijing',
+                        },
+                        {
+                            label: '上海',
+                            key: 'shanghai',
+                        },
+                    ],
+                },
+                {
+                    label: '日本',
+                    key: 'riben',
+                },
+            ],
+        },
+        {
+            label: '北美洲',
+            key: 'beimeizhou',
+        },
+    ];
+    const expandIconFunc = useCallback((props) => {
+        const { expanded, onClick, className } = props;
+        if (expanded) {
+        return <IconMinus size="small" className={className} onClick={onClick}/>
+        } else {
+        return <IconPlus size="small" className={className} onClick={onClick}/>
+        }
+    });
+    const style = {
+        width: 260,
+        height: 200,
+        border: '1px solid var(--semi-color-border)'
+    };
+
+  return (
+    <>
+      <p>expandIcon 是  ReactNode</p>
+      <Tree
+        style={{ width: 300}}
+        expandIcon={<IconChevronDown size="small" className='testCls'/>}
+        multiple
+        defaultExpandedKeys={['yazhou']}
+        treeData={treeData}
+        style={style}
+      />
+      <br />
+      <p>expandIcon 是函数 </p>
+      <Tree
+        style={{ width: 300}}
+        multiple
+        expandIcon={expandIconFunc}
+        defaultExpandedKeys={['yazhou']}
+        treeData={treeData}
+        style={style}
+      />
+    </>
+  );
+}
+```
+
 ### 连接线
 
 通过 `showLine` 设置节点之间的连接线,默认为 false,从 2.50.0 开始支持
@@ -2286,7 +2372,7 @@ import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSec
 |-------------   | ----------- | -------------- | -------------- | --------|
 | autoExpandParent | 是否自动展开父节点,默认为 false,当组件初次挂载时为 true | boolean | false | 0.34.0 |
 | autoExpandWhenDragEnter | 是否允许拖拽到节点上时自动展开改节点 | boolean | true | 1.8.0 | 
-| autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将包括该节点以及该子孙节点。(在leafOnly为false的情况下生效)| boolean | true | 2.61.0 | 
+| autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将不包括该节点的子孙节点。(在leafOnly为false的情况下生效)| boolean | true | 2.61.0 | 
 | blockNode | 行显示节点 | boolean | true | - |
 | checkRelation | 多选时,节点之间选中状态的关系,可选:'related'、'unRelated' | string | 'related' | 2.5.0 |
 | className | 类名 | string | - | - |
@@ -2301,6 +2387,7 @@ import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSec
 | expandAction             | 展开逻辑,可选 false, 'click', 'doubleClick'。默认值为 false,即仅当点击展开按钮时才会展开  | boolean \| string   | false | 0.35.0       |
 | expandAll | 设置是否默认展开所有节点,若后续数据(`treeData`/`treeDataSimpleJson`)发生改变,默认展开情况也是会受到这个 api 影响的 | boolean | false | 1.30.0 |
 | expandedKeys | (受控)展开的节点,默认展开节点显示其直接子级 | string[] | - | - |
+| expandIcon | 自定义展开图标 | ReactNode \| (props: expandProps)=>ReactNode | - | 2.75.0 |
 | keyMaps | 自定义节点中 key、label、value 的字段 | object |  - | 2.47.0 |
 | filterTreeNode | 是否根据输入项进行筛选,默认用 `treeNodeFilterProp` 的值作为要筛选的 `TreeNodeData` 的属性值,  data 参数自 v2.28.0 开始提供 | boolean \| ((inputValue: string, treeNodeString: string, data?: TreeNodeData) => boolean) | false | - |
 | hideDraggingNode | 是否隐藏正在拖拽的节点的 dragImg | boolean | false | 1.8.0 | 

+ 2 - 0
content/order.js

@@ -67,6 +67,7 @@ const order = [
     'empty',
     'highlight',
     'image',
+    'cropper',
     'list',
     'modal',
     'overflowlist',
@@ -88,6 +89,7 @@ const order = [
     'configprovider',
     'locale',
     'jsonviewer',
+    'audioPlayer',
 ];
 let { exec } = require('child_process');
 let fs = require('fs');

+ 1 - 1
content/other/configprovider/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 87
+order: 88
 category: Other
 title: ConfigProvider
 icon: doc-configprovider

+ 1 - 1
content/other/configprovider/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 87
+order: 88
 category: 其他
 title:  ConfigProvider 全局配置
 icon: doc-configprovider

+ 2 - 2
content/other/locale/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 88
+order: 89
 category: Other
 title: LocaleProvider
 subTitle: LocaleProvider
@@ -28,7 +28,7 @@ brief: Internationalized components to provide multilingual support for Semi com
 
 ## Components supported
 
-> DatePicker、TimePicker、Modal、Pagination、Select、Table、Cascader、Calendar、TreeSelect、List、Typography、Transfer、Nav、Upload、Form、Navigation、Image
+Calendar、Cascader、Chat、DatePicker、Form、Image、List、List、Modal、Navigation、Nav、Pagination、Popconfirm、Select、Table、TimePicker、Transfer、Tree、TreeSelect、Typography、Upload
 
 ## How to use
 

+ 5 - 3
content/other/locale/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 88
+order: 89
 category: 其他
 title:  LocaleProvider 多语言
 icon: doc-i18n
@@ -23,9 +23,11 @@ brief: 国际化组件,为 Semi 组件提供多语言支持
 | v2.15.0     | 意大利语: it、法语:fr、德语:de   |
 | v2.21.0     | 罗马尼亚语: ro   |
 | v2.29.0     | 瑞典语: sv_SE、波兰语: pl_PL 、荷兰语: nl_NL |
-## 已支持组件
 
-> DatePicker、TimePicker、Modal、Pagination、Select、Table、Cascader、Calendar、TreeSelect、List、Typography、Transfer、Nav、Upload、Form、Navigation、Image
+## 已支持组件  
+目前有以下组件存在内置默认文本,均已实现国际化多语言适配  
+Calendar、Cascader、Chat、DatePicker、Form、Image、List、List、Modal、Navigation、Nav、Pagination、Popconfirm、Select、Table、TimePicker、Transfer、Tree、TreeSelect、Typography、Upload
+
 
 ## 使用
 

+ 171 - 0
content/plus/audioPlayer/index-en-US.md

@@ -0,0 +1,171 @@
+---
+localeCode: en-US
+order: 91
+category: Plus
+title: AudioPlayer
+icon: doc-audioplayer
+width: 60%
+brief: Used to play audio
+showNew: true
+---
+
+## Demos
+
+### How to import
+
+```jsx import
+import { AudioPlayer } from '@douyinfe/semi-ui';
+```
+
+### Basic Usage
+
+Basic usage by passing audio URL through `audioUrl`.  
+audioUrl supports string, string array, object, and object array. See [AudioPlayer](#AudioPlayer) for detailed parameters.
+
+```jsx live=true noInline=true dir="column"
+import React from 'react';
+import { AudioPlayer } from '@douyinfe/semi-ui';
+
+function Demo() {
+    const audioUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio2.mp3';
+    const audioUrlArr = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio1.mp3',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio2.mp3',
+    ];
+    const audioUrlObj = {
+        src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio1.mp3',
+        title: 'Audio Title',
+        cover: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg',
+    };
+    const audioUrlArrObj = [
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio1.mp3',
+            title: 'Audio Title 1',
+            cover: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg',
+        },
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio2.mp3',
+            title: 'Audio Title 2',
+            cover: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg',
+        },
+    ];
+  
+    return (
+        <div style={{ width: '100%' }}>
+            <div style={{ marginTop: 10 }}>
+                <AudioPlayer
+                    autoPlay={false}
+                    audioUrl={audioUrl}
+                />
+            </div>
+            <div style={{ marginTop: 10 }}>
+                <AudioPlayer
+                    autoPlay={false}
+                    audioUrl={audioUrlObj}
+                />
+            </div>
+            <div style={{ marginTop: 10 }}>
+                <AudioPlayer
+                    autoPlay={false}
+                    audioUrl={audioUrlArr}
+                />
+            </div>
+            <div style={{ marginTop: 10 }}>
+                <AudioPlayer
+                    autoPlay={false}
+                    audioUrl={audioUrlArrObj}
+                />
+            </div>
+        </div>
+    );
+}
+
+render(Demo);
+```
+
+### Hide Toolbar
+
+Set showToolbar to false to hide the toolbar
+
+```jsx live=true noInline=true dir="column"
+import React from 'react';
+import { AudioPlayer } from '@douyinfe/semi-ui';
+
+function Demo() {
+    const audioUrlObj = {
+        src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio1.mp3',
+        title: 'Audio Title'
+    };
+  
+    return (
+        <div style={{ width: '100%' }}>
+            <AudioPlayer
+                autoPlay={false}
+                audioUrl={audioUrlObj}
+                showToolbar={false}
+            />
+        </div>
+    );
+}
+
+render(Demo);
+```
+
+### Theme
+
+Set the audio player theme through `theme`, supports `light` and `dark`, default is `dark`
+
+```jsx live=true noInline=true dir="column"
+import React from 'react';
+import { AudioPlayer } from '@douyinfe/semi-ui';
+
+function Demo() {
+    const audioUrlArrObj = [
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio1.mp3',
+            title: 'Audio Title 1',
+            cover: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg',
+        },
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio2.mp3',
+            title: 'Audio Title 2',
+            cover: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg',
+        },
+    ];
+  
+    return (
+        <div style={{ width: '100%' }}>
+            <AudioPlayer
+                audioUrl={audioUrlArrObj}
+                theme="light"
+            />
+        </div>
+    );
+}
+
+render(Demo);
+```
+
+## API Reference
+
+### AudioPlayer
+
+| Property | Description | Type | Default |
+|----------|-------------|------|---------|
+| audioUrl | Audio address | string | string[] | AudioInfo | AudioInfo[] | - |
+| autoPlay | Auto play | boolean | false |
+| theme | Theme, optional values: `dark` and `light` | string | "dark" |
+| showToolbar | Whether to display the toolbar | boolean | true |
+| skipDuration | Skip time | number | 10 |
+| className | Class name | string | - |
+| style | Inline style | object | - |
+
+### AudioInfo
+
+| Property | Description | Type | Default |
+|----------|-------------|------|---------|
+| src | Audio address | string | - |
+| title | Audio title | string | - |
+| cover | Cover image | string | - |
+
+

+ 178 - 0
content/plus/audioPlayer/index.md

@@ -0,0 +1,178 @@
+---
+localeCode: zh-CN
+order: 91
+category: Plus
+title: AudioPlayer 音频播放器
+icon: doc-audioplayer
+width: 60%
+brief: 用于播放音频
+showNew: true
+---
+
+## 代码演示
+
+### 如何引入
+
+```jsx import
+import { AudioPlayer } from '@douyinfe/semi-ui';
+```
+
+
+### 基本用法
+
+基本使用,通过`audioUrl`传入音频地址  
+audioUrl 可以传入字符串,字符串数组,对象,对象数组, 具体参数参考 [AudioPlayer](#AudioPlayer)
+
+```jsx live=true noInline=true dir="column"
+import React from 'react';
+import { AudioPlayer } from '@douyinfe/semi-ui';
+
+function Demo() {
+    const audioUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio2.mp3';
+    const audioUrlArr = [
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio1.mp3',
+        'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio2.mp3',
+    ];
+    const audioUrlObj = {
+        src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio1.mp3',
+        title: '音频标题',
+        cover: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg',
+    };
+    const audioUrlArrObj = [
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio1.mp3',
+            title: '音频标题1',
+            cover: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg',
+        },
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio2.mp3',
+            title: '音频标题2',
+            cover: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg',
+        },
+    ];
+  
+    return (
+        <div style={{ width: '100%' }}>
+            <div style={{ marginTop: 10 }}>
+                <AudioPlayer
+                    autoPlay={false}
+                    audioUrl={audioUrl}
+                />
+            </div>
+            <div style={{ marginTop: 10 }}>
+                <AudioPlayer
+                    autoPlay={false}
+                    audioUrl={audioUrlObj}
+                />
+            </div>
+            <div style={{ marginTop: 10 }}>
+                <AudioPlayer
+                    autoPlay={false}
+                    audioUrl={audioUrlArr}
+                />
+            </div>
+            <div style={{ marginTop: 10 }}>
+                <AudioPlayer
+                    autoPlay={false}
+                    audioUrl={audioUrlArrObj}
+                />
+            </div>
+        </div>
+    );
+}
+
+render(Demo);
+
+```
+
+
+### 隐藏工具栏
+
+showToolbar 设置为false,则隐藏工具栏
+
+
+```jsx live=true noInline=true dir="column"
+import React from 'react';
+import { AudioPlayer } from '@douyinfe/semi-ui';
+
+function Demo() {
+    const audioUrlObj = {
+        src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio1.mp3',
+        title: '音频标题'
+    };
+  
+    return (
+        <div style={{ width: '100%' }}>
+            <AudioPlayer
+                autoPlay={false}
+                audioUrl={audioUrlObj}
+                showToolbar={false}
+            />
+        </div>
+    );
+}
+
+render(Demo);
+
+```
+
+### 主题
+
+通过 `theme` 设置音频播放器主题,支持 `light` 和 `dark`,默认 `dark`
+
+
+```jsx live=true noInline=true dir="column"
+import React from 'react';
+import { AudioPlayer } from '@douyinfe/semi-ui';
+
+function Demo() {
+    const audioUrlArrObj = [
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio1.mp3',
+            title: '音频标题1',
+            cover: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg',
+        },
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/components/audio2.mp3',
+            title: '音频标题2',
+            cover: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg',
+        },
+    ];
+  
+    return (
+        <div style={{ width: '100%' }}>
+            <AudioPlayer
+                audioUrl={audioUrlArrObj}
+                theme="light"
+            />
+        </div>
+    );
+}
+
+render(Demo);
+
+```
+
+## API 参考
+
+### AudioPlayer
+
+| 属性                | 说明                                             | 类型                              | 默认值    |
+|-------------------|------------------------------------------------|---------------------------------|--------------|
+| audioUrl             | 音频地址                                    | string | string[] | AudioInfo | AudioInfo[]                                 | -  |
+| autoPlay            | 自动播放                                     | boolean                                  | false  |
+| theme             | 主题,可选值:`dark` 和 `light`                  | string        |                         "dark"  |
+| showToolbar       | 是否显示工具栏                           | boolean                                  | true      |
+| skipDuration       | 跳转时间                                     | number                                  | 10   |
+| className         | 类名                           | string                                  | -   |
+| style             | 内联样式                           | object                                  | -   |
+
+### AudioInfo
+
+| 属性                | 说明                                          | 类型                              | 默认值    |
+|-------------------|------------------------------------------------|---------------------------------|-----------|
+| src               | 音频地址                                    | string                          | -  |
+| title             | 音频标题                                    | string                          | -  |
+| cover             | 封面图片                                    | string                          | -  |
+
+

+ 4 - 3
content/plus/chat/index-en-US.md

@@ -1617,8 +1617,9 @@ render(DefaultChat);
 | inputBoxStyle | Input box style | CSSProperties | - |
 | inputBoxCls | Input box className | string | - |
 | sendHotKey | Keyboard shortcut for sending content, supports `enter` \| `shift+enter`. The former will send the message in the input box when you press enter alone. When the shift and enter keys are pressed at the same time, it will only wrap the line and not send it. The latter is the opposite | string | `enter` |
+| markdownRenderProps | This parameter will be passed to the MarkdownRender component used for dialog rendering. For details, see [MarkdownRenderProps](/en-US/plus/markdownrender#API)| MarkdownRenderProps |-|
 | mode | Conversation mode, support `bubble` \| `noBubble` \| `userBubble`  | string | `bubble` |
-| roleConfig | Role information configuration, see[RoleConfig](#RoleConfig) | RoleConfig | - |
+| roleConfig | Role information configuration, see [RoleConfig](#RoleConfig) | RoleConfig | - |
 | renderDivider | Custom render divider, supported since v2.67.0 | (message?: Message) => ReactNode | - |
 | renderHintBox | Custom rendering prompt information | (props: {content: string; index: number,onHintClick: () => void}) => React.ReactNode| - |
 | onChatsChange | Triggered when the conversation list changes | (chats: Message[]) => void | - |
@@ -1637,8 +1638,8 @@ render(DefaultChat);
 | showClearContext | Whether to display the clear context button| boolean | false |
 | showStopGenerate | Whether to display the stop generation button| boolean | false |
 | topSlot | top slot for chat | React.ReactNode | - |
-| uploadProps | Upload component properties, refer to details [Upload](/zh-CN/input/upload#API%20%E5%8F%82%E8%80%83) | UploadProps | - |
-| uploadTipProps | Upload component prompt attribute, refer to details [Tooltip](/zh-CN/show/tooltip#API%20%E5%8F%82%E8%80%83) | TooltipProps | - |
+| uploadProps | Upload component properties, refer to details [Upload](/en-US/input/upload#API%20%E5%8F%82%E8%80%83) | UploadProps | - |
+| uploadTipProps | Upload component prompt attribute, refer to details [Tooltip](/en-US/show/tooltip#API%20%E5%8F%82%E8%80%83) | TooltipProps | - |
 
 
 #### RoleConfig

+ 2 - 1
content/plus/chat/index.md

@@ -1620,8 +1620,9 @@ render(DefaultChat);
 | inputBoxStyle | 输入框样式 | CSSProperties | - |
 | inputBoxCls | 输入框类名 | string | - |
 | sendHotKey | 发送输入内容的键盘快捷键,支持 `enter` \| `shift+enter`。前者在单独按下 enter 将发送输入框中的消息, shift 和 enter 按键同时按下时,仅换行,不发送。后者相反 | string | `enter` |
+| markdownRenderProps | 该参数将透传给对话框渲染所用的 MarkdownRender 组件,详见 [MarkdownRenderProps](/zh-CN/plus/markdownrender#API)| MarkdownRenderProps |-|
 | mode | 对话模式,支持 `bubble` \| `noBubble` \| `userBubble`  | string | `bubble` |
-| roleConfig | 角色信息配置,具体见[RoleConfig](#RoleConfig) | RoleConfig | - |
+| roleConfig | 角色信息配置,具体见 [RoleConfig](#RoleConfig) | RoleConfig | - |
 | renderDivider | 自定义渲染分割线, 自 v2.67.0 支持 | (message?: Message) => ReactNode | - |
 | renderHintBox | 自定义渲染提示信息 | (props: {content: string; index: number,onHintClick: () => void}) => React.ReactNode| - |
 | onChatsChange | 对话列表变化时触发 | (chats: Message[]) => void | - |

+ 1 - 1
content/plus/dragMove/index-en-US.md

@@ -3,7 +3,7 @@ localeCode: en-US
 order: 26
 category: Plus
 title:  DragMove
-icon: doc-configprovider
+icon: doc-dragmove
 dir: column
 brief: Set elements to change their position by dragging
 showNew: true

+ 1 - 1
content/plus/dragMove/index.md

@@ -3,7 +3,7 @@ localeCode: zh-CN
 order: 26
 category: Plus
 title:  DragMove 拖拽移动
-icon: doc-configprovider
+icon: doc-dragmove
 dir: column
 brief: 可通过拖拽改变位置
 showNew: true

+ 3 - 0
content/plus/jsonviewer/index-en-US.md

@@ -36,6 +36,7 @@ import { JsonViewer } from '@douyinfe/semi-ui';
 ### Basic Usage
 
 Basic usage of JsonViewer. Pass in the `height` and `width` parameters to set the height, width and initial value of the component. Pass in the JSON string through the `value`.
+Note: JsonViewer is an uncontrolled component. If the value prop is passed as a controlled property (i.e., React State), it is not recommended to modify the value prop (setState) in onChange. If you need to get the component's value, you can get it through ref, please refer to [Methods](#Methods) for details.
 
 ```jsx live=true dir="column" noInline=true
 import React from 'react';
@@ -161,6 +162,7 @@ render(FormatJsonComponent);
 | width | Width of wrapper DOM | number | - |
 | className | className of wrapper DOM | string | - |
 | style | InlineStyle of wrapper DOM | object | - |
+| showSearch | Whether to show search icon | boolean | true |
 | options | Formatting configuration | JsonViewerOptions | - |
 | onChange | Callback for content change | (value: string) => void | - |
 
@@ -170,6 +172,7 @@ render(FormatJsonComponent);
 | ------------- | --------------------------------------- | ----------------- | ------- |
 | lineHeight    | Height of each line of content, unit:px | number            | 20      |
 | autoWrap      | Whether to wrap lines automatically.    | boolean           | true    |
+| readOnly      | Whether to be read-only.    | boolean           | false    |
 | formatOptions | Content format setting                  | FormattingOptions | -       |
 
 ### FormattingOptions

+ 4 - 1
content/plus/jsonviewer/index.md

@@ -31,7 +31,8 @@ import { JsonViewer } from '@douyinfe/semi-ui';
 
 ### 基本用法
 
-JsonViewer 的基本用法。传入 height 和 width 参数,设置组件的高度和宽度和初始值。通过 value 传入 Json 字符串
+JsonViewer 的基本用法。传入 height 和 width 参数,设置组件的高度和宽度和初始值。通过 value 传入 Json 字符串  
+注意:JsonViewer 为非受控组件,若传入 value 属性为受控属性即React State,不建议在 onChange 中修改 value 属性即setState操作。若需要获取组件的值,可以通过 ref 获取,具体可参考 [Methods](#Methods)。
 
 ```jsx live=true dir="column" noInline=true
 import React from 'react';
@@ -158,6 +159,7 @@ render(FormatJsonComponent);
 | width             | 宽度                                     | number                                  | -  |
 | className         | 类名                           | string                                  | -   |
 | style             | 内联样式                           | object                                  | -   |
+| showSearch        | 是否显示搜索Icon                           | boolean                                  | true   |
 | options           | 格式化配置                                | JsonViewerOptions                       | -   |
 | onChange          | 内容变化回调                           | (value: string) => void                  | -   |
 
@@ -167,6 +169,7 @@ render(FormatJsonComponent);
 |-------------------|------------------------------------------------|---------------------------------|-----------|
 | lineHeight        | 行高                                    | number                          | 20  |
 | autoWrap        | 是否自动换行                             | boolean                            | true  |
+| readOnly        | 是否只读                             | boolean                            | false  |
 | formatOptions     | 格式化配置                               | FormattingOptions                |  -  |
 
 ### FormattingOptions

+ 1 - 0
content/plus/markdownrender/index-en-US.md

@@ -22,6 +22,7 @@ Usually used in the following scenarios:
 - Front-end rendering when the server dynamically generates rich text content
 - A light interactive website that focuses on content display
 
+**Note: Safari versions prior to 16.3 do not support regular lookaround assertions, which will cause upstream dependencies on mdxjs to [report errors](https://github.com/syntax-tree/mdast-util-gfm-autolink-literal/issues/10). You can pass remarkGfm as false to disable gfm syntax parsing (which will cause markdown features such as tables to fail to parse), and use null-loader or alias or other methods to ignore the remark-gfm package when compiling the project. **
 
 ## Demos
 

+ 2 - 0
content/plus/markdownrender/index.md

@@ -23,6 +23,8 @@ Semi 提供的 MarkdownRender 组件支持渲染 Markdown 和 MDX,无需特别
 - 偏内容展示的轻交互网站
 
 
+**注意:Safari 16.3 之前的版本不支持正则环视断言,会导致上游依赖 mdxjs [报错]( https://github.com/syntax-tree/mdast-util-gfm-autolink-literal/issues/10),可以传入 remarkGfm 为 false 关闭 gfm 语法解析(会导致table 等markdown 特性无法解析),并且在项目编译时使用 null-loader 或 alias 其他方式忽略掉 remark-gfm 这个包。**
+
 ## 代码演示
 
 ### 如何引入

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 79
+order: 80
 category: Show
 title: Data Visualization
 icon: doc-vchart

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 79
+order: 80
 category: 展示类
 title:  Data Visualization 数据可视化
 icon: doc-vchart

+ 281 - 0
content/show/cropper/index-en-US.md

@@ -0,0 +1,281 @@
+---
+localeCode: en-US
+order: 69
+category: Plus
+title:  Cropper
+icon: doc-cropper
+dir: column
+brief: Freely crop pictures
+showNew: true
+---
+
+## When to use
+
+Cropper is used to crop pictures. It supports custom cropping box styles. The positions of the cropping box, cropped image can be adjusted by dragging. It can zoom and rotate the cropped pictures.
+
+## Demos
+
+Cropper is supported starting from version v2.73.0.
+
+```jsx
+import { Cropper } from '@douyinfe/semi-ui';
+```
+
+### Basic usage
+
+Use `sr` to set the cropped image; use `shape` to set the shape of the cropping box, which defaults to square.
+
+```jsx live=true dir=column noInline=true
+import { Cropper, Button, RadioGroup, Radio } from '@douyinfe/semi-ui';
+
+const containerStyle = {
+  width: 550,
+  height: 300,
+  margin: 20,
+}
+
+function Demo() {
+    const ref = useRef(null);
+  const [shape, setShape] = useState('rect');
+
+    const onButtonClick = useCallback(() => {
+        const value = ref.current.getCropperCanvas();
+        const previewContainer = document.getElementById('previewContainer');
+        previewContainer.innerHTML = '';
+        previewContainer.appendChild(value);
+    }, []);
+
+    const onShapeChange = useCallback((e) => {
+        setShape(e.target.value);
+    }, []);
+
+    return <>
+        <RadioGroup onChange={onShapeChange} value={shape}>
+            <Radio value={'rect'}>rect</Radio>
+            <Radio value={'round'}>round</Radio>
+            <Radio value={'roundRect'}>roundRect</Radio>
+        </RadioGroup>
+        <Cropper
+            ref={ref} 
+            src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/image.png'}
+            style={containerStyle}
+            shape={shape}
+        />
+        <Button onClick={onButtonClick}>Get Cropped Image</Button>
+        <div id='previewContainer'/>
+    </>;
+}
+
+render(<Demo />)
+```
+
+### Customize crop box ratio
+
+The initial crop box ratio can be passed through `defaultAspectRatio` (default is 1). A fixed crop box ratio can be set via `aspectRatio`.
+
+Setting `defaultAspectRatio` only takes effect on the initial crop box ratio. When dragging, the crop box ratio will change with dragging.
+
+When setting `aspectRatio`, the crop box ratio is fixed, and the crop box will change according to this ratio when dragging.
+
+```jsx live=true dir=column noInline=true
+import { Cropper, Button, RadioGroup, Radio } from '@douyinfe/semi-ui';
+
+const containerStyle = {
+  width: 550,
+  height: 300,
+  margin: 20,
+}
+
+function Demo() {
+    const ref = useRef(null);
+    const shape = useState('rect');
+
+    const onButtonClick = useCallback(() => {
+        const value = ref.current.getCropperCanvas();
+        const previewContainer = document.getElementById('previewContainer-aspect');
+        previewContainer.innerHTML = '';
+        previewContainer.appendChild(value);
+    }, []);
+
+    return <>
+        <Cropper
+            aspectRatio={3/4}
+            ref={ref} 
+            src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/image.png'}
+            style={containerStyle}
+        />
+        <Button onClick={onButtonClick}>Get Cropped Image</Button>
+        <div id='previewContainer-aspect' />
+    </>;
+}
+
+render(<Demo />)
+```
+
+### Controlled rotation/zooming of images
+
+Control image rotation and zoom through `rotate` and `zoom`, and get the latest `zoom` value through `onZoomChange`
+
+```jsx live=true dir=column noInline=true
+import { Cropper, Button, Slider } from '@douyinfe/semi-ui';
+
+const containerStyle = {
+  width: 550,
+  height: 300,
+  margin: 20,
+}
+
+const actionStyle = {
+  marginTop: 20,
+  display: 'flex',
+  alignItems: 'center',
+  justifyContent: 'center',
+  width: 'fit-content'
+}
+
+function Demo() {
+  const [rotate, setRotate] = useState(0);
+  const [zoom, setZoom] = useState(1);
+  const ref = useRef();
+
+  const onZoomChange = useCallback((value) => {
+    setZoom(value);
+  })
+
+  const onSliderChange = useCallback((value) => {
+    setRotate(value);
+  }, []);
+
+  const onButtonClick = useCallback(() => {
+    const value = ref.current.getCropperCanvas();
+    const previewContainer = document.getElementById('previewContainer-control');
+    previewContainer.innerHTML = '';
+    previewContainer.appendChild(value);
+  }, []);
+
+  return (
+      <div id='cropper-container'>
+           <Cropper 
+              ref={ref} 
+              src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/image.png'}
+              style={containerStyle}
+              rotate={rotate}
+              zoom={zoom}
+              onZoomChange={onZoomChange}
+           />
+           <div style={actionStyle} >
+            <span>Rotate</span>
+            <Slider
+              style={{ width: 500}}
+              value={rotate}
+              step={1}
+              min={-360}
+              max={360}
+              onChange={onSliderChange}
+            />
+           </div>
+           <div style={actionStyle} >
+            <span>Zoom</span>
+            <Slider
+              style={{ width: 500}}
+              value={zoom}
+              step={0.1}
+              min={0.1}
+              max={3}
+              onChange={onZoomChange}
+            />
+           </div>
+           <br />
+           <Button onClick={onButtonClick}>Get Cropped Image</Button>
+           <br />
+           <div >
+            <div id='previewContainer-control'
+            />
+          </div>
+      </div>
+  );
+};
+
+render(<Demo />)
+```
+
+### Crop box settings
+
+The crop box style can be customized through `cropperBoxStyle`, `cropperBoxClassName`. You can use `showResizeBox` to set whether to display the adjustment blocks at the corners of the crop box.
+
+```jsx live=true dir=column noInline=true
+import { Cropper, Button, Switch } from '@douyinfe/semi-ui';
+
+const containerStyle = {
+  width: 550,
+  height: 300,
+  margin: 20,
+}
+
+const centerStyle = {
+    display: 'flex', 
+    alignItems: 'center', 
+    justifyContent: 'center',
+    width: 'fit-content'
+}
+
+function Demo() {
+    const ref = useRef(null);
+
+    const onButtonClick = useCallback(() => {
+        const value = ref.current.getCropperCanvas();
+        const previewContainer = document.getElementById('previewContainer-cropperBox');
+        previewContainer.innerHTML = '';
+        previewContainer.appendChild(value);
+    }, []);
+
+    return <>
+        <strong>showResizeBox = false,and change the outline color of cropper box</strong>
+        <Cropper
+            ref={ref} 
+            src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/image.png'}
+            style={containerStyle}
+            cropperBoxStyle={{ outlineColor: 'var(--semi-color-bg-0)'}}
+            showResizeBox={false}
+        />
+        <Button onClick={onButtonClick}>Get Cropped Image</Button>
+        <div id='previewContainer-cropperBox'/>
+    </>;
+}
+
+render(<Demo />)
+```
+
+### API
+
+| PROPERTIES | INSTRUCTIONS | TYPE | DEFAULT |
+|-----|------|-----|------|
+| aspectRatio | Crop box width to height ratio | number | - |
+| className | className | string | - |
+| cropperBoxClassName | The class name passed to the crop box | string | - |
+| cropperBoxStyle | The style passed to the crop box | CSSProperties | - |
+| defaultAspectRatio | Initial crop box ratio | number | 1 |
+| imgProps | Attributes passed through to the img tag | object | - |
+| fill | The fill color of the non-picture parts in the cropped result | string | 'rgba(0, 0, 0, 0)'  |
+| maxZoom | Maximum zoom factor | number | 3 |
+| minZoom | Minimum zoom factor | number | 0.1 |
+| onZoomChange | Callback during zoom transformation | (zoom: number) => void | - |
+| rotate | rotation angle | number | - |
+| shape | Crop box shape | 'rect' \| 'round' \| 'roundRect' | 'rect' |
+| src | The address of the cropped image | string | - |
+| showResizeBox | Whether to display the adjustment block of the cropping box | boolean | true |
+| style | Style  | CSSProperties | - |
+| zoom | Zoom value | number | - |
+| zoomStep | Zoom step size | number | 0.1 |
+
+### Methods
+
+Methods bound to component instances can be called through ref to achieve certain special interactions
+
+| Name    | Description  |
+|---------|--------------|
+| getCropperCanvas  | Get the canvas of the cropped image |
+
+## Design Token
+
+<DesignToken/>

+ 286 - 0
content/show/cropper/index.md

@@ -0,0 +1,286 @@
+---
+localeCode: zh-CN
+order: 69
+category: Plus
+title:  Cropper 图片裁切
+icon: doc-cropper
+dir: column
+brief: 通过设定裁切框的宽高比例,自由裁切图片
+showNew: true
+---
+
+## 使用场景
+
+Cropper 用于裁切图片,支持自定义裁切框样式,可通过拖动调整裁切框位置,被裁切图片位置;可缩放,旋转被裁切图片。
+
+
+## 代码演示
+
+### 如何引入
+
+Cropper 从 v2.73.0 开始支持
+
+```jsx
+import { Cropper } from '@douyinfe/semi-ui';
+```
+
+### 基本用法
+
+通过 `sr` 设置被裁切的图片; 可通过 `shape` 设置裁切框形状,默认为方形。
+
+```jsx live=true dir=column noInline=true
+import { Cropper, Button, RadioGroup, Radio } from '@douyinfe/semi-ui';
+
+const containerStyle = {
+  width: 550,
+  height: 300,
+  margin: 20,
+}
+
+function Demo() {
+    const ref = useRef(null);
+  const [shape, setShape] = useState('rect');
+
+    const onButtonClick = useCallback(() => {
+        const value = ref.current.getCropperCanvas();
+        const previewContainer = document.getElementById('previewContainer');
+        previewContainer.innerHTML = '';
+        previewContainer.appendChild(value);
+    }, []);
+
+    const onShapeChange = useCallback((e) => {
+        setShape(e.target.value);
+    }, []);
+
+    return <>
+        <RadioGroup onChange={onShapeChange} value={shape}>
+            <Radio value={'rect'}>rect</Radio>
+            <Radio value={'round'}>round</Radio>
+            <Radio value={'roundRect'}>roundRect</Radio>
+        </RadioGroup>
+        <Cropper
+            ref={ref} 
+            src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/image.png'}
+            style={containerStyle}
+            shape={shape}
+        />
+        <Button onClick={onButtonClick}>裁切</Button>
+        <div id='previewContainer'/>
+    </>;
+}
+
+render(<Demo />)
+```
+
+### 自定义裁切框比例
+
+可通过 `defaultAspectRatio` 初始的裁切框比例(默认为 1)。可通过 `aspectRatio` 设置固定的裁切框比例。
+
+设置 `defaultAspectRatio`仅对初始的裁切框比例生效, 拖动时,裁切框比例会随着拖动而变化。
+
+设置 `aspectRatio` 时,裁切框比例固定,拖动时将裁切框将以此比例变化。
+
+```jsx live=true dir=column noInline=true
+import { Cropper, Button, RadioGroup, Radio } from '@douyinfe/semi-ui';
+
+const containerStyle = {
+  width: 550,
+  height: 300,
+  margin: 20,
+}
+
+function Demo() {
+    const ref = useRef(null);
+    const shape = useState('rect');
+
+    const onButtonClick = useCallback(() => {
+        const value = ref.current.getCropperCanvas();
+        const previewContainer = document.getElementById('previewContainer-aspect');
+        previewContainer.innerHTML = '';
+        previewContainer.appendChild(value);
+    }, []);
+
+    return <>
+        <Cropper
+            aspectRatio={3/4}
+            ref={ref} 
+            src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/image.png'}
+            style={containerStyle}
+        />
+        <Button onClick={onButtonClick}>裁切</Button>
+        <div id='previewContainer-aspect' />
+    </>;
+}
+
+render(<Demo />)
+```
+
+### 受控旋转/缩放图片
+
+通过 `rotate` 和 `zoom` 控制图片旋转和缩放, 可通过 `onZoomChange` 拿到最新的 `zoom` 值。
+
+```jsx live=true dir=column noInline=true
+import { Cropper, Button, Slider } from '@douyinfe/semi-ui';
+
+const containerStyle = {
+  width: 550,
+  height: 300,
+  margin: 20,
+}
+
+const actionStyle = {
+  marginTop: 20,
+  display: 'flex',
+  alignItems: 'center',
+  justifyContent: 'center',
+  width: 'fit-content'
+}
+
+function Demo() {
+  const [rotate, setRotate] = useState(0);
+  const [zoom, setZoom] = useState(1);
+  const ref = useRef();
+
+  const onZoomChange = useCallback((value) => {
+    setZoom(value);
+  })
+
+  const onSliderChange = useCallback((value) => {
+    setRotate(value);
+  }, []);
+
+  const onButtonClick = useCallback(() => {
+    const value = ref.current.getCropperCanvas();
+    const previewContainer = document.getElementById('previewContainer-control');
+    previewContainer.innerHTML = '';
+    previewContainer.appendChild(value);
+  }, []);
+
+  return (
+      <div id='cropper-container'>
+           <Cropper 
+              ref={ref} 
+              src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/image.png'}
+              style={containerStyle}
+              rotate={rotate}
+              zoom={zoom}
+              onZoomChange={onZoomChange}
+           />
+           <div style={actionStyle} >
+            <span>Rotate</span>
+            <Slider
+              style={{ width: 500}}
+              value={rotate}
+              step={1}
+              min={-360}
+              max={360}
+              onChange={onSliderChange}
+            />
+           </div>
+           <div style={actionStyle} >
+            <span>Zoom</span>
+            <Slider
+              style={{ width: 500}}
+              value={zoom}
+              step={0.1}
+              min={0.1}
+              max={3}
+              onChange={onZoomChange}
+            />
+           </div>
+           <br />
+           <Button onClick={onButtonClick}>裁切</Button>
+           <br />
+           <div 
+            // style={{ background: 'pink' }} 
+           >
+            <div id='previewContainer-control'
+            />
+          </div>
+      </div>
+  );
+};
+
+render(<Demo />)
+```
+
+### 裁切框设置
+
+可通过 `cropperBoxStyle`, `cropperBoxClassName` 自定义裁切框样式。可通过 `showResizeBox` 设置是否展示裁切框边角的调整块。
+
+```jsx live=true dir=column noInline=true
+import { Cropper, Button, Switch } from '@douyinfe/semi-ui';
+
+const containerStyle = {
+  width: 550,
+  height: 300,
+  margin: 20,
+}
+
+const centerStyle = {
+    display: 'flex', 
+    alignItems: 'center', 
+    justifyContent: 'center',
+    width: 'fit-content'
+}
+
+function Demo() {
+    const ref = useRef(null);
+
+    const onButtonClick = useCallback(() => {
+        const value = ref.current.getCropperCanvas();
+        const previewContainer = document.getElementById('previewContainer-cropperBox');
+        previewContainer.innerHTML = '';
+        previewContainer.appendChild(value);
+    }, []);
+
+    return <>
+        <strong>showResizeBox = false,并修改边框颜色</strong>
+        <Cropper
+            ref={ref} 
+            src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/other/image.png'}
+            style={containerStyle}
+            cropperBoxStyle={{ outlineColor: 'var(--semi-color-bg-0)'}}
+            showResizeBox={false}
+        />
+        <Button onClick={onButtonClick}>裁切</Button>
+        <div id='previewContainer-cropperBox'/>
+    </>;
+}
+
+render(<Demo />)
+```
+
+### API
+
+| 属性 | 说明 | 类型 | 默认值 |
+|-----|------|-----|------|
+| aspectRatio | 裁切框比例 | number | - |
+| className | 类名 | string | - |
+| cropperBoxClassName | 裁切框类名 | string | - |
+| cropperBoxStyle | 裁切框样式 | CSSProperties | - |
+| defaultAspectRatio | 初始裁切框比例 | number | 1 |
+| imgProps | 透传给 img 标签的属性 | object | - |
+| fill | 裁切结果中非图片部分的填充色 | string | 'rgba(0, 0, 0, 0)'  |
+| maxZoom | 最大缩放倍数 | number | 3 |
+| minZoom | 最小缩放倍数 | number | 0.1 |
+| onZoomChange | 缩放回调 | (zoom: number) => void | - |
+| rotate | 旋转角度 | number | - |
+| shape | 裁切框形状 | 'rect' \| 'round' \| 'roundRect' | 'rect' |
+| src | 图片地址 | string | - |
+| showResizeBox | 是否展示调整块 | boolean | true |
+| style | 样式  | CSSProperties | - |
+| zoom | 缩放比例 | number | - |
+| zoomStep | 缩放步长 | number | 0.1 |
+
+### Methods
+
+绑定在组件实例上的方法,可以通过 ref 调用实现某些特殊交互
+
+| Name    | Description  |
+|---------|--------------|
+| getCropperCanvas  | 获取裁剪图片的 canvas |
+
+## 设计变量
+
+<DesignToken/>

+ 1 - 0
content/show/dropdown/index-en-US.md

@@ -322,6 +322,7 @@ function DropdownEvents() {
 | children | Child elements wrapped by the drop layer                                                                                                                                                                                                      | ReactNode |  |  |
 | clickToHide | Whether to close the drop-down layer automatically when clicking on the drop-down layer                                                                                                                                                       | boolean |  | **0.24.0** |
 | contentClassName | Drop-down menu root element class name                                                                                                                                                                                                        | string |  |  |
+| disableFocusListener | When trigger is `hover`, does not respond to the keyboard focus popup event, see details at [issue#977](https://github.com/DouyinFE/semi-design/issues/977)                                                                                   | boolean | true | **2.17.0** |
 | keepDOM | Whether to keep the internal component DOM from being destroyed when closing | boolean | false | **2.31.0** |
 | getPopupContainer | Specifies the parent DOM, and the bullet layer will be rendered to the DOM, you need to set 'position: relative`  This will change the DOM tree position, but not the view's rendering position.                                                                                                                              | function():HTMLElement | () => document.body |
 | margin | Popup layer calculates the size of the safe area when the current direction overflows, used in scenes covered by fixed elements, more detail refer to [issue#549](https://github.com/DouyinFE/semi-design/issues/549), same as Tooltip margin | object\|number |  | 2.25.0 |

+ 1 - 0
content/show/dropdown/index.md

@@ -346,6 +346,7 @@ function DropdownEvents() {
 | children | 触发弹出层的 Trigger 元素 | ReactNode |  |  |
 | clickToHide | 在弹出层内点击时是否自动关闭弹出层 | boolean |  | |
 | contentClassName | 下拉菜单根元素类名 | string |  |  |
+| disableFocusListener | trigger为`hover`时,不响应键盘聚焦弹出浮层事件,详见[issue#977](https://github.com/DouyinFE/semi-design/issues/977) | boolean | false | **2.17.0** |
 | getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative` 这会改变浮层 DOM 树位置,但不会改变视图渲染位置。 | function():HTMLElement | () => document.body |  |
 | keepDOM | 关闭时是否保留内部组件 DOM 不销毁 | boolean | false | **2.31.0** |
 | margin | 弹出层计算溢出时的增加的冗余值,详见[issue#549](https://github.com/DouyinFE/semi-design/issues/549),作用同 Tooltip margin | number\|object |  | **2.25.0** |

+ 104 - 133
content/show/list/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 69
+order: 70
 category: Show
 title: List
 subTitle: List
@@ -755,165 +755,136 @@ render(VirtualizedScroll);
 ```
 
 ### Drag Sort
+You can integrate [dnd-kit](https://github.com/clauderic/dnd-kit/tree/master) to implement drag and drop sort.
 
-You can integrate [react-dnd](https://github.com/react-dnd/react-dnd) to implement drag and drop sort.
-
-```jsx live=true dir="column" noInline=true hideInDSM
-import React from 'react';
+```jsx live=true dir="column" hideInDSM
+import React, { useState } from 'react';
 import { List, Avatar } from '@douyinfe/semi-ui';
-import { DndProvider, DragSource, DropTarget } from 'react-dnd';
-import HTML5Backend from 'react-dnd-html5-backend';
-import ReactDOM from 'react-dom';
-
-class DraggableItem extends React.Component {
-    render() {
-        const { component, draggingItem, index, connectDragSource, connectDropTarget } = this.props;
-        const opacity = draggingItem && draggingItem.index === index ? 0.3 : 1;
-        const style = {
-            border: '1px solid var(--semi-color-border)',
-            marginBottom: 12,
-            backgroundColor: 'var(--semi-color-bg-2)',
-            cursor: 'move',
-        };
-
-        return connectDragSource(
-            connectDropTarget(
-                <div ref={node => (this.node = node)} style={{ ...style, opacity }}>
-                    {component}
-                </div>
-            )
-        );
-    }
-}
+import { DndContext, PointerSensor, MouseSensor, useSensors, useSensor } from '@dnd-kit/core';
+import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
+import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
+import { CSS as cssDndKit } from '@dnd-kit/utilities';
+import classNames from 'classnames';
 
-const cardSource = {
-    beginDrag(props) {
-        return {
-            id: props.id,
-            index: props.index,
-        };
-    },
-};
-
-const cardTarget = {
-    hover(props, monitor, component) {
-        const dragIndex = monitor.getItem().index;
-        const hoverIndex = props.index;
+() => {
+    const data = [
+        {
+            id: 1,  // 添加唯一id
+            title: 'Semi Design Title 1',
+            color: 'red',
+        },
+        {
+            id: 2,
+            title: 'Semi Design Title 2',
+            color: 'grey',
+        },
+        {
+            id: 3,
+            title: 'Semi Design Title 3',
+            color: 'light-green',
+        },
+        {
+            id: 4,
+            title: 'Semi Design Title 4',
+            color: 'light-blue',
+        },
+        {
+            id: 5,
+            title: 'Semi Design Title 5',
+            color: 'pink',
+        },
+    ];
+    const [listItems, setListItems] = useState(data);
 
-        if (dragIndex === hoverIndex) {
-            return;
-        }
-        const hoverBoundingRect = ReactDOM.findDOMNode(component).getBoundingClientRect();
-        const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
-        const clientOffset = monitor.getClientOffset();
-        const hoverClientY = clientOffset.y - hoverBoundingRect.top;
-        if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
-            return;
-        }
+    const sensors = useSensors(
+        useSensor(MouseSensor, {
+            activationConstraint: { distance: 1 },
+        })
+    );
 
-        if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
-            return;
+    const handleDragEnd = event => {
+        const { active, over } = event;
+        if (active.id !== over.id) {
+            setListItems((items) => {
+                const oldIndex = items.findIndex(item => item.id === active.id);
+                const newIndex = items.findIndex(item => item.id === over.id);
+                return arrayMove(items, oldIndex, newIndex);
+            });
         }
-
-        monitor.getItem().index = hoverIndex;
-        props.moveItem(dragIndex, hoverIndex);
-    },
-};
-
-function collectDragSource(connect, monitor) {
-    return {
-        connectDragSource: connect.dragSource(),
-        draggingItem: monitor.getItem(),
     };
-}
 
-function collectDropTarget(connect) {
-    return {
-        connectDropTarget: connect.dropTarget(),
-    };
-}
+    const ListItem = (props) => {
+        const { attributes, listeners, setNodeRef, transform, transition, isDragging, isOver } = useSortable({
+            id: props['id'],
+        });
 
-DraggableItem = DragSource('item', cardSource, collectDragSource)(DraggableItem);
-DraggableItem = DropTarget('item', cardTarget, collectDropTarget)(DraggableItem);
+        const styles = {
+            ...props.style,
+            transform: cssDndKit.Transform.toString(transform),
+            transition,
+            border: '1px solid var(--semi-color-border)',
+            marginBottom: 12,
+            cursor: 'grabbing',
+            ...(isDragging ? { zIndex: 999, position: 'relative', backgroundColor: 'var(--semi-color-bg-0)' } : {}),
 
-class DraggableList extends React.Component {
-    constructor() {
-        const listItems = [
-            {
-                title: 'Semi Design Title 1',
-                color: 'red',
-            },
-            {
-                title: 'Semi Design Title 2',
-                color: 'grey',
-            },
-            {
-                title: 'Semi Design Title 3',
-                color: 'light-green',
-            },
-            {
-                title: 'Semi Design Title 4',
-                color: 'light-blue',
-            },
-            {
-                title: 'Semi Design Title 5',
-                color: 'pink',
-            },
-        ];
-        super();
-        this.state = {
-            data: listItems,
         };
-        this.moveItem = this.moveItem.bind(this);
-        this.renderDraggable = this.renderDraggable.bind(this);
-    }
 
-    moveItem(dragIndex, hoverIndex) {
-        const { data } = this.state;
-        let temp = data[dragIndex];
-        data[dragIndex] = data[hoverIndex];
-        data[hoverIndex] = temp;
-        this.setState(
+        
+        const itemCls = classNames(
             {
-                ...this.state,
-                data
+                ['isDragging']: isDragging,
+                ['isOver']: isOver,
             }
         );
-    }
 
-    renderDraggable(item, id) {
-        const content = (
-            <List.Item
+        return (
+            <div
+                ref={setNodeRef} 
+                style={styles} 
+                className={itemCls}
+                {...listeners} 
+                {...attributes}
+            >
+                <List.Item {...props} ></List.Item>
+            </div>
+        );
+    };
+
+    const RenderDraggable = (item, id) => {
+        return (
+            <ListItem
+                id={id}
+                {...item}
                 header={<Avatar color={item.color}>SE</Avatar>}
                 main={
                     <div>
                         <span style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>{item.title}</span>
                         <p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>
-                            Create a consistent, good-looking, easy-to-use, and efficient user experience with a
-                            user-centric, content-first, and human-friendly design system
+                            Semi Design
+                            设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的
+                            Web 应用。
                         </p>
                     </div>
                 }
             />
         );
-        return (
-            <DraggableItem key={item.title} index={id} id={item.title} component={content} moveItem={this.moveItem} />
-        );
-    }
-
-    render() {
-        const { data } = this.state;
-        return (
-            <div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}>
-                <DndProvider backend={HTML5Backend}>
-                    <List dataSource={data} renderItem={this.renderDraggable} />
-                </DndProvider>
-            </div>
-        );
-    }
-}
+    };
 
-render(DraggableList);
+    return (
+        <div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}>
+            <DndContext
+                autoScroll={true}
+                sensors={sensors}
+                modifiers={[restrictToVerticalAxis]}
+                onDragEnd={handleDragEnd}
+            >
+                <SortableContext items={listItems.map(data => data.id)} strategy={verticalListSortingStrategy}>
+                    <List dataSource={listItems} renderItem={RenderDraggable} />
+                </SortableContext>
+            </DndContext>
+        </div>
+    );
+};
 ```
 
 ### With Pagination

+ 101 - 131
content/show/list/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 69
+order: 70
 category: 展示类
 title: List 列表
 icon: doc-list
@@ -757,135 +757,106 @@ render(VirtualizedScroll);
 ```
 
 ### 拖拽排序
+使用 [dnd-kit](https://github.com/clauderic/dnd-kit/tree/master) 可轻松实现拖拽排序。
 
-可以通过集成 [react-dnd](https://github.com/react-dnd/react-dnd) 来实现拖拽排序。
-
-```jsx live=true dir="column" noInline=true hideInDSM
-import React from 'react';
+```jsx live=true dir="column" hideInDSM
+import React, { useState } from 'react';
 import { List, Avatar } from '@douyinfe/semi-ui';
-import { DndProvider, DragSource, DropTarget } from 'react-dnd';
-import HTML5Backend from 'react-dnd-html5-backend';
-import ReactDOM from 'react-dom';
-
-class DraggableItem extends React.Component {
-    render() {
-        const { component, draggingItem, index, connectDragSource, connectDropTarget } = this.props;
-        const opacity = draggingItem && draggingItem.index === index ? 0.3 : 1;
-        const style = {
-            border: '1px solid var(--semi-color-border)',
-            marginBottom: 12,
-            backgroundColor: 'var(--semi-color-bg-2)',
-            cursor: 'move',
-        };
-
-        return connectDragSource(
-            connectDropTarget(
-                <div ref={node => (this.node = node)} style={{ ...style, opacity }}>
-                    {component}
-                </div>
-            )
-        );
-    }
-}
+import { DndContext, PointerSensor, MouseSensor, useSensors, useSensor } from '@dnd-kit/core';
+import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
+import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
+import { CSS as cssDndKit } from '@dnd-kit/utilities';
+import classNames from 'classnames';
 
-const cardSource = {
-    beginDrag(props) {
-        return {
-            id: props.id,
-            index: props.index,
-        };
-    },
-};
-
-const cardTarget = {
-    hover(props, monitor, component) {
-        const dragIndex = monitor.getItem().index;
-        const hoverIndex = props.index;
+() => {
+    const data = [
+        {
+            id: 1,  // 添加唯一id
+            title: 'Semi Design Title 1',
+            color: 'red',
+        },
+        {
+            id: 2,
+            title: 'Semi Design Title 2',
+            color: 'grey',
+        },
+        {
+            id: 3,
+            title: 'Semi Design Title 3',
+            color: 'light-green',
+        },
+        {
+            id: 4,
+            title: 'Semi Design Title 4',
+            color: 'light-blue',
+        },
+        {
+            id: 5,
+            title: 'Semi Design Title 5',
+            color: 'pink',
+        },
+    ];
+    const [listItems, setListItems] = useState(data);
 
-        if (dragIndex === hoverIndex) {
-            return;
-        }
-        const hoverBoundingRect = ReactDOM.findDOMNode(component).getBoundingClientRect();
-        const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
-        const clientOffset = monitor.getClientOffset();
-        const hoverClientY = clientOffset.y - hoverBoundingRect.top;
-        if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
-            return;
-        }
+    const sensors = useSensors(
+        useSensor(MouseSensor, {
+            activationConstraint: { distance: 1 },
+        })
+    );
 
-        if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
-            return;
+    const handleDragEnd = event => {
+        const { active, over } = event;
+        if (active.id !== over.id) {
+            setListItems((items) => {
+                const oldIndex = items.findIndex(item => item.id === active.id);
+                const newIndex = items.findIndex(item => item.id === over.id);
+                return arrayMove(items, oldIndex, newIndex);
+            });
         }
-
-        monitor.getItem().index = hoverIndex;
-        props.moveItem(dragIndex, hoverIndex);
-    },
-};
-
-function collectDragSource(connect, monitor) {
-    return {
-        connectDragSource: connect.dragSource(),
-        draggingItem: monitor.getItem(),
     };
-}
 
-function collectDropTarget(connect) {
-    return {
-        connectDropTarget: connect.dropTarget(),
-    };
-}
+    const ListItem = (props) => {
+        const { attributes, listeners, setNodeRef, transform, transition, isDragging, isOver } = useSortable({
+            id: props['id'],
+        });
 
-DraggableItem = DragSource('item', cardSource, collectDragSource)(DraggableItem);
-DraggableItem = DropTarget('item', cardTarget, collectDropTarget)(DraggableItem);
+        const styles = {
+            ...props.style,
+            transform: cssDndKit.Transform.toString(transform),
+            transition,
+            border: '1px solid var(--semi-color-border)',
+            marginBottom: 12,
+            cursor: 'grabbing',
+            ...(isDragging ? { zIndex: 999, position: 'relative', backgroundColor: 'var(--semi-color-bg-0)' } : {}),
 
-class DraggableList extends React.Component {
-    constructor() {
-        const listItems = [
-            {
-                title: 'Semi Design Title 1',
-                color: 'red',
-            },
-            {
-                title: 'Semi Design Title 2',
-                color: 'grey',
-            },
-            {
-                title: 'Semi Design Title 3',
-                color: 'light-green',
-            },
-            {
-                title: 'Semi Design Title 4',
-                color: 'light-blue',
-            },
-            {
-                title: 'Semi Design Title 5',
-                color: 'pink',
-            },
-        ];
-        super();
-        this.state = {
-            data: listItems,
         };
-        this.moveItem = this.moveItem.bind(this);
-        this.renderDraggable = this.renderDraggable.bind(this);
-    }
 
-    moveItem(dragIndex, hoverIndex) {
-        const { data } = this.state;
-        let temp = data[dragIndex];
-        data[dragIndex] = data[hoverIndex];
-        data[hoverIndex] = temp;
-        this.setState(
+        
+        const itemCls = classNames(
             {
-                ...this.state,
-                data
+                ['isDragging']: isDragging,
+                ['isOver']: isOver,
             }
         );
-    }
 
-    renderDraggable(item, id) {
-        const content = (
-            <List.Item
+        return (
+            <div
+                ref={setNodeRef} 
+                style={styles} 
+                className={itemCls}
+                {...listeners} 
+                {...attributes}
+            >
+                <List.Item {...props} ></List.Item>
+            </div>
+        );
+    };
+
+    const RenderDraggable = (item, id) => {
+        return (
+            <ListItem
+                id={id}
+                {...item}
                 header={<Avatar color={item.color}>SE</Avatar>}
                 main={
                     <div>
@@ -899,24 +870,23 @@ class DraggableList extends React.Component {
                 }
             />
         );
-        return (
-            <DraggableItem key={item.title} index={id} id={item.title} component={content} moveItem={this.moveItem} />
-        );
-    }
-
-    render() {
-        const { data } = this.state;
-        return (
-            <div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}>
-                <DndProvider backend={HTML5Backend}>
-                    <List dataSource={data} renderItem={this.renderDraggable} />
-                </DndProvider>
-            </div>
-        );
-    }
-}
+    };
 
-render(DraggableList);
+    return (
+        <div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}>
+            <DndContext
+                autoScroll={true}
+                sensors={sensors}
+                modifiers={[restrictToVerticalAxis]}
+                onDragEnd={handleDragEnd}
+            >
+                <SortableContext items={listItems.map(data => data.id)} strategy={verticalListSortingStrategy}>
+                    <List dataSource={listItems} renderItem={RenderDraggable} />
+                </SortableContext>
+            </DndContext>
+        </div>
+    );
+};
 ```
 
 

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 70
+order: 71
 category: Show
 title:  Modal
 subTitle: Modal

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 70
+order: 71
 category: 展示类
 title: Modal 模态对话框
 icon: doc-modal

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 71
+order: 72
 category: Show
 title: OverflowList
 subTitle: OverflowList

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 71
+order: 72
 category: 展示类
 title: OverflowList 折叠列表
 icon: doc-overflowList

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 72
+order: 73
 category: Show
 title: Popover
 subTitle: Popover

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 72
+order: 73
 category: 展示类
 title: Popover 气泡卡片
 icon: doc-popover

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 73
+order: 74
 category: Show
 title:  ScrollList
 subTitle: ScrollList

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 73
+order: 74
 category: 展示类
 title: ScrollList 滚动列表
 icon: doc-scrolllist

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 74
+order: 75
 category: Show
 title: SideSheet
 subTitle: SideSheet

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 74
+order: 75
 category: 展示类
 title: SideSheet 滑动侧边栏
 icon: doc-sidesheet

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 75
+order: 76
 category: Show
 title: Table
 subTitle: Table
@@ -5291,7 +5291,7 @@ render(App);
 | 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?: '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 |  |
+| onChange | Trigger when paging, sorting, filtering changes. extra.changeType is supported in v2.72                                                                           | ({ pagination: TablePaginationProps, <br/>filters: Array<\*>, sorter: object, extra: { changeType: 'sorter' \| 'filter' \| 'pagination' } }) => 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 |  |
 | onGroupedRow | Similar to onRow, but this parameter is used to define the row attribute of the grouping header alone                     | (record: RecordType, index: number) => object |  | **0.29.0** |

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 75
+order: 76
 category: 展示类
 title: Table 表格
 icon: doc-table
@@ -5414,7 +5414,7 @@ render(App);
 | virtualized | 虚拟化配置                                                               | Virtualized | false | **0.33.0** |
 | virtualized.itemSize | 每行的高度                                                               | number\|(index: number) => number | 56 | **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 |  |
+| onChange | 分页、排序、筛选变化时触发。extra.changeType 自 v2.72 支持。                                                       | ({ pagination: TablePaginationProps, <br/>filters: Array<\*>, sorter: object, extra: { changeType: 'sorter' \| 'filter' \| 'pagination' } }) => void |  |
 | onExpand | 点击行展开图标时进行触发                                                        | (expanded: boolean, record: RecordType, DOMEvent: MouseEvent) => void |  | 第三个参数 DOMEvent 需版本 **>=0.28.0** |
 | onExpandedRowsChange | 展开的行变化时触发                                                           | (rows: RecordType[]) => void |  |
 | onGroupedRow | 类似于 onRow,不过这个参数单独用于定义分组表头的行属性                                      | (record: RecordType, index: number) => object |  | **0.29.0** |

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 76
+order: 77
 category: Show
 title: Tag
 subTitle: Tag

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 76
+order: 77
 category: 展示类
 title: Tag 标签
 icon: doc-tag

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 77
+order: 78
 category: Show
 title:  Timeline
 subTitle: Timeline

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 77
+order: 78
 category: 展示类
 title: Timeline 时间轴
 icon: doc-timeline

+ 2 - 2
content/show/tooltip/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 78
+order: 79
 category: Show
 title: Tooltip
 subTitle: Tooltip
@@ -259,7 +259,7 @@ function Demo() {
 
 ### Override Style
 
-Configure specific styles for the pop-up layer through the `className` and `style` API, such as overriding the default maxWidth (280px)
+Configure specific styles for the pop-up layer through the `className` and `style` API, such as overriding the default maxWidth (240px)
 
 ```jsx live=true
 import React from 'react';

+ 4 - 4
content/show/tooltip/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 78
+order: 79
 category: 展示类
 title: Tooltip 工具提示
 icon: doc-tooltip
@@ -260,7 +260,7 @@ function Demo() {
 
 ### 覆盖特定样式
 
-你可以通过 className、style 为弹出层配置特定样式,例如覆盖默认的 maxWidth (280px)
+你可以通过 className、style 为弹出层配置特定样式,例如覆盖默认的 maxWidth (240px)
 ```jsx live=true
 import React from 'react';
 import { Tooltip, Tag } from '@douyinfe/semi-ui';
@@ -285,7 +285,7 @@ import { Tooltip, Tag } from '@douyinfe/semi-ui';
 
 传入 `getPopupContainer`,弹层将会渲染至该函数返回的 DOM 中。 这会改变浮层 DOM 树位置,但不会改变视图渲染位置。
 
-**需要注意的是:** 返回的容器如果不是 `document.body`,**`position` 需要设为 `"relative"`**(版本 >= 0.18.0)。
+**需要注意的是:** 返回的容器如果不是 `document.body`,**`position` 需要设为 `"relative"`**
 
 ```jsx live=true hideInDSM
 import React from 'react';
@@ -387,7 +387,7 @@ function Demo() {
 | style    | 弹出层的内联样式                                                                                                                                             | object |  |  |
 | spacing | 弹出层与 `children` 元素的距离,单位 px(object类型自 v2.45后支持)                                                                                                                     | number | <ApiType detail='{ x: number; y: number }'>SpacingObject</ApiType>  | 8 |  |
 | showArrow | 是否显示箭头三角形                                                                                                                                            | boolean | true |  |
-| stopPropagation | 是否阻止弹层上的点击事件冒泡                                                                                                                                       | boolean | false | **0.34.0** |
+| stopPropagation | 是否阻止弹层上的点击事件冒泡                                                                                                                                       | boolean | false |  |
 | transformFromCenter | 是否从包裹的元素水平或垂直中心处变换,该参数仅影响动效变换的 `transform-origin`,一般无需改动                                                                                             | boolean | true |  |
 | trigger | 触发展示的时机,可选值:`hover` / `focus` / `click` / `custom` / `contextMenu` (v2.42后提供)                                                                                                   | string | 'hover' |  |
 | visible | 是否展示弹出层                                                                                                                                              | boolean |  |  |

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

@@ -121,6 +121,108 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
     - Fixed the problem that  Form Label lost padding right(effect version v2.23.1) [#1258](https://github.com/DouyinFE/semi-design/pull/1258)
     - The Switch component Design Token is updated, adding `$spacing-switch_knob-left`; `$motion-switch_unchecked-translateX` is corrected to more semantically `$spacing-switch_unchecked-translateX` [#1267](https://github.com/DouyinFE/semi-design/pull/1267)
 
+#### 🎉 2.75.0 (2025-02-21)
+- 【Design Token】
+    - Select adds $color-select_prefix_suffix_text-default, Cascader adds $color-cascader_prefix_suffix_text-default, and TreeSelect adds $color-treeSelect_prefix_text-default to control the prefix and suffix colors. In addition, keep the font-size and font-weight settings of the prefix and suffix consistent with the settings of insetLabal (**Note: There are changes in styles before and after modification**)  [#2721](https://github.com/DouyinFE/semi-design/issues/2721)
+- 【Fix】
+    - Fixed the bug where the accessibility rendering of aria attributes in Chrome v133 caused Chrome to crash after clicking the month selector of the DatePicker [#2723](https://github.com/DouyinFE/semi-design/pull/2723)
+    - Fixed the issue that Resizable could not be used on touchscreens [#2697](https://github.com/DouyinFE/semi-design/issues/2697) [#2712](https://github.com/DouyinFE/semi-design/pull/2712)
+    - Removed the use of the outdated React syntax ReactDOM.render() in Typography and replaced it with other methods to clear the container used for testing the appropriate ellipsis length  [#2699](https://github.com/DouyinFE/semi-design/issues/2699)
+    - Fixed the problem that when using formApi.scrollToField in Form, if there are multiple Forms on the page and the fields have the same name, only the DOM of the first field with the same name can be scrolled to [#2719](https://github.com/DouyinFE/semi-design/pull/2719)
+    - Fixed the problem that when only extraText is configured in Form.InputGroup and extraPosition is not configured, extraText cannot be displayed correctly [#2719](https://github.com/DouyinFE/semi-design/pull/2719)
+- 【Chore】
+    - Fix the problem of the TypeScript type definition error of Form formApi.scrollToError.[#2719](https://github.com/DouyinFE/semi-design/pull/2719)
+
+
+#### 🎉 2.75.0-beta.1 (2025-02-19)
+- 【Docs】
+    - List component drag demo updated to use dnd-kit
+- 【Feat】
+    - add renderPicClose to custom close icon under listType picture
+    - Tree/TreeSelect supports expandIcon API for customizing the expand icon  [#2704](https://github.com/DouyinFE/semi-design/issues/2704)
+- 【Fix】
+    - Fixed the problem that Pagination's page capacity switcher cannot switch languages ​​correctly in multi-language scenarios  [#2696](https://github.com/DouyinFE/semi-design/issues/2696)
+    - fix iOS input interruption in PinCode component with format='number' (switches from number/character to letter keyboard after input a digit) [@SaltyfishEd](https://github.com/SaltyfishEd)
+
+
+#### 🎉 2.74.0 (2025-02-07)
+- 【Fix】
+    - Fix the issue where the List component's dataSource is empty and it is covered by the Spin component. [@LonelySnowman](https://github.com/LonelySnowman)
+    - Fix the TypeError when closing the panel of the TreeSelect component when search is enabled and treeData is undefined
+    - fixed the issue that the Steps icon and title of type basic were not centered with the line  [#2688 ](https://github.com/DouyinFE/semi-design/issues/2688)
+    - Fixed the problem that after the single-select and searchable Select loses focus when the panel is open, it cannot be focused again by clicking the trigger.
+    - Fixed the white edge issue of the AudioPlayer speed pop-up box style [@anjiazhuyouxing](https://github.com/anjiazhuyouxing)
+    - The internal ref usage of the AudioPlay component is modified to be compatible with other frameworks [@rashagu](https://github.com/rashagu)
+
+#### 🎉 2.74.0-beta.0 (2025-01-20)
+- 【Feat】
+  - The Chat component supports the markdownRenderProps API, which is used to set the MarkdownRender component for message rendering.  [#2640 ](https://github.com/DouyinFE/semi-design/issues/2640)
+- 【Fix】
+  - fixed the issue that the lowercase z input in JsonViewer is invalid [@anjiazhuyouxing](https://github.com/anjiazhuyouxing)
+
+#### 🎉 2.73.0 (2025-01-13)
+- 【Fix】
+    - Fix the problem that JsonViewer is not configured with default parameters [@anjiazhuyouxing](https://github.com/anjiazhuyouxing)
+    - Fix JsonViewer the judgment condition for whether to re-init. [@rashagu](https://github.com/rashagu)
+
+#### 🎉 2.73.0-beta.0 (2025-01-07)
+- 【New Component】
+    - Added audio player component [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2650](https://github.com/DouyinFE/semi-design/pull/2650)
+    - Added Cropper component [#2642](https://github.com/DouyinFE/semi-design/pull/2642)
+- 【Feat】
+    - Added read-only mode to JsonViewer [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - JsonViewer supports hidden search icon [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - JsonViewer adds Json format error information prompt function [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2638](https://github.com/DouyinFE/semi-design/pull/2638)
+    - JsonViewer Core package hot update problem [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2638](https://github.com/DouyinFE/semi-design/pull/2638)
+- 【Fix】
+    - Fix the cursor problem when clicking on the non-content area of ​​JsonViewer [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - Fix JsonViewer cursor problem after automatic indentation [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - Fix the missing content problem after JsonViewer folding [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - Fix the Chinese input method input problem in the JsonViewer search box [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2651](https://github.com/DouyinFE/semi-design/pull/2651)
+    - Fix the JsonViewer Undo&Redo text model out of sync problem [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2638](https://github.com/DouyinFE/semi-design/pull/2638)
+    - After setting the handler in DragMove, the child elements of DragMove can still be dragged [#2661](https://github.com/DouyinFE/semi-design/issues/2661) [#2662](https://github.com/DouyinFE/semi-design/pull/2662)
+    - Fixed the display problem of Loading when there is no Spin component in the project of Button [#2664](https://github.com/DouyinFE/semi-design/pull/2664)
+- 【Chore】
+    - JsonViewer refactors the underlying data structure of the folding model [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - Add JsonViewer E2E test [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2626](https://github.com/DouyinFE/semi-design/pull/2626)
+
+#### 🎉 2.72.2 (2025-01-06)
+- 【Fix】
+    - Fix the problem that Cascader's placeHolder and searchPlaceholder cannot be updated dynamically
+
+#### 🎉 2.72.1 (2025-01-02)
+- 【Fix】
+    - Fixed the problem of Typography’s JS omitting calculation error when display is none [#2656](https://github.com/DouyinFE/semi-design/pull/2656)
+
+#### 🎉 2.72.0 (2024-12-20)
+- 【Fix】
+  - Fix the problem of JsonViewer using Chinese input method incorrectly [#2616](https://github.com/DouyinFE/semi-design/pull/2616)
+  - Fix The problem that the code prompt box cannot be hidden when clicked [#2616](https://github.com/DouyinFE/semi-design/pull/2616)
+  - Fixed the rendering problem caused by multiple carriage returns [#2616](https://github.com/DouyinFE/semi-design/pull/2616)
+  - Fixed the abnormal display of the scroll bar [#2623](https://github.com/DouyinFE/semi-design/pull/2623)
+
+#### 🎉 2.72.0-beta.0 (2024-12-16)
+- 【Feat】
+    - Table onChange add extra.changeType API to idetify the type of changing  [#1238](https://github.com/DouyinFE/semi-design/issues/1238)
+- 【Fix】
+    - fix render state value in Carousel children always get the init one
+
+#### 🎉 2.71.3 (2024-12-17)
+- 【Fix】
+    - Fixed the problem of incorrect onChange callback result in Tree component treeDataSimpleJson mode  [#2508 ](https://github.com/DouyinFE/semi-design/issues/2508)
+    - fixed the issue that the display of disabled subNavItem in vertical Navigation does not meet expectations when it is collapsed
+
+#### 🎉 2.71.2 (2024-12-13)
+- 【Fix】
+    - Remove redundant calculations to determine whether the properties have changed when the Collapsible component is updated. [#2631](https://github.com/DouyinFE/semi-design/pull/2631)
+    - Fixed the issue where in react18, regardless of whether animation is on or not, when the mouse is quickly moved over the tooltip trigger, the DOM would become transparent but not disappear, causing the page element to be unclickable [#2605](https://github.com/DouyinFE/semi-design/pull/2605)
+    - Fixed the problem that the dynamic mode change of the Chat component did not take effect [#2625](https://github.com/DouyinFE/semi-design/pull/2625)
+    - Set the max-width of the img node of the image preview to none to avoid enlargement display errors when using tailwind at the same time. [#2624](https://github.com/DouyinFE/semi-design/pull/2624)
+
+#### 🎉 2.71.1 (2024-12-11)
+- 【Feat】
+    - Button icon mode add $height-button_iconOnly_small $width-button_iconOnly_small $height-button_iconOnly_default $width-button_iconOnly_default $height-button_iconOnly_large $width-button_iconOnly_large token [#2618](https://github.com/DouyinFE/semi-design/pull/2618)
+
 #### 🎉 2.71.0 (2024-12-06)
 - 【Fix】
     - For invisible Tooltips, position calculation is not performed when resizing [#2606](https://github.com/DouyinFE/semi-design/pull/2606)

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

@@ -14,6 +14,107 @@ Semi 版本号遵循 **Semver** 规范(主版本号-次版本号-修订版本
 -   不同版本间的详细关系,可查阅 [FAQ](/zh-CN/start/faq)
 
 
+#### 🎉 2.75.0 (2025-02-21)
+- 【Design Token】
+    - Select 新增 $color-select_prefix_suffix_text-default, Cascader 新增 $color-cascader_prefix_suffix_text-default, TreeSelect 新增 $color-treeSelect_prefix_text-default 用于控制控制前后缀颜色。另外,将前后缀的 font-size 和 font-weight 的设置和 insetLabal的设置保持统一(**注意:修改前后样式有变化**) [#2721](https://github.com/DouyinFE/semi-design/issues/2721)
+- 【Fix】
+    - 修复 Chrome v133 版本无障碍渲染 aria 属性 Bug 导致的点击 DatePicker 月份选择器后 Chrome 崩溃问题 [#2723](https://github.com/DouyinFE/semi-design/pull/2723)
+    - 修复 Resizable 无法在触摸屏使用问题 [#2697](https://github.com/DouyinFE/semi-design/issues/2697) [#2712](https://github.com/DouyinFE/semi-design/pull/2712)
+    - 去除 Typography 中过时 React 语法ReactDOM.render() 的使用,改为其他方式清空用于测试合适省略长度的容器  [#2699](https://github.com/DouyinFE/semi-design/issues/2699)
+    - 修复 Form 使用 formApi.scrollToField时,若页面存在多个 Form,且 Field 同名时,仅可滚动到首个同名 Field DOM 的问题 [#2719](https://github.com/DouyinFE/semi-design/pull/2719)
+    - 修复 Form.InputGroup 仅配置 extraText,未配置 extraPosition时,extraText未能正确显示的问题 [#2719](https://github.com/DouyinFE/semi-design/pull/2719)
+- 【Chore】
+    - 修复 Form formApi.scrollToError TS 类型定义错误的问题 [#2719](https://github.com/DouyinFE/semi-design/pull/2719)
+
+#### 🎉 2.75.0-beta.1 (2025-02-19)
+- 【Docs】
+    - List 组件拖拽 Demo 更新为用 dnd-kit 实现 [#2717](https://github.com/DouyinFE/semi-design/pull/2717)
+- 【Feat】
+    - Upload 在图片墙场景下添加 renderPicClose 用于自定义关闭图标 [#2714](https://github.com/DouyinFE/semi-design/pull/2714)
+    - Tree/TreeSelect 支持 expandIcon API 用于自定义展开图标  [#2704](https://github.com/DouyinFE/semi-design/issues/2704) 
+- 【Fix】
+    - 修复 Pagination 的页容量切换器在多语言场景无法切换语言问题  [#2696 ](https://github.com/DouyinFE/semi-design/issues/2696) [#2698](https://github.com/DouyinFE/semi-design/pull/2698)
+    - 修复 PinCode 组件 format='number' 情况下,iOS端输入被打断问题(输入一个数字后,自动从数字/字符键盘切换到字母键盘) [@SaltyfishEd](https://github.com/SaltyfishEd) [#2702](https://github.com/DouyinFE/semi-design/pull/2702)
+
+#### 🎉 2.74.0 (2025-02-07)
+- 【Fix】
+    - 修复 List 组件 dataSource 为空时被 Spin 组件遮挡问题 [@LonelySnowman](https://github.com/LonelySnowman) [#2693](https://github.com/DouyinFE/semi-design/pull/2693)
+    - 修复 TreeSelect 在开启搜索并且 treeData 为 undefined 时,关闭面板时候的 TypeError [#2694](https://github.com/DouyinFE/semi-design/pull/2694)
+    - 修复类型为 basic 的 Steps icon 和 title 未与 line 居中对齐问题  [#2688](https://github.com/DouyinFE/semi-design/issues/2688) [#2689](https://github.com/DouyinFE/semi-design/pull/2689)
+    - 修复单选,可搜索的 Select 在面板打开状态下失去焦点后,无法再次通过点击 trigger 聚焦问题 [#2668](https://github.com/DouyinFE/semi-design/pull/2668)
+    - 修复 AudioPlayer倍速弹出层样式白边问题 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2685](https://github.com/DouyinFE/semi-design/pull/2685)
+    - AudioPlay 组件内部 ref 使用修改,兼容其他框架[@rashagu](https://github.com/rashagu) [#2673](https://github.com/DouyinFE/semi-design/pull/2673)
+
+#### 🎉 2.74.0-beta.0 (2025-01-20)
+- 【Feat】
+  - Chat 组件支持 markdownRenderProps API,用于设置对话渲染的 MarkdownRender 组件  [#2640 ](https://github.com/DouyinFE/semi-design/issues/2640) [#2679](https://github.com/DouyinFE/semi-design/pull/2679)
+- 【Fix】
+  - 修复 JsonViewer 输入小写z无效的问题 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2680](https://github.com/DouyinFE/semi-design/pull/2680)
+
+#### 🎉 2.73.0 (2025-01-13)
+- 【Fix】
+    - 修复 JsonViewer 未配置默认参数问题 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2670](https://github.com/DouyinFE/semi-design/pull/2670)
+    - 修复 JsonViewer 是否重新init的判断条件 [@rashagu](https://github.com/rashagu) [#2667](https://github.com/DouyinFE/semi-design/pull/2667)
+
+#### 🎉 2.73.0-beta.0 (2025-01-07)
+- 【New Component】
+    - 新增 AudioPlayer 音频播放器组件 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2650](https://github.com/DouyinFE/semi-design/pull/2650)
+    - 新增 Cropper 图片裁切组件 [#2642](https://github.com/DouyinFE/semi-design/pull/2642)
+- 【Feat】
+    - JsonViewer 新增只读模式 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - JsonViewer 支持隐藏搜索Icon [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - JsonViewer 新增Json格式错误信息提示功能 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2638](https://github.com/DouyinFE/semi-design/pull/2638)
+    - JsonViewer Core 包热更新问题 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2638](https://github.com/DouyinFE/semi-design/pull/2638)
+- 【Fix】
+    - 修复 JsonViewer 点击非内容区域下光标问题 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - 修复 JsonViewer 自动缩进后光标问题 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - 修复 JsonViewer 折叠后复制内容缺失问题 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - 修复 JsonViewer 搜索框中文输入法输入问题 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2651](https://github.com/DouyinFE/semi-design/pull/2651)
+    - 修复 JsonViewer Undo&Redo 文本模型不同步问题 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2638](https://github.com/DouyinFE/semi-design/pull/2638)
+    - 修复 DragMove 中设置 handler 后,DragMove 的子元素仍然可以被拖动问题  [#2661 ](https://github.com/DouyinFE/semi-design/issues/2661) [#2662](https://github.com/DouyinFE/semi-design/pull/2662)
+    - 修复 Button 在项目内不存在 Spin 组件时 Loading 的显示问题 [#2664](https://github.com/DouyinFE/semi-design/pull/2664)
+- 【Chore】
+    - JsonViewer 重构折叠模型底层数据结构 [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2658](https://github.com/DouyinFE/semi-design/pull/2658)
+    - 新增 JsonViewer E2E 测试  [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2626](https://github.com/DouyinFE/semi-design/pull/2626)
+
+#### 🎉 2.72.2 (2025-01-06)
+- 【Fix】
+    - 修复 Cascader 的 placeHolder,searchPlaceholder 无法动态更新问题 [#2663](https://github.com/DouyinFE/semi-design/pull/2663)
+
+#### 🎉 2.72.1 (2025-01-02)
+- 【Fix】
+    - 修复在 display 为 none 时,Typography 的JS 省略计算错误问题 [#2656](https://github.com/DouyinFE/semi-design/pull/2656)
+
+#### 🎉 2.72.0 (2024-12-20)
+- 【Fix】
+  - 修复 JsonViewer使用中文输入法错误的问题 [#2616](https://github.com/DouyinFE/semi-design/pull/2616)
+  - 修复 代码提示框点击无法隐藏的问题 [#2616](https://github.com/DouyinFE/semi-design/pull/2616)
+  - 修复多次回车导致的渲染问题 [#2616](https://github.com/DouyinFE/semi-design/pull/2616)
+  - 修复滚动条显示异常的问题 [#2623](https://github.com/DouyinFE/semi-design/pull/2623)
+
+#### 🎉 2.72.0-beta.0 (2024-12-16)
+- 【Feat】
+    - Table onChange 新增 extra.changeType API,用于表示 change 类型  [#1238](https://github.com/DouyinFE/semi-design/issues/1238) [#2617](https://github.com/DouyinFE/semi-design/pull/2617)
+- 【Fix】
+    - 修复 Carousel 在 children 中渲染 state 的值不更新问题 [#2634](https://github.com/DouyinFE/semi-design/pull/2634)
+
+#### 🎉 2.71.3 (2024-12-17)
+- 【Fix】
+  - 修复 Tree 组件 treeDataSimpleJson 模式下,onChange 回调结果错误问题  [#2508 ](https://github.com/DouyinFE/semi-design/issues/2508) [#2601](https://github.com/DouyinFE/semi-design/pull/2601)
+  - 修复竖向 Navigation 在收起状态下 disabled subNavItem 展示不符合预期问题 [#2637](https://github.com/DouyinFE/semi-design/pull/2637)
+
+
+#### 🎉 2.71.2 (2024-12-13)
+- 【Fix】
+    - 去除 Collapsible 组件更新时多余的属性前后是否变化对比 [#2631](https://github.com/DouyinFE/semi-design/pull/2631)
+    - 修复在 React18 下无论是否动画开启,快速移动鼠标在 tooltip trigger 上时,概率性 dom 只透明但不消失,导致无法点击页面元素的问题 [#2605](https://github.com/DouyinFE/semi-design/pull/2605)
+    - 修复 Chat 组件 mode 动态变化未生效问题 [#2625](https://github.com/DouyinFE/semi-design/pull/2625)
+    - 设置图片预览的 img 节点的 max-width 为 none,避免同时使用 tailwind 时放大显示错误问题 [#2624](https://github.com/DouyinFE/semi-design/pull/2624)
+
+####  🎉 2.71.1 (2024-12-11)
+- 【Feat】
+    - 图标模式按钮新增 $height-button_iconOnly_small $width-button_iconOnly_small $height-button_iconOnly_default $width-button_iconOnly_default $height-button_iconOnly_large $width-button_iconOnly_large token [#2618](https://github.com/DouyinFE/semi-design/pull/2618)
+
 #### 🎉 2.71.0 (2024-12-06)
 - 【Fix】
     - 对于非展示状态的 Tooltip,页面尺寸变化时不做位置计算 [#2606](https://github.com/DouyinFE/semi-design/pull/2606) 

+ 3 - 1
content/start/overview/index-en-US.md

@@ -24,7 +24,8 @@ CodeHighlight,
 Markdown,
 Lottie,
 Chat,
-HotKeys
+HotKeys,
+DragMove
 ```
 
 
@@ -79,6 +80,7 @@ Descriptions,
 Dropdown,
 Empty,
 Image,
+Cropper,
 List,
 Modal,
 OverflowList,

+ 6 - 2
content/start/overview/index.md

@@ -24,8 +24,11 @@ Typography 版式
 CodeHighlight 代码高亮,
 Markdown 渲染器,
 Lottie 动画,
-Chat 聊天,
-HotKeys 快捷键
+Chat 对话,
+HotKeys 快捷键,
+DragMove 拖拽移动,
+JsonViewer Json编辑器,
+AudioPlayer 音频播放器
 ```
 
 ## 输入类
@@ -80,6 +83,7 @@ Dropdown 下拉框,
 Empty 空状态,
 Highlight 高亮文本,
 Image 图片,
+Cropper 图片裁切,
 List 列表,
 Modal 模态对话框,
 OverflowList 折叠列表,

+ 164 - 0
cypress/e2e/jsonViewer.spec.js

@@ -0,0 +1,164 @@
+
+function typeTextAtPosition(lineNumber, column, text) {
+    let rightArrow = '{rightArrow}';
+    let leftArrow = '{leftArrow}';
+    for (let i = 0; i < column - 1; i++) {
+        rightArrow += '{rightArrow}';
+    }
+    if (lineNumber === 1) {
+        cy.get('.lines-content').children().eq(0).type(`${leftArrow}${rightArrow}${text}`);
+    } else {
+        cy.get('.lines-content').children().eq(lineNumber - 2).type(`${rightArrow}${text}`);
+    }
+}
+
+function undo(times) {
+    let z = '{meta+z}';
+
+    for (let i = 0; i < times - 1; i++) {
+        z += '{meta+z}';
+    }
+    cy.get('.lines-content').type(z);
+}
+
+function redo(times) {
+    let z = '{meta+shift+z}';
+
+    for (let i = 0; i < times - 1; i++) {
+        z += '{meta+shift+z}';
+    }
+    cy.get('.lines-content').type(z);
+}
+
+describe('jsonViewer', () => {
+    it('scroll to bottom', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/jsonviewer--default-json-viewer');
+        cy.get('.json-viewer-container').scrollTo('bottom');
+        cy.get('.lines-content').children().last().should('have.attr', 'data-line-number', '36');
+    });
+
+    it('scroll to top', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/jsonviewer--default-json-viewer');
+        cy.get('.json-viewer-container').scrollTo('top');
+        cy.get('.lines-content').children().first().should('have.attr', 'data-line-number', '1');
+    });
+
+    it('fold', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/jsonviewer--default-json-viewer');
+        cy.get('.line-scroll-container').trigger('mouseover', { which: 1 });
+        cy.get('.semi-json-viewer-line-number[data-line-number="1"]').children().should('have.length', 2);
+        cy.get('.semi-json-viewer-line-number[data-line-number="1"]').children().eq(1).click();
+        cy.get('.lines-content').children().should('have.length', 2);
+        cy.get('.lines-content').children().last().should('have.attr', 'data-line-number', '36');
+
+        cy.get('.line-scroll-container').trigger('mouseover', { which: 1 });
+        cy.get('.semi-json-viewer-line-number[data-line-number="1"]').children().should('have.length', 2);
+        cy.get('.semi-json-viewer-line-number[data-line-number="1"]').children().eq(1).click();
+        cy.get('.lines-content').children().should('have.length', 21);
+
+        cy.get('.line-scroll-container').trigger('mouseover', { which: 1 });
+        cy.get('.semi-json-viewer-line-number[data-line-number="13"]').children().should('have.length', 2);
+        cy.get('.semi-json-viewer-line-number[data-line-number="13"]').children().eq(1).click();
+        cy.get('.lines-content').children().should('have.length', 15);
+
+        cy.get('.line-scroll-container').trigger('mouseover', { which: 1 });
+        cy.get('.semi-json-viewer-line-number[data-line-number="13"]').children().should('have.length', 2);
+        cy.get('.semi-json-viewer-line-number[data-line-number="13"]').children().eq(1).click();
+        cy.get('.lines-content').children().should('have.length', 21);
+    });
+
+    it('edit', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/jsonviewer--default-json-viewer');
+        //insert
+        typeTextAtPosition(1, 1, '{enter}');
+        cy.get('.lines-content').children().eq(1).children().should('have.length', 1);
+        typeTextAtPosition(2, 2, `"`);
+        typeTextAtPosition(2, 3, `key`);
+        typeTextAtPosition(2, 7, `:`);
+        typeTextAtPosition(2, 8, `1`);
+        typeTextAtPosition(2, 9, `,`);
+        cy.get('.lines-content').children().eq(1).children().should('have.length', 5);
+
+
+        // undo redo
+        undo(1);
+        cy.get('.lines-content').children().eq(1).children().should('have.length', 4);
+        redo(1);
+        cy.get('.lines-content').children().eq(1).children().should('have.length', 5);
+        undo(8);
+        cy.get('.lines-content').children().eq(1).children().should('have.length', 6);
+
+        //del
+        typeTextAtPosition(2, 1, `{backspace}`);
+        cy.get('.lines-content').children().eq(0).children().should('have.length', 7);
+        undo(1);
+        cy.get('.lines-content').children().eq(0).children().should('have.length', 1);
+        cy.get('.lines-content').children().eq(1).children().should('have.length', 6);
+
+        // cut
+        // typeTextAtPosition(2, 1, `{meta+x}`);
+        // cy.get('.lines-content').children().eq(1).children().should('have.length', 0);
+        // cy.get('.lines-content').type('{meta+z}');
+        // cy.get('.lines-content').children().eq(1).children().should('have.length', 6);
+
+        //complete
+        typeTextAtPosition(14, 4, '{enter}');
+        cy.get('.lines-content').children().eq(14).children().should('have.length', 1);
+        typeTextAtPosition(15, 4, `c`);
+        cy.get('.semi-json-viewer-complete-suggestions-container').children().should('have.length', 2);
+        cy.get('.lines-content').type('{enter}');
+        cy.get('.semi-json-viewer-complete-container').should('have.css', 'display', 'none');
+        cy.get('.lines-content').children().eq(14).children().should('have.length', 2);
+        typeTextAtPosition(15, 11, `:`);
+        cy.get('.semi-json-viewer-complete-container').should('have.css', 'display', 'block');
+        cy.get('.semi-json-viewer-complete-suggestions-container').children().should('have.length', 2);
+        cy.get('.lines-content').type('{enter}');
+        cy.get('.semi-json-viewer-complete-container').should('have.css', 'display', 'none');
+        typeTextAtPosition(15, 19, `,{enter}`);
+        cy.get('.lines-content').children().eq(14).children().should('have.length', 5);
+        typeTextAtPosition(16, 4, `a`);
+        cy.get('.semi-json-viewer-complete-suggestions-container').children().should('have.length', 2);
+        typeTextAtPosition(16, 5, `{rightArrow}`);
+        cy.get('.semi-json-viewer-complete-container').should('have.css', 'display', 'none');
+        typeTextAtPosition(16, 5, `g`);
+        cy.get('.semi-json-viewer-complete-suggestions-container').children().should('have.length', 2);
+        cy.get('.lines-content').type('{enter}');
+        cy.get('.semi-json-viewer-complete-container').should('have.css', 'display', 'none');
+        typeTextAtPosition(16, 9, `:`);
+        cy.get('.lines-content').type('{enter}');
+        cy.get('.semi-json-viewer-complete-container').should('have.css', 'display', 'none');
+        cy.get('.lines-content').children().eq(15).children().should('have.length', 4);
+
+        //search
+        cy.get('.semi-json-viewer-search-bar-trigger').click();
+        cy.get('.semi-json-viewer-search-bar').children().eq(0).type('a');
+        cy.get('.semi-json-viewer-search-result').should('have.length.at.least', 1);
+        cy.get('.semi-icon.semi-icon-default.semi-icon-whole_word').click();
+        cy.get('.semi-json-viewer-search-result').should('have.length', 0);
+        cy.get('.semi-icon.semi-icon-default.semi-icon-whole_word').click();
+        cy.get('.semi-json-viewer-search-bar').children().eq(0).clear();
+        cy.get('.semi-json-viewer-search-result').should('have.length', 0);
+        const str = '\\d+';
+        cy.get('.semi-json-viewer-search-bar').children().eq(0).type(str);
+        cy.get('.semi-icon.semi-icon-default.semi-icon-reg_exp').click();
+        cy.get('.semi-json-viewer-search-result').should('have.length.at.least', 1);
+        cy.get('.semi-icon.semi-icon-default.semi-icon-reg_exp').click();
+        cy.get('.semi-json-viewer-search-bar').children().eq(0).clear();
+
+        //replace
+        cy.scrollTo('right');
+        cy.get('.semi-json-viewer-search-bar').children().eq(0).type('a');
+        cy.get('.semi-json-viewer-replace-bar').children().eq(0).type('x');
+        cy.get('.semi-json-viewer-search-result').then(($el) => {
+            let length = $el.length;
+            cy.get('.semi-json-viewer-replace-bar').children().eq(1).click();
+            cy.get('.semi-json-viewer-search-result').should('have.length', length - 1);
+            cy.get('.semi-json-viewer-replace-bar').children().eq(2).click();
+            cy.get('.semi-json-viewer-search-result').should('have.length', 0);
+        });
+
+
+    });
+
+
+});

+ 4 - 2
cypress/e2e/scrollList.spec.js

@@ -6,7 +6,8 @@ describe('scrollList', () => {
         cy.get('.semi-scrolllist-item-sel').contains('5');
     });
 
-    it('infinite scroll', () => {
+    // todo: due to the https://github.com/DouyinFE/semi-design/pull/2723, temporarily skip this test case
+    it.skip('infinite scroll', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=scrolllist--single-scroll-list&args=&viewMode=story');
         cy.wait(500);
         cy.get('li[aria-selected="true"]').contains(0);
@@ -17,7 +18,8 @@ describe('scrollList', () => {
         cy.get('.semi-scrolllist-item-wheel .semi-scrolllist-list-outer').scrollTo('bottom', { duration: 2000 });
     });
 
-    it('click option', () => {
+    // todo: due to the https://github.com/DouyinFE/semi-design/pull/2723, temporarily skip this test case
+    it.skip('click option', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=scrolllist--single-scroll-list&args=&viewMode=story');
         cy.get('li[aria-selected="true"]').contains(0);
         cy.get('.semi-scrolllist-list-outer').contains(59).click();

+ 18 - 3
gatsby-node.js

@@ -112,7 +112,7 @@ exports.onCreateWebpackConfig = ({ stage, rules, loaders, plugins, actions }) =>
                 'semi-site-header': process.env.SEMI_SITE_HEADER || '@douyinfe/semi-site-header',
                 'semi-site-banner': process.env.SEMI_SITE_BANNER || '@douyinfe/semi-site-banner',
                 'univers-webview': process.env.SEMI_SITE_UNIVERS_WEBVIEW || resolve('packages/semi-ui'),
-                '@douyinfe/semi-json-viewer-core': resolve('packages/semi-json-viewer-core'),
+                '@douyinfe/semi-json-viewer-core': resolve('packages/semi-json-viewer-core/src'),
                 '@douyinfe/semi-ui': resolve('packages/semi-ui'),
                 '@douyinfe/semi-foundation': resolve('packages/semi-foundation'),
                 '@douyinfe/semi-icons': resolve('packages/semi-icons/src/'),
@@ -170,16 +170,31 @@ exports.onCreateWebpackConfig = ({ stage, rules, loaders, plugins, actions }) =>
                         },
                     },
                 },
+                {
+                    test: /jsonWorkerManager\.ts$/,
+                    use: [{
+                        loader: 'webpack-replace-loader',
+                        options: {
+                            search: '%WORKER_RAW%',
+                            replace: () => {
+                                const workFilePath = resolve('packages/semi-json-viewer-core/workerLib/worker.js');
+                                const result = fs.readFileSync(workFilePath, 'utf-8');
+                                const encodedResult = encodeURIComponent(result);
+                                return encodedResult;
+                            }
+                        }
+                    }],
+                },
                 {
                     test: [/\.tsx?$/],
                     include: [path.resolve(__dirname, 'src')],
-                    use: {
+                    use: [{
                         loader: 'esbuild-loader',
                         options: {
                             loader: 'tsx', // Remove this if you're not using JSX
                             target: 'esnext' // Syntax to compile to (see options below for possible values)
                         },
-                    },
+                    }],
                 },
                 {
                     test: /\.mjs$/,

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.71.1"
+    "version": "2.74.0"
 }

+ 1 - 0
package.json

@@ -214,6 +214,7 @@
         "webpack": "^5.77.0",
         "webpack-cli": "^5.1.4",
         "webpack-dev-server": "^3.11.2",
+        "webpack-replace-loader": "^5.0.1",
         "webpackbar": "^5.0.0-3",
         "worker-loader": "^3.0.8"
     },

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation-react",
-    "version": "2.71.1",
+    "version": "2.74.0",
     "description": "motion library for semi-ui-react",
     "keywords": [
         "motion",
@@ -25,8 +25,8 @@
         "prepublishOnly": "npm run build:lib"
     },
     "dependencies": {
-        "@douyinfe/semi-animation": "2.71.1",
-        "@douyinfe/semi-animation-styled": "2.71.1",
+        "@douyinfe/semi-animation": "2.74.0",
+        "@douyinfe/semi-animation-styled": "2.74.0",
         "classnames": "^2.2.6"
     },
     "devDependencies": {

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation-styled",
-    "version": "2.71.1",
+    "version": "2.74.0",
     "description": "semi styled animation",
     "keywords": [
         "semi",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation",
-    "version": "2.71.1",
+    "version": "2.74.0",
     "description": "animation base library for semi-ui",
     "keywords": [
         "animation",

+ 1 - 1
packages/semi-eslint-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "eslint-plugin-semi-design",
-    "version": "2.71.1",
+    "version": "2.74.0",
     "description": "semi ui eslint plugin",
     "keywords": [
         "semi",

+ 217 - 0
packages/semi-foundation/audioPlayer/audioPlayer.scss

@@ -0,0 +1,217 @@
+@import './variables.scss';
+
+$module: #{$prefix}-audio-player;
+
+.#{$module} {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: $gap-audio-player-large;
+    max-width: $width-audio-player-max;
+    height: $height-audio-player;
+    background: $color-audio-player-background;
+    &-control {
+        display: flex;
+        align-items: center;
+        gap: $gap-audio-player-medium;
+    }
+
+    &-control-button-icon {
+        color: $color-audio-player-control-icon;
+    }
+
+    &-control-button-play {
+        background: $color-audio-player-control-icon !important;
+        color: $color-audio-player-control-icon-play !important;
+    }
+
+    &-control-button-play-disabled {
+        background: $color-audio-player-disabled-bg !important;
+        color: $color-audio-player-disabled-text !important;
+    }
+
+    &-slider-container {
+        width: $width-audio-player-slider;
+        height: 100%;
+    }
+
+    &-info-container {
+        display: flex;
+        align-items: center;
+        gap: $gap-audio-player-medium;
+    }
+
+    &-info {
+        display: flex;
+        flex-direction: column;
+        gap: $gap-audio-player-small;
+    }
+
+    &-info-title {
+        font-size: $font-size-audio-player-text;
+        color: $color-audio-player-font-color;
+        font-weight: 600;
+        display: flex;
+        align-items: center;
+    }
+    &-info-time {
+        width: 100%;
+        height: $height-audio-player-time;
+        font-size: $font-size-audio-player-text;
+        color: $color-audio-player-font-color;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        gap: $gap-audio-player-small;
+        user-select: none;
+    }
+    &-control-speed {
+        width: $width-audio-player-speed;
+        height: $height-audio-player-speed;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: $gap-audio-player-small;
+        background: $color-audio-player-font-color-speed;
+        border-radius: $border-radius-audio-player-speed;
+        font-size: $font-size-audio-player-small;
+        line-height: $line-height-audio-player-small;
+        color: var(--semi-color-default);
+        font-weight: 600;
+        user-select: none;
+    }
+
+    &-control-speed-menu {
+        background: $color-audio-player-font-color-speed;
+        width: $width-audio-player-speed-menu;
+    }
+
+    &-control-speed-menu-item {
+        color: $color-audio-player-text-default;
+    }
+
+    &-control-speed-menu-item:hover {
+        background: var(--semi-color-tertiary-active) !important;
+    }
+
+    &-control-volume {
+        width: $width-audio-player-volume;
+        height: $height-audio-player-volume;
+        background: $color-audio-player-font-color-speed;
+        border-radius: $border-radius-audio-player-volume;
+        padding: 4px 0;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-direction: column;
+        gap: $gap-audio-player-small * 2;
+    }
+
+    &-control-volume-title {
+        font-size: $font-size-audio-player-small;
+        line-height: $line-height-audio-player-small;
+        color: $color-audio-player-text-default;
+        font-weight: 600;
+        user-select: none;
+    }
+
+    &-error {
+        display: flex;
+        align-items: center;
+        gap: $gap-audio-player-small;
+        margin-left: 4px;
+        color: var(--semi-color-danger);
+    }
+
+    &-light {
+        background: $color-audio-player-background-light;
+        border: 1px solid var(--semi-color-border);
+
+        .#{$module}-control-button-icon {
+            color: $color-audio-player-control-icon-light;
+        }
+
+        .#{$module}-control-button-play {
+            background: $color-audio-player-control-icon-light !important;
+            color: $color-audio-player-control-icon-play-light !important;
+        }
+
+        .#{$module}-control-button-play-disabled {
+            background: $color-audio-player-light-disabled-bg !important;
+            color: $color-audio-player-light-disabled-text !important;
+        }
+
+        .#{$module}-info-title,
+        .#{$module}-info-time {
+            color: $color-audio-player-font-color-light;
+        }
+
+        .#{$module}-control-speed-menu-item,
+        .#{$module}-control-volume-title {
+            color: $color-audio-player-light-text;
+        }
+
+        .#{$module}-control-speed-menu-item:hover {
+            background: $color-audio-player-light-hover-bg !important;
+        }
+    }
+}
+
+.#{$module}-slider {
+    background: $color-audio-player-slider-bg;
+    border-radius: $border-radius-audio-player-slider;
+    position: relative;
+    
+    &-light {
+        background: $color-audio-player-slider-bg-light;
+    }
+
+    &-wrapper {
+        position: relative;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        &-vertical {
+            width: 100%;
+        }
+        &-horizontal {
+            height: 100%;
+        }
+    }
+
+    &-vertical {
+        width: $width-audio-player-slider-bar;
+        height: 100%;
+    }
+
+    &-horizontal {
+        width: 100%;
+        height: $width-audio-player-slider-bar;
+    }
+
+    &-progress {
+        position: absolute;
+        background: $color-audio-player-slider-progress;
+        border-radius: $border-radius-audio-player-slider;
+    }
+
+    &-progress-vertical {
+        bottom: 0;
+    }
+
+    &-progress-horizontal {
+        left: 0;
+    }
+
+    &-dot {
+        position: absolute;
+        width: $size-audio-player-slider-dot;
+        height: $size-audio-player-slider-dot;
+        background: $color-audio-player-slider-dot-bg;
+        border: 1px solid var(--semi-color-primary);
+        box-shadow: 0px 0px 4px 0px var(--semi-color-shadow);
+        border-radius: 50%;
+        transition: opacity 0.2s;
+    }
+}

+ 7 - 0
packages/semi-foundation/audioPlayer/constants.ts

@@ -0,0 +1,7 @@
+import { BASE_CLASS_PREFIX } from '../base/constants';
+
+const cssClasses = {
+    PREFIX: `${BASE_CLASS_PREFIX}-audio-player`,
+};
+
+export { cssClasses };

+ 103 - 0
packages/semi-foundation/audioPlayer/foundation.ts

@@ -0,0 +1,103 @@
+
+
+import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+
+export interface AudioPlayerAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    init: () => void;
+    resetAudioState: () => void;
+    handleStatusClick: () => void;
+    handleTimeUpdate: () => void;
+    handleTrackChange: (direction: 'next' | 'prev') => void;
+    getAudioRef: () => HTMLAudioElement;
+    handleTimeChange: (value: number) => void;
+    handleSpeedChange: (value: { label: string; value: number }) => void;
+    handleSeek: (direction: number) => void;
+    handleRefresh: () => void;
+    handleVolumeChange: (value: number) => void;
+    destroy: () => void
+}
+
+class AudioPlayerFoundation extends BaseFoundation<AudioPlayerAdapter> {
+    constructor(adapter: AudioPlayerAdapter) {
+        super({ ...AudioPlayerFoundation, ...adapter });
+    }
+
+    initAudioState() {
+        const audioElement = this.getAudioRef();
+        const props = this.getProps();
+
+        this.setState({
+            totalTime: audioElement?.duration || 0,
+            isPlaying: props.autoPlay,
+            volume: audioElement?.volume * 100 || 100,
+            currentRate: { label: '1.0x', value: audioElement?.playbackRate || 1 },
+        });
+    }
+
+    endHandler() {
+        const props = this.getProps();
+        if (Array.isArray(props.audioUrl)) {
+            this.handleTrackChange('next');
+        } else {
+            this.setState({
+                isPlaying: false,
+            });
+        }
+    }
+
+    errorHandler() {
+        this.setState({
+            error: true,
+        });
+    }
+
+    init() {
+        this._adapter.init();
+    }
+
+    destroy() {
+        this._adapter.destroy();
+    }
+
+    resetAudioState() {
+        this._adapter.resetAudioState();
+    }
+
+    handleStatusClick() {
+        this._adapter.handleStatusClick();
+    }
+
+    handleTimeUpdate() {
+        this._adapter.handleTimeUpdate();
+    }
+
+    handleTrackChange(direction: 'next' | 'prev') {
+        this._adapter.handleTrackChange(direction);
+    }
+
+    getAudioRef() {
+        return this._adapter.getAudioRef();
+    }
+
+    handleTimeChange(value: number) {
+        this._adapter.handleTimeChange(value);
+    }
+
+    handleSpeedChange(value: { label: string; value: number }) {
+        this._adapter.handleSpeedChange(value);
+    }
+
+    handleSeek(direction: number) {
+        this._adapter.handleSeek(direction);
+    }
+
+    handleRefresh() {
+        this._adapter.handleRefresh();
+    }
+
+    handleVolumeChange(value: number) {
+        this._adapter.handleVolumeChange(value);
+    }
+}
+
+export default AudioPlayerFoundation;

+ 55 - 0
packages/semi-foundation/audioPlayer/variables.scss

@@ -0,0 +1,55 @@
+$color-audio-player-background: rgba(var(--semi-grey-9), .8);
+$color-audio-player-control-icon: var(--semi-color-bg-0);
+$color-audio-player-control-icon-play: var(--semi-color-text-0);
+$color-audio-player-font-color: var(--semi-color-bg-0);
+$color-audio-player-font-color-speed: rgba(var(--semi-grey-8), 1);
+
+
+$color-audio-player-background-light: var(--semi-color-bg-0);
+$color-audio-player-control-icon-light: rgba(var(--semi-grey-9), 1);
+$color-audio-player-control-icon-play-light: var(--semi-color-bg-0);
+$color-audio-player-font-color-light: rgba(var(--semi-grey-9), 1);
+
+$font-size-audio-player-text: 14px;
+
+$gap-audio-player-small: 4px;
+$gap-audio-player-medium: 16px;
+$gap-audio-player-large: 24px;
+
+// Size variables
+$width-audio-player-max: 1440px;
+$height-audio-player: 78px;
+$width-audio-player-slider: 323px;
+$width-audio-player-speed: 40px;
+$height-audio-player-speed: 24px;
+$width-audio-player-speed-menu: 65px;
+$width-audio-player-volume: 43px;
+$height-audio-player-volume: 164px;
+$height-audio-player-time: 22px;
+
+// Border radius
+$border-radius-audio-player-speed: 3px;
+$border-radius-audio-player-volume: 4px;
+$border-radius-audio-player-slider: 9999px;
+
+// Font sizes
+$font-size-audio-player-small: 12px;
+$line-height-audio-player-small: 16px;
+
+// Slider dimensions
+$width-audio-player-slider-bar: 4px;
+$size-audio-player-slider-dot: 16px;
+
+// Colors
+$color-audio-player-disabled-bg: rgba(var(--semi-grey-0), .35);
+$color-audio-player-slider-bg: rgba(var(--semi-grey-5), 1);
+$color-audio-player-slider-bg-light: rgba(var(--semi-grey-2), 1);
+$color-audio-player-slider-progress: rgba(var(--semi-blue-4), 1);
+$color-audio-player-slider-dot-bg: rgba(var(--semi-white), 1);
+
+$color-audio-player-disabled-text: var(--semi-color-grey-7);
+$color-audio-player-text-default: var(--semi-color-default);
+$color-audio-player-light-disabled-bg: var(--semi-color-disabled-text);
+$color-audio-player-light-disabled-text: rgba(var(--semi-white), 1);
+$color-audio-player-light-text: rgba(var(--semi-grey-9), 1);
+$color-audio-player-light-hover-bg: rgba(var(--semi-grey-1), 1);

+ 8 - 0
packages/semi-foundation/button/iconButton.scss

@@ -4,6 +4,14 @@
 $module: #{$prefix}-button;
 
 .#{$module} {
+    @keyframes #{$prefix}-animation-rotate {
+        from {
+            transform: rotate(0);
+        }
+        to {
+            transform: rotate(360deg);
+        }
+    }
     &.#{$module}-with-icon {
         display: inline-flex;
         align-items: center;

+ 4 - 3
packages/semi-foundation/carousel/foundation.ts

@@ -7,7 +7,8 @@ export interface CarouselAdapter<P = Record<string, any>, S = Record<string, any
     setNewActiveIndex: (activeIndex: number) => void;
     setPreActiveIndex: (activeIndex: number) => void;
     setIsReverse: (isReverse: boolean) => void;   
-    setIsInit: (isInit: boolean) => void   
+    setIsInit: (isInit: boolean) => void;
+    getChildren: () => any[]  
 }
 
 class CarouselFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<CarouselAdapter<P, S>, P, S> {
@@ -102,7 +103,7 @@ class CarouselFoundation<P = Record<string, any>, S = Record<string, any>> exten
     }
 
     getValidIndex(index: number): number {
-        const { children } = this.getStates();
+        const children = this._adapter.getChildren();
         return (index + children.length) % children.length;
     }
 
@@ -124,7 +125,7 @@ class CarouselFoundation<P = Record<string, any>, S = Record<string, any>> exten
 
     handleAutoPlay(): void { 
         const { autoPlay } = this.getProps(); 
-        const { children } = this.getStates();
+        const children = this._adapter.getChildren();
         const autoPlayType = typeof autoPlay;
         // when user manually call the play function, force play
         // only when carousel children length > 1 to start play

+ 3 - 0
packages/semi-foundation/cascader/cascader.scss

@@ -246,6 +246,9 @@ $module: #{$prefix}-cascader;
 
         &-text {
             margin: 0 $spacing-cascader_text-marginX;
+            font-weight: $font-weight-bold;
+            @include font-size-regular;
+            color: $color-cascader_prefix_suffix_text-default;
         }
 
         &-icon {

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

@@ -43,6 +43,8 @@ $color-cascader_danger-border-focus: var(--semi-color-danger); // 级联选择
 $color-cascader_danger-bg-active: var(--semi-color-danger-light-active); // 级联选择触发器危险背景颜色 - 按下
 $color-cascader_danger-border-active: var(--semi-color-danger-light-active); // 级联选择触发器危险描边颜色 - 按下
 
+$color-cascader_prefix_suffix_text-default: var(--semi-color-text-2); // 级联选择前后缀文字颜色
+
 $spacing-cascader_selection-paddingLeft: 12px; // 级联选择触发器左侧内边距
 $spacing-cascader_selection-paddingRight: 12px; // 级联选择触发器右侧内边距
 $spacing-cascader_selection_multiple-paddingLeft: $spacing-extra-tight; // 级联选择触发器多选时左侧内边距

+ 26 - 0
packages/semi-foundation/cropper/constants.ts

@@ -0,0 +1,26 @@
+import { BASE_CLASS_PREFIX } from '../base/constants';
+
+const moduleName = `${BASE_CLASS_PREFIX}-cropper`;
+
+const cssClasses = {
+    PREFIX: `${moduleName}`,
+    IMG: `${moduleName}-img`,
+    IMG_WRAPPER: `${moduleName}-img-wrapper`,
+    CROPPER_BOX: `${moduleName}-box`,
+    CROPPER_VIEW_BOX: `${moduleName}-view-box`,
+    CROPPER_VIEW_BOX_ROUND: `${moduleName}-view-box-round`,
+    CROPPER_IMG: `${moduleName}-view-img`,
+    MASK: `${moduleName}-mask`,
+    CORNER: `${moduleName}-box-corner`,
+};
+
+const strings = {
+    shape: ['rect', 'round', 'roundRect'],
+    corner: ['tl', 'tm', 'tr',
+        'ml', 'mr',
+        'bl', 'bm', 'br'],
+    roundCorner: ['tm', 'ml', 'mr', 'bm'],
+
+};
+
+export { cssClasses, strings };

+ 116 - 0
packages/semi-foundation/cropper/cropper.scss

@@ -0,0 +1,116 @@
+@import "./variables.scss";
+$module: #{$prefix}-cropper;
+
+$half_corner_width: calc($width-cropper_box_corner / 2);
+
+.#{$module} {
+    position: relative;
+
+    &-img {
+      position: absolute;
+      user-select: none;
+    }
+
+    &-img-wrapper {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      overflow: hidden;
+    }
+
+    &-mask {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background: $color-cropper_mask-bg;
+      cursor: move;;
+    }
+
+    &-box {
+      position: absolute;
+      outline: $width-cropper_box-outline solid $color-cropper_box-outline;
+
+      &-corner {
+        position: absolute;
+        background: $color-cropper_box_corner-bg;
+        width: $width-cropper_box_corner;
+        height: $width-cropper_box_corner;
+        z-index: 1;
+
+        &-tl {
+          top: -$half_corner_width;
+          left: -$half_corner_width;
+          cursor: nwse-resize;
+        }
+
+        &-tr {
+          top: -$half_corner_width;
+          right: -$half_corner_width;
+          cursor: nesw-resize;
+        }
+
+        &-tm {
+          top: -$half_corner_width;
+          left: 50%;
+          margin-left: -$half_corner_width;
+          cursor: ns-resize;
+        }
+
+        &-ml {
+          top: 50%;
+          left: -$half_corner_width;
+          margin-top: -$half_corner_width;
+          cursor: ew-resize;
+        }
+
+        &-mr {
+          top: 50%;
+          right: -$half_corner_width;
+          margin-top: -$half_corner_width;
+          cursor: ew-resize;
+        }
+
+        &-bl {
+          bottom: -$half_corner_width;
+          left: -$half_corner_width;
+          cursor: nesw-resize;
+        }
+
+        &-bm {
+          bottom: -$half_corner_width;
+          left: 50%;
+          margin-left: -$half_corner_width; 
+          cursor: ns-resize;
+        }
+
+        &-br {
+          bottom: -$half_corner_width;
+          right: -$half_corner_width;
+          cursor: nwse-resize
+        }
+      }
+    }
+
+    &-view-box {
+      position: absolute;
+      overflow: hidden;
+      cursor: move;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+
+      &-round {
+        border-radius: 50%;
+      }
+    }
+
+    &-view-img {
+      // position: absolute;
+      user-select: none;
+    }
+}

+ 821 - 0
packages/semi-foundation/cropper/foundation.ts

@@ -0,0 +1,821 @@
+import BaseFoundation, { DefaultAdapter } from "../base/foundation";
+import { getMiddle, getAspectHW } from "./utils";
+
+export interface CropperAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    getContainer: () => HTMLElement;
+    notifyZoomChange: (zoom: number) => void;
+    getImg: () => HTMLImageElement
+}
+
+interface Point {
+    x: number;
+    y: number
+}
+
+export interface ImageData {
+    originalWidth: number;
+    originalHeight: number;
+    scale: number
+}
+
+export interface ImageDataState {
+    width: number;
+    height: number;
+    centerPoint: Point
+}
+
+export interface CropperBox {
+    width: number;
+    height: number;
+    centerPoint: Point
+}
+
+export interface ContainerData {
+    width: number;
+    height: number
+}
+
+export interface CropperBoxBorder {
+    borderTop: number;
+    borderLeft: number
+}
+
+export default class CropperFoundation <P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<CropperAdapter<P, S>, P, S> {
+    imgData: ImageData;
+    containerData: ContainerData;
+    boxMoveDir: string;
+    cropperBoxMoveStart: Point;
+    imgMoveStart: Point;
+    moveRange: {
+        xMax: number;
+        xMin: number;
+        yMax: number;
+        yMin: number
+    };
+    boxMoveParam: {
+        paramX: number;
+        paramY: number
+    }
+    cropperBox: CropperBoxBorder;
+    rangeX: [number, number];
+    rangeY: [number, number];
+    initial: boolean;
+    
+    constructor(adapter: CropperAdapter<P, S>) {
+        super({ ...adapter });
+
+        this.containerData = {} as ContainerData;
+        this.imgData = {} as ImageData;
+        this.boxMoveDir = '';
+        this.boxMoveParam = {
+            paramX: 0,
+            paramY: 0,
+        };
+        this.rangeX = null;
+        this.rangeY = null;
+        this.initial = false;
+    }
+
+    init() {
+        // 获取容器的宽高
+        // get cropping Container 's width & height
+        const container = this._adapter.getContainer();
+        this.containerData.width = container.clientWidth;
+        this.containerData.height = container.clientHeight;
+        this.cropperBoxMoveStart = null;
+    }
+
+    destroy() {
+        this.unBindMoveEvent();
+        this.unBindResizeEvent();
+    }
+
+    getImgDataWhenResize = (ratio: number) => {
+        const { imgData } = this.getStates();
+        const newImgData = {
+            width: imgData.width * ratio,
+            height: imgData.height * ratio,
+            centerPoint: {
+                x: imgData.centerPoint.x * ratio,
+                y: imgData.centerPoint.y * ratio,
+            }
+        };
+        this.imgData.scale *= ratio;
+        return newImgData;
+    }
+
+    getCropperBoxWhenResize = (ratio: number, newContainerData: ContainerData) => {
+        const { cropperBox } = this.getStates();
+        const { aspectRatio } = this.getProps();
+        const tempCropperBox = {
+            width: cropperBox.width * ratio,
+            height: cropperBox.height * ratio,
+            centerPoint: {
+                x: cropperBox.centerPoint.x * ratio,
+                y: cropperBox.centerPoint.y * ratio,
+            }
+        };
+        let xMin = tempCropperBox.centerPoint.x - tempCropperBox.width / 2;
+        let xMax = tempCropperBox.centerPoint.x + tempCropperBox.width / 2;
+        let yMin = tempCropperBox.centerPoint.y - tempCropperBox.height / 2;
+        let yMax = tempCropperBox.centerPoint.y + tempCropperBox.height / 2;
+        if (aspectRatio) {
+            if (xMax > newContainerData.width) {
+                xMax = newContainerData.width;
+                xMin = tempCropperBox.width > newContainerData.width ? 
+                    0 : newContainerData.width - tempCropperBox.width;
+                tempCropperBox.width = xMax - xMin;
+                tempCropperBox.height = tempCropperBox.width / aspectRatio;
+                yMax = yMin + tempCropperBox.height;
+            }
+            if (yMax > newContainerData.height) {
+                yMax = newContainerData.height;
+                yMin = tempCropperBox.height > newContainerData.height ? 
+                    0 : newContainerData.height - tempCropperBox.height;
+                tempCropperBox.height = yMax - yMin;
+                tempCropperBox.width = tempCropperBox.height * aspectRatio;
+                xMax = xMin + tempCropperBox.width;
+            }
+        } else {
+            if (xMax > newContainerData.width) {
+                xMax = newContainerData.width;
+                xMin = tempCropperBox.width > newContainerData.width ? 
+                    0 : newContainerData.width - tempCropperBox.width;
+            }
+            if (yMax > newContainerData.height) {
+                yMax = newContainerData.height;
+                yMin = tempCropperBox.height > newContainerData.height ? 
+                    0 : newContainerData.height - tempCropperBox.height;
+            }
+        }
+        return {
+            width: xMax - xMin,
+            height: yMax - yMin,
+            centerPoint: {
+                x: (xMax + xMin) / 2,
+                y: (yMax + yMin) / 2,
+            }
+        };
+    }
+
+
+    handleResize = () => {
+        const { loaded } = this.getStates();
+        if (!this.initial) {
+            this.initial = true;
+            return;
+        }
+        if (!loaded) {
+            return;
+        }
+        const container = this._adapter.getContainer();
+        const newContainerData = {
+            width: container.clientWidth,
+            height: container.clientHeight,
+        };
+        const ratio = newContainerData.width / this.containerData.width;
+        const newImgData = this.getImgDataWhenResize(ratio);
+        const newCropperBox = this.getCropperBoxWhenResize(ratio, newContainerData);
+       
+        this.containerData = newContainerData;
+        this.setState({
+            imgData: newImgData,
+            cropperBox: newCropperBox,
+        } as any);
+    }
+
+    handleImageLoad = (e: any) => {
+        /**
+         * 1. 图片加载完成后,获得图片的原始大小
+         * 2. 计算图片的缩放比例,中心点位置
+         */
+        const { naturalWidth, naturalHeight } = e.target;
+        const { width: containerWidth, height: containerHeight } = this.containerData;
+        this.imgData.originalWidth = naturalWidth;
+        this.imgData.originalHeight = naturalHeight;
+        let scale = 1;
+        const newImgDataState = {} as ImageDataState;
+        /* 计算图片加载后的初始显示尺寸 */
+        if (naturalWidth / containerWidth > naturalHeight / containerHeight) {
+            scale = containerWidth / naturalWidth;
+            newImgDataState.width = containerWidth;
+            newImgDataState.height = naturalHeight * scale; 
+        } else {
+            scale = containerHeight / naturalHeight;
+            newImgDataState.width = naturalWidth * scale;
+            newImgDataState.height = containerHeight;
+        }
+        this.imgData.scale = scale;
+        newImgDataState.centerPoint = {} as Point;
+        newImgDataState.centerPoint.x = containerWidth / 2;
+        newImgDataState.centerPoint.y = containerHeight / 2;
+        /* 计算裁切框大小 */
+        const newCropperBoxState = {} as CropperBox;
+        const { defaultAspectRatio, aspectRatio } = this.getProps();
+        const calcAspect = aspectRatio || defaultAspectRatio;
+        if (containerWidth / containerHeight > calcAspect) {
+            newCropperBoxState.width = containerHeight * calcAspect;
+            newCropperBoxState.height = containerHeight;
+        } else {
+            newCropperBoxState.width = containerWidth;
+            newCropperBoxState.height = containerWidth / calcAspect;
+        }
+        newCropperBoxState.centerPoint = {} as Point;
+        newCropperBoxState.centerPoint.x = containerWidth / 2;
+        newCropperBoxState.centerPoint.y = containerHeight / 2;
+        this.setState({
+            imgData: newImgDataState,
+            cropperBox: newCropperBoxState,
+            loaded: true,
+        } as any);
+    }
+
+    handleWheel = (e: any) => {
+        // 防止双手缩放导致页面被放大
+        e.preventDefault();
+        const { imgData, zoom: currZoom } = this.getStates();
+        const { maxZoom, minZoom, zoomStep } = this.getProps();
+
+        let _zoom: number;
+        if (e.deltaY < 0) {
+            /* zoom in */
+            if (currZoom + zoomStep <= maxZoom) {
+                _zoom = Number((currZoom + zoomStep).toFixed(2));
+            }
+        } else if (e.deltaY > 0) {
+            /* zoom out */
+            if (currZoom - zoomStep >= minZoom) {
+                _zoom = Number((currZoom - zoomStep).toFixed(2));
+            }
+        }
+        if (_zoom === undefined) {
+            return;
+        }
+        const boundingRect = e.currentTarget.getBoundingClientRect();
+        const offsetX = e.clientX - boundingRect.left;
+        const offsetY = e.clientY - boundingRect.top;
+        const scaleCenter = {
+            x: offsetX,
+            y: - offsetY,
+        };
+        
+        // 计算新的中心点位置
+        const currentPoint = { ...imgData.centerPoint } as Point;
+        currentPoint.y = - currentPoint.y;
+
+        const newCenterPoint = {
+            x: (currentPoint.x - scaleCenter.x) / currZoom * _zoom + scaleCenter.x,
+            y: - [(currentPoint.y - scaleCenter.y) / currZoom * _zoom + scaleCenter.y],
+        };
+
+        const newWidth = imgData.width / currZoom * _zoom;
+        const newHeight = imgData.height / currZoom * _zoom;
+       
+        const newImgDataState = {
+            width: newWidth,
+            height: newHeight,
+            centerPoint: newCenterPoint
+        };
+        this.setState({
+            imgData: newImgDataState,
+            zoom: _zoom
+        } as any);
+
+        this._adapter.notifyZoomChange(_zoom);
+    }
+
+    getMoveParamByDir(dir: string) {
+        let paramX = 0, paramY = 0;
+        switch (dir) {
+            case 'tl':
+                paramX = -1; paramY = -1; break;
+            case 'tm':
+                paramY = -1; break;
+            case 'tr':
+                paramX = 1; paramY = -1; break;
+            case 'ml':
+                paramX = -1; break;
+            case 'mr':
+                paramX = 1; break;
+            case 'bl':
+                paramX = -1; paramY = 1; break;
+            case 'bm':
+                paramY = 1; break;
+            case 'br':
+                paramX = 1; paramY = 1; break;
+            default:
+                break; 
+        }
+        return {
+            paramX,
+            paramY
+        };
+    }
+
+    getRangeForAspectChange = () => {
+        const { cropperBox } = this.getStates();
+        const { aspectRatio } = this.getProps();
+        const { width: containerWidth, height: containerHeight } = this.containerData;
+        // 可能的最大宽高
+        let height: number, width: number;
+        // 裁剪框当前的位置
+        const xMin = cropperBox.centerPoint.x - cropperBox.width / 2;
+        const xMax = cropperBox.centerPoint.x + cropperBox.width / 2;
+        const yMin = cropperBox.centerPoint.y - cropperBox.height / 2;
+        const yMax = cropperBox.centerPoint.y + cropperBox.height / 2;
+        switch (this.boxMoveDir) {
+            case 'tl':
+                height = yMax;
+                width = xMax;
+                [width, height] = getAspectHW(width, height, aspectRatio);
+                this.rangeX = [xMax - width, xMax];
+                this.rangeY = [yMax - height, yMax];
+                break;
+            case 'tm':
+                height = yMax;
+                const leftHalfWidth = cropperBox.centerPoint.x;
+                const rightHalfWidth = containerWidth - cropperBox.centerPoint.x;
+                width = 2 * (leftHalfWidth < rightHalfWidth ? leftHalfWidth : rightHalfWidth);
+                [width, height] = getAspectHW(width, height, aspectRatio);
+                this.rangeX = [
+                    cropperBox.centerPoint.x - width / 2, 
+                    cropperBox.centerPoint.x + width / 2
+                ];
+                this.rangeY = [yMax - height, yMax];
+                break;
+            case 'tr':
+                height = yMax;
+                width = containerWidth - xMin;
+                [width, height] = getAspectHW(width, height, aspectRatio);
+                this.rangeX = [xMin, xMin + width];
+                this.rangeY = [yMax - height, yMax];
+                break;
+            case 'ml':
+                width = xMax;
+                const topHalfHeight = cropperBox.centerPoint.y;
+                const bottomHalfHeight = containerHeight - cropperBox.centerPoint.y;
+                height = 2 * (topHalfHeight < bottomHalfHeight ? topHalfHeight : bottomHalfHeight);
+                [width, height] = getAspectHW(width, height, aspectRatio);
+                this.rangeX = [xMax - width, xMax];
+                this.rangeY = [
+                    cropperBox.centerPoint.y - height / 2, 
+                    cropperBox.centerPoint.y + height / 2
+                ];
+                break;
+            case 'mr':
+                width = containerWidth - xMin;
+                const topHalfHeight2 = cropperBox.centerPoint.y;
+                const bottomHalfHeight2 = containerHeight - cropperBox.centerPoint.y;
+                height = 2 * (topHalfHeight2 < bottomHalfHeight2 ? topHalfHeight2 : bottomHalfHeight2);
+                [width, height] = getAspectHW(width, height, aspectRatio);
+                this.rangeX = [xMin, xMin + width];
+                this.rangeY = [
+                    cropperBox.centerPoint.y - height / 2,
+                    cropperBox.centerPoint.y + height / 2
+                ];
+                break;
+            case 'bl':
+                height = containerHeight - yMin;
+                width = xMax;
+                [width, height] = getAspectHW(width, height, aspectRatio);
+                this.rangeX = [xMax - width, xMax];
+                this.rangeY = [yMin, yMin + height];
+                break;
+            case 'bm':
+                height = containerHeight - yMin;
+                const leftHalfWidth2 = cropperBox.centerPoint.x;
+                const rightHalfWidth2 = containerWidth - cropperBox.centerPoint.x;
+                width = 2 * (leftHalfWidth2 < rightHalfWidth2 ? leftHalfWidth2 : rightHalfWidth2);
+                [width, height] = getAspectHW(width, height, aspectRatio); 
+                this.rangeX = [
+                    cropperBox.centerPoint.x - width / 2,
+                    cropperBox.centerPoint.x + width / 2,
+                ];
+                this.rangeY = [yMin, yMin + height]; 
+                break;
+            case 'br':
+                height = containerHeight - yMin;
+                width = containerWidth - xMin;
+                [width, height] = getAspectHW(width, height, aspectRatio);
+                this.rangeX = [xMin, xMin + width];
+                this.rangeY = [yMin, yMin + height];
+                break;
+            default:
+                break;
+        }
+    }
+
+    handleCornerMouseDown = (e: any) => {
+        const currentTarget = e.currentTarget;
+        if (!currentTarget) {
+            return;
+        }
+        e.preventDefault();
+        const dir = currentTarget.dataset.dir;
+        this.boxMoveDir = dir;
+        this.boxMoveParam = this.getMoveParamByDir(dir);
+        this.bindResizeEvent();
+        const { aspectRatio } = this.getProps();
+        if (aspectRatio) {
+            this.getRangeForAspectChange();
+        } else {
+            this.rangeX = [0, this.containerData.width];
+            this.rangeY = [0, this.containerData.height];
+        }
+        
+    }
+
+    bindResizeEvent = () => {
+        const { aspectRatio } = this.getProps();
+        document.addEventListener('mousemove', aspectRatio ? this.handleCornerAspectMouseMove : this.handleCornerMouseMove);
+        document.addEventListener('mouseup', this.handleCornerMouseUp);
+    }
+
+    unBindResizeEvent = () => {
+        const { aspectRatio } = this.getProps();
+        document.removeEventListener('mousemove', aspectRatio ? this.handleCornerAspectMouseMove : this.handleCornerMouseMove);
+        document.removeEventListener('mouseup', this.handleCornerMouseUp);
+    }
+
+    viewIMGDragStart = (e: any) => {
+        e.preventDefault();
+    }
+
+    handleCornerAspectMouseMove = (e: any) => {
+        e.preventDefault();
+        const { clientX, clientY } = e;
+        const { cropperBox } = this.getStates();
+        const { aspectRatio } = this.getProps();
+        const boundingRect = this._adapter.getContainer().getBoundingClientRect();
+        const newCropperBoxPos = {
+            width: cropperBox.width,
+            height: cropperBox.height,
+            centerPoint: { ...cropperBox.centerPoint }
+        };
+        let offsetX: number, offsetY: number;
+        if (['ml', 'mr'].includes(this.boxMoveDir)) {
+            offsetX = getMiddle(clientX - boundingRect.left, this.rangeX);
+        } else {
+            offsetY = getMiddle(clientY - boundingRect.top, this.rangeY);
+        }
+        switch (this.boxMoveDir) {
+            case 'tl':
+                newCropperBoxPos.height = this.rangeY[1] - offsetY;
+                newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
+                newCropperBoxPos.centerPoint = {
+                    x: this.rangeX[1] - newCropperBoxPos.width / 2,
+                    y: this.rangeY[1] - newCropperBoxPos.height / 2,
+                };
+                break;
+            case 'tm':
+                newCropperBoxPos.height = this.rangeY[1] - offsetY;
+                newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
+                newCropperBoxPos.centerPoint = {
+                    x: cropperBox.centerPoint.x,
+                    y: this.rangeY[1] - newCropperBoxPos.height / 2,
+                };
+                break;
+            case 'tr':
+                newCropperBoxPos.height = this.rangeY[1] - offsetY;
+                newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
+                newCropperBoxPos.centerPoint = {
+                    x: this.rangeX[0] + newCropperBoxPos.width / 2,
+                    y: this.rangeY[1] - newCropperBoxPos.height / 2,
+                };
+                break;
+            case 'ml':
+                newCropperBoxPos.width = this.rangeX[1] - offsetX;
+                newCropperBoxPos.height = newCropperBoxPos.width / aspectRatio;
+                newCropperBoxPos.centerPoint = {
+                    x: this.rangeX[1] - newCropperBoxPos.width / 2,
+                    y: cropperBox.centerPoint.y,
+                };
+                break;
+            case 'mr':
+                newCropperBoxPos.width = offsetX - this.rangeX[0];
+                newCropperBoxPos.height = newCropperBoxPos.width / aspectRatio;
+                newCropperBoxPos.centerPoint = {
+                    x: this.rangeX[0] + newCropperBoxPos.width / 2,
+                    y: cropperBox.centerPoint.y,
+                };
+                break;
+            case 'bl':
+                newCropperBoxPos.height = offsetY - this.rangeY[0];
+                newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
+                newCropperBoxPos.centerPoint = {
+                    x: this.rangeX[1] - newCropperBoxPos.width / 2,
+                    y: this.rangeY[0] + newCropperBoxPos.height / 2,
+                };
+                break;
+            case 'bm':
+                newCropperBoxPos.height = offsetY - this.rangeY[0];
+                newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
+                newCropperBoxPos.centerPoint = {
+                    x: cropperBox.centerPoint.x,
+                    y: this.rangeY[0] + newCropperBoxPos.height / 2,
+                };
+                break;
+            case 'br':
+                newCropperBoxPos.height = offsetY - this.rangeY[0];
+                newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
+                newCropperBoxPos.centerPoint = {
+                    x: this.rangeX[0] + newCropperBoxPos.width / 2,
+                    y: this.rangeY[0] + newCropperBoxPos.height / 2,
+                };
+                break;
+            default:
+                break;
+        }
+        if (newCropperBoxPos.height === 0 && newCropperBoxPos.width === 0) {
+            this.changeDir();
+            this.getRangeForAspectChange();
+        } 
+        this.setState({
+            cropperBox: newCropperBoxPos
+        } as any);
+    }
+
+    changeDir = () => {
+        if (this.boxMoveDir.includes('t')) {
+            this.boxMoveDir = this.boxMoveDir.replace('t', 'b');
+        } else if (this.boxMoveDir.includes('b')) {
+            this.boxMoveDir = this.boxMoveDir.replace('b', 't');
+        }
+        if (this.boxMoveDir.includes('l')) {
+            this.boxMoveDir = this.boxMoveDir.replace('l', 'r');
+        } else if (this.boxMoveDir.includes('r')) {
+            this.boxMoveDir = this.boxMoveDir.replace('r', 'l');
+        }
+    }
+
+    handleCornerMouseMove = (e: any) => {
+        e.preventDefault();
+        const { clientX, clientY } = e;
+        const { cropperBox } = this.getStates();
+        const boundingRect = this._adapter.getContainer().getBoundingClientRect();
+        let offsetX = getMiddle(clientX - boundingRect.left, this.rangeX);
+        let offsetY = getMiddle(clientY - boundingRect.top, this.rangeY);
+        const newCropperBoxPos = {
+            width: cropperBox.width,
+            height: cropperBox.height,
+            centerPoint: {
+                x: cropperBox.centerPoint.x,
+                y: cropperBox.centerPoint.y
+            }
+        };
+        const { paramX, paramY } = this.boxMoveParam;
+        let x: number, y: number;
+        if (paramX) {
+            x = cropperBox.centerPoint.x + paramX * cropperBox.width / 2;
+            newCropperBoxPos.width = cropperBox.width + paramX * (offsetX - x);
+            if (newCropperBoxPos.width < 0) {
+                newCropperBoxPos.width = - newCropperBoxPos.width;
+                this.boxMoveParam.paramX = -paramX;
+            }
+            newCropperBoxPos.centerPoint.x = offsetX - paramX * newCropperBoxPos.width / 2;
+        }
+        if (paramY) {
+            y = cropperBox.centerPoint.y + paramY * cropperBox.height / 2;
+            newCropperBoxPos.height = cropperBox.height + paramY * (offsetY - y);
+            if (newCropperBoxPos.height < 0) {
+                newCropperBoxPos.height = -newCropperBoxPos.height;
+                this.boxMoveParam.paramY = -paramY;
+            }
+            newCropperBoxPos.centerPoint.y = offsetY - paramY * newCropperBoxPos.height / 2;
+        }
+        
+        this.setState({
+            cropperBox: newCropperBoxPos
+        } as any);
+    }
+
+    handleCornerMouseUp = (e: any) => {
+        this.boxMoveParam = { paramX: 0, paramY: 0 };
+        this.unBindResizeEvent();
+    }
+
+    handleCropperBoxMouseDown = (e: any) => {
+        const target = e.target;
+        const { cropperBox } = this.getStates();
+        const container = this._adapter.getContainer();
+        const boundingRect = container.getBoundingClientRect();
+        if (target.dataset.dir) {
+            // 如果鼠标是落在了corner上,那么不做任何操作
+            return;
+        }
+        // 移动裁切框
+        this.cropperBoxMoveStart = {
+            x: e.clientX,
+            y: e.clientY
+        };
+        this.bindMoveEvent();
+        // 计算 cropperBox 中心点移动范围
+        this.moveRange = {
+            xMin: cropperBox.width / 2,
+            xMax: boundingRect.width - cropperBox.width / 2,
+            yMin: cropperBox.height / 2,
+            yMax: boundingRect.height - cropperBox.height / 2,
+        };
+    }
+
+    bindMoveEvent = () => {
+        document.addEventListener('mousemove', this.handleCropperBoxMouseMove);
+        document.addEventListener('mouseup', this.handleCropperBoxMouseUp);
+    }
+
+    unBindMoveEvent = () => {
+        document.removeEventListener('mousemove', this.handleCropperBoxMouseMove);
+        document.removeEventListener('mouseup', this.handleCropperBoxMouseUp);
+    }
+
+    handleCropperBoxMouseMove = (e: any) => {
+        if (!this.cropperBoxMoveStart) {
+            return;
+        }
+        const { clientX, clientY } = e;
+        const { cropperBox } = this.getStates();
+        const offsetX = clientX - this.cropperBoxMoveStart.x;
+        const offsetY = clientY - this.cropperBoxMoveStart.y;
+        const newCenterPointX = getMiddle(cropperBox.centerPoint.x + offsetX, [this.moveRange.xMin, this.moveRange.xMax]);
+        const newCenterPointY = getMiddle(cropperBox.centerPoint.y + offsetY, [this.moveRange.yMin, this.moveRange.yMax]);
+        const newCropperBoxPos = {
+            width: cropperBox.width,
+            height: cropperBox.height,
+            centerPoint: {
+                x: newCenterPointX,
+                y: newCenterPointY
+            }
+        };
+        this.cropperBoxMoveStart = {
+            x: clientX,
+            y: clientY
+        };
+        this.setState({
+            cropperBox: newCropperBoxPos
+        } as any);
+    }
+
+    handleCropperBoxMouseUp = (e: any) => {
+        if (!this.cropperBoxMoveStart) {
+            return;
+        }
+        this.cropperBoxMoveStart = null;
+        this.unBindMoveEvent();
+    }
+
+    handleMaskMouseDown = (e: any) => {
+        if (e.currentTarget !== e.target) {
+            return;
+        }
+        this.bindImgMoveEvent();
+        // 记录开始移动的位置
+        this.imgMoveStart = {
+            x: e.clientX,
+            y: e.clientY
+        };
+    }
+
+    bindImgMoveEvent = () => {
+        document.addEventListener('mousemove', this.handleImgMove);
+        document.addEventListener('mouseup', this.handleImgMoveUp);
+    }
+
+    unBindImgMoveEvent = () => {
+        document.removeEventListener('mousemove', this.handleImgMove);
+        document.removeEventListener('mouseup', this.handleImgMoveUp);
+    }
+
+    handleImgMove = (e: any) => {
+        if (!this.imgMoveStart) {
+            return;
+        }
+        const { clientX, clientY } = e;
+        const { imgData } = this.getStates();
+        const offsetX = clientX - this.imgMoveStart.x;
+        const offsetY = clientY - this.imgMoveStart.y;
+        const newCenterPointX = imgData.centerPoint.x + offsetX;
+        const newCenterPointY = imgData.centerPoint.y + offsetY;
+        const newImgData = {
+            width: imgData.width,
+            height: imgData.height,
+            centerPoint: {
+                x: newCenterPointX,
+                y: newCenterPointY
+            }
+        };
+        this.imgMoveStart = {
+            x: clientX,
+            y: clientY
+        };
+        this.setState({
+            imgData: newImgData
+        } as any);
+    }
+
+    handleImgMoveUp = (e: any) => {
+        if (!this.imgMoveStart) {
+            return;
+        }
+        this.imgMoveStart = null;
+        this.unBindImgMoveEvent();
+    }
+
+    getCropperCanvas = () => {
+        const { cropperBox, imgData, rotate, zoom } = this.getStates();
+        const { fill } = this.getProps();
+        const canvas = document.createElement('canvas');
+        const ctx = canvas.getContext('2d');
+        const img = this._adapter.getImg();
+
+        // 计算包含旋转后的图片的矩形容器的宽高
+        const angle = rotate * Math.PI / 180;
+        const sine = Math.abs(Math.sin(angle));
+        const cosine = Math.abs(Math.cos(angle));
+        const imgWidth = this.imgData.originalWidth;
+        const imgHeight = this.imgData.originalHeight;
+        const containerWidth = imgWidth * cosine + imgHeight * sine;
+        const containerHeight = imgHeight * cosine + imgWidth * sine;
+
+        // 判断裁切区域和外接矩形是否存在交集,如果不存在,则直接返回空白图片
+        // 计算需要裁剪的区域实际大小和位置
+        const cropperContainerWidth = containerWidth * zoom * this.imgData.scale;
+        const cropperContainerHeight = containerHeight * zoom * this.imgData.scale;
+        const cropperContainerTop = imgData.centerPoint.y - cropperContainerHeight / 2;
+        const cropperContainerLeft = imgData.centerPoint.x - cropperContainerWidth / 2;
+        const cropperBoxLeft = cropperBox.centerPoint.x - cropperBox.width / 2;
+        const cropperBoxTop = cropperBox.centerPoint.y - cropperBox.height / 2;
+        const realZoom = zoom * this.imgData.scale;
+        
+        const relativeCropLeft = (cropperBoxLeft - cropperContainerLeft) / realZoom;
+        const relativeCropTop = (cropperBoxTop - cropperContainerTop) / realZoom;
+        const relativeWidth = cropperBox.width / realZoom;
+        const relativeHeight = cropperBox.height / realZoom;
+        const relativeCropRight = relativeCropLeft + relativeWidth;
+        const relativeCropBottom = relativeCropTop + relativeHeight;
+
+        if (relativeCropRight < 0 || relativeCropBottom < 0 || relativeCropLeft > containerWidth || relativeCropTop > containerHeight) {
+            // 没有交集,直接返回空白图片
+            const emptyCanvas = document.createElement('canvas');
+            const ctx = emptyCanvas.getContext('2d');
+            emptyCanvas.width = relativeWidth;
+            emptyCanvas.height = relativeHeight;
+            ctx.fillStyle = fill;
+            ctx.fillRect(0, 0, relativeWidth, relativeHeight);
+            return emptyCanvas;
+        }
+
+        canvas.width = containerWidth;
+        canvas.height = containerHeight;
+        ctx.fillStyle = fill;
+        ctx.fillRect(0, 0, containerWidth, containerHeight);
+
+        const halfWidth = containerWidth / 2;
+        const halfHeight = containerHeight / 2;
+        ctx.translate(halfWidth, halfHeight);
+        ctx.rotate(rotate * Math.PI / 180);
+        ctx.translate(-halfWidth, -halfHeight);
+
+        const imgX = (containerWidth - imgWidth) / 2;
+        const imgY = (containerHeight - imgHeight) / 2;
+        ctx.drawImage(img, 0, 0, imgWidth, imgHeight, imgX, imgY, imgWidth, imgHeight);
+
+        const canvas2 = document.createElement('canvas');
+        const ctx2 = canvas2.getContext('2d');
+        // 为了避免裁剪时候,超出被裁切的画布的部分颜色不正常,需要将裁切区域限制在画布范围内。
+        // 相对位置会在后续进行修正
+        let realLeft = relativeCropLeft;
+        let realTop = relativeCropTop;
+        let realWidth = relativeWidth;
+        let realHeight = relativeHeight;
+       
+        if (relativeCropLeft < 0) {
+            realLeft = 0;
+        }
+        if (relativeCropTop < 0) {
+            realTop = 0;
+        }
+        if (relativeCropRight > containerWidth) {
+            realWidth = containerWidth - realLeft;
+        } else if (relativeCropLeft < 0) {
+            realWidth = relativeCropRight;
+        }
+
+        if (relativeCropBottom > containerHeight) {
+            realHeight = containerHeight - realTop;
+        } else if (relativeCropTop < 0) {
+            realHeight = relativeCropBottom;
+        }
+
+        const imgDataResult = ctx.getImageData(realLeft, realTop, realWidth, realHeight);
+        canvas2.width = relativeWidth;
+        canvas2.height = relativeHeight;
+        ctx2.fillStyle = fill;
+        ctx2.fillRect(0, 0, relativeWidth, relativeHeight);
+        ctx2.putImageData(
+            imgDataResult, 
+            relativeCropLeft < 0 ? - relativeCropLeft : 0,  
+            relativeCropTop < 0 ? - relativeCropTop : 0,
+        );
+        return canvas2;
+    }
+}

+ 12 - 0
packages/semi-foundation/cropper/utils.ts

@@ -0,0 +1,12 @@
+export function getMiddle(value: number, [min, max]) {
+    return Math.min(Math.max(value, min), max);
+}
+
+export function getAspectHW(width: number, height: number, aspect: number) {
+    if (width / height > aspect) {
+        width = height * aspect;
+    } else {
+        height = width / aspect;
+    }
+    return [width, height];
+}

+ 6 - 0
packages/semi-foundation/cropper/variables.scss

@@ -0,0 +1,6 @@
+$color-cropper_mask-bg: var(--semi-color-overlay-bg); // 裁切框遮罩背景颜色
+$color-cropper_box-outline: var(--semi-color-primary); // 裁切框边框颜色
+$color-cropper_box_corner-bg: var(--semi-color-primary); // 裁切框调整块背景色
+
+$width-cropper_box-outline: 1px; // 裁切框边框宽度
+$width-cropper_box_corner: 10px; // 裁切框调整块宽高

+ 12 - 0
packages/semi-foundation/dragMove/foundation.ts

@@ -46,9 +46,21 @@ export default class DragMoveFoundation<P = Record<string, any>, S = Record<stri
         this.element = element;
         this.element.style.position = 'absolute';
         this.handler.style.cursor = 'move';
+        this._registerStartEvent();
+    }
+
+    _registerStartEvent = () => {
+        this.handler.addEventListener('mousedown', this.onMouseDown);
+        this.handler.addEventListener('touchstart', this.onTouchStart);
+    }
+
+    _unRegisterStartEvent = () => {
+        this.handler.removeEventListener('mousedown', this.onMouseDown);
+        this.handler.removeEventListener('touchstart', this.onTouchStart);
     }
 
     destroy() {
+        this._unRegisterStartEvent();
         this._unRegisterEvent();
     }
 

+ 1 - 1
packages/semi-foundation/form/interface.ts

@@ -67,7 +67,7 @@ export type FieldPathValue<T, P extends FieldPath<T>> =
 export type ScrollToErrorOptions<K> = {
     field?: K;
     index?: number;
-    scrollConfig?: ScrollIntoViewOptions
+    scrollOpts?: ScrollIntoViewOptions
 }
 
 // use object replace Record<string, any>, fix issue 933

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

@@ -204,6 +204,12 @@ $module: #{$prefix}-image;
             // transition: transform $transition_duration-image_preview_image_img  $transition_delay-image_preview_image_img;
             z-index: 0;
             user-select: none;
+            /**
+             * In tailwind, the max-width of img/video is set to 100%, 
+             * which will affect the amplification effect of the picture.
+             * So we need to set max-width to none.
+            */
+            max-width: none;
         }
 
         &-spin {

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно