Browse Source

Merge branch 'DouyinFE:main' into main

uiuing 3 years ago
parent
commit
f7a1200e65
100 changed files with 2746 additions and 477 deletions
  1. 2 2
      .vscode/settings.json
  2. 5 1
      content/basic/tokens/index-en-US.md
  3. 4 1
      content/basic/tokens/index.md
  4. 1 1
      content/feedback/banner/index-en-US.md
  5. 1 1
      content/feedback/banner/index.md
  6. 1 1
      content/feedback/notification/index-en-US.md
  7. 1 1
      content/feedback/notification/index.md
  8. 1 1
      content/feedback/popconfirm/index-en-US.md
  9. 1 1
      content/feedback/popconfirm/index.md
  10. 1 1
      content/feedback/progress/index-en-US.md
  11. 1 1
      content/feedback/progress/index.md
  12. 5 4
      content/feedback/skeleton/index-en-US.md
  13. 16 17
      content/feedback/skeleton/index.md
  14. 1 1
      content/feedback/spin/index-en-US.md
  15. 1 1
      content/feedback/spin/index.md
  16. 28 1
      content/feedback/toast/index-en-US.md
  17. 30 3
      content/feedback/toast/index.md
  18. 13 12
      content/input/button/index-en-US.md
  19. 15 14
      content/input/button/index.md
  20. 57 56
      content/navigation/navigation/index.md
  21. 1 0
      content/order.js
  22. 1 1
      content/other/configprovider/index-en-US.md
  23. 1 1
      content/other/configprovider/index.md
  24. 1 1
      content/other/locale/index-en-US.md
  25. 1 1
      content/other/locale/index.md
  26. 1 1
      content/show/dropdown/index-en-US.md
  27. 509 0
      content/show/image/index-en-US.md
  28. 509 0
      content/show/image/index.md
  29. 2 2
      content/show/list/index-en-US.md
  30. 1 1
      content/show/list/index.md
  31. 7 3
      content/show/modal/index-en-US.md
  32. 228 219
      content/show/modal/index.md
  33. 1 1
      content/show/overflowlist/index-en-US.md
  34. 1 1
      content/show/overflowlist/index.md
  35. 1 1
      content/show/popover/index-en-US.md
  36. 1 1
      content/show/popover/index.md
  37. 1 1
      content/show/scrolllist/index-en-US.md
  38. 1 1
      content/show/scrolllist/index.md
  39. 1 1
      content/show/sidesheet/index-en-US.md
  40. 1 1
      content/show/sidesheet/index.md
  41. 1 1
      content/show/table/index-en-US.md
  42. 1 1
      content/show/table/index.md
  43. 18 1
      content/show/tag/index-en-US.md
  44. 18 1
      content/show/tag/index.md
  45. 1 1
      content/show/timeline/index-en-US.md
  46. 1 1
      content/show/timeline/index.md
  47. 1 1
      content/show/tooltip/index-en-US.md
  48. 1 1
      content/show/tooltip/index.md
  49. 29 1
      content/start/changelog/index-en-US.md
  50. 35 8
      content/start/changelog/index.md
  51. 1 0
      content/start/overview/index-en-US.md
  52. 1 0
      content/start/overview/index.md
  53. 1 0
      gatsby-node.js
  54. 1 1
      lerna.json
  55. 4 4
      package.json
  56. 2 2
      packages/semi-animation-react/package.json
  57. 0 12
      packages/semi-animation-styled/getBabelConfig.js
  58. 1 4
      packages/semi-animation-styled/package.json
  59. 1 1
      packages/semi-animation/package.json
  60. 1 1
      packages/semi-eslint-plugin/package.json
  61. 1 1
      packages/semi-foundation/anchor/anchor.scss
  62. 1 1
      packages/semi-foundation/autoComplete/autoComplete.scss
  63. 2 2
      packages/semi-foundation/badge/badge.scss
  64. 2 2
      packages/semi-foundation/breadcrumb/breadcrumb.scss
  65. 1 1
      packages/semi-foundation/button/button.scss
  66. 1 2
      packages/semi-foundation/calendar/foundation.ts
  67. 2 2
      packages/semi-foundation/calendar/variables.scss
  68. 1 1
      packages/semi-foundation/carousel/carousel.scss
  69. 2 1
      packages/semi-foundation/cascader/cascader.scss
  70. 2 2
      packages/semi-foundation/checkbox/checkbox.scss
  71. 6 5
      packages/semi-foundation/datePicker/foundation.ts
  72. 6 6
      packages/semi-foundation/datePicker/monthsGridFoundation.ts
  73. 5 5
      packages/semi-foundation/datePicker/variables.scss
  74. 2 2
      packages/semi-foundation/datePicker/yearAndMonthFoundation.ts
  75. 1 1
      packages/semi-foundation/dropdown/dropdown.scss
  76. 14 0
      packages/semi-foundation/form/form.scss
  77. 2 2
      packages/semi-foundation/form/variables.scss
  78. 1 1
      packages/semi-foundation/grid/grid.scss
  79. 10 10
      packages/semi-foundation/grid/mixin.scss
  80. 14 6
      packages/semi-foundation/gulpfile.js
  81. 11 0
      packages/semi-foundation/image/animation.scss
  82. 7 0
      packages/semi-foundation/image/constants.ts
  83. 224 0
      packages/semi-foundation/image/image.scss
  84. 64 0
      packages/semi-foundation/image/imageFoundation.ts
  85. 41 0
      packages/semi-foundation/image/previewFooterFoundation.ts
  86. 25 0
      packages/semi-foundation/image/previewFoundation.ts
  87. 260 0
      packages/semi-foundation/image/previewImageFoundation.ts
  88. 264 0
      packages/semi-foundation/image/previewInnerFoundation.ts
  89. 51 0
      packages/semi-foundation/image/rtl.scss
  90. 88 0
      packages/semi-foundation/image/utils.ts
  91. 47 0
      packages/semi-foundation/image/variables.scss
  92. 2 2
      packages/semi-foundation/input/input.scss
  93. 2 2
      packages/semi-foundation/input/textarea.scss
  94. 0 1
      packages/semi-foundation/inputNumber/inputNumber.scss
  95. 3 3
      packages/semi-foundation/inputNumber/variables.scss
  96. 2 9
      packages/semi-foundation/navigation/foundation.ts
  97. 1 1
      packages/semi-foundation/navigation/navigation.scss
  98. 2 2
      packages/semi-foundation/navigation/variables.scss
  99. 1 2
      packages/semi-foundation/package.json
  100. 1 1
      packages/semi-foundation/pagination/pagination.scss

+ 2 - 2
.vscode/settings.json

@@ -17,7 +17,7 @@
         "editor.defaultFormatter": "michelemelluso.code-beautifier"
     },
     "[typescriptreact]": {
-        "editor.defaultFormatter": "dbaeumer.vscode-eslint"
+        "editor.defaultFormatter": "vscode.typescript-language-features"
     },
     "typescript.updateImportsOnFileMove.enabled": "always",
     "files.autoSave": "onFocusChange",
@@ -44,4 +44,4 @@
         "backtop",
         "Splited"
     ]
-}
+}

+ 5 - 1
content/basic/tokens/index-en-US.md

@@ -197,6 +197,10 @@ It is used to describe the sequence of interface elements
 
 <DesignToken componentName='global' reg={/z-/}/>
 
+## Animation
+
+<DesignToken componentName="global" isAnimation={true} />
+
 ## Variables not yet supported
 
 Currently, Semi does not support global variables in the following categories. If you have related requirements, you can give feedback through issue and describe your expected needs in detail.
@@ -210,4 +214,4 @@ Currently, Semi does not support global variables in the following categories. I
 **Media query**
 
 ## Customization
-If you need to customize the global variable style, please go to [Semi DSM](https://semi.design/dsm), make your own theme and publish it
+If you need to customize the global variable style, please go to [Semi DSM](https://semi.design/dsm), make your own theme and publish it

+ 4 - 1
content/basic/tokens/index.md

@@ -201,6 +201,10 @@ brief: Semi Design Tokens
 
 <DesignToken componentName='global' reg={/z-/}/>
 
+## 动画
+
+<DesignToken componentName="global" isAnimation={true} />
+
 ## 尚未支持的变量
 目前,Semi 尚未支持以下类别的全局变量,如果你有相关需求,可以通过issue进行反馈,详细描述你的预期需求,我们会在评估后进行处理
 
@@ -208,7 +212,6 @@ brief: Semi Design Tokens
 
 **字间距 letter spacing**
 
-**时长 duration**
 
 **媒体查询 media query**
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 5 - 4
content/feedback/skeleton/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 68
+order: 69
 category: Feedback
 title: Skeleton
 subTitle: Skeleton
@@ -10,7 +10,7 @@ brief: A placeholder preview of content before the data loaded.
 
 ## Overview
 
--   `Avatar`: Avatar placeholder, by default uses Avatar medium sizing: `width: 48px`, `height: 48px`. Supports other sizes after v1.0.
+-   `Avatar`: Avatar placeholder, by default uses Avatar medium sizing: `width: 48px`, `height: 48px`. Supports Avatar's size (after v1.0) and shape attributes (after v2.20)
 -   `Image`: Image placeholder, default size: `width: 100%`, `height: 100%`.
 -   `Title`: Title placeholder, default size: `width: 100%`, `height: 24px`.
 -   `Paragraph`: Content part placeholder, default size: `width: 100%`, `height: 16px`, `margin-bottom: 10px`.
@@ -386,13 +386,14 @@ import { Skeleton, Avatar } from '@douyinfe/semi-ui';
 
 ### Skeleton.Avatar
 
-> `Skeleton.Image`,`Skeleton.Title`,`Skeleton.Button` have same APIs with `Skeleton.Avatar`.
+> `Skeleton.Image`,`Skeleton.Title`,`Skeleton.Button` have same APIs with `Skeleton.Avatar`. `shape` only supported in `Skeleton.Avatar`  
 
 | Property | Instructions | type | Default |
 | --- | --- | --- | --- |
 | class Name | Class name | string | - |
 | size | Size of the avatar, one of `extra-extra-small`, `extra-small`, `small`, `medium`, `large`, `extra-large`, **v>=1.0** | string | `medium` |
 | style | Inline style | CSSProperties | - |
+| shape | Shape of the avatar, one of `circle`, `square` | string | `circle` |
 
 ### Skeleton.Paragraph
 
@@ -404,7 +405,7 @@ import { Skeleton, Avatar } from '@douyinfe/semi-ui';
 
 ## Content Guidelines
 
-- Unchanged fixed content directly displays fixed content, and variable content is displayed using skeleton screen
+-   Unchanged fixed content directly displays fixed content, and variable content is displayed using skeleton screen
 
 ## Design Tokens
 

+ 16 - 17
content/feedback/skeleton/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 68
+order: 69
 category: 反馈类
 title: Skeleton 骨架屏
 icon: doc-skeleton
@@ -9,7 +9,7 @@ brief: 在需要等待加载内容的位置提供的占位组件。
 
 ## 概述
 
--   `Avatar`:占位头像,默认为圆形,默认尺寸:Avatar medium: `width: 48px`,`height: 48px`。支持 Avatar 的 size 属性 (**v>=1.0**)
+-   `Avatar`:占位头像,默认为圆形,默认尺寸:Avatar medium: `width: 48px`,`height: 48px`。支持 Avatar 的 size(v1.0后支持)、shape 属性 (v2.20后支持)
 -   `Image`:占位图像,默认尺寸:`width: 100%`,`height: 100%`。
 -   `Title`:占位标题,默认尺寸:`width: 100%`, `height: 24px`。
 -   `Paragraph`:占位内容部分,默认尺寸:`width: 100%`,`height: 16px`,`margin-bottom: 10px`。
@@ -364,37 +364,36 @@ import { Skeleton, Avatar } from '@douyinfe/semi-ui';
 
 ### Skeleton
 
-| 属性        | 说明                                       | 类型       | 默认值 |
-| ----------- | ------------------------------------------ | ---------- | ------ |
-| active      | 是否展示动画效果                           | boolean    | false  |
-| className   | 类名                                       | string     | -      |
-| loading     | 为 true 时,显示占位元素。反之则显示子组件 | boolean    | true   |
-| placeholder | 加载等待时的占位元素                       | ReactNode | -      |
-| style       | 样式                                       | CSSProperties     | -      |
+| 属性        | 说明                                       | 类型          | 默认值 |
+| ----------- | ------------------------------------------ | ------------- | ------ |
+| active      | 是否展示动画效果                           | boolean       | false  |
+| className   | 类名                                       | string        | -      |
+| loading     | 为 true 时,显示占位元素。反之则显示子组件 | boolean       | true   |
+| placeholder | 加载等待时的占位元素                       | ReactNode     | -      |
+| style       | 样式                                       | CSSProperties | -      |
 
 ### Skeleton.Avatar
 
-> `Skeleton.Image`,`Skeleton.Title`,`Skeleton.Button` API 与 `Skeleton.Avatar` 相同
+> `Skeleton.Image`,`Skeleton.Title`,`Skeleton.Button` 大部分API 与 `Skeleton.Avatar` 相同。其中 shape 仅 `Skeleton.Avatar支持`
 
 | 属性 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
 | className | 类名 | string | - |
 | size | 设置头像的大小,支持 `extra-extra-small`, `extra-small`、`small`、`medium`、`large`、`extra-large` **v>=1.0** | string | `medium` |
 | style | 样式 | CSSProperties | - |
+| shape | 指定头像的形状,支持 `circle`、`square` | string | `circle` |
 
 ### Skeleton.Paragraph
 
-| 属性      | 说明                 | 类型   | 默认值 |
-| --------- | -------------------- | ------ | ------ |
-| className | 类名                 | string | -      |
-| rows      | 设置段落占位图的行数 | number | 4      |
+| 属性      | 说明                 | 类型          | 默认值 |
+| --------- | -------------------- | ------------- | ------ |
+| className | 类名                 | string        | -      |
+| rows      | 设置段落占位图的行数 | number        | 4      |
 | style     | 样式                 | CSSProperties | -      |
 
 ## 文案规范
-- 不变的固定内容直接展示固定内容,可变的内容使用骨架屏展示
-
-
 
+-   不变的固定内容直接展示固定内容,可变的内容使用骨架屏展示
 
 ## 设计变量
 

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

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

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

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

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 70
+order: 71
 category: Feedback
 title: Toast
 subTitle: Toast
@@ -237,6 +237,33 @@ function Demo() {
 render(Demo);
 ```
 
+### Update Toast Content
+
+Use unique Toast `id` to update toast content.
+
+```jsx live=true noInline=true hideInDSM
+import React, { useState } from 'react';
+import { Toast, Button } from '@douyinfe/semi-ui';
+
+function Demo() {
+    function show() {
+        const id = 'toastid';
+        Toast.info({ content: 'Update Content By Id', id });
+        setTimeout(() => {
+            Toast.success({ content: 'Id By Content Update', id });
+        }, 1000);
+    }
+
+    return (
+        <Button type="primary" onClick={show}>
+            Update Content By Id
+        </Button>
+    );
+}
+
+render(Demo);
+```
+
 ### useToast Hooks
 
 You could use `Toast.useToast` to create a `contextHolder` that could access context. Created toast will be inserted to where contextHolder is placed.

+ 30 - 3
content/feedback/toast/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 70
+order: 71
 category: 反馈类
 title: Toast 提示
 icon: doc-toast
@@ -238,6 +238,33 @@ function Demo() {
 render(Demo);
 ```
 
+### 更新消息内容
+
+可以通过唯一的 `id` 来更新内容。
+
+```jsx live=true noInline=true hideInDSM
+import React, { useState } from 'react';
+import { Toast, Button } from '@douyinfe/semi-ui';
+
+function Demo() {
+    function show() {
+        const id = 'toastid';
+        Toast.info({ content: 'Update Content By Id', id });
+        setTimeout(() => {
+            Toast.success({ content: 'Id By Content Update', id });
+        }, 1000);
+    }
+
+    return (
+        <Button type="primary" onClick={show}>
+            Update Content By Id
+        </Button>
+    );
+}
+
+render(Demo);
+```
+
 ### Hooks 用法
 
 通过 Toast.useToast 创建支持读取 context 的 contextHolder。此时的 toast 会渲染在 contextHolder 所在的节点处。
@@ -438,8 +465,8 @@ HookToast ( >= 1.2.0 ):
   - 不使用类似于「已读」类的动作,例如 OK, Got it, Dismiss, Cancel
 
 
-| ✅ 推荐用法 | ❌ 不推荐用法 |   
-| --- | --- | 
+| ✅ 推荐用法 | ❌ 不推荐用法 |
+| --- | --- |
 |  <ToastCard type='error' content={<div>Ticket transfer failed <span style={{ color: 'var(--semi-color-primary)', marginLeft: 4, cursor: 'pointer' }}>Retry</span> </div>} /> |  <ToastCard type='error' content={<div>Ticket transfer failed <span style={{ color: 'var(--semi-color-primary)', marginLeft: 4, cursor: 'pointer' }}>Dismiss</span> </div>} /> |
 
 ## 设计变量

+ 13 - 12
content/input/button/index-en-US.md

@@ -508,23 +508,24 @@ function SplitButtonDemo(){
 | onMouseEnter             | Mouse Enter                                                                                                   | Function(MouseEvent)                        |           |
 | onMouseLeave             | Mouse Leave                                                                                                   | Function(MouseEvent)                        |           |
 
-
 ### ButtonGroup
 
-| Properties | Instructions                                                                            | Type    | Default   |
-| ---------- | --------------------------------------------------------------------------------------- | ------- | --------- |
-| aria-label          | Label of the button group                                                                                      | string                            | -    |
-| disabled   | Disabled status                                                                         | boolean | false     |
-| size       | Button size, optional value: `"large"`,`"default"`,`"small"`                            | string  | "default" |
+| Properties | Instructions                | Type    | Default   | Version |
+| ---------- | ----------------------------| ------- | --------- |---------|
+| aria-label | Label of the button group   | string  | - | |
+| className  | Custom class name           | string   | - | |
+| disabled   | Disabled status             | boolean | false | |
+| size       | Button size, optional value: `"large"`,`"default"`,`"small"` | string  | "default" |
+| style      | Custom style                | CSSProperties   | - | 2.20.0 |
+| theme      | Button theme, optional values: `"solid"` (with background color), `"borderless"` (without background color), `"light"` (light background color) | string | "light"  | |
 | type       | Type, optional values: `"primary"`,`"secondary"`, `"tertiary"`, `"warning"`, `"danger"` | string  | "primary" |
-| className     | Custom class name                                  | string   |        |
 
 ### SplitButtonGroup **V1.12.0**
-| Properties   | Instructions                                                            | Type      | Default     |
-| -----------  | --------------------------------------------------------------  | -------- | --------- |
-| aria-label          | Label of the button group                                                                                       | string                            | -    |
-| style     | Custom style                                  | CSSProperties   |        |
-| className     | Custom class name                                  | string   |        |
+| Properties   | Instructions                     | Type     | Default   |
+| -----------  | ---------------------------------| -------- | --------- |
+| aria-label   | Label of the button group        | string   | -    |
+| className    | Custom class name                | string   | - |
+| style        | Custom style                     | CSSProperties | -  |
 
 ## Accessibility
 

+ 15 - 14
content/input/button/index.md

@@ -470,28 +470,29 @@ function SplitButtonDemo(){
 
 ### ButtonGroup
 
-| 属性     | 说明                                                                   | 类型    | 默认值    |
-| -------- | ---------------------------------------------------------------------- | ------- | --------- |
-| aria-label          | 按钮组的标签                                                                                       | string                            | -    |
-| disabled | 禁用状态                                                               | boolean | false     |
-| size     | 按钮大小,可选值:`large`、`default`、`small`                          | string  | "default" |
-| theme               | 按钮主题,可选值:`solid`(有背景色)、 `borderless`(无背景色)、 `light`(浅背景色)                     | string                          | "light"   |
-| type     | 类型,可选值:`primary`、`secondary`、`tertiary`、`warning`、 `danger` | string  | "primary" |
-| className     | 自定义类名                               | string   |         |
+| 属性     | 说明          | 类型    | 默认值      | 版本 |
+| -------- | -------------| ------- | --------- |---- |
+| aria-label | 按钮组的标签 | string  | - | |
+| className  | 自定义类名   | string  | - | |
+| disabled   | 禁用状态     | boolean | false | |
+| size       | 按钮大小,可选值:`large`、`default`、`small` | string  | "default" | |
+| style      | 自定义样式   | CSSProperties   | - | 2.20.0 |
+| theme      | 按钮主题,可选值:`solid`(有背景色)、 `borderless`(无背景色)、 `light`(浅背景色) | string | "light"   | |
+| type     | 类型,可选值:`primary`、`secondary`、`tertiary`、`warning`、 `danger` | string  | "primary" | |
 
 ### SplitButtonGroup **V1.12.0新增**
-| 属性          | 说明                                                            | 类型      | 默认值     |
-| -----------  | --------------------------------------------------------------  | -------- | --------- |
-| aria-label          | 分裂按钮组的标签                                                                                       | string                            | -    |
-| style     | 自定义样式                               | CSSProperties   |         |
-| className     | 自定义类名                               | string   |         |
+| 属性          | 说明                 | 类型      | 默认值     |
+| -----------  | ---------------------| -------- | --------- |
+| aria-label   | 分裂按钮组的标签        | string   | - |
+| className    | 自定义类名             | string   | - |
+| style        | 自定义样式             | CSSProperties   | - |
 
 ## Accessibility
 
 ### ARIA
 
 - `aria-label` 用于表示按钮的作用,对于图标按钮,我们推荐使用此属性
-- `aria-disabled` 与 disabled 属性同步,表示按钮禁用 
+- `aria-disabled` 与 disabled 属性同步,表示按钮禁用
 
 ### 键盘和焦点
 

+ 57 - 56
content/navigation/navigation/index.md

@@ -42,7 +42,7 @@ class NavApp extends React.Component {
                 bodyStyle={{ height: 320 }}
                 items={[
                     { itemKey: 'user', text: '用户管理', icon: <IconUser /> },
-                    { itemKey: 'union', text: '公会中心', icon: <IconStar /> },
+                    { itemKey: 'union', text: '活动管理', icon: <IconStar /> },
                     {
                         text: '任务平台',
                         icon: <IconSetting />,
@@ -82,11 +82,11 @@ class NavApp extends React.Component {
                 defaultOpenKeys={[ 'job' ]}
                 items={[
                     { itemKey: 'user', text: '用户管理' },
-                    { itemKey: 'union', text: '公会中心' },
+                    { itemKey: 'union', text: '活动管理' },
                     {
                         itemKey: 'union-management',
-                        text: '公会管理',
-                        items: ['公告设置', '公会查询', '信息录入']
+                        text: '任务管理',
+                        items: ['公告设置', '任务查询', '信息录入']
                     },
                     {
                         text: '任务平台',
@@ -123,7 +123,7 @@ class NavApp extends React.Component {
                 bodyStyle={{ height: 320 }}
                 items={[
                     { itemKey: 'user', text: '用户管理', icon: <IconUser /> },
-                    { itemKey: 'union', text: '公会中心', icon: <IconStar /> },
+                    { itemKey: 'union', text: '活动管理', icon: <IconStar /> },
                     {
                         text: '任务平台',
                         icon: <IconSetting />,
@@ -165,7 +165,7 @@ class NavApp extends React.Component {
                 defaultOpenKeys={['job', 'resource']}
                 items={[
                     { itemKey: 'user', text: '用户管理', icon: <IconUser /> },
-                    { itemKey: 'union', text: '公会中心', icon: <IconStar /> },
+                    { itemKey: 'union', text: '活动管理', icon: <IconStar /> },
                     {
                         text: '任务平台',
                         icon: <IconSetting />,
@@ -212,14 +212,14 @@ class NavApp extends React.Component {
                 onClick={data => console.log('trigger onClick: ', data)}
             >
                 <Nav.Header logo={<img src="https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/webcast_logo.svg" />} text={'Semi 运营后台'} />
-                <Nav.Item itemKey={'union'} text={'公会中心'} icon={<IconStar />} />
+                <Nav.Item itemKey={'union'} text={'活动管理'} icon={<IconStar />} />
                 <Nav.Sub itemKey={'user'} text="用户管理" icon={<IconUser />}>
-                    <Nav.Item itemKey={'golder'} text={'金主管理'} />
-                    <Nav.Item itemKey={'ban'} text={'用户封禁'} />
+                    <Nav.Item itemKey={'active'} text={'活跃用户'} />
+                    <Nav.Item itemKey={'negative'} text={'非活跃用户'} />
                 </Nav.Sub>
-                <Nav.Sub itemKey={'union-management'} text="公会管理" icon={<IconUserGroup />}>
-                    <Nav.Item itemKey={'notice'} text={'公告设置'} />
-                    <Nav.Item itemKey={'query'} text={'公会查询'} />
+                <Nav.Sub itemKey={'union-management'} text="任务管理" icon={<IconUserGroup />}>
+                    <Nav.Item itemKey={'notice'} text={'任务设置'} />
+                    <Nav.Item itemKey={'query'} text={'任务查询'} />
                     <Nav.Item itemKey={'info'} text={'信息录入'} />
                 </Nav.Sub>
                 <Nav.Footer collapseButton={true} />
@@ -234,10 +234,9 @@ class NavApp extends React.Component {
 
 Navigation 目前提供两种方向的导航:
 
--   垂直方向(默认)
--   水平方向
+-   垂直方向(默认),  `mode = "vertical"`
+-   水平方向, `mode = "horizontal"`
 
-你可以通过 `mode = "vertical"` (默认)或者 `mode = "horizontal"` 来控制。
 
 特别注意的是,有一些功能(参数)仅在 `mode = "vertical"` 时有效:
 
@@ -260,18 +259,18 @@ class NavApp extends React.Component {
                     bodyStyle={{ height: 320 }}
                     items={[
                         { itemKey: 'user', text: '用户管理', icon: <IconUser /> },
-                        { itemKey: 'union', text: '公会中心', icon: <IconStar /> },
+                        { itemKey: 'union', text: '活动管理', icon: <IconStar /> },
                         {
                             itemKey: 'union-management',
-                            text: '公会管理',
+                            text: '任务管理',
                             icon: <IconUserGroup />,
-                            items: ['公告设置', '公会查询', '信息录入']
+                            items: ['任务设置', '任务查询', '信息录入']
                         },
                         {
-                            text: '任务平台',
+                            text: '公告管理',
                             icon: <IconSetting />,
                             itemKey: 'job',
-                            items: ['任务管理', '用户任务查询'],
+                            items: ['推送管理', '已推送查询'],
                         },
                     ]}
                     onSelect={key => console.log(key)}
@@ -304,7 +303,7 @@ class NavApp extends React.Component {
                     mode={'horizontal'}
                     items={[
                         { itemKey: 'user', text: '用户管理', icon: <IconUser /> },
-                        { itemKey: 'union', text: '公会中心', icon: <IconStar /> },
+                        { itemKey: 'union', text: '活动管理', icon: <IconStar /> },
                         {
                             itemKey: 'approve-management',
                             text: '审批管理',
@@ -343,7 +342,7 @@ class NavApp extends React.Component {
                                 </Dropdown.Menu>
                             }
                         >
-                            <Avatar size="small" color='light-blue' style={{margin: 4}}>BD</Avatar>
+                            <Avatar size="small" color='light-blue' style={{ margin: 4 }}>BD</Avatar>
                             <span>Bytedancer</span>
                         </Dropdown>
                     }
@@ -367,12 +366,12 @@ class NavApp extends React.Component {
     constructor() {
         this.items = [
             { itemKey: 'user', text: '用户管理', icon: <IconUser /> },
-            { itemKey: 'union', text: '公会中心', icon: <IconStar /> },
+            { itemKey: 'union', text: '活动管理', icon: <IconStar /> },
             {
                 itemKey: 'union-management',
-                text: '公会管理',
+                text: '任务管理',
                 icon: <IconUserGroup />,
-                items: ['公告设置', '公会查询', '信息录入']
+                items: ['公告设置', '任务查询', '信息录入']
             },
             {
                 itemKey: 'approve-management',
@@ -419,7 +418,7 @@ class NavApp extends React.Component {
                             <Select.Option value='Korean'>한국어</Select.Option>
                             <Select.Option value='Japanese'>日本語</Select.Option>
                         </Select>
-                        <Button style={{ marginRight: 10 }}>切换至海外版</Button>
+                        <Button style={{ marginRight: 10 }}>切换至全球版</Button>
                         <Dropdown
                             position="bottomRight"
                             render={
@@ -429,7 +428,7 @@ class NavApp extends React.Component {
                                 </Dropdown.Menu>
                             }
                         >
-                            <Avatar size="small" color='light-blue' style={{margin: 4}}>BD</Avatar>
+                            <Avatar size="small" color='light-blue' style={{ margin: 4 }}>BD</Avatar>
                             <span>Bytedancer</span>
                         </Dropdown>
                     </>
@@ -480,7 +479,7 @@ class NavApp extends React.Component {
                 bodyStyle={{ height: 320 }}
                 items={[
                     { itemKey: 'user', text: '用户管理', icon: <IconUser /> },
-                    { itemKey: 'union', text: '公会中心', icon: <IconStar /> },
+                    { itemKey: 'union', text: '活动管理', icon: <IconStar /> },
                     {
                         text: '任务平台',
                         icon: <IconSetting />,
@@ -533,13 +532,15 @@ class NavApp extends React.Component {
                             text: '任务1',
                             icon: <IconSetting />,
                             itemKey: 'mission1',
-                            items: ['任务2',{
-                                text: '任务3拆解',
-                                icon: <IconSetting />,
-                                itemKey: 'mission3',
-                                items: ['子任务1', '子任务2'],
-                            }, ],
-                        },],
+                            items: ['任务2',
+                                {
+                                    text: '任务3拆解',
+                                    icon: <IconSetting />,
+                                    itemKey: 'mission3',
+                                    items: ['子任务1', '子任务2'],
+                                }
+                            ],
+                        }],
                     },
                 ]}
                 onSelect={key => console.log(key)}
@@ -580,12 +581,12 @@ class NavApp extends React.Component {
                 bodyStyle={{ height: 320 }}
                 items={[
                     { itemKey: 'user', text: '用户管理', icon: <IconUser /> },
-                    { itemKey: 'union', text: '公会中心', icon: <IconStar /> },
+                    { itemKey: 'union', text: '活动管理', icon: <IconStar /> },
                     {
                         itemKey: 'union-management',
-                        text: '公会管理',
+                        text: '任务管理',
                         icon: <IconUserGroup />,
-                        items: ['公告设置', '公会查询', '信息录入']
+                        items: ['公告设置', '任务查询', '信息录入']
                     },
                     {
                         text: '任务平台',
@@ -649,12 +650,12 @@ function NavApp (props = {}) {
 
     const items = useMemo(() => [
         { itemKey: 'user', text: '用户管理', icon: <IconUser /> },
-        { itemKey: 'union', text: '公会中心', icon: <IconStar /> },
+        { itemKey: 'union', text: '活动管理', icon: <IconStar /> },
         {
             itemKey: 'union-management',
-            text: '公会管理',
+            text: '任务管理',
             icon: <IconUserGroup />,
-            items: ['公告设置', '公会查询', '信息录入']
+            items: ['公告设置', '任务查询', '信息录入']
         },
         {
             text: '任务平台',
@@ -690,10 +691,10 @@ function NavApp (props = {}) {
 
 ### Nav
 
-| 属性                | 类型                                                                                                                             | 描述                                                                                                                           | 默认值     |
+| 属性                | 描述                                                                                                                             | 类型                                                                                                                           | 默认值     |
 | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------- |
-| bodyStyle           | 导航项列表的自定义样式                                                                             | object                                                                                                                           |           |
-| className           | 最外层元素的样式名                                                                               | boolean                                                                                                                          |           |
+| bodyStyle           | 导航项列表的自定义样式                                                                             | CSSProperties                                                                                                                           |           |
+| className           | 最外层元素的样式名                                                                               | string                                                                                                                          |           |
 | defaultIsCollapsed  | 默认是否处于收起状态,仅 `mode = "vertical"` 时有效                                                    | boolean                                                                                                                          | false     |
 | defaultOpenKeys     | 初始打开的子导航 `itemKey` 数组,仅 `mode = "vertical"` 且侧边栏处于展开状态时有效                               | string[]                                                                                                                         | []        |
 | defaultSelectedKeys | 初始选中的导航项 `itemKey` 数组                                                                   | string[]                                                                                                                         | []        |
@@ -702,7 +703,7 @@ function NavApp (props = {}) {
 | isCollapsed         | 是否处于收起状态的受控属性,仅 `mode = "vertical"` 时有效                                                 | boolean                                                                                                                          |           |
 | items               | 导航项目列表,每一项可以继续带有 items 属性。如果为 string 数组,则会取每一项作为 text 和 itemKey                         | object\| string[] \| [Item](#Nav.Item)[] \| [Sub](#Nav.Sub)[] |  |
 | limitIndent         | 解除缩进限制,可使用 level 自定义导航项缩进,水平模式只能为true >=1.27.0                                          | boolean                                                                                                                          | true      |
-| mode                | 导航类型,目前支持横向与竖直,可选值:`vertical`\                                                          | `horizontal`                                                                                                                     | string    | `vertical`           |
+| mode                | 导航类型,目前支持横向与竖直,可选值:`vertical`/| `horizontal`                                                                                                                     | string    | `vertical`           |
 | openKeys            | 受控的打开的子导航 `itemKey` 数组,配合 `onOpenChange` 回调控制子导航项展开,仅 `mode = "vertical"` 且侧边栏处于展开状态时有效 | string[]                                                                                                                         |           |
 | prefixCls           | 类名前缀                                                                                    | string                                                                                                                           | `semi`    |
 | selectedKeys        | 受控的导航项 `itemKey` 数组,配合 `onSelect` 回调控制导航项选择                                             | string[]                                                                                                                         |           |
@@ -713,10 +714,10 @@ function NavApp (props = {}) {
 | toggleIconPosition  | 带有子导航项的的父级导航项箭头位置 >=1.27.0                                                              | 'left' \| 'right'   | 'right'              |
 | tooltipHideDelay    | tooltip 隐藏的延迟,collapse 为 true 时有效,单位为 ms                                                | number                                                                                                                           | 100       |
 | tooltipShowDelay    | tooltip 显示的延迟,collapse 为 true 时有效,单位为 ms                                                | number                                                                                                                           | 0         |
-| onClick             | 点击任意导航项时触发                                                                              | ({ itemKey: string, domEvent: MouseEvent, isOpen: boolean }) => {}                                                               | () => {}  |
-| onCollapseChange    | 收起状态变化时的回调                                                                              | (isCollapsed: boolean) => {}                                                                                                     | () => {}  |
-| onOpenChange        | 切换某个子导航项目显隐状态时触发                                                                        | ({ itemKey: string, openKeys: string[], domEvent: MouseEvent, isOpen: boolean }) => {}                                           | () => {}  |
-| onSelect            | 第一次选中某个可选中导航项目时触发,其中 selectedItems 这个字段版本 >= 0.17.0 后才支持 | ({ itemKey: string, selectedKeys: string[], selectedItems: [Item](#Nav.Item)[], domEvent: MouseEvent, isOpen: boolean }) => {} | () => {}  |
+| onClick             | 点击任意导航项时触发                                                                              |<ApiType detail='({ itemKey: string, domEvent: MouseEvent, isOpen: boolean }) => void'>(itemKey, event, isOpen)=> void </ApiType>                                                                | () => {}  |
+| onCollapseChange    | 收起状态变化时的回调                                                                              |<ApiType detail='(isCollapsed: boolean) => void'>(isCollapsed)=>{} </ApiType>                                                                                                     | () => {}  |
+| onOpenChange        | 切换某个子导航项目显隐状态时触发                                                                    | <ApiType detail='({ itemKey: string, openKeys: string[], domEvent: MouseEvent, isOpen: boolean }) => void'> ({itemKey, openKeys, event, isOpen})=>{}</ApiType>                                      | () => {}  |
+| onSelect            | 第一次选中某个可选中导航项目时触发,其中 selectedItems 这个字段版本 >= 0.17.0 后才支持 | <ApiType detail='({ itemKey: string, selectedKeys: string[], selectedItems: Item[], domEvent: MouseEvent, isOpen: boolean }) => void'>(onSelectProps)=>{}</ApiType> | () => {}  |
 
 ### Nav.Item
 
@@ -765,13 +766,13 @@ function NavApp (props = {}) {
 
 ### Nav.Footer
 
-| 属性           | 描述                                                                                     | 类型                                      | 默认值 | 版本           |
-| -------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------- | ------ | -------------- |
-| children       | 子元素                                                                                   | ReactNode                                 |        |                |
-| className      | 最外层样式名                                                                             | string                                    |        |                |
-| collapseButton | 是否展示底部“收起侧边栏”按钮,mode="vertical" 且 Footer 组件的 children 参数为空才有效果 | boolean\|ReactNode                        | false  |                |
-| collapseText   | “收起”按钮的文案                                                                         | (collapsed:boolean) => string\|ReactNode |        | 0.35.0 |
-| style          | 最外层样式                                                                               | CSSProperties                                    |        |                |
+| 属性           | 描述                                                                                     | 类型                                      | 默认值 |
+| -------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------- | ------ |
+| children       | 子元素                                                                                   | ReactNode                                 |        |         
+| className      | 最外层样式名                                                                             | string                                    |        |      
+| collapseButton | 是否展示底部“收起侧边栏”按钮,mode="vertical" 且 Footer 组件的 children 参数为空才有效果 | boolean\|ReactNode                        | false  |          
+| collapseText   | “收起”按钮的文案                                                                         | (collapsed:boolean) => string\|ReactNode |        |
+| style          | 最外层样式                                                                               | CSSProperties                                    |        |  
 
 
 ## 文案规范

+ 1 - 0
content/order.js

@@ -52,6 +52,7 @@ const order = [
     'descriptions',
     'dropdown',
     'empty',
+    'image',
     'list',
     'modal',
     'overflowlist',

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

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

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

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

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

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

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 72
+order: 73
 category: 其他
 title:  LocaleProvider 多语言
 icon: doc-i18n

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

@@ -427,7 +427,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 |  |  |
-| getPopupContainer | Specifies the parent DOM, and the bullet layer will be rendered to the DOM, you need to set 'position: relative` | function():HTMLElement | () = > document.body |
+| getPopupContainer | Specifies the parent DOM, and the bullet layer will be rendered to the DOM, you need to set 'position: relative` | function():HTMLElement | () => document.body |
 | mouseEnterDelay | After the mouse is moved into the Trigger, the display time is delayed, in milliseconds (only effective when the trigger is hover/focus) | number | 50 |  |
 | mouseLeaveDelay | The time for the delay to disappear after the mouse moves out of the pop-up layer, in milliseconds (only effective when the trigger is hover/focus) | number | 50 |  |
 | menu | Menu content config | Array<DropdownMenuItem\> | [] | **1.12.0** |

+ 509 - 0
content/show/image/index-en-US.md

@@ -0,0 +1,509 @@
+---
+localeCode: en-US
+order: 54
+category: Show
+title: Image
+icon: doc-image
+brief: Used to display and preview images.
+---
+
+## Demos
+
+### How to import
+
+Image, ImagePreview supported since v2.20.0
+
+```jsx import
+import { Image, ImagePreview } from '@douyinfe/semi-ui';
+```
+
+### Basic usage
+
+You can get an image with preview function by specifying the image path through `src`, and specify the width and height of the image through `width`, `height`
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Image } from '@douyinfe/semi-ui';
+
+() => (  
+    <Image 
+        width={360}
+        height={200}
+        src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg"
+    />
+);
+```
+
+### Loading failed placeholder
+
+You can customize the placeholder for failed loading through `fallback`, which supports string and ReactNode
+
+```jsx live=true
+import React from 'react';
+import { Image } from '@douyinfe/semi-ui';
+
+() => (
+    <div style={{ display: 'flex', alignItem: 'center', flexDirection: 'column' }}>
+        <span>Failed to load default style</span>
+        <Image 
+            width={200}
+            height={200}
+            src="https://load-error.jpeg"
+        />
+        <br />
+        <span>Custom loading failed placeholder map</span>
+        <Image 
+            width={200}
+            height={200}
+            src="https://load-error.jpeg"
+            fallback={<IconUploadError style={{ fontSize: 50 }} />}
+            />
+    </div>
+);
+```
+
+### Progressive loading
+
+Large images can be progressively loaded through `placeholder`
+
+```jsx live=true
+import React from 'react';
+import { Image, Button } from '@douyinfe/semi-ui';
+
+() => {
+    const [timestamp, setTimestamp] = React.useState('');
+    return (  
+        <>
+            <Image 
+                width={300}
+                height={200}
+                src={`https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract-big.png?${timestamp}`}
+                placeholder={<Image 
+                    src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract-small.jpeg'
+                    width={300}
+                    height={200}
+                    preview={false}
+                />}
+            />
+            <br />
+            <Button 
+                theme={'solid'}
+                onClick={() => {
+                    setTimestamp(Date.now());
+                }}
+                style={{ marginTop: 10 }}
+            >Reload</Button>
+        </>
+    );
+};
+```
+
+### Customize the preview image
+
+You can customize the preview image by setting the `src` of the Image component to be different from the `src` in the `preview` parameter
+
+ ```jsx live=true
+import React from 'react';
+import { Image } from '@douyinfe/semi-ui';
+
+() => {
+     return ( 
+          <Image
+             width={300}
+             height={200}
+             src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract-small.jpeg'}
+             preview={{
+                 src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract-big.png'
+             }}
+         />
+     );
+};
+```
+
+### Multi-image preview
+
+Use ImagePreview to wrap Image to achieve multi-image preview
+
+```jsx live=true dir="column"
+import React, { useMemo } from 'react';
+import { Image, ImagePreview } from '@douyinfe/semi-ui';
+
+() => {
+    const srcList = useMemo(() => ([
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/greenleaf.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/colorful.jpg",
+    ]), []);
+
+    return ( 
+        <ImagePreview>
+            {srcList.map((src, index) => {
+                return (
+                    <Image 
+                        key={index} 
+                        src={src} 
+                        width={200} 
+                        alt={`lamp${index + 1}`}
+                        style={{ marginRight: 5 }} 
+                    />
+                );
+            })}
+        </ImagePreview>
+    );
+};
+```
+
+### Use the preview component alone
+
+The preview component ImagePreview can be used alone, through `visible` and `onVisibleChange` to control whether to preview, and `src` to pass in the image that can be previewed
+
+```jsx live=true
+import React, { useMemo, useCallback } from 'react';
+import { ImagePreview, Button } from '@douyinfe/semi-ui';
+
+
+() => {
+    const srcList = useMemo(() => ([
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/greenleaf.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/colorful.jpg",
+    ]), []);
+
+    const [visible1, setVisible1] = useState(false);
+    const [visible2, setVisible2] = useState(false);
+
+    const visibleChange1 = useCallback((v) => {
+        setVisible1(v);
+    }, []);
+
+    const visibleChange2 = useCallback((v) => {
+        setVisible2(v);
+    }, []);
+
+    const onButton1Click = useCallback((v) => {
+        setVisible1(true);
+    }, []);
+
+    const onButton2Click = useCallback((v) => {
+        setVisible2(true);
+    }, []);
+
+    return ( 
+        <>
+            <Button onClick={onButton1Click}>Preview single Image</Button>
+            <ImagePreview
+                src={"https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg"}
+                visible={visible1}
+                onVisibleChange={visibleChange1}
+            />
+            <br /> 
+            <Button onClick={onButton2Click} style={{ marginTop: 20 }}>Preview multiple Images</Button>
+            <ImagePreview
+                src={srcList}
+                visible={visible2}
+                onVisibleChange={visibleChange2}
+            />
+        </>
+    );
+};
+```
+
+### Render in the specified container
+
+You can specify the parent DOM of the preview component through `getPopupContainer` (you need to specify `position: relative`), and the image preview will be rendered into this DOM
+
+```jsx live=true dir="column"
+import React, { useMemo } from 'react';
+import { Image, ImagePreview } from '@douyinfe/semi-ui';
+
+() => {
+    const srcList = useMemo(() => ([
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/greenleaf.jpg",
+    ]), []);
+
+    return ( 
+        <>
+            <div 
+                id="container" 
+                style={{ 
+                    height: 400, 
+                    position: "relative" 
+                }} 
+            >
+                <ImagePreview
+                    getPopupContainer={() => {
+                        const node = document.getElementById("container");
+                        return node;
+                    }}
+                    style={{
+                        height: '100%',
+                        display: 'flex',
+                        alignItems: 'center',
+                        justifyContent: 'center',
+        
+                    }}
+                >
+                    {srcList.map((src, index) => {
+                        return (
+                            <Image 
+                                key={index} 
+                                src={src} 
+                                width={200} 
+                                alt={`lamp${index + 1}`}
+                                style={{ marginRight: 5 }} 
+                            />
+                        );
+                    })}
+                </ImagePreview>
+            </div>
+        </>
+    );
+};
+```
+
+### Customize the bottom operation area of the preview
+
+The bottom action area of the preview can be customized using `renderPreviewMenu`
+
+```jsx live=true dir="column"
+import React, { useMemo, useCallback } from 'react';
+import { Image, ImagePreview, Button } from '@douyinfe/semi-ui';
+import { IconChevronLeft, IconChevronRight, IconMinus, IconPlus, IconRotate, IconDownload, IconRealSizeStroked, IconWindowAdaptionStroked } from "@douyinfe/semi-icons";
+
+() => {
+    const srcList = useMemo(() => ([
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/greenleaf.jpg",
+    ]), []);
+
+    const renderPreviewMenu = useCallback((props) => {
+        const {
+            ratio,
+            disabledPrev,
+            disabledNext,
+            disableZoomIn,
+            disableZoomOut,
+            disableDownload,
+            onDownload,
+            onNext,
+            onPrev,
+            onRotateLeft,
+            onRatioClick,
+            onZoomIn,
+            onZoomOut,
+        } = props;
+        return (
+            <div 
+                style={{ 
+                    background: "grey", 
+                    height: 40, 
+                    width: 280, 
+                    display: "flex",
+                    alignItems: "center",
+                    justifyContent: "space-around",
+                    borderRadius: 3,
+                }}
+            >
+                <Button
+                    icon={<IconChevronLeft size="large" />}
+                    type="tertiary"
+                    onClick={!disabledPrev ? onPrev : undefined}
+                    disabled={disabledPrev}
+                />
+                <Button
+                    icon={<IconChevronRight size="large" />}
+                    type="tertiary"                     
+                    onClick={!disabledNext ? onNext : undefined}
+                    disabled={disabledNext}
+                />
+                <Button
+                    icon={<IconMinus size="large" />}
+                    type="tertiary"
+                    onClick={!disableZoomOut ? onZoomOut : undefined}
+                    disabled={disableZoomOut} 
+                />
+                <Button
+                    icon={<IconPlus size="large" />}
+                    type="tertiary"
+                    onClick={!disableZoomIn ? onZoomIn : undefined} 
+                    disabled={disableZoomIn}
+                />
+                <Button
+                    icon={ratio === "adaptation" ? <IconRealSizeStroked size="large" /> : <IconWindowAdaptionStroked size="large" />}
+                    type="tertiary"
+                    onClick={onRatioClick} 
+                />
+                <Button
+                    icon={<IconRotate size="large" />}
+                    type="tertiary"
+                    onClick={onRotateLeft}
+                />
+                <Button
+                    icon={<IconDownload size="large" />}
+                    type="tertiary"
+                    onClick={!disableDownload ? onDownload : undefined}
+                    disabled={disableDownload}
+                />
+            </div>);
+    }, []);
+
+
+    return ( 
+        <ImagePreview renderPreviewMenu={renderPreviewMenu}>
+            {srcList.map((src, index) => {
+                return (
+                    <Image 
+                        key={index} 
+                        src={src} 
+                        width={200} 
+                        alt={`lamp${index + 1}`}
+                        style={{ marginRight: 5 }} 
+                    />
+                );
+            })}
+        </ImagePreview>
+    );
+};
+```
+
+### Customize the preview top display area
+
+You can customize the preview top display area through `renderHeader`
+
+```jsx live=true dir="column"
+import React, { useMemo } from 'react';
+import { Image, ImagePreview } from '@douyinfe/semi-ui';
+
+() => {
+    const srcList = useMemo(() => ([
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg",
+    ]), []);
+
+    return (
+        <>  
+            <ImagePreview
+                renderHeader={(title) => (
+                    <div 
+                        style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center" }}>
+                        <span style={{ background: "black", padding: '0 10px' }}>Custom title:{title}</span>
+                    </div>
+                )}
+            >
+                <div >
+                    {srcList.map((src, index) => {
+                        return (
+                            <Image 
+                                key={index} 
+                                src={src} 
+                                width={200} 
+                                alt={`lamp${index + 1}`} 
+                                preview={{
+                                    previewTitle: `lamp${index + 1}`,
+                                }} 
+                                style={{ marginRight: 5 }}
+                            />
+                        );
+                    })}
+                </div>
+            </ImagePreview>
+        </>
+    );
+};
+```
+
+## API reference
+
+### Image
+
+| Properties       |Instructions                          | Type             | Default | Version |
+|------------------|--------------------------------------|------------------|---------|---------|
+| alt              | Image description                    | string            | - | |
+| className        | custom style class name              | string            | - | |
+| crossOrigin      | Passthrough to the crossorigin of the native img tag | 'anonymous' \| 'use-credentials' |-| |
+| fallback         | Custom loading failed display content | ReactNode  | - | |
+| height           | Image display height                 | number            | - | |
+| onError          | Load error callback                  | (event: Event) => void | - | |
+| onLoad           | Load success callback                | (event: Event) => void | - | |
+| placeholder      | Placeholder content when the image is not loaded | ReactNode | - | |
+| preview          | Preview parameter, when false, disable preview   | boolean \| ImagePreview | - | |
+| src              | Image acquisition address            | string            | - | |
+| style            | custom style                         | CSSProperties     | - | |
+| width            | Image display width                  | number            | - | |
+
+### ImagePreview
+
+| Properties       | Instructions            | Type            | Default | Version |
+|------------------|-------------------------|-----------------|---------|---------|
+| adaptiveTip      | Adapt to page action button prompts |string  | "Adapt to the page" | |
+| className        | Custom style class name | string          | - | |
+| closable         | Whether to show the close button   | Boolean | true | |
+| closeOnEsc       | Hit esc to close the preview | boolean        | true | |
+| currentIndex     | Controlled property, the current preview image subscript | number | - | |
+| defaultCurrentIndex | First display image subscript | number | - | |
+| defaultVisible   | Whether to open the preview for the first time | boolean | - | |
+| disableDownload  | Disable downloads        | boolean        | false | |
+| downloadTip      | Download action button prompt | string  | "Download" | |
+| getPopupContainer | Specify the parent DOM, and the pop-up layer will be rendered into the DOM. For customization, you need to set container `position: relative`|() => HTMLElement;  | () => document.body | |
+| infinite         | Whether to loop infinitely  | boolean        | false | |
+| lazyLoad         | Whether to enable lazy loading | boolean      | true | |
+| lazyLoadMargin   | Pass to the rootMargin parameter in options, refer to [Intersection Observer API](https://developer.mozilla.org/zh-CN/docs/Web/API/Intersection_Observer_API#interfaces) | string | "0px 100px 100px 0px" | |
+| maskClosable     | Whether the mask can be closed by clicking | Boolean  | true | |
+| nextTip          | Next action button prompt   | string  | "Next" | |
+| originTip        | Original size action button tips |string  | "Original size" | |
+| onChange         | Event triggered by switching pictures  | (index: number) => void | - | |
+| onClose          | The callback function when the close button is clicked  | () => void | - | |
+| onDownload       | Image download callback function  | (src: string, index: number) => void | - | |
+| onRotateLeft     | Callback for rotating the image     | (angle: number) => void | - | |
+| onNext           | Callback for switching pictures backwards   | (index: number) => void | - | |
+| onPrev           | Callback for switching the picture forward  | (index: number) => void | - | |
+| onZoomIn         | The callback function when the image is zoomed in  | (zoom: number) => void | - | |
+| onZoomOut        | The callback function when the image is zoomed out  | (zoom: number) => void | - | |
+| onVisibleChange  | Callback triggered by toggle visible state   | (visible: boolean) => void  | - | |
+| preLoad          | Whether to enable preloading | boolean | true | |
+| preLoadGap       | Preloaded step size      | number         | 2 | |
+| previewTitle     | Custom preview title     | ReactNode      | - | |
+| prevTip          | Previous operation button prompt   | string  | "Previous" | |
+| renderHeader     | Custom render preview top info  |(info: reactNode) => ReactNode  | - | |
+| renderPreviewMenu | Custom render preview bottom menu information | (props: MenuProps) => ReactNode; | - | |
+| rotateTip        | Tips for rotating action buttons  |string        | "Rotate" | |
+| showTooltip      | Whether to display the bottom operation area prompt | boolean | false | |
+| src              | Image list information  | string \| string[] | - | |
+| style            | Custom style            | CSSProperties   | - | |
+| viewerVisibleDelay | The length of time of inactivity before hiding the preview action button | number | 10000 | |
+| visible          | Controlled property, whether to preview  | boolean | - | |
+| zIndex           | Preview layer hierarchy  | number        | 1070 | |
+| zoomInTip        | Zoom in action button tips | string | "Zoom in" | |
+| zoomOutTip       | Zoom out action button prompt | string | "Zoom out" | |
+| zoomStep         | Image reduction/enlargement ratio each time | number | 0.1 | |
+
+### MenuProps
+
+| Properties       | Instructions            | Type             |
+|------------------|-------------------------|------------------|
+| curPage          | Current image page subscript         | number |
+| disabledPrev     | Whether to disable the left toggle button  | boolean |
+| disabledNext     | Whether to disable the right toggle button | boolean |
+| disableDownload  | Whether to disable the download button     | boolean |
+| max              | The maximum ratio of image zoom      | number |
+| min              | The minimum ratio of image scaling   | number |
+| onDownload       | Call function when the image is downloaded | () => void |
+| onZoomIn         | Call function when the image is zoomed in  | () => void |
+| onZoomOut        | Call function when the image is zoomed out | () => void |
+| onPrev           | Call function to switch the picture forward  | () => void |
+| onNext           | Call function to switch the picture backward | () => void |
+| onRotateLeft     | Call function to rotate the image counterclockwise | () => void |
+| onRotateRight    | Call function to rotate the image clockwise | () => void |
+| ratio            | Original size or Fit to page button state  | "adaptation" \| "realSize" |
+| step             | Step size of scaling                 | number |
+| totalNum         | The total number of images that can be previewed | number |
+| zoom             | Current image magnification ratio    | number |
+
+## Design Token
+
+<DesignToken/>

+ 509 - 0
content/show/image/index.md

@@ -0,0 +1,509 @@
+---
+localeCode: zh-CN
+order: 54
+category: 展示类
+title: Image 图片
+icon: doc-image
+brief: 用于展示和预览图片。
+---
+
+## 代码演示
+
+### 如何引入
+
+Image, ImagePreview 从 v2.20.0 版本开始支持
+
+```jsx import
+import { Image, ImagePreview } from '@douyinfe/semi-ui';
+```
+
+### 基本用法
+
+通过 `src` 指定图片路径即可获取一个具有预览功能的图片,通过 `width`,`height` 指定图片的宽高
+
+```jsx live=true dir="column"
+import React from 'react';
+import { Image } from '@douyinfe/semi-ui';
+
+() => (  
+    <Image 
+        width={360}
+        height={200}
+        src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg"
+    />
+);
+```
+
+### 加载失败的占位图
+
+可通过 `fallback` 自定义加载失败的占位图,该参数类型支持 string 和 ReactNode
+
+```jsx live=true
+import React from 'react';
+import { Image } from '@douyinfe/semi-ui';
+
+() => (
+    <div style={{ display: 'flex', alignItem: 'center', flexDirection: 'column' }}>
+        <span>加载失败默认样式</span>
+        <Image 
+            width={200}
+            height={200}
+            src="https://load-error.jpeg"
+        />
+        <br />
+        <span>自定义加载失败占位图</span>
+        <Image 
+            width={200}
+            height={200}
+            src="https://load-error.jpeg"
+            fallback={<IconUploadError style={{ fontSize: 50 }} />}
+        />
+    </div>
+);
+```
+
+### 渐进加载
+
+大图可通过`placeholder`实现渐进加载
+
+```jsx live=true
+import React from 'react';
+import { Image, Button } from '@douyinfe/semi-ui';
+
+() => {
+    const [timestamp, setTimestamp] = React.useState('');
+    return (  
+        <>
+            <Image 
+                width={300}
+                height={200}
+                src={`https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract-big.png?${timestamp}`}
+                placeholder={<Image 
+                    src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract-small.jpeg'
+                    width={300}
+                    height={200}
+                    preview={false}
+                />}
+            />
+            <br />
+            <Button 
+                theme={'solid'}
+                onClick={() => {
+                    setTimestamp(Date.now());
+                }}
+                style={{ marginTop: 10 }}
+            >Reload</Button>
+        </>
+    );
+};
+```
+
+### 自定义预览图片
+
+可以通过设置 Image 组件的 `src` 和 `preview` 参数中的 `src` 不同来自定义预览图片
+
+ ```jsx live=true
+import React from 'react';
+import { Image } from '@douyinfe/semi-ui';
+
+() => {
+     return ( 
+         <Image
+             width={300}
+             height={200}
+             src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract-small.jpeg'}
+             preview={{
+                 src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract-big.png'
+             }}
+         />
+     );
+};
+```
+
+### 多图预览
+
+使用 ImagePreview 包裹 Image 即可实现多图片预览
+
+```jsx live=true dir="column"
+import React, { useMemo } from 'react';
+import { Image, ImagePreview } from '@douyinfe/semi-ui';
+
+() => {
+    const srcList = useMemo(() => ([
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/greenleaf.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/colorful.jpg",
+    ]), []);
+
+    return ( 
+        <ImagePreview>
+            {srcList.map((src, index) => {
+                return (
+                    <Image 
+                        key={index} 
+                        src={src} 
+                        width={200} 
+                        alt={`lamp${index + 1}`} 
+                        style={{ marginRight: 5 }}
+                    />
+                );
+            })}
+        </ImagePreview>
+    );
+};
+```
+
+### 单独使用预览组件
+
+预览组件 ImagePreview 可以单独使用,通过 `visible` 和 `onVisibleChange` 控制是否预览,通过 `src` 传入可以预览的图片
+
+```jsx live=true
+import React, { useMemo, useCallback } from 'react';
+import { ImagePreview, Button } from '@douyinfe/semi-ui';
+
+
+() => {
+    const srcList = useMemo(() => ([
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/greenleaf.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/colorful.jpg",
+    ]), []);
+
+    const [visible1, setVisible1] = useState(false);
+    const [visible2, setVisible2] = useState(false);
+
+    const visibleChange1 = useCallback((v) => {
+        setVisible1(v);
+    }, []);
+
+    const visibleChange2 = useCallback((v) => {
+        setVisible2(v);
+    }, []);
+
+    const onButton1Click = useCallback((v) => {
+        setVisible1(true);
+    }, []);
+
+    const onButton2Click = useCallback((v) => {
+        setVisible2(true);
+    }, []);
+
+    return ( 
+        <>
+            <Button onClick={onButton1Click}>Preview single Image</Button>
+            <ImagePreview
+                src={"https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg"}
+                visible={visible1}
+                onVisibleChange={visibleChange1}
+            />
+            <br /> 
+            <Button onClick={onButton2Click} style={{ marginTop: 20 }}>Preview multiple Images</Button>
+            <ImagePreview
+                src={srcList}
+                visible={visible2}
+                onVisibleChange={visibleChange2}
+            />
+        </>
+    );
+};
+```
+
+### 渲染在指定容器
+
+可以通过 `getPopupContainer` 指定预览组件的父级 DOM(需要指定 `position: relative`),图片预览将会渲染至该 DOM 中
+
+```jsx live=true dir="column"
+import React, { useMemo } from 'react';
+import { Image, ImagePreview } from '@douyinfe/semi-ui';
+
+() => {
+    const srcList = useMemo(() => ([
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/greenleaf.jpg",
+    ]), []);
+
+    return ( 
+        <>
+            <div 
+                id="container" 
+                style={{ 
+                    height: 400, 
+                    position: "relative" 
+                }} 
+            >
+                <ImagePreview
+                    getPopupContainer={() => {
+                        const node = document.getElementById("container");
+                        return node;
+                    }}
+                    style={{
+                        height: '100%',
+                        display: 'flex',
+                        alignItems: 'center',
+                        justifyContent: 'center',
+        
+                    }}
+                >
+                    {srcList.map((src, index) => {
+                        return (
+                            <Image 
+                                key={index} 
+                                src={src} 
+                                width={200} 
+                                alt={`lamp${index + 1}`}
+                                style={{ marginRight: 5 }} 
+                            />
+                        );
+                    })}
+                </ImagePreview>
+            </div>
+        </>
+    );
+};
+```
+
+### 自定义预览底部操作区
+
+可以使用 `renderPreviewMenu` 自定义预览底部操作区域
+
+```jsx live=true dir="column"
+import React, { useMemo, useCallback } from 'react';
+import { Image, ImagePreview, Button } from '@douyinfe/semi-ui';
+import { IconChevronLeft, IconChevronRight, IconMinus, IconPlus, IconRotate, IconDownload, IconRealSizeStroked, IconWindowAdaptionStroked } from "@douyinfe/semi-icons";
+
+() => {
+    const srcList = useMemo(() => ([
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/greenleaf.jpg",
+    ]), []);
+
+    const renderPreviewMenu = useCallback((props) => {
+        const {
+            ratio,
+            disabledPrev,
+            disabledNext,
+            disableZoomIn,
+            disableZoomOut,
+            disableDownload,
+            onDownload,
+            onNext,
+            onPrev,
+            onRotateLeft,
+            onRatioClick,
+            onZoomIn,
+            onZoomOut,
+        } = props;
+        return (
+            <div 
+                style={{ 
+                    background: "grey", 
+                    height: 40, 
+                    width: 280, 
+                    display: "flex",
+                    alignItems: "center",
+                    justifyContent: "space-around",
+                    borderRadius: 3,
+                }}
+            >
+                <Button
+                    icon={<IconChevronLeft size="large" />}
+                    type="tertiary"
+                    onClick={!disabledPrev ? onPrev : undefined}
+                    disabled={disabledPrev}
+                />
+                <Button
+                    icon={<IconChevronRight size="large" />}
+                    type="tertiary"                     
+                    onClick={!disabledNext ? onNext : undefined}
+                    disabled={disabledNext}
+                />
+                <Button
+                    icon={<IconMinus size="large" />}
+                    type="tertiary"
+                    onClick={!disableZoomOut ? onZoomOut : undefined}
+                    disabled={disableZoomOut} 
+                />
+                <Button
+                    icon={<IconPlus size="large" />}
+                    type="tertiary"
+                    onClick={!disableZoomIn ? onZoomIn : undefined} 
+                    disabled={disableZoomIn}
+                />
+                <Button
+                    icon={ratio === "adaptation" ? <IconRealSizeStroked size="large" /> : <IconWindowAdaptionStroked size="large" />}
+                    type="tertiary"
+                    onClick={onRatioClick} 
+                />
+                <Button
+                    icon={<IconRotate size="large" />}
+                    type="tertiary"
+                    onClick={onRotateLeft}
+                />
+                <Button
+                    icon={<IconDownload size="large" />}
+                    type="tertiary"
+                    onClick={!disableDownload ? onDownload : undefined}
+                    disabled={disableDownload}
+                />
+            </div>);
+    }, []);
+
+
+    return ( 
+        <ImagePreview renderPreviewMenu={renderPreviewMenu}>
+            {srcList.map((src, index) => {
+                return (
+                    <Image 
+                        key={index} 
+                        src={src} 
+                        width={200} 
+                        alt={`lamp${index + 1}`} 
+                        style={{ marginRight: 5 }}
+                    />
+                );
+            })}
+        </ImagePreview>
+    );
+};
+```
+
+### 自定义预览顶部展示区
+
+通过 `renderHeader` 可以自定义预览顶部展示区
+
+```jsx live=true dir="column"
+import React, { useMemo } from 'react';
+import { Image, ImagePreview } from '@douyinfe/semi-ui';
+
+() => {
+    const srcList = useMemo(() => ([
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg",
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/sky.jpg",
+    ]), []);
+
+    return (
+        <>  
+            <ImagePreview
+                renderHeader={(title) => (
+                    <div 
+                        style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center" }}>
+                        <span style={{ background: "black", padding: '0 10px' }}>自定义标题:{title}</span>
+                    </div>
+                )}
+            >
+                <div >
+                    {srcList.map((src, index) => {
+                        return (
+                            <Image 
+                                key={index} 
+                                src={src} 
+                                width={200} 
+                                alt={`lamp${index + 1}`} 
+                                preview={{
+                                    previewTitle: `lamp${index + 1}`,
+                                }}
+                                style={{ marginRight: 5 }} 
+                            />
+                        );
+                    })}
+                </div>
+            </ImagePreview>
+        </>
+    );
+};
+```
+
+## API 参考
+
+### Image
+
+| 属性              | 说明                                    | 类型              | 默认值 | 版本 |
+|-------------------|---------------------------------------|-------------------|------|------|
+| alt               | 图像描述                                | string            | - | |
+| className         | 自定义样式类名                           | string            | - | |
+| crossOrigin       | 透传给原生 img 标签的 crossorigin         | 'anonymous'|'use-credentials'| - | |
+| fallback          | 加载失败容错地址或者自定义加载失败时的显示内容 | ReactNode  | - | |
+| height            | 图片显示高度                             | number            | - | |
+| onError           | 加载错误回调                              | (event: Event) => void | - | |
+| onLoad            | 加载成功回调                              | (event: Event) => void | - | |
+| placeholder       | 图片未加载时候的占位内容                   | ReactNode         | - | |
+| preview           | 预览参数,为 false 时候禁用预览            | boolean \| ImagePreview | - | |
+| src               | 图片获取地址                             | string            | - | |
+| style             | 自定义样式                              | CSSProperties     | - | |
+| width             | 图片显示宽度                             | number            | - | |
+
+### ImagePreview
+
+| 属性               | 说明                    | 类型              | 默认值 | 版本 |
+|-------------------|-------------------------|------------------|-------|-----|
+| adaptiveTip       | 适应页面操作按钮提示       | string        | "适应页面" | |
+| className         | 自定义样式类名            | string           | - | |
+| closable          | 是否显示关闭按钮           | boolean        | true | |
+| closeOnEsc        | 点击 esc 关闭预览         | boolean        | true | |
+| currentIndex      | 受控属性,当前预览图片下标  | number               | - | |
+| defaultCurrentIndex | 首次展示图片下标        | number             | - | |
+| defaultVisible    | 首次是否开启预览           | boolean         | - | |
+| disableDownload   | 禁用下载                  | boolean        | false | |
+| downloadTip       | 下载操作按钮提示          | string         | "下载" | |
+| getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 container `position: relative` | () => HTMLElement  | () => document.body | |
+| infinite          | 是否无限循环              | boolean       | false | |
+| lazyLoad          | 是否开启懒加载             | boolean      | true | |
+| lazyLoadMargin    | 传给 options 中的rootMargin 参数,参考 [Intersection Observer API](https://developer.mozilla.org/zh-CN/docs/Web/API/Intersection_Observer_API#interfaces) | string | "0px 100px 100px 0px" | |
+| maskClosable      | 点击遮罩是否可关闭         | boolean        | true | |
+| nextTip           | 下一步操作按钮提示         | string         | "下一步" | |
+| originTip         | 原始尺寸操作按钮提示       | string        | "原始尺寸" | |
+| onChange          | 切换图片触发的事件  | (index: number) => void | - | |
+| onClose           | 点击关闭按钮时的回调函数  | () => void | - | |
+| onDownload        | 图片下载回调函数     | (src: string, index: number) => void | - | |
+| onRotateLeft      | 旋转图片的回调      | (angle: number) => void | - | |
+| onNext            | 向后切换图片的回调   | (index: number) => void | - | |
+| onPrev            | 向前切换图片的回调   | (index: number) => void | - | |
+| onZoomIn          | 图片放大时的回调函数  | (zoom: number) => void | - | |
+| onZoomOut         | 图片缩小时的回调函数  | (zoom: number) => void | - | |
+| onVisibleChange   | 切换可见状态触发的回调   | (visible: boolean) => void | - | |
+| preLoad           | 是否开启预加载             | boolean        | true | |
+| preLoadGap        | 预加载的步长               | number         | 2 | |
+| previewTitle      | 自定义预览 title          | ReactNode      | - | |
+| prevTip           | 上一步操作按钮提示         | string         | "上一步" | |
+| renderHeader      | 自定义渲染预览顶部信息     | (info: ReactNode) => ReactNode  | - | |
+| renderPreviewMenu | 自定义渲染预览底部菜单信息  | (props: MenuProps) => ReactNode;| - | |
+| rotateTip         | 旋转操作按钮提示                    | string         | "旋转" | |
+| showTooltip       | 是否展示底部操作区提示      | boolean        | false | |
+| src               | 图片列表信息              | string \| string[] | - | |
+| style             | 自定义样式               | CSSProperties    | - | |
+| viewerVisibleDelay | 隐藏预览操作按钮前的无操作时长 | number         | 10000 | |
+| visible           | 受控属性,是否预览         | boolean         | - | |
+| zIndex            | 预览层层级                | number        | 1070 | |
+| zoomInTip         | 放大操作按钮提示           | string         | "放大" | |
+| zoomOutTip        | 缩小操作按钮提示           | string        | "缩小" | |
+| zoomStep          | 图片每次缩小/放大比例       | number        | 0.1 | |
+
+### MenuProps
+
+| 属性               | 说明                     | 类型    |
+|-------------------|--------------------------|--------|
+| curPage           | 当前图片页下标              | number |
+| disabledPrev      | 是否禁用向左切换按钮         | boolean |
+| disabledNext      | 是否禁用向右切换按钮         | boolean |
+| disableDownload   | 是否禁用下载按钮            | boolean |
+| max               | 图片缩放最大比例            | number |
+| min               | 图片缩放最小比例            | number |
+| onDownload        | 图片下载的调用函数           | () => void |
+| onZoomIn          | 图片放大时的调用函数         | () => void |
+| onZoomOut         | 图片缩小时的调用函数         | () => void |
+| onPrev            | 向前切换图片的调用函数       | () => void |
+| onNext            | 向后切换图片的调用函数       | () => void |
+| onRotateLeft      | 逆时针旋转图片的调用函数     | () => void |
+| onRotateRight     | 顺时针旋转图片的调用函数     | () => void |
+| ratio             | 原始尺寸或适应页面按钮状态  | "adaptation" \| "realSize"|
+| step              | 缩放的比例步长              | number |
+| totalNum          | 可预览的总图片数            | number |
+| zoom              | 当前图片缩放比例            | number |
+
+## 设计变量
+
+<DesignToken/>

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 54
+order: 55
 category: Show
 title: List
 subTitle: List
@@ -13,7 +13,7 @@ brief: Lists display a set of related contents
 
 ### How to import
 
-```jsx import 
+```jsx import
 import { List } from '@douyinfe/semi-ui';
 ```
 

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 54
+order: 55
 category: 展示类
 title: List 列表
 icon: doc-list

+ 7 - 3
content/show/modal/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 55
+order: 56
 category: Show
 title:  Modal
 subTitle: Modal
@@ -696,11 +696,15 @@ WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/
 
 ## FAQ
 
--   Why the button texts in Modal.confirm are not internationalized even when I use LocaleProvider? 
+- #### Why the button texts in Modal.confirm are not internationalized even when I use LocaleProvider? 
     In version >= 1.2.0, you could use `Modal.useModal` to create a `contextHolder` that is accessible to config from ConfigProvider or LocaleProvider.
 
     For version before 1.2 or if you don't want to use Hooks, you could also use `okText` and `cancelText` to set i18 texts at this moment.  
-    
+
+- #### Why is the spacing between title and content different under imperative and non-imperative calls?
+    In the imperative call scenario, the title and content are more closely related, so expressing this strong correlation with a closer distance is in line with expectations. If users don't want this effect, they can do their own style overrides.
+
+
 <!-- ## Related Material
 ```material
 1, 55

+ 228 - 219
content/show/modal/index.md

@@ -1,13 +1,12 @@
 ---
 localeCode: zh-CN
-order: 55
+order: 56
 category: 展示类
-title:  Modal 模态对话框
+title: Modal 模态对话框
 icon: doc-modal
 brief: 模态对话框用于等待用户响应、告知用户重要信息或在不丢失上下文的情况下展示更多信息
 ---
 
-
 ## 代码演示
 
 ### 如何引入
@@ -22,55 +21,41 @@ import { Modal } from '@douyinfe/semi-ui';
 import React from 'react';
 import { Modal, Button } from '@douyinfe/semi-ui';
 
-class modalDemo extends React.Component {
-    constructor() {
-        super();
-        this.state = {visible: false};
-        this.showDialog = this.showDialog.bind(this);
-        this.handleOk = this.handleOk.bind(this);
-        this.handleCancel = this.handleCancel.bind(this);
-    }
-    showDialog() {
-        this.setState({
-            visible: true
-        });
-    }
-    handleOk(e) {
-        this.setState({
-            visible: false
-        });
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    const handleOk = () => {
+        setVisible(false);
         console.log('Ok button clicked');
-    }
-    handleAfterClose(){
-        console.log('After Close callback executed');
-    }
-    handleCancel(e) {
-        this.setState({
-            visible: false
-        });
+    };
+    const handleCancel = () => {
+        setVisible(false);
         console.log('Cancel button clicked');
-    }
-    render() {
-        return (
-            <>
-                <Button onClick={this.showDialog}>打开弹窗</Button>
-                <Modal
-                    title="基本对话框"
-                    visible={this.state.visible}
-                    onOk={this.handleOk}
-                    afterClose={this.handleAfterClose} //>=1.16.0
-                    onCancel={this.handleCancel}
-                    closeOnEsc={true}
-                >
-                    This is the content of a basic modal.
-                    <br/>
-                    More content...
-                </Modal>
-            </>
-        );
-    }
-}
+    };
+    const handleAfterClose = () => {
+        console.log('After Close callback executed');
+    };
 
+    return (
+        <>
+            <Button onClick={showDialog}>打开弹窗</Button>
+            <Modal
+                title="基本对话框"
+                visible={visible}
+                onOk={handleOk}
+                afterClose={handleAfterClose} //>=1.16.0
+                onCancel={handleCancel}
+                closeOnEsc={true}
+            >
+                This is the content of a basic modal.
+                <br />
+                More content...
+            </Modal>
+        </>
+    );
+};
 ```
 
 ### 点击遮罩层不可关闭
@@ -84,24 +69,24 @@ import { Modal, Button } from '@douyinfe/semi-ui';
 class modalDemo extends React.Component {
     constructor() {
         super();
-        this.state = {visible: false};
+        this.state = { visible: false };
         this.showDialog = this.showDialog.bind(this);
         this.handleOk = this.handleOk.bind(this);
         this.handleCancel = this.handleCancel.bind(this);
     }
     showDialog() {
         this.setState({
-            visible: true
+            visible: true,
         });
     }
     handleOk(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     handleCancel(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     render() {
@@ -128,7 +113,7 @@ class modalDemo extends React.Component {
 
 通过设置 `okText` 与 `cancelText` 属性可自定义按钮显示的文字。
 
-注意:命令式调用的 Modal 需要通过这两个属性来设置 i18 的文本,因为我们无法修改React组件树,命令式调用插入的Component无法消费到Locale相关的Context
+注意:命令式调用的 Modal 需要通过这两个属性来设置 i18 的文本,因为我们无法修改 React 组件树,命令式调用插入的 Component 无法消费到 Locale 相关的 Context
 
 ```jsx live=true
 import React from 'react';
@@ -137,24 +122,24 @@ import { Modal, Button } from '@douyinfe/semi-ui';
 class modalDemo extends React.Component {
     constructor() {
         super();
-        this.state = {visible: false};
+        this.state = { visible: false };
         this.showDialog = this.showDialog.bind(this);
         this.handleOk = this.handleOk.bind(this);
         this.handleCancel = this.handleCancel.bind(this);
     }
     showDialog() {
         this.setState({
-            visible: true
+            visible: true,
         });
     }
     handleOk(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     handleCancel(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     render() {
@@ -189,24 +174,24 @@ import { Modal, Button } from '@douyinfe/semi-ui';
 class modalDemo extends React.Component {
     constructor() {
         super();
-        this.state = {visible: false};
+        this.state = { visible: false };
         this.showDialog = this.showDialog.bind(this);
         this.handleOk = this.handleOk.bind(this);
         this.handleCancel = this.handleCancel.bind(this);
     }
     showDialog() {
         this.setState({
-            visible: true
+            visible: true,
         });
     }
     handleOk(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     handleCancel(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     render() {
@@ -218,8 +203,8 @@ class modalDemo extends React.Component {
                     visible={this.state.visible}
                     onOk={this.handleOk}
                     onCancel={this.handleCancel}
-                    okButtonProps={{size: 'small', type: 'warning'}}
-                    cancelButtonProps={{size: 'small', disabled: true}}
+                    okButtonProps={{ size: 'small', type: 'warning' }}
+                    cancelButtonProps={{ size: 'small', disabled: true }}
                 >
                     <p>This is a modal with customized button props.</p>
                     <p>More content...</p>
@@ -241,24 +226,24 @@ import { Modal, Button } from '@douyinfe/semi-ui';
 class modalDemo extends React.Component {
     constructor() {
         super();
-        this.state = {visible: false};
+        this.state = { visible: false };
         this.showDialog = this.showDialog.bind(this);
         this.handleOk = this.handleOk.bind(this);
         this.handleCancel = this.handleCancel.bind(this);
     }
     showDialog() {
         this.setState({
-            visible: true
+            visible: true,
         });
     }
     handleOk(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     handleCancel(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     render() {
@@ -287,8 +272,7 @@ class modalDemo extends React.Component {
 
 ### 自定义对话框的样式
 
-通过设置 `style` 可以自定义样式及位置如 `style.top`,也可以通过 `centered` 使对话框居中显示。
-也可以通过设置 `maskStyle` 自定义遮罩样式,及 `bodyStyle` 自定义对话框内容样式。
+通过设置 `style` 可以自定义样式及位置如 `style.top`,也可以通过 `centered` 使对话框居中显示。也可以通过设置 `maskStyle` 自定义遮罩样式,及 `bodyStyle` 自定义对话框内容样式。
 
 ```jsx live=true
 import React from 'react';
@@ -297,24 +281,24 @@ import { Modal, Button } from '@douyinfe/semi-ui';
 class modalDemo extends React.Component {
     constructor() {
         super();
-        this.state = {visible: false};
+        this.state = { visible: false };
         this.showDialog = this.showDialog.bind(this);
         this.handleOk = this.handleOk.bind(this);
         this.handleCancel = this.handleCancel.bind(this);
     }
     showDialog() {
         this.setState({
-            visible: true
+            visible: true,
         });
     }
     handleOk(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     handleCancel(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     render() {
@@ -327,15 +311,29 @@ class modalDemo extends React.Component {
                     onOk={this.handleOk}
                     onCancel={this.handleCancel}
                     centered
-                    bodyStyle={{overflow: 'auto', height: 200}}
+                    bodyStyle={{ overflow: 'auto', height: 200 }}
                 >
-                    <p style={{lineHeight: 1.8}}>Semi Design 是由互娱社区前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。</p>
-                    <p style={{lineHeight: 1.8}}>区别于其他的设计系统而言,Semi Design 以用户中心、内容优先、设计人性化为设计理念,具有以下优势:</p>
+                    <p style={{ lineHeight: 1.8 }}>
+                        Semi Design 是由互娱社区前端团队与 UED
+                        团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的
+                        Web 应用。
+                    </p>
+                    <p style={{ lineHeight: 1.8 }}>
+                        区别于其他的设计系统而言,Semi Design 以用户中心、内容优先、设计人性化为设计理念,具有以下优势:
+                    </p>
                     <ul>
-                        <li><p>Semi Design 以内容优先进行设计。</p></li>
-                        <li><p>更容易地自定义主题。</p></li>
-                        <li><p>适用国际化场景。</p></li>
-                        <li><p>效率场景加入人性化关怀</p></li>
+                        <li>
+                            <p>Semi Design 以内容优先进行设计。</p>
+                        </li>
+                        <li>
+                            <p>更容易地自定义主题。</p>
+                        </li>
+                        <li>
+                            <p>适用国际化场景。</p>
+                        </li>
+                        <li>
+                            <p>效率场景加入人性化关怀</p>
+                        </li>
                     </ul>
                 </Modal>
             </>
@@ -356,42 +354,42 @@ import { IconVigoLogo, IconSemiLogo } from '@douyinfe/semi-icons';
 class modalDemo extends React.Component {
     constructor() {
         super();
-        this.state = {visible: false};
+        this.state = { visible: false };
         this.showDialog = this.showDialog.bind(this);
         this.handleOk = this.handleOk.bind(this);
         this.handleCancel = this.handleCancel.bind(this);
     }
     showDialog() {
         this.setState({
-            visible: true
+            visible: true,
         });
     }
     handleOk(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     handleCancel(e) {
         this.setState({
-            visible: false
+            visible: false,
         });
     }
     render() {
         const data = [
             {
-                icon: <IconSemiLogo style={{fontSize: 48}} />,
+                icon: <IconSemiLogo style={{ fontSize: 48 }} />,
                 title: 'Boost new feature adoption with Integration',
-                content: 'Sample data is prepared for you to demostrate how Integration may be useful for your team'
+                content: 'Sample data is prepared for you to demostrate how Integration may be useful for your team',
             },
             {
-                icon: <IconVigoLogo style={{fontSize: 48}} />,
+                icon: <IconVigoLogo style={{ fontSize: 48 }} />,
                 title: 'Introducing Dark Mode',
-                content: 'Sample data is prepared for you to demostrate how Integration may be useful for your team'
+                content: 'Sample data is prepared for you to demostrate how Integration may be useful for your team',
             },
             {
-                icon: <IconSemiLogo style={{fontSize: 48}} />,
+                icon: <IconSemiLogo style={{ fontSize: 48 }} />,
                 title: 'New List Component',
-                content: 'Sample data is prepared for you to demostrate how Integration may be useful for your team'
+                content: 'Sample data is prepared for you to demostrate how Integration may be useful for your team',
             },
         ];
         const btnStyle = {
@@ -399,7 +397,7 @@ class modalDemo extends React.Component {
             margin: '4px 50px',
         };
         const footer = (
-            <div style={{textAlign: 'center'}}>
+            <div style={{ textAlign: 'center' }}>
                 <Button type="primary" theme="solid" onClick={this.handleOk} style={btnStyle}>
                     Continue
                 </Button>
@@ -418,7 +416,7 @@ class modalDemo extends React.Component {
                     onCancel={this.handleCancel}
                     footer={footer}
                 >
-                    <h3 style={{textAlign: 'center', fontSize: 24, margin: 40}}>Semi Design New Features</h3>
+                    <h3 style={{ textAlign: 'center', fontSize: 24, margin: 40 }}>Semi Design New Features</h3>
                     <List
                         dataSource={data}
                         split={false}
@@ -427,12 +425,14 @@ class modalDemo extends React.Component {
                                 header={item.icon}
                                 main={
                                     <div>
-                                        <h6 style={{margin: 0, fontSize: 16}}>{item.title}</h6>
-                                        <p style={{marginTop: 4, color: 'var(--semi-color-text-1)'}}>{item.content}</p>
+                                        <h6 style={{ margin: 0, fontSize: 16 }}>{item.title}</h6>
+                                        <p style={{ marginTop: 4, color: 'var(--semi-color-text-1)' }}>
+                                            {item.content}
+                                        </p>
                                     </div>
                                 }
-                            />)
-                        }
+                            />
+                        )}
                     />
                 </Modal>
             </>
@@ -441,8 +441,7 @@ class modalDemo extends React.Component {
 }
 ```
 
-
-### 全屏Modal
+### 全屏 Modal
 
 使用 `fullScreen={true}` 可以开启全屏对话框
 
@@ -458,13 +457,7 @@ import { Modal, Button } from '@douyinfe/semi-ui';
     return (
         <>
             <Button onClick={() => setVisible(true)}>打开全屏弹窗</Button>
-            <Modal
-                title="全屏对话框标题"
-                fullScreen
-                visible={visible}
-                onOk={onClose}
-                onCancel={onClose}
-            >
+            <Modal title="全屏对话框标题" fullScreen visible={visible} onOk={onClose} onCancel={onClose}>
                 <p>This is a full screen modal</p>
                 <p>More content...</p>
             </Modal>
@@ -473,8 +466,6 @@ import { Modal, Button } from '@douyinfe/semi-ui';
 };
 ```
 
-
-
 ### 命令式调用
 
 使用 `confirm()` 可以设置一个确认框。支持各种类型的信息提示。命令式调用也可以自定义 icon , 支持 string 和 ReactNode 类型。其他 Modal 支持的 props 都可以传入。
@@ -486,54 +477,68 @@ import { IconSend } from '@douyinfe/semi-icons';
 
 ModalComponent = function(props) {
     function success() {
-        Modal.success({ 'title': 'This is a success message', 'content': 'bla bla bla...'});
+        Modal.success({ title: 'This is a success message', content: 'bla bla bla...' });
     }
 
     function info() {
-        Modal.info({ 'title': 'Here is some info', 'content': 'bla bla bla...' });
+        Modal.info({ title: 'Here is some info', content: 'bla bla bla...' });
     }
 
     function error() {
-        Modal.error({ 'title': 'Unfortunately, there is an error', 'content': 'bla bla bla...' });
+        Modal.error({ title: 'Unfortunately, there is an error', content: 'bla bla bla...' });
     }
 
     function warning() {
-        Modal.warning({ 'title': 'Warning: be cautious ahead', 'content': 'bla bla bla...' });
+        Modal.warning({ title: 'Warning: be cautious ahead', content: 'bla bla bla...' });
     }
 
     function confirm() {
-        Modal.confirm({ 'title': 'Are you sure ?', 'content': 'bla bla bla...' });
+        Modal.confirm({ title: 'Are you sure ?', content: 'bla bla bla...' });
     }
 
     function custom() {
-        Modal.info({ 'title': 'This is a custom modal', 'content': 'bla bla bla...', icon: <IconSend />, cancelButtonProps: { theme: 'borderless' }, okButtonProps: { theme: 'solid' }, });
+        Modal.info({
+            title: 'This is a custom modal',
+            content: 'bla bla bla...',
+            icon: <IconSend />,
+            cancelButtonProps: { theme: 'borderless' },
+            okButtonProps: { theme: 'solid' },
+        });
     }
 
     return (
         <div>
             <Button onClick={info}>Info</Button>
-            <br/>
-            <br/>
+            <br />
+            <br />
             <Button onClick={success}>Success</Button>
-            <br/>
-            <br/>
-            <Button onClick={error} type="danger">Error</Button>
-            <br/>
-            <br/>
-            <Button onClick={warning} type="warning">Warning</Button>
-            <br/>
-            <br/>
-            <Button onClick={confirm} type="primary">Confirm</Button>
-            <br/>
-            <br/>
+            <br />
+            <br />
+            <Button onClick={error} type="danger">
+                Error
+            </Button>
+            <br />
+            <br />
+            <Button onClick={warning} type="warning">
+                Warning
+            </Button>
+            <br />
+            <br />
+            <Button onClick={confirm} type="primary">
+                Confirm
+            </Button>
+            <br />
+            <br />
             <Button onClick={custom}>Custom</Button>
         </div>
     );
 };
 ```
 
-### Hooks用法
-通过  Modal.useModal 创建支持读取 context 的 contextHolder。
+### Hooks 用法
+
+通过 Modal.useModal 创建支持读取 context 的 contextHolder。
+
 ```jsx live=true hideInDSM
 import React from 'react';
 import { ConfigProvider, Button, Modal } from '@douyinfe/semi-ui';
@@ -541,7 +546,7 @@ import en_GB from '@douyinfe/semi-ui/lib/es/locale/source/en_GB';
 
 function Demo(props = {}) {
     const [modal, contextHolder] = Modal.useModal();
-    const config = { 'title': 'This is a success message', 'content': 'Context consumer' };
+    const config = { title: 'This is a success message', content: 'Context consumer' };
 
     return (
         <ConfigProvider locale={en_GB}>
@@ -560,50 +565,48 @@ function Demo(props = {}) {
 }
 ```
 
-
-## API参考
+## API 参考
 
 ### Modal
 
-| 属性              | 说明                             | 类型 | 默认值  |
-| ----------------- | -------------------------------- | -- | ------- |
-| afterClose         | 对话框完全关闭后的回调函数   <br/>**v1.16.0 后提供**           | () => void | 无      |
-| bodyStyle         | 对话框内容的样式                 | CSSProperties | 无      |
-| cancelButtonProps | 取消按钮的 props                 | [ButtonProps](/zh-CN/input/button#API参考) | 无      |
-| cancelText        | 取消按钮的文字                   | string | 无      |
-| centered          | 是否居中显示                     | boolean | false   |
-| className         | 可用于设置样式类名               | string | 无      |
-| closable          | 是否显示右上角的关闭按钮         | boolean | true    |
-| closeIcon            | 关闭按钮的icon  <br/>**v1.0.0 后提供**                                              | ReactNode     | <IconClose /\>    |
-| closeOnEsc              | 允许通过键盘事件Esc触发关闭  <br/>**v1.0.0 后提供**                                               | boolean | true       | 
-| confirmLoading    | 确认按钮 loading                 | boolean | false   |
-| content            | 对话框内容            | ReactNode  | 无      |
-| footer            | 对话框底部                       | ReactNode | 无      |
-| fullScreen        | 对话是否是全屏(会覆盖 width height)  <br/>**v1.18.0 后提供**                          | boolean     | false      |
-| getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative` <br/>**v0.33.0 后提供**  | () => HTMLElement | () => document.body |     
-| hasCancel        | 是否显示取消按钮                  | boolean | true      |
-| header            | 对话框头部                       | ReactNode | 无      |
-| height            | 高度                             | number | 无      |
-| icon              | 自定义icon       <br/>**v1.1.0 后提供**                                          | ReactNode | -       |
-| keepDOM | 关闭对话框时是否销毁 <br/>**v1.0.0 后提供**  | boolean | false |
-| lazyRender | 配合 keepDOM 使用,为 true 时挂载时不会渲染对话框组件 <br/>**v1.0.0 后提供**  | boolean | true |   
-| mask              | 是否显示遮罩                     | boolean | true    |
-| maskClosable      | 是否允许通过点击遮罩来关闭对话框 | boolean | true    |
-| maskStyle         | 遮罩的样式                       | CSSProperties | 无      |
-| motion         | 动画效果开关                      | boolean    | true      |
-| okButtonProps     | 确认按钮的 props                 | [ButtonProps](/zh-CN/input/button#API参考) | 无      |
-| okText            | 确认按钮的文字                   | string | 无      |
-| okType            | 确认按钮的类型, 可选: 'primary'、'secondary'、'tertiary'、'warning'、'danger'                   | string | primary |
+| 属性 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| afterClose | 对话框完全关闭后的回调函数 <br/>**v1.16.0 后提供** | () => void | 无 |
+| bodyStyle | 对话框内容的样式 | CSSProperties | 无 |
+| cancelButtonProps | 取消按钮的 props | [ButtonProps](/zh-CN/input/button#API参考) | 无 |
+| cancelText | 取消按钮的文字 | string | 无 |
+| centered | 是否居中显示 | boolean | false |
+| className | 可用于设置样式类名 | string | 无 |
+| closable | 是否显示右上角的关闭按钮 | boolean | true |
+| closeIcon | 关闭按钮的 icon <br/>**v1.0.0 后提供** | ReactNode | <IconClose /\> |
+| closeOnEsc | 允许通过键盘事件 Esc 触发关闭 <br/>**v1.0.0 后提供** | boolean | true |
+| confirmLoading | 确认按钮 loading | boolean | false |
+| content | 对话框内容 | ReactNode | 无 |
+| footer | 对话框底部 | ReactNode | 无 |
+| fullScreen | 对话是否是全屏(会覆盖 width height) <br/>**v1.18.0 后提供** | boolean | false |
+| getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative` <br/>**v0.33.0 后提供** | () => HTMLElement | () => document.body |
+| hasCancel | 是否显示取消按钮 | boolean | true |
+| header | 对话框头部 | ReactNode | 无 |
+| height | 高度 | number | 无 |
+| icon | 自定义 icon <br/>**v1.1.0 后提供** | ReactNode | - |
+| keepDOM | 关闭对话框时是否销毁 <br/>**v1.0.0 后提供** | boolean | false |
+| lazyRender | 配合 keepDOM 使用,为 true 时挂载时不会渲染对话框组件 <br/>**v1.0.0 后提供** | boolean | true |
+| mask | 是否显示遮罩 | boolean | true |
+| maskClosable | 是否允许通过点击遮罩来关闭对话框 | boolean | true |
+| maskStyle | 遮罩的样式 | CSSProperties | 无 |
+| motion | 动画效果开关 | boolean | true |
+| okButtonProps | 确认按钮的 props | [ButtonProps](/zh-CN/input/button#API参考) | 无 |
+| okText | 确认按钮的文字 | string | 无 |
+| okType | 确认按钮的类型, 可选: 'primary'、'secondary'、'tertiary'、'warning'、'danger' | string | primary |
 | preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法,不包含用户传入的组件 | boolean |  |  |
-| size | 对话框宽度尺寸,支持 `small`(448px), `medium`(684px), `large`(920px),`full-width`(100vw - 64px) <br/>**v1.0.0 后提供**  | string | 'small' |     
-| style             | 可用于设置样式                   | CSSProperties | 无      |
-| title             | 对话框的标题                     | ReactNode | 无      |
-| visible           | 对话框是否可见                   | boolean | false   |
-| width             | 宽度                             | number | 448     |
-| zIndex            | 遮罩的 z-index 值                | number | 1000    |
-| onCancel          | 取消对话框时的回调函数           | (e: any) => void \| Promise<any\>  | 无      |
-| onOk              | 点击确认按钮时的回调函数         | (e: any) => void \| Promise<any\>  | 无      |
-
+| size | 对话框宽度尺寸,支持 `small`(448px), `medium`(684px), `large`(920px),`full-width`(100vw - 64px) <br/>**v1.0.0 后提供** | string | 'small' |
+| style | 可用于设置样式 | CSSProperties | 无 |
+| title | 对话框的标题 | ReactNode | 无 |
+| visible | 对话框是否可见 | boolean | false |
+| width | 宽度 | number | 448 |
+| zIndex | 遮罩的 z-index 值 | number | 1000 |
+| onCancel | 取消对话框时的回调函数 | (e: any) => void \| Promise<any\> | 无 |
+| onOk | 点击确认按钮时的回调函数 | (e: any) => void \| Promise<any\> | 无 |
 
 ### Modal.method()
 
@@ -613,32 +616,32 @@ function Demo(props = {}) {
 -   `Modal.warning`
 -   `Modal.confirm`
 
-| 属性              | 说明                                                             | 类型              | 默认值  |
-| ----------------- | ---------------------------------------------------------------- | ----------------- | ------- |
-| bodyStyle         | 对话框内容的样式                                                 | CSSProperties            | 无      |
-| cancelButtonProps | 取消按钮的 props                                                 | [ButtonProps](/zh-CN/input/button#API参考)            | 无      |
-| cancelText        | 取消按钮的文字                                                   | string            | 无      |
-| centered          | 是否居中显示                                                     | boolean           | false   |
-| className         | 可用于设置样式类名                                               | string            | 无      |
-| closable          | 是否显示右上角的关闭按钮                                         | boolean           | true    |
-| confirmLoading    | 确认按钮 loading                                                 | boolean           | false   |
-| content            | 对话框内容                                                      | ReactNode         | 无      |
-| footer            | 对话框底部                                                       | ReactNode         | 无      |
-| header            | 对话框头部                                                       | ReactNode         | 无      |
-| height            | 高度                                                             | number            | 无      |
-| icon              | 自定义icon                                                 | ReactNode | -       |
-| mask              | 是否显示遮罩                                                     | boolean           | true    |
-| maskClosable      | 是否允许通过点击遮罩来关闭对话框                                 | boolean           | true    |
-| maskStyle         | 遮罩的样式                                                       | CSSProperties            | 无      |
-| okButtonProps     | 确认按钮的 props                                                 | [ButtonProps](/zh-CN/input/button#API参考)            | 无      |
-| okText            | 确认按钮的文字                                                   | string            | 无      |
-| okType            | 确认按钮的类型                                                   | string            | primary |
-| style             | 可用于设置样式                                                   | CSSProperties            | 无      |
-| title             | 对话框的标题                                                     | ReactNode            | 无      |
-| width             | 宽度                                                             | number            | 520     |
-| zIndex            | 遮罩的 z-index 值                                                | number            | 1000    |
-| onCancel          | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭     | (e: any) => void \| Promise<any\>          | 无      |
-| onOk              | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | (e: any) => void \| Promise<any\>          | 无      |
+| 属性 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| bodyStyle | 对话框内容的样式 | CSSProperties | 无 |
+| cancelButtonProps | 取消按钮的 props | [ButtonProps](/zh-CN/input/button#API参考) | 无 |
+| cancelText | 取消按钮的文字 | string | 无 |
+| centered | 是否居中显示 | boolean | false |
+| className | 可用于设置样式类名 | string | 无 |
+| closable | 是否显示右上角的关闭按钮 | boolean | true |
+| confirmLoading | 确认按钮 loading | boolean | false |
+| content | 对话框内容 | ReactNode | 无 |
+| footer | 对话框底部 | ReactNode | 无 |
+| header | 对话框头部 | ReactNode | 无 |
+| height | 高度 | number | 无 |
+| icon | 自定义 icon | ReactNode | - |
+| mask | 是否显示遮罩 | boolean | true |
+| maskClosable | 是否允许通过点击遮罩来关闭对话框 | boolean | true |
+| maskStyle | 遮罩的样式 | CSSProperties | 无 |
+| okButtonProps | 确认按钮的 props | [ButtonProps](/zh-CN/input/button#API参考) | 无 |
+| okText | 确认按钮的文字 | string | 无 |
+| okType | 确认按钮的类型 | string | primary |
+| style | 可用于设置样式 | CSSProperties | 无 |
+| title | 对话框的标题 | ReactNode | 无 |
+| width | 宽度 | number | 520 |
+| zIndex | 遮罩的 z-index 值 | number | 1000 |
+| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | (e: any) => void \| Promise<any\> | 无 |
+| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | (e: any) => void \| Promise<any\> | 无 |
 
 以上函数调用后,会返回一个引用,可以通过该引用更新和关闭弹窗。
 
@@ -653,59 +656,65 @@ modal.update({
 modal.destroy();
 ```
 
--   `Modal.destroyAll` **v>=0.37.0**  
+-   `Modal.destroyAll` **v>=0.37.0**
 
 使用 Modal.destroyAll() 可以销毁命令式及以上`.info()`等创建的弹窗。
 
 -   `Modal.useModal` **v>=1.2.0**  
-当你需要使用 Context 时,可以通过 Modal.useModal 创建一个 contextHolder 插入相应的节点中。此时通过 hooks 创建的 Modal 将会得到 contextHolder 所在位置的所有上下文。创建的 modal 对象拥有与 [Modal.method](#Modal.method()) 相同的创建通知方法。
+    当你需要使用 Context 时,可以通过 Modal.useModal 创建一个 contextHolder 插入相应的节点中。此时通过 hooks 创建的 Modal 将会得到 contextHolder 所在位置的所有上下文。创建的 modal 对象拥有与 [Modal.method](<#Modal.method()>) 相同的创建通知方法。
 
 ## Accessibility
 
 ### ARIA
+
 WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/
-- role 设置为 `dialog`
-- aria-modal 设置为 true
-- aria-labelledby 对应 Modal header
-- aria-describedby 对应 Modal body
+
+-   role 设置为 `dialog`
+-   aria-modal 设置为 true
+-   aria-labelledby 对应 Modal header
+-   aria-describedby 对应 Modal body
 
 ### 键盘和焦点
-- Modal 在弹出时自动获得焦点,关闭时焦点自动回归到打开前元素。
-- 键盘用户可以使用 `Tab` 键和 `Shift + Tab`,将焦点在 Modal 内移动,包括 Modal 自带的关闭按钮和确定取消按钮,此时 Modal 背后元素不可被 tab 聚焦。
-- Modal 打开时默认聚焦到取消按钮, 可通过在 cancelButtonProps 或 okButtonProps 传入 autoFocus 来控制该行为。
-- 可通过在 Modal 内容中需要聚焦的表单元素上添加 autoFocus 来让 Modal 打开时自动聚焦到该元素 (需同时设置 cancelButtonProps 的 autoFocus 为 false)。
-- 修改 closeOnEsc 默认值为 true,允许用户通过键盘直接关闭 Modal 带来更好的体验
 
+-   Modal 在弹出时自动获得焦点,关闭时焦点自动回归到打开前元素。
+-   键盘用户可以使用 `Tab` 键和 `Shift + Tab`,将焦点在 Modal 内移动,包括 Modal 自带的关闭按钮和确定取消按钮,此时 Modal 背后元素不可被 tab 聚焦。
+-   Modal 打开时默认聚焦到取消按钮, 可通过在 cancelButtonProps 或 okButtonProps 传入 autoFocus 来控制该行为。
+-   可通过在 Modal 内容中需要聚焦的表单元素上添加 autoFocus 来让 Modal 打开时自动聚焦到该元素 (需同时设置 cancelButtonProps 的 autoFocus 为 false)。
+-   修改 closeOnEsc 默认值为 true,允许用户通过键盘直接关闭 Modal 带来更好的体验
 
 ## 文案规范
 
-- 命令式 Modal 与 默认 Modal 两种模态对话框的标题使用 动词 + 名词 的格式,无论是陈述句还是问句
+-   命令式 Modal 与 默认 Modal 两种模态对话框的标题使用 动词 + 名词 的格式,无论是陈述句还是问句
 
-| ✅ 推荐用法 | ❌ 不推荐用法 |   
-| --- | --- | 
-| Edit ticket | Edit |
+| ✅ 推荐用法   | ❌ 不推荐用法                         |
+| ------------- | ------------------------------------- |
+| Edit ticket   | Edit                                  |
 | Delete form? | Are you sure you want to delete form? |
 
-- 两种模态对话框的操作按钮在保证标题描述清楚的前提下,只需要使用标题内的动词即可
+-   两种模态对话框的操作按钮在保证标题描述清楚的前提下,只需要使用标题内的动词即可
 
-| ✅ 推荐用法 | ❌ 不推荐用法 |   
-| --- | --- | 
-| Edit | Edit ticket |
+| ✅ 推荐用法 | ❌ 不推荐用法 |
+| ----------- | ------------- |
+| Edit        | Edit ticket   |
 
-- 命令式 Modal 的正文规范
-  - 对标题进行具体的解释说明,不要重复标题的信息
-  - 确保用户知道在必要时如何采取行动
+-   命令式 Modal 的正文规范
+    -   对标题进行具体的解释说明,不要重复标题的信息
+    -   确保用户知道在必要时如何采取行动
 
 ## 设计变量
+
 <DesignToken/>
 
 ## FAQ
 
--   为什么使用 LocaleProvider 后, Modal.confirm 确认、取消按钮的文本没有国际化?
+-  #### 为什么使用 LocaleProvider 后, Modal.confirm 确认、取消按钮的文本没有国际化?
     Modal 使用 Portal 将浮层节点插入到 DOM 树中。但这个操作仅能改变节点在 DOM 树中的位置,无法改变节点在 React 节点树中的位置,LocalProvider是基于 Contenxt 机制传递的,必须是从属的 React 子结点才可消费到 Local 相关 Contenxt。因此命令式的 Modal 的内置文本无法自动适配国际化。
     你可以通过 `okText` 和 `cancelText` 这两个属性来根据 Locale 重新设置 i18 的文本。   
     在1.2版本之后,你也可以通过 Modal.useModal 方法来返回 modal 实体以及 contextHolder 节点。将 contextHolder 插入到你需要获取 context 位置,即可使 Modal 获取到对应的 Context,如 ConfigProvider 或者 LocaleProvider 的配置。
 
+-  #### 为什么 title 和 content 的间距在命令式调用和非命令式调用下不同?
+    命令式调用场景下,标题和内容的相关性更强,所以用更近的距离表达这种强相关性,符合预期。用户如果不想要这种效果,可以自己做样式覆盖。
+
 <!-- ## 相关物料
 ```material
 1, 55

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 60
+order: 61
 category: Show
 title: Table
 subTitle: Table

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 60
+order: 61
 category: 展示类
 title:  Table 表格
 icon: doc-table

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 61
+order: 62
 category: Show
 title: Tag
 subTitle: Tag
@@ -57,6 +57,22 @@ import { Tag } from '@douyinfe/semi-ui';
 );
 ```
 
+### Shape
+
+Supports two Shape: `square`(default)、`circle`。
+
+```jsx live=true
+import React from 'react';
+import { Tag, Space } from '@douyinfe/semi-ui';
+
+() => (
+    <Space>
+        <Tag size="small" shape='circle'> small circle tag </Tag>
+        <Tag size="large" shape='circle'> large circle tag </Tag>
+    </Space>
+);
+```
+
 ### Color
 
 Tag supports 16 colors including whites from Semi's palette: `amber`, `blue`, `cyan`, `green`, `grey`, `indigo`, `light-blue`, `light-green`, `lime`, `orange`, `pink`, `purple`, `red`, `teal`, `violet`, `yellow`, `white`. You can also customize color through `style`.
@@ -294,6 +310,7 @@ class TagGroupCloseableDemo extends React.Component {
 | className | Class name | string |  |  |
 | closable | Toggle whether the tag can be closed | boolean | false |  |
 | color | Color of tags, one of `amber`、 `blue`、 `cyan`、 `green`、 `grey`、 `indigo`、 `light-blue`、 `light-green`、 `lime`、 `orange`、 `pink`、 `purple`、 `red`、 `teal`、 `violet`、 `yellow`、 `white` | string | `grey` |  |
+| shape | Shape of tag, one of `square`、 `circle` | string | `square` | 2.20.0 |
 | size | Size, one of `small`, `large` | string | `small` |  |
 | style | Inline style | object |  |  |
 | type | Style type, one of `ghost`, `solid`, `light` | string | `light` |  |

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 61
+order: 62
 category: 展示类
 title: Tag 标签
 icon: doc-tag
@@ -53,6 +53,22 @@ import { Tag, Space } from '@douyinfe/semi-ui';
 );
 ```
 
+### 形状
+
+默认定义了两种形状:`square`(默认)、`circle`。
+
+```jsx live=true
+import React from 'react';
+import { Tag, Space } from '@douyinfe/semi-ui';
+
+() => (
+    <Space>
+        <Tag size="small" shape='circle'> small circle tag </Tag>
+        <Tag size="large" shape='circle'> large circle tag </Tag>
+    </Space>
+);
+```
+
 ### 颜色
 
 标签支持默认色板的 16 种颜色和白色,包括:`amber`、 `blue`、 `cyan`、 `green`、 `grey`、 `indigo`、 `light-blue`、 `light-green`、 `lime`、 `orange`、 `pink`、 `purple`、 `red`、 `teal`、 `violet`、 `yellow`、 `white`,也可以通过 style 来自定义颜色样式。
@@ -282,6 +298,7 @@ class TagGroupCloseableDemo extends React.Component {
 | className | 类名 | string |     | |
 | closable | 标签是否可以关闭 | boolean  |  false   | |
 | color  | 标签的颜色,可选 `amber`、 `blue`、 `cyan`、 `green`、 `grey`、 `indigo`、 `light-blue`、 `light-green`、 `lime`、 `orange`、 `pink`、 `purple`、 `red`、 `teal`、 `violet`、 `yellow`、 `white` | string  | `grey`| |
+| shape | 标签的形状,可选 `square`、 `circle` | string | `square` | 2.20.0 |
 | size | 标签的尺寸,可选 `small`、 `large` | string | `small` | |
 | style | 样式 | CSSProperties |     | |
 | type  | 标签的样式类型,可选 `ghost`、 `solid`、 `light` | string  | `light`     | |

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

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

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

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

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 63
+order: 64
 category: Show
 title: Tooltip
 subTitle: Tooltip

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 63
+order: 64
 category: 展示类
 title: Tooltip 工具提示
 icon: doc-tooltip

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

@@ -16,10 +16,38 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
 
 ---
 
+#### 🎉 2.20.0 (2022-09-23)
+- 【Fix】
+  - fix the issue that Form.InputGroup does not support FormProps.wrapperCol , labelCol layout
+  - fix the drag and drop problem after the Image preview is enlarged
+  - fix DataPicker component foundation contains React related type definitions [@rashagu](https://github.com/rashagu)
+  - fix the problem that the foundation of Navigation and Tree components reports errors in high versions of typescript [@rashagu](https://github.com/rashagu)
+  - fix the expansion of the controlled tree when the data was loaded remotely unsuccessfully [#1124](https://github.com/DouyinFE/semi-design/issues/1124)
+  - fix ButtonGroup does not support style API issue
+  - fix the problem that when the virtualized Table has a fixed column on the left, the first rendering of the fixed column on the left will have an extra box-shadow [#1134](https://github.com/DouyinFE/semi-design/issues/1134)
+
+
+#### 🎉 2.20.0-beta.1 (2022-09-20)
+- 【Fix】
+    - Fix the issue that `@douyinfe/semi-foundation` Image relative file not export
+
+#### 🎉 2.20.0-beta.0 (2022-09-19)
+
+- 【New Component】
+    - Add Image Component [#344](https://github.com/DouyinFE/semi-design/issues/344)
+- 【Feat】
+    - Tag add shape property, supports `square`、`circle` [#89](https://github.com/DouyinFE/semi-design/issues/89)
+    - Progress supports automatically filling gradient colors according to progress, and automatically switching colors according to progress presets [#1092](https://github.com/DouyinFE/semi-design/issues/1092) [@uiuing](https://github.com/uiuing)
+    - Toast supports dynamic modification of content by ID [#1035](https://github.com/DouyinFE/semi-design/issues/1035) [@gwsbhqt](https://github.com/gwsbhqt)
+    - Skeleton.Avatar supports shape property [#1117](https://github.com/DouyinFE/semi-design/issues/1117) [@MuxinFeng](https://github.com/MuxinFeng)
+- 【Chore】
+    - Remove the ts source code in the package product of @douyinfe/semi-ui, leaving only the lib and dist directories
+    - Modify the writing of some components scss, convert division into multiplication and math.div syntax, to avoid the problem of repeatedly throwing warnings when compiling higher versions of sass
+  
 #### 🎉 2.19.0 (2022-09-09)
 - 【Fix】
     - fix the issue that the keyboard focus style is blocked after the Anchor spacing can trigger the selection
-    - fixed the issue that the Select onblur event was not triggered when the panel was closed
+    - fix the issue that the Select onblur event was not triggered when the panel was closed
     - fix Anchor spacing in front of anchor text cannot trigger selection [@edc-hui](https://github.com/edc-hui)
     - fix the problem that the location of event rendering did not change after Calendar weekStartsOn was changed
     - fix the problem that Input onEnterPress event does not take effect when using Input in DropDown

+ 35 - 8
content/start/changelog/index.md

@@ -16,17 +16,44 @@ Semi 版本号遵循 **Semver** 规范(主版本号-次版本号-修订版本
 
 ---
 
+#### 🎉 2.20.0 (2022-09-23)
+- 【Fix】
+  - 修复 Form.InputGroup 不支持 FormProps.wrapperCol 、 labelCol 布局的问题
+  - 修复 Image 预览放大后拖拽问题
+  - 修复 DataPicker 组件 foundation 中包含 React 相关类型定义问题 [@rashagu](https://github.com/rashagu)
+  - 修复 Navigation、Tree组件 foundation 在高版本typescript中报错问题 [@rashagu](https://github.com/rashagu)
+  - 修复受控 Tree 在远程加载数据时展开不成功 [#1124](https://github.com/DouyinFE/semi-design/issues/1124)
+  - 修复 ButtonGroup 未支持 style API 问题
+  - 修复虚拟化 Table 在有左边固定列时,首次渲染左边固定列会有多余的 box-shadow 问题 [#1134](https://github.com/DouyinFE/semi-design/issues/1134)
+
+#### 🎉 2.20.0-beta.1 (2022-09-20)
+- 【Fix】
+    - 修复 @douyinfe/semi-foundation Image相关未导出的问题
+
+#### 🎉 2.20.0-beta.0 (2022-09-19)
+
+- 【New Component】
+    - 新增 Image 组件 [#344](https://github.com/DouyinFE/semi-design/issues/344)
+- 【Feat】
+    - Tag 新增 shape 选择,可选 square、circle [#89](https://github.com/DouyinFE/semi-design/issues/89)
+    - Progress 支持根据进度自动填充渐变颜色,根据进度预设自动切换颜色 [#1092](https://github.com/DouyinFE/semi-design/issues/1092) [@uiuing](https://github.com/uiuing)
+    - Toast 支持新增通过 ID 动态修改内容 [#1035](https://github.com/DouyinFE/semi-design/issues/1035) [@gwsbhqt](https://github.com/gwsbhqt)
+    - Skeleton.Avatar 支持 shape属性 [#1117](https://github.com/DouyinFE/semi-design/issues/1117) [@MuxinFeng](https://github.com/MuxinFeng)
+- 【Chore】
+    -  移除 @douyinfe/semi-ui 发包产物中的ts源码,仅保留 lib、dist目录
+    -  修改部分组件 scss 的写法,将除法转换为乘法及math.div语法,避免 sass 高版本编译时重复抛出 warning 的问题 
+
 #### 🎉 2.19.0 (2022-09-09)
 - 【Fix】
-    - 修复 Anchor 间距可触发选中后,键盘聚焦样式被遮挡问题
-    - 修复面板关闭时, Select onblur事件未被触发问题
-    - 修复锚点组件的子节点单行文本前面的空白处,点击不能选中的问题 [@edc-hui](https://github.com/edc-hui)
-    - 修复 Calendar weekStartsOn 改变后,事件渲染的位置没有跟着改变问题
-    - 修复在 DropDown 中使用 Input,Input onEnterPress 事件不生效问题
-    - 修复 tagGroup 污染传入 tagList 数据问题  [#1107 ](https://github.com/DouyinFE/semi-design/issues/1107)
-    - 修复 当 datepicker 的 type 为 dateTime 时,切换年月会导致时间部分被重置为8点  [@rojer95](https://github.com/rojer95)
+    - 修复当 Datepicker 的 type 为 dateTime 时,切换年月会导致时间部分被重置为8点 [#1078](https://github.com/DouyinFE/semi-design/issues/1078) [@rojer95](https://github.com/rojer95)
+    - 修复锚点组件的子节点单行文本前面的空白处,点击不能选中的问题 [#512](https://github.com/DouyinFE/semi-design/issues/512) [@edc-hui](https://github.com/edc-hui)
+    - 修复面板关闭时, Select onblur事件未被触发问题 [#1110](https://github.com/DouyinFE/semi-design/issues/1110)
+    - 修复 Calendar weekStartsOn 改变后,事件渲染的位置没有跟着改变问题 [#1101](https://github.com/DouyinFE/semi-design/issues/1101)
+    - 修复在 DropDown 中使用 Input,Input onEnterPress 事件不生效问题 [#1102](https://github.com/DouyinFE/semi-design/issues/1102)
+    - 修复 TagGroup 污染传入 tagList 数据问题  [#1107](https://github.com/DouyinFE/semi-design/issues/1107)
+    - 修复 Anchor 间距可触发选中后,键盘聚焦样式被遮挡问题 
 - 【Chore】
-    - 移除 semi-foundation 、semi-ui中的 corejs 依赖
+    - 移除 semi-foundation 、semi-ui中的 corejs 依赖 [#1095](https://github.com/DouyinFE/semi-design/issues/1095)
 
 #### 🎉 2.19.0-beta.0 (2022-09-05)
 - 【Feat】

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

@@ -66,6 +66,7 @@ Collapsible,
 Descriptions,
 Dropdown,
 Empty,
+Image,
 List,
 Modal,
 OverflowList,

+ 1 - 0
content/start/overview/index.md

@@ -67,6 +67,7 @@ Collapsible 折叠,
 Descriptions 描述列表,
 Dropdown 下拉框,
 Empty 空状态,
+Image 图片,
 List 列表,
 Modal 模态对话框,
 OverflowList 折叠列表,

+ 1 - 0
gatsby-node.js

@@ -182,6 +182,7 @@ exports.onCreateWebpackConfig = ({ stage, rules, loaders, plugins, actions }) =>
             "DSM_URL":JSON.stringify(process.env['DSM_URL']),
             'process.env.SEMI_SITE_HEADER':JSON.stringify(process.env.SEMI_SITE_HEADER),
             'process.env.SEMI_SITE_BANNER':JSON.stringify(process.env.SEMI_SITE_BANNER),
+            'process.env.D2C_URL': JSON.stringify(process.env.D2C_URL),
         })],
     });
 };

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.19.0"
+    "version": "2.20.0"
 }

+ 4 - 4
package.json

@@ -15,7 +15,7 @@
         "develop": "npm run pre-develop && gatsby clean && lerna run build:lib --scope @douyinfe/semi-webpack-plugin --scope eslint-plugin-semi-design && gatsby develop -H 0.0.0.0 --port=3666 --verbose",
         "scripts:changelog": "node scripts/changelog.js",
         "start": "npm run story",
-        "pre-story": "lerna exec --scope=@douyinfe/semi-ui --scope=@douyinfe/semi-foundation -- rm -rf ./lib && lerna run build:lib --scope @douyinfe/semi-webpack-plugin --scope eslint-plugin-semi-design",
+        "pre-story": "lerna exec --scope=@douyinfe/semi-ui --scope=@douyinfe/semi-foundation -- rimraf ./lib && lerna run build:lib --scope @douyinfe/semi-webpack-plugin --scope eslint-plugin-semi-design",
         "story": "npm run pre-story && start-storybook -c ./.storybook/js/ -p 6006",
         "story:ts": "npm run pre-story && && start-storybook -c ./.storybook/ts/ -p 6007",
         "story:ani": "npm run pre-story && && start-storybook -c ./.storybook/animation/react -p 6008",
@@ -34,7 +34,7 @@
         "build:js": "lerna run build:js",
         "build:css": "lerna run build:css",
         "build-storybook": "build-storybook  -c ./.storybook/js/",
-        "build:gatsbydoc": "lerna run build:lib --scope @douyinfe/semi-webpack-plugin --scope eslint-plugin-semi-design && cross-env NODE_ENV=production node --max_old_space_size=16384 ./node_modules/gatsby/cli.js build --prefix-paths --verbose && rm -rf build && mv public build",
+        "build:gatsbydoc": "lerna run build:lib --scope @douyinfe/semi-webpack-plugin --scope eslint-plugin-semi-design && cross-env NODE_ENV=production node --max_old_space_size=16384 ./node_modules/gatsby/cli.js build --prefix-paths --verbose && rimraf build && mv public build",
         "build:icon": "lerna run build:icon --scope='@douyinfe/semi-{icons,illustrations}'",
         "cypress:coverage": "npx wait-on http://127.0.0.1:6006 && ./node_modules/.bin/cypress run",
         "postcypress:coverage": "yarn coverage:merge",
@@ -88,7 +88,7 @@
         "react-sortable-hoc": "^2.0.0",
         "react-virtualized": "^9.22.3",
         "reset-css": "^5.0.1",
-        "sass": "1.32.13",
+        "sass": "1.54.9",
         "typeface-inconsolata": "0.0.72",
         "typeface-inter": "^3.18.1",
         "unist-util-remove": "^1.0.3",
@@ -187,7 +187,7 @@
         "react-dnd-cjs": "^9.5.1",
         "react-storybook-addon-props-combinations": "^1.1.0",
         "rimraf": "^2.7.1",
-        "sass-loader": "^7.3.1",
+        "sass-loader": "^10.1.1",
         "semver": "^7.3.5",
         "sha1": "^1.1.1",
         "sinon": "^6.3.5",

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-react",
-  "version": "2.19.0",
+  "version": "2.20.0",
   "description": "motion library for semi-ui-react",
   "keywords": [
     "motion",
@@ -26,7 +26,7 @@
   },
   "dependencies": {
     "@douyinfe/semi-animation": "2.12.0",
-    "@douyinfe/semi-animation-styled": "2.19.0",
+    "@douyinfe/semi-animation-styled": "2.20.0",
     "classnames": "^2.2.6"
   },
   "devDependencies": {

+ 0 - 12
packages/semi-animation-styled/getBabelConfig.js

@@ -18,18 +18,6 @@ module.exports = ({ isESM }) => {
             ],
         ],
         plugins: [
-            [
-                '@babel/plugin-transform-runtime',
-                {
-                    corejs: 3
-                },
-            ],
-            [
-                '@babel/plugin-proposal-decorators',
-                {
-                    legacy: true,
-                },
-            ],
         ]
     };
 };

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-styled",
-  "version": "2.19.0",
+  "version": "2.20.0",
   "description": "semi styled animation",
   "keywords": [
     "semi",
@@ -29,9 +29,6 @@
     "build:lib": "node scripts/compileLib",
     "prepublishOnly": "npm run build:lib"
   },
-  "dependencies": {
-    "@babel/runtime-corejs3": "^7.15.4"
-  },
   "devDependencies": {
     "@babel/plugin-proposal-decorators": "^7.15.8",
     "@babel/plugin-transform-runtime": "^7.15.8",

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

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation",
-  "version": "2.19.0",
+  "version": "2.20.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.19.0",
+  "version": "2.20.0",
   "description": "semi ui eslint plugin",
   "keywords": [
     "semi",

+ 1 - 1
packages/semi-foundation/anchor/anchor.scss

@@ -1,5 +1,5 @@
-@import './variables.scss';
 @import "./animation.scss";
+@import './variables.scss';
 
 $module: #{$prefix}-anchor;
 

+ 1 - 1
packages/semi-foundation/autoComplete/autoComplete.scss

@@ -1,5 +1,5 @@
-@import "./variables.scss";
 @import "./animation.scss";
+@import "./variables.scss";
 @import "./option.scss";
 
 $module: #{$prefix}-autocomplete;

+ 2 - 2
packages/semi-foundation/badge/badge.scss

@@ -20,7 +20,7 @@ $module: #{$prefix}-badge;
         box-sizing: border-box;
         height: $height-badge_count;
         min-width: $height-badge_count;
-        border-radius: $height-badge_count / 2;
+        border-radius: $height-badge_count * 0.5;
         padding: $spacing-badge_count-paddingY $spacing-badge_count-paddingX;
         background-color: $color-badge_default-bg-default;
         border: $width-badge-border $color-badge_default-border-default solid;
@@ -162,7 +162,7 @@ $module: #{$prefix}-badge;
             color: $color-badge_warning_inverted-text-default;
         }
     }
-    
+
 }
 
 @import "./rtl.scss";

+ 2 - 2
packages/semi-foundation/breadcrumb/breadcrumb.scss

@@ -1,8 +1,8 @@
 //@import '../theme/variables.scss';
 // @import '../theme/mixin.scss';
 // @import '../theme/_font.scss';
-@import './variables.scss';
 @import "./animation.scss";
+@import './variables.scss';
 
 $module: #{$prefix}-breadcrumb;
 
@@ -120,4 +120,4 @@ $module: #{$prefix}-breadcrumb;
 
 }
 
-@import './rtl.scss';
+@import './rtl.scss';

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

@@ -1,5 +1,5 @@
-@import './variables.scss';
 @import './animation.scss';
+@import './variables.scss';
 @import './splitButtonGroup.scss';
 
 $module: #{$prefix}-button;

+ 1 - 2
packages/semi-foundation/calendar/foundation.ts

@@ -32,9 +32,8 @@ import {
     parseRangeAllDayEvent,
     DateObj,
     checkWeekend,
-    weeekStartsOnEnum
 } from './eventUtil';
-
+import type { weeekStartsOnEnum } from './eventUtil';
 
 export { weeekStartsOnEnum };
 export interface EventObject {

+ 2 - 2
packages/semi-foundation/calendar/variables.scss

@@ -17,7 +17,7 @@ $width-calendar_day-minWidth: 70px;
 $height-calendar_day_grid: 60px;
 $width-calendar_day_grid: 130px;
 $width-calendar_day_grid-minWidth: 130px;
-$height-calendar_time_grid: $height-calendar_day_grid / 2;
+$height-calendar_time_grid: $height-calendar_day_grid * 0.5;
 $height-calendar_allDay: 26px;
 $height-calendar_allDay-minHeight: 26px;
 $width-calendar_currCircle: 8px;
@@ -29,7 +29,7 @@ $height-calendar_month_day: 24px;
 $height-calendar_month_week_skeletion: 100%;
 $height-calendar_month_grid_wrapper: calc(100% - 27px);
 $width-calendar_card: 220px;
-$height-calendar_day_gridTime: $height-calendar_day_grid / 2;
+$height-calendar_day_gridTime: $height-calendar_day_grid * 0.5;
 $width-calendar_today_date: 24px;
 $height-calendar_today_date: 24px;
 $height-calendar_body_li: 24px;

+ 1 - 1
packages/semi-foundation/carousel/carousel.scss

@@ -1,5 +1,5 @@
-@import './variables.scss';
 @import './animation.scss';
+@import './variables.scss';
 
 $module: #{$prefix}-carousel;
 

+ 2 - 1
packages/semi-foundation/cascader/cascader.scss

@@ -1,5 +1,6 @@
-@import './variables.scss';
 @import "./animation.scss";
+@import './variables.scss';
+
 
 $module: #{$prefix}-cascader;
 

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

@@ -1,7 +1,7 @@
 //@import '../theme/variables.scss';
 // @import '../theme/mixin.scss';
-@import "./variables.scss";
 @import "./animation.scss";
+@import "./variables.scss";
 @import "./mixin.scss";
 
 $module: #{$prefix}-checkbox;
@@ -412,4 +412,4 @@ $module: #{$prefix}-checkbox;
     }
 }
 
-@import "./rtl.scss";
+@import "./rtl.scss";

+ 6 - 5
packages/semi-foundation/datePicker/foundation.ts

@@ -12,13 +12,14 @@ import { getDefaultFormatTokenByType } from './_utils/getDefaultFormatToken';
 import { strings } from './constants';
 import { strings as inputStrings } from '../input/constants';
 
-import { Type, DateInputFoundationProps, InsetInputValue } from './inputFoundation';
-import { MonthsGridFoundationProps } from './monthsGridFoundation';
-import { WeekStartNumber } from './_utils/getMonthTable';
-import { ArrayElement, Motion } from '../utils/type';
 import getInsetInputFormatToken from './_utils/getInsetInputFormatToken';
 import getInsetInputValueFromInsetInputStr from './_utils/getInsetInputValueFromInsetInputStr';
 
+import type { ArrayElement, Motion } from '../utils/type';
+import type { Type, DateInputFoundationProps, InsetInputValue } from './inputFoundation';
+import type { MonthsGridFoundationProps } from './monthsGridFoundation';
+import type { WeekStartNumber } from './_utils/getMonthTable';
+
 export type ValidateStatus = ArrayElement<typeof strings.STATUS>;
 export type InputSize = ArrayElement<typeof strings.SIZE_SET>;
 export type Position = ArrayElement<typeof strings.POSITION_SET>;
@@ -956,7 +957,7 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
             if (insetInput) {
                 const insetInputFormatToken = getInsetInputFormatToken({ format, type });
                 const insetInputStr = this._isMultiple() ? this.formatMultipleDates(dates, undefined, insetInputFormatToken) : this.formatDates(dates, insetInputFormatToken);
-                insetInputValue = getInsetInputValueFromInsetInputStr({ inputValue: insetInputStr, type, rangeSeparator  });
+                insetInputValue = getInsetInputValueFromInsetInputStr({ inputValue: insetInputStr, type, rangeSeparator });
             }
             const isRangeTypeAndInputIncomplete = this._isRangeType() && !this._isRangeValueComplete(dates);
             /**

+ 6 - 6
packages/semi-foundation/datePicker/monthsGridFoundation.ts

@@ -92,8 +92,8 @@ export interface MonthsGridFoundationProps extends MonthsGridElementProps {
     triggerRender?: (props: Record<string, any>) => any;
     insetInput: boolean;
     presetPosition?: PresetPosition;
-    renderQuickControls?: React.ReactNode;
-    renderDateInput?: React.ReactNode;
+    renderQuickControls?: any;
+    renderDateInput?: any;
 }
 
 export interface MonthInfo {
@@ -284,7 +284,7 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
      * sync change another panel month when change months from the else yam panel
      * call it when
      *  - current change panel targe date month is same with another panel date
-     * 
+     *
      * @example
      *  - panelType=right, target=new Date('2022-09-01') and left panel is in '2022-09' => call it, left panel minus one month to '2022-08'
      *  - panelType=left, target=new Date('2021-12-01') and right panel is in '2021-12' => call it, right panel add one month to '2021-01'
@@ -310,7 +310,7 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
         const { monthRight, monthLeft } = this._adapter.getStates();
         const currentDate = panelType === 'left' ? monthLeft.pickerDate : monthRight.pickerDate;
         let target: Date;
-        
+
         switch (switchType) {
             case 'prevMonth':
                 target = addMonths(currentDate, -1);
@@ -335,7 +335,7 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
         const { type } = this._adapter.getProps();
         const diff = this._getDiff('month', target, panelType);
         this.handleYearOrMonthChange(diff < 0 ? 'prevMonth' : 'nextMonth', panelType, Math.abs(diff), false);
-    
+
         if (this.isRangeType(type)) {
             this.handleSyncChangeMonths({ panelType, target });
         }
@@ -936,7 +936,7 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
 
     /**
      * Get year and month panel open type
-     * 
+     *
      * It is useful info to set minHeight of weeks.
      *  - When yam open type is 'left' or 'right', weeks minHeight should be set
      *    If the minHeight is not set, the change of the number of weeks will cause the scrollList to be unstable

+ 5 - 5
packages/semi-foundation/datePicker/variables.scss

@@ -36,7 +36,7 @@ $height-datepicker_month_grid_yearType_insetInput: 317px;
 $height-datepicker_month_grid_timeType_insetInput: 317px;
 
 // Spacing
-$spacing-datepicker_day-marginX: ($width-datepicker_day - $width-datepicker_day_main) / 2; // 日期格子水平外边距
+$spacing-datepicker_day-marginX: ($width-datepicker_day - $width-datepicker_day_main) * 0.5; // 日期格子水平外边距
 $spacing-datepicker_yam_header-paddingX: $spacing-base; // 年月选择 header 水平内边距
 $spacing-datepicker_yam_header-paddingY: $spacing-base-tight; // 年月选择 header 垂直内边距
 $spacing-datepicker_scrolllist_header-padding: $spacing-base; // 时间选择 header 内边距
@@ -182,7 +182,7 @@ $height-datepicker_insetInput_compact: 26px;
 $fontSize-datepicker_insetInput_compact-fontSize: 12px;
 
 $spacing-datepicker_switch_compact-padding: 6px;
-$spacing-datepicker_day_compact-margin: ($width-datepicker_day_compact - $width-datepicker_day_main_compact) / 2;
+$spacing-datepicker_day_compact-margin: ($width-datepicker_day_compact - $width-datepicker_day_main_compact) * 0.5;
 $spacing-datepicker_weeks_compact-padding: 10px;
 $spacing-datepicker_weeks_compact-paddingTop: $spacing-tight - 2px;
 $spacing-datepicker_weekday_compact-paddingLeft: 10px;
@@ -234,12 +234,12 @@ $height-datepicker_date_time_panel_compact: $height-datepicker_date_panel_compac
 $height-datepicker_presetPanel_left_and_right_except_content_compact: 20px + $spacing-datepicker_quick_control_header_compact-paddingTop + $spacing-datepicker_quick_control_content_compact-marginTop; // compact,除去content以外的高度,默认48px
 
 $width-datepicker_presetPanel_left_and_right_two_col_button: ($width-datepicker_presetPanel_left_and_right_content - $spacing-datepicker_quick_control_item-margin) * 0.5; // 左右方位快捷选择面板,固定两列,按钮宽度
-$width-datepicker_presetPanel_top_and_bottom_three_col_button: ($width-datepicker_presetPanel_top_and_bottom_content_date - $spacing-datepicker_quick_control_item-margin * 2) / 3; // 上下方位快捷选择面板,固定三列,按钮宽度
+$width-datepicker_presetPanel_top_and_bottom_three_col_button: ($width-datepicker_presetPanel_top_and_bottom_content_date - $spacing-datepicker_quick_control_item-margin * 2) * 0.333; // 上下方位快捷选择面板,固定三列,按钮宽度
 $width-datepicker_presetPanel_top_and_bottom_five_col_button: ($width-datepicker_presetPanel_top_and_bottom_content_range - $spacing-datepicker_quick_control_item-margin * 4) * 0.2; // 上下方位快捷选择面板,固定五列,按钮宽度
 $width-datepicker_presetPanel_top_and_bottom_two_col_button:($width-datepicker_presetPanel_top_and_bottom_content_month - $spacing-datepicker_quick_control_item-margin) * 0.5; // 上下方位快捷选择面板,固定两列,按钮宽度
 
 // compact
-$width-datepicker_presetPanel_top_and_bottom_three_col_button_compact: ($width-datepicker_presetPanel_top_and_bottom_content_date_compact - $spacing-datepicker_quick_control_item-margin * 2) / 3; // 上下方位快捷选择面板,固定三列,按钮宽度
+$width-datepicker_presetPanel_top_and_bottom_three_col_button_compact: ($width-datepicker_presetPanel_top_and_bottom_content_date_compact - $spacing-datepicker_quick_control_item-margin * 2) * 0.333; // 上下方位快捷选择面板,固定三列,按钮宽度
 $width-datepicker_presetPanel_top_and_bottom_five_col_button_compact: ($width-datepicker_presetPanel_top_and_bottom_content_range_compact - $spacing-datepicker_quick_control_item-margin * 4) * 0.2; // 上下方位快捷选择面板,固定五列,按钮宽度
 $width-datepicker_presetPanel_top_and_bottom_two_col_button_compact: ($width-datepicker_presetPanel_top_and_bottom_content_month_compact - $spacing-datepicker_quick_control_item-margin) * 0.5; // 上下方位快捷选择面板,固定两列,按钮宽度
 
@@ -250,4 +250,4 @@ $height-datepicker_preset_panel_inset_input: $height-datepicker_month_max + $spa
 
 // insetinput compact
 $height-datepicker_inset_input_compact: 28px + $spacing-datepicker_insetInput_wrapper_compact-paddingY; // compact,insetInput高度, 默认36px
-$height-datepicker_preset_panel_inset_input_compact: $height-datepicker_month_max_compact + $width-datepicker_nav_compact + $spacing-datepicker_insetInput_wrapper_compact-paddingY * 2 + $height-datepicker_inset_input_compact; // inset_input下,非month面板渲染最大高度,默认296px
+$height-datepicker_preset_panel_inset_input_compact: $height-datepicker_month_max_compact + $width-datepicker_nav_compact + $spacing-datepicker_insetInput_wrapper_compact-paddingY * 2 + $height-datepicker_inset_input_compact; // inset_input下,非month面板渲染最大高度,默认296px

+ 2 - 2
packages/semi-foundation/datePicker/yearAndMonthFoundation.ts

@@ -14,8 +14,8 @@ export interface YearAndMonthFoundationProps {
     disabledDate?: (date: Date) => boolean;
     density?: string;
     presetPosition?: PresetPosition;
-    renderQuickControls?: React.ReactNode;
-    renderDateInput?: React.ReactNode;
+    renderQuickControls?: any;
+    renderDateInput?: any;
 }
 
 export interface YearAndMonthFoundationState {

+ 1 - 1
packages/semi-foundation/dropdown/dropdown.scss

@@ -1,6 +1,6 @@
 //@import '../theme/variables.scss';
-@import './variables.scss';
 @import './animation.scss';
+@import './variables.scss';
 
 $module: #{$prefix}-dropdown;
 

+ 14 - 0
packages/semi-foundation/form/form.scss

@@ -66,6 +66,20 @@ $rating: #{$prefix}-rating;
                 }
             }
         }
+
+        .#{$field}-group {
+            .#{$col} {
+                &-right {
+                    display: flex;
+                    justify-content: flex-end;
+                }
+                &-left {
+                    display: flex;
+                    justify-content: flex-start;
+                }
+            }
+        }
+
         .#{$field}-pure {
             // padding-top: 0;
             // padding-bottom: 0;

+ 2 - 2
packages/semi-foundation/form/variables.scss

@@ -1,5 +1,5 @@
-$spacing-form_label-paddingTop: ($height-control-default - 20px) / 2; // 水平布局表单标题顶部内边距
-$spacing-form_label_small-paddingTop: ($height-control-default - 24px) / 2; // 水平布局 小尺寸表单标题顶部内边距
+$spacing-form_label-paddingTop: ($height-control-default - 20px) * 0.5; // 水平布局表单标题顶部内边距
+$spacing-form_label_small-paddingTop: ($height-control-default - 24px) * 0.5; // 水平布局 小尺寸表单标题顶部内边距
 
 $spacing-form_field_horizontal-paddingRight: $spacing-base; // 水平布局表单标题右侧内边距
 $spacing-form_field_group_horizontal-paddingRight: $spacing-base; // 水平布局表单组标题右侧内边距

+ 1 - 1
packages/semi-foundation/grid/grid.scss

@@ -118,4 +118,4 @@ $module: #{$prefix};
     @include make-grid(-xxl);
 }
 
-@import "./rtl.scss";
+@import "./rtl.scss";

+ 10 - 10
packages/semi-foundation/grid/mixin.scss

@@ -1,6 +1,6 @@
+@use "sass:math";
 // mixins for clearfix
 // ------------------------
-
 //TODO 提出到公共mixin
 @mixin clearfix() {
     zoom: 1;
@@ -21,8 +21,8 @@
 @mixin make-row($gutter: $width-grid_gutter) {
     position: relative;
     height: auto;
-    margin-right: ($gutter / -2);
-    margin-left: ($gutter / -2);
+    margin-right: ($gutter * -0.5);
+    margin-left: ($gutter * -0.5);
     @include clearfix();
 }
 
@@ -35,8 +35,8 @@
         #{$item} {
             position: relative;
             min-height: 1px;
-            padding-right: ($width-grid_gutter / 2);
-            padding-left: ($width-grid_gutter / 2);
+            padding-right: ($width-grid_gutter * 0.5);
+            padding-left: ($width-grid_gutter * 0.5);
         }
     }
 }
@@ -59,19 +59,19 @@
         .#{$module}-col#{$class}-#{$i} {
             display: block;
             box-sizing: border-box;
-            width: percentage(($i / $width-grid_columns));
+            width: percentage(math.div($i , $width-grid_columns));
         }
 
         .#{$module}-col#{$class}-push-#{$i} {
-            left: percentage(($i / $width-grid_columns));
+            left: percentage(math.div($i , $width-grid_columns));
         }
 
         .#{$module}-col#{$class}-pull-#{$i} {
-            right: percentage(($i / $width-grid_columns));
+            right: percentage(math.div($i , $width-grid_columns));
         }
 
         .#{$module}-col#{$class}-offset-#{$i} {
-            margin-left: percentage(($i / $width-grid_columns));
+            margin-left: percentage(math.div($i , $width-grid_columns));
         }
 
         .#{$module}-col#{$class}-order-#{$i} {
@@ -101,7 +101,7 @@
         .#{$module}-col#{$class}-offset-#{$i} {
             .#{$prefix}-rtl & {
                 margin-left: auto;
-                margin-right: percentage(($i / $width-grid_columns));
+                margin-right: percentage(math.div($i , $width-grid_columns));
             }
         }
     }

+ 14 - 6
packages/semi-foundation/gulpfile.js

@@ -34,7 +34,7 @@ gulp.task('compileTSForCJS', function compileTSForCJS() {
 });
 
 const excludeScss = [
-    '!**/button/splitButtonGroup.scss', 
+    '!**/button/splitButtonGroup.scss',
     '!**/steps/bacisSteps.scss',
     '!**/steps/fillSteps.scss',
     '!**/steps/navSteps.scss',
@@ -49,8 +49,16 @@ gulp.task('compileScss', function compileScss() {
             function (chunk, enc, cb) {
                 const rootPath = path.join(__dirname, '../../');
                 const scssVarStr = `@import "${rootPath}/packages/semi-theme-default/scss/index.scss";\n`;
-                const scssBuffer = Buffer.from(scssVarStr);
-                chunk.contents = Buffer.concat([scssBuffer, chunk.contents]);
+                let scssRaw = chunk.contents.toString('utf-8');
+                if (scssRaw.startsWith("@use")) {
+                    const scssRawSplit = scssRaw.split("\n");
+                    const codeStartIndex = scssRawSplit.findIndex(item => !item.startsWith("@use"));
+                    scssRawSplit.splice(codeStartIndex, 0, scssVarStr);
+                    scssRaw = scssRawSplit.join("\n");
+                } else {
+                    scssRaw = `${scssVarStr}\n${scssRaw}`;
+                }
+                chunk.contents = Buffer.from(scssRaw, 'utf-8');
                 cb(null, chunk);
             }
         ))
@@ -67,11 +75,11 @@ gulp.task('moveScss', function moveScss() {
         .pipe(gulp.dest('lib/cjs'));
 });
 
-gulp.task('compileLib', 
+gulp.task('compileLib',
     gulp.series(
         [
-            'cleanLib', 'compileScss', 
-            'moveScss', 
+            'cleanLib', 'compileScss',
+            'moveScss',
             gulp.parallel('compileTSForESM', 'compileTSForCJS'),
         ]
     )

+ 11 - 0
packages/semi-foundation/image/animation.scss

@@ -0,0 +1,11 @@
+$transform_rotate-image_preview_spinner: var(--semi-transform_rotate-clockwise360deg); // 预览图像加载spin旋转角度
+$transform_scale3d-image_preview_image_img: 1, 1, 1; // 预览图片放大
+$transform_rotate-image_preview_image_img: var(--semi-transform-rotate-none); // 预览图片旋转角度
+
+$transition_duration-image_preview_image_img: 0.3s; // 预览图像动画持续时间
+$transition_function-image_preview_image_img: cubic-bezier(0.215, 0.61, 0.355, 1); // 预览图片动画函数
+$transition_delay-image_preview_image_img: 0s; // 预览图片延迟时间
+
+$transition_duration-image_preview: 500ms; // 预览图片透明度变化动画时间
+
+$transform_rotate-image_preview_icon_rtl: var(--semi-transform_rotate-clockwise180deg); // rtl模式下向前/向后切换图片按钮旋转角度

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

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

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

@@ -0,0 +1,224 @@
+@import "./animation.scss";
+@import "./variables.scss";
+
+$module: #{$prefix}-image;
+
+.#{$module} {
+
+    border-radius: $radius-image;
+    position: relative;
+    display: inline-block;
+    overflow: hidden;
+
+    &-img {
+        vertical-align: middle;
+        border-radius: inherit;
+        user-select: none;
+
+        &-preview {
+            cursor: zoom-in;
+        }
+
+        &-error {
+            opacity: 0;
+        }
+    }
+
+    &-overlay {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+    }
+}
+
+.#{$module}-status {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    border-radius: $radius-image;
+    background-color: $color-image_status-bg;
+    color: $color-image_status;
+}
+
+.#{$module}-preview {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: $z-image_preview;
+    background-color: var(--semi-color-overlay-bg);
+    transition: opacity $transition_duration-image_preview;
+    overflow: hidden;
+
+    &-popup {
+        position: absolute;
+    }
+
+    .#{$module}-preview-hide {
+        opacity: 0;
+    }
+
+    &-icon {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: $width-image_preview_icon;
+        height: $height-image_preview_icon;
+        border-radius: 50%;
+        position: absolute;
+        top: 50%;
+        transform: translateY(-50%);
+        background: $color-image_preview_icon-bg;
+        cursor: pointer;
+        color: $color-image_preview_icon;
+    }
+
+    &-prev {
+        left: $spacing-image_preview_icon-x;
+    }
+
+    &-next {
+        right: $spacing-image_preview_icon-x;
+    }
+
+    &-header {
+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 0;
+        font-weight: normal;
+        @include font-size-regular;
+        color: $color-image_preview_header;
+        height: $height-image_preview_header;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: $spacing-image_preview_header-paddingY $spacing-image_preview_header-paddingX;
+        z-index: $z-image_preview_header;
+
+        &-title {
+            flex: 1;
+        }
+
+        &-close {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            cursor: pointer;
+            width: $width-image_preview_header_close;
+            height: $height-image_preview_header_close;
+            border-radius: 50%;
+
+            &:hover {
+                background-color: $color-image_header_close-bg;
+            }
+        }
+    }
+
+    &-footer {
+        display: flex;
+        align-items: center;
+        padding: $spacing-image_preview_footer-paddingY $spacing-image_preview_footer-paddingX;
+        background: $color-image_preview_footer-bg;
+        border-radius: $radius-image_preview_footer;
+        height: $height-image_preview_footer;
+
+        &-wrapper {
+            position: absolute;
+            left: 50%;
+            bottom: 16px;
+            transform: translateX(-50%);
+        }
+
+        &-page {
+            user-select: none;
+            color: $color-image_preview_footer_icon;
+            @include font-size-header-6;
+            margin: $spacing-image_preview_footer_page-marginY $spacing-image_preview_footer_page-marginX;
+        }
+
+        .#{$prefix}-icon {
+            color: $color-image_preview_footer_icon;
+            cursor: pointer;
+        }
+
+        &-gap {
+            margin-left: $spacing-image_preview_footer_gap-marginLeft;
+        }
+    
+        .#{$prefix}-slider {
+            width: $width-image_preview_footer_slider;
+            padding: $spacing-image_preview_footer_slider-paddingY $spacing-image_preview_footer_slider-paddingX;
+    
+            .#{$prefix}-slider-rail {
+                color: $color-image_preview_footer_slider_rail;
+                height: $height-image_preview_footer_slider;
+            }
+    
+            .#{$prefix}-slider-track {
+                height: $height-image_preview_footer_slider;
+            }
+    
+            .#{$prefix}-slider-handle {
+                width: $width-image_preview_footer_slider_handle;
+                height: $height-image_preview_footer_slider_handle;
+                margin-top: $spacing-image_preview_footer_slider_handle-marginTop;
+                box-sizing: border-box;
+            }
+        }
+
+        .#{$prefix}-divider {
+            background: $color-image-preview_divider-bg;
+            margin: $spacing-image_preview_footer_divider-marginY $spacing-image_preview_footer_divider-marginX;
+        }
+        
+        .#{$module}-preview-footer-disabled {
+            color: $color-image_preview_disabled;
+            cursor: default;
+            pointer-events: none;
+        }
+
+    }
+
+    &-image {
+        position: relative;
+        height: 100%;
+
+        &-img {
+            position: absolute;
+            transform: scale3d($transform_scale3d-image_preview_image_img) $transform_rotate-image_preview_image_img;
+            transition: transform $transition_duration-image_preview_image_img  $transition_delay-image_preview_image_img;
+            z-index: 0;
+            user-select: none;
+        }
+
+        &-spin {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+
+            .#{$prefix}-spin-wrapper {
+                color: $color-image_preview_image_spin;
+            }
+        }
+    }
+    
+    @keyframes spinner {
+        to {
+            transform: $transform_rotate-image_preview_spinner;
+        }
+    }
+}
+
+// Remove the default border of img when src is empty or src is invalid
+img[src=""], img:not([src]) {
+    opacity: 0;
+}
+
+@import "./rtl.scss";

+ 64 - 0
packages/semi-foundation/image/imageFoundation.ts

@@ -0,0 +1,64 @@
+import BaseFoundation, { DefaultAdapter } from "../base/foundation";
+import { isObject } from "lodash";
+
+
+export interface ImageAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    getIsInGroup: () => boolean;
+}
+
+
+export default class ImageFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<ImageAdapter<P, S>, P, S> {
+    constructor(adapter: ImageAdapter<P, S>) {
+        super({ ...adapter });
+    }
+
+    handleClick = (e: any) => {
+        const { imageID, preview } = this.getProps();
+        // if preview = false, then it cannot preview
+        if (!preview) {
+            return;
+        }
+        // if image in group, then use group's Preview components
+        if (this._adapter.getIsInGroup()) {
+            const { setCurrentIndex, handleVisibleChange } = this._adapter.getContexts();
+            setCurrentIndex(imageID);
+            handleVisibleChange(true);
+        } else {
+            // image isn't in group, then use it's own Preview components
+            this.handlePreviewVisibleChange(true);
+        }
+    }
+
+    handleLoaded = (e: any) => {
+        const { onLoad } = this.getProps();
+        onLoad && onLoad(e);
+        this.setState ({
+            loadStatus: "success",
+        } as any);
+    }
+
+    handleError = (e: any) => {
+        const { onError } = this.getProps();
+        onError && onError(e);
+        this.setState ({
+            loadStatus: "error",
+        } as any);
+    }
+
+    handlePreviewVisibleChange = (newVisible: boolean) => {
+        const { preview } = this.getProps();
+        if (isObject(preview)) {
+            const { onVisibleChange } = preview as any;
+            onVisibleChange && onVisibleChange(newVisible);
+            if (!("visible" in this.getProps())) {
+                this.setState({
+                    previewVisible: newVisible,
+                } as any);
+            }
+        } else {
+            this.setState({
+                previewVisible: newVisible,
+            } as any);
+        }
+    }
+}

+ 41 - 0
packages/semi-foundation/image/previewFooterFoundation.ts

@@ -0,0 +1,41 @@
+import BaseFoundation, { DefaultAdapter } from "../base/foundation";
+
+export interface PreviewFooterAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    setStartMouseOffset: (time: number) => void;
+}
+
+export default class PreviewFooterFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<PreviewFooterAdapter<P, S>, P, S> {
+    
+    changeSliderValue = (type: string): void => {
+        const { zoom, step, min, max } = this.getProps();
+        let newValue = type === "plus" ? zoom + step : zoom - step;
+        if (newValue > max) {
+            newValue = max;
+        } else if (newValue < min) {
+            newValue = min;
+        }
+        this.handleValueChange(newValue);
+    };
+
+    handleValueChange = (value: number): void => {
+        const { onZoomIn, onZoomOut, zoom } = this.getProps();
+        if (value > zoom) {
+            onZoomIn(value / 100);
+        } else {
+            onZoomOut(value / 100);
+        }
+        this._adapter.setStartMouseOffset(value);
+    };
+
+    handleRatioClick = (): void => {
+        const { ratio, onAdjustRatio } = this.getProps();
+        const type = ratio === "adaptation" ? "realSize" : "adaptation";
+        onAdjustRatio(type);
+    }
+
+    handleRotate = (direction: string): void => {
+        const { onRotate } = this.getProps();
+        onRotate && onRotate(direction);
+    }
+    
+}

+ 25 - 0
packages/semi-foundation/image/previewFoundation.ts

@@ -0,0 +1,25 @@
+import BaseFoundation, { DefaultAdapter } from "../base/foundation";
+
+export default class PreviewFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<Partial<DefaultAdapter>> {
+    
+    handleVisibleChange = (newVisible : boolean) => {
+        const { visible, onVisibleChange } = this.getProps();
+        if (!(visible in this.getProps())) {
+            this.setState({
+                visible: newVisible,
+            });
+        }
+        onVisibleChange && onVisibleChange(newVisible);
+    };
+
+    handleCurrentIndexChange = (index: number) => {
+        const { currentIndex, onChange } = this.getProps();
+        if (!(currentIndex in this.getProps())) {
+            this.setState({
+                currentIndex: index,
+            } as any);
+        }
+        onChange && onChange(index);
+    };
+    
+}

+ 260 - 0
packages/semi-foundation/image/previewImageFoundation.ts

@@ -0,0 +1,260 @@
+import BaseFoundation, { DefaultAdapter } from "../base/foundation";
+import { handlePrevent } from "../utils/a11y";
+import { throttle, isUndefined } from "lodash";
+
+export interface PreviewImageAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    getOriginImageSize: () => { originImageWidth: number; originImageHeight: number; }; 
+    setOriginImageSize: (size: { originImageWidth: number; originImageHeight: number; }) => void;
+    getContainerRef: () => any;
+    getImageRef: () => any;
+    getMouseMove: () => boolean;
+    setStartMouseMove: (move: boolean) => void;
+    getMouseOffset: () => { x: number; y: number };
+    setStartMouseOffset: (offset: { x: number; y: number }) => void;
+    setLoading: (loading: boolean) => void;
+}
+
+export interface DragDirection {
+    canDragVertical: boolean;
+    canDragHorizontal: boolean;
+}
+
+export interface ExtremeBounds {
+    left: number;
+    top: number;
+}
+
+export interface ImageOffset {
+    x: number;
+    y: number;
+}
+
+export default class PreviewImageFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<PreviewImageAdapter<P, S>, P, S> {
+    constructor(adapter: PreviewImageAdapter<P, S>) {
+        super({ ...adapter });
+    }
+
+    _isImageVertical = (): boolean => this.getProp("rotation") % 180 !== 0;
+
+    _getImageBounds = (): DOMRect => {
+        const imageRef = this._adapter.getImageRef();
+        return imageRef?.getBoundingClientRect();
+    };
+
+    _getContainerBounds = (): DOMRect => {
+        const containerRef = this._adapter.getContainerRef();
+        return containerRef?.current?.getBoundingClientRect();
+    }
+
+    _getOffset = (e: any): ImageOffset => {
+        const { left, top } = this._getImageBounds();
+        return {
+            x: e.clientX - left,
+            y: e.clientY - top,
+        };
+    }
+
+    setLoading = (loading: boolean) => {
+        this._adapter.setLoading(loading);
+    }
+
+    handleWindowResize = (): void => {
+        const { setRatio } = this.getProps();
+        const { ratio } = this.getProps();
+        const { originImageWidth, originImageHeight } = this._adapter.getOriginImageSize();
+        if (originImageWidth && originImageHeight) {
+            if (ratio !== "adaptation") {
+                setRatio("adaptation");
+            } else {
+                this.handleResizeImage();
+            } 
+        }
+    };
+
+    handleLoad = (e: any): void => {
+        if (e.target) {
+            const { width: w, height: h } = e.target as any;
+            this._adapter.setOriginImageSize({ originImageWidth: w, originImageHeight: h });
+            this.setState({
+                loading: false,
+            } as any);
+            this.handleResizeImage();
+        }
+        const { src, onLoad } = this.getProps();
+        onLoad && onLoad(src);
+    }
+
+    handleError = (e: any): void => {
+        const { onError, src } = this.getProps();
+        this.setState({
+            loading: false,
+        } as any);
+        onError && onError(src);
+    }
+
+    handleResizeImage = () => {
+        const horizontal = !this._isImageVertical();
+        const { originImageWidth, originImageHeight } = this._adapter.getOriginImageSize();
+        const imgWidth = horizontal ? originImageWidth : originImageHeight;
+        const imgHeight = horizontal ? originImageHeight : originImageWidth;
+        const { onZoom } = this.getProps();
+        const containerRef = this._adapter.getContainerRef();
+        if (containerRef && containerRef.current) {
+            const { width: containerWidth, height: containerHeight } = this._getContainerBounds();
+            const reservedWidth = containerWidth - 80;
+            const reservedHeight = containerHeight - 80;
+            const _zoom = Number(
+                Math.min(reservedWidth / imgWidth, reservedHeight / imgHeight).toFixed(2)
+            );
+            onZoom(_zoom);
+        }
+    }
+
+    handleRightClickImage = (e: any) => {
+        const { disableDownload } = this.getProps();
+        if (disableDownload) {
+            e.preventDefault();
+            e.stopPropagation();
+            return false;
+        } else {
+            return true;
+        }
+    };
+
+    handleWheel = (e: React.WheelEvent<HTMLImageElement>) => {
+        this.onWheel(e);
+        handlePrevent(e);
+    }
+
+    onWheel = throttle((e: React.WheelEvent<HTMLImageElement>): void => {
+        const { onZoom, zoomStep, maxZoom, minZoom } = this.getProps();
+        const { currZoom } = this.getStates();
+        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 (!isUndefined(_zoom)) {
+            onZoom(_zoom);
+        }
+    }, 50);
+
+    calcCanDragDirection = (): DragDirection => {
+        const { width, height } = this.getStates();
+        const { rotation } = this.getProps();
+        const { width: containerWidth, height: containerHeight } =this._getContainerBounds();
+        let canDragHorizontal = width > containerWidth;
+        let canDragVertical = height > containerHeight;
+        if (this._isImageVertical()) {
+            canDragHorizontal = height > containerWidth;
+            canDragVertical = width > containerHeight;
+        }
+        return {
+            canDragVertical,
+            canDragHorizontal,
+        };
+    };
+
+    handleZoomChange = (newZoom: number, e: any): void => {
+        const imageRef = this._adapter.getImageRef();
+        const { originImageWidth, originImageHeight } = this._adapter.getOriginImageSize();
+        const { canDragVertical, canDragHorizontal } = this.calcCanDragDirection();
+        const canDrag = canDragVertical || canDragHorizontal;
+        const { width: containerWidth, height: containerHeight } = this._getContainerBounds();
+        const newWidth = Math.floor(originImageWidth * newZoom);
+        const newHeight = Math.floor(originImageHeight * newZoom);
+
+        // debugger;
+        let _offset;
+        const horizontal = !this._isImageVertical();
+        let newTop = 0;
+        let newLeft = 0;
+        if (horizontal) {
+            _offset = {
+                x: 0.5 * (containerWidth - newWidth),
+                y: 0.5 * (containerHeight - newHeight),
+            };
+           
+            newLeft = _offset.x;
+            newTop= _offset.y;
+        } else {
+            _offset = {
+                x: 0.5 * (containerWidth - newHeight),
+                y: 0.5 * (containerHeight - newWidth),
+            };
+            newLeft = _offset.x - (newWidth - newHeight) / 2;
+            newTop = _offset.y + (newWidth - newHeight) / 2;
+        }
+        
+        this.setState({
+            width: newWidth,
+            height: newHeight,
+            offset: _offset,
+            left: newLeft,
+            top: newTop,
+            currZoom: newZoom,
+        } as any);
+        imageRef && (imageRef.style.cursor = canDrag ? "grab" : "default");
+    };
+
+    calcExtremeBounds = (): ExtremeBounds => {
+        const { width, height } = this.getStates(); 
+        const { width: containerWidth, height: containerHeight } = this._getContainerBounds();
+        let extremeLeft = containerWidth - width;
+        let extremeTop = containerHeight - height;
+        if (this._isImageVertical()) {
+            extremeLeft = containerWidth - height;
+            extremeTop = containerHeight - width;
+        }
+        return {
+            left: extremeLeft,
+            top: extremeTop,
+        };
+    };
+
+    handleMoveImage = (e: any): void => {
+        const { offset, width, height } = this.getStates();
+        const startMouseMove = this._adapter.getMouseMove();
+        const startMouseOffset = this._adapter.getMouseOffset();
+        const { canDragVertical, canDragHorizontal } = this.calcCanDragDirection();
+        if (startMouseMove && (canDragVertical || canDragHorizontal)) {
+            const { clientX, clientY } = e;
+            const { left: containerLeft, top: containerTop } = this._getContainerBounds();
+            const { left: extremeLeft, top: extremeTop } = this.calcExtremeBounds();
+            let newX = canDragHorizontal ? clientX - containerLeft - startMouseOffset.x : offset.x;
+            let newY = canDragVertical ? clientY - containerTop - startMouseOffset.y : offset.y;
+            if (canDragHorizontal) {
+                newX = newX > 0 ? 0 : newX < extremeLeft ? extremeLeft : newX;
+            }
+            if (canDragVertical) {
+                newY = newY > 0 ? 0 : newY < extremeTop ? extremeTop : newY;
+
+            }
+            const _offset = {
+                x: newX,
+                y: newY,
+            };
+            this.setState({
+                offset: _offset,
+                left: this._isImageVertical() ? _offset.x - (width - height) / 2 : _offset.x,
+                top: this._isImageVertical() ? _offset.y + (width - height) / 2 : _offset.y,
+            } as any);
+        }
+    };
+
+    handleImageMouseDown = (e: any): void => {
+        this._adapter.setStartMouseOffset(this._getOffset(e));
+        this._adapter.setStartMouseMove(true);
+    };
+
+    handleImageMouseUp = (): void => {
+        this._adapter.setStartMouseMove(false);
+    };
+}

+ 264 - 0
packages/semi-foundation/image/previewInnerFoundation.ts

@@ -0,0 +1,264 @@
+import BaseFoundation, { DefaultAdapter } from "../base/foundation";
+import KeyCode from "../utils/keyCode";
+import { getPreloadImagArr, downloadImage, isTargetEmit } from "./utils";
+
+export interface PreviewInnerAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    getIsInGroup: () => boolean;
+    notifyChange: (index: number) => void;
+    notifyZoom: (zoom: number, increase: boolean) => void;
+    notifyClose: () => void;
+    notifyVisibleChange: (visible: boolean) => void;
+    notifyRatioChange: (type: string) => void;
+    notifyRotateChange: (angle: number) => void;
+    notifyDownload: (src: string, index: number) => void;
+    registerKeyDownListener: () => void;
+    unregisterKeyDownListener: () => void;
+    getMouseActiveTime: () => number;
+    getStopTiming: () => boolean;
+    setStopTiming: (value: boolean) => void;
+    getStartMouseDown: () => {x: number, y: number};
+    setStartMouseDown: (x: number, y: number) => void;
+    setMouseActiveTime: (time: number) => void;
+}
+
+const NOT_CLOSE_TARGETS = ["icon", "footer"];
+const STOP_CLOSE_TARGET = ["icon", "footer", "header"];
+
+export default class PreviewInnerFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<PreviewInnerAdapter<P, S>, P, S> {
+    constructor(adapter: PreviewInnerAdapter<P, S>) {
+        super({ ...adapter });
+    }
+
+    beforeShow() {
+        this._adapter.registerKeyDownListener();
+    }
+
+    afterHide() {
+        this._adapter.unregisterKeyDownListener();
+    }
+
+    handleRatio(type: string) {
+        this.setState({
+            ratio: type,
+        } as any);
+    }
+
+    handleViewVisibleChange = () => {
+        const nowTime = new Date().getTime();
+        const mouseActiveTime = this._adapter.getMouseActiveTime();
+        const stopTiming = this._adapter.getStopTiming();
+        const { viewerVisibleDelay } = this.getProps();
+        const { viewerVisible } = this.getStates();
+        if (nowTime - mouseActiveTime > viewerVisibleDelay && !stopTiming) {
+            viewerVisible && this.setState({
+                viewerVisible: false,
+            } as any);
+        }
+    }
+
+    handleMouseMoveEvent = (e: any, event: string) => {
+        const isTarget = isTargetEmit(e.nativeEvent, STOP_CLOSE_TARGET);
+        if (isTarget && event === "over") {
+            this._adapter.setStopTiming(true);
+        } else if (isTarget && event === "out") {
+            this._adapter.setStopTiming(false);
+        }
+    }
+
+    handleMouseMove = (e: any) => {
+        this._adapter.setMouseActiveTime(new Date().getTime());
+        this.setState({
+            viewerVisible: true,
+        } as any);
+    }
+
+    handleMouseUp = (e: any) => {
+        const { maskClosable } = this.getProps();
+        let couldClose = !isTargetEmit(e.nativeEvent, NOT_CLOSE_TARGETS);
+        const { clientX, clientY } = e;
+        const { x, y } = this._adapter.getStartMouseDown();
+        // 对鼠标移动做容错处理,当 x 和 y 方向在 mouseUp 的时候移动距离都小于等于 5px 时候就可以关闭预览
+        // Error-tolerant processing of mouse movement, when the movement distance in the x and y directions is less than or equal to 5px in mouseUp, the preview can be closed
+        // 不做容错处理的话,直接用 clientX !== x || y !== clientY 做判断,鼠标在用户点击时候无意识的轻微移动无法关闭预览,不符合用户预期
+        // If you do not do fault-tolerant processing, but directly use clientX !== x || y !== clientY to make judgments, the slight movement of the mouse when the user clicks will not be able to close the preview, which does not meet the user's expectations.
+        if (Math.abs(clientX - x) > 5 || Math.abs(y - clientY) > 5) {
+            couldClose = false;
+        }
+        if (couldClose && maskClosable) {
+            this.handlePreviewClose();
+        }
+    }
+
+    handleMouseDown = (e: any) => {
+        const { clientX, clientY } = e;
+        this._adapter.setStartMouseDown(clientX, clientY);
+    }
+
+    handleKeyDown = (e: any) => {
+        const { closeOnEsc } = this.getProps();
+        if (closeOnEsc && e.keyCode === KeyCode.ESC) {
+            e.stopPropagation();
+            this._adapter.notifyVisibleChange(false);
+            this._adapter.notifyClose();
+            return;
+        }
+    }
+
+    handleSwitchImage = (direction: string) => {
+        const step = direction === "prev" ? -1 : 1;
+        const { imgSrc, currentIndex: currentIndexInState } = this.getStates();
+        const srcLength = imgSrc.length;
+        const newIndex = (currentIndexInState + step + srcLength) % srcLength;
+        if ("currentIndex" in this.getProps()) {
+            if (this._adapter.getIsInGroup()) {
+                const setCurrentIndex = this._adapter.getContext("setCurrentIndex");
+                setCurrentIndex(newIndex);
+            }
+        } else {
+            this.setState({
+                currentIndex: newIndex,
+            } as any);
+        }
+        this._adapter.notifyChange(newIndex);
+        this.setState({
+            direction,
+            rotation: 0,
+        } as any);
+        this._adapter.notifyRotateChange(0);
+    }  
+
+    handleDownload = () => {
+        const { currentIndex, imgSrc } = this.getStates();
+        const downloadSrc = imgSrc[currentIndex];
+        const downloadName = downloadSrc.slice(downloadSrc.lastIndexOf("/") + 1);
+        downloadImage(downloadSrc, downloadName);
+        this._adapter.notifyDownload(downloadSrc, currentIndex);
+    }
+
+    handlePreviewClose = () => {
+        this._adapter.notifyVisibleChange(false);
+        this._adapter.notifyClose();
+    }
+
+    handleAdjustRatio = (type: string) => {
+        this.setState({
+            ratio: type,
+        } as any);
+        this._adapter.notifyRatioChange(type);
+    }
+
+    handleRotateImage = (direction: string) => {
+        const { rotation } = this.getStates();
+        const newRotation = rotation + (direction === "left" ? 90 : (-90));
+        this.setState({
+            rotation: newRotation,
+        } as any);
+        this._adapter.notifyRotateChange(newRotation);
+    }
+
+    handleZoomImage = (newZoom: number) => {
+        const { zoom } = this.getStates();
+        this._adapter.notifyZoom(newZoom, newZoom > zoom);
+        this.setState({
+            zoom: newZoom,
+        } as any);
+    }
+
+    // 当 visible 改变之后,预览组件完成首张图片加载后,启动预加载
+    // 如: 1,2,3,4,5,6,7,8张图片, 点击第 4 张图片,preLoadGap 为 2
+    // 当 visible 从 false 变为 true ,首先加载第 4 张图片,当第 4 张图片加载完成后,
+    // 再按照 5,3,6,2的顺序预先加载这几张图片
+    // When visible changes, the preview component finishes loading the first image and starts preloading
+    // Such as: 1, 2, 3, 4, 5, 6, 7, 8 pictures, click the 4th picture, preLoadGap is 2
+    // When visible changes from false to true , load the 4th image first, when the 4th image is loaded,
+    // Preload these pictures in the order of 5, 3, 6, 2
+    preloadGapImage = () => {
+        const { preLoad, preLoadGap, infinite, currentIndex } = this.getProps();
+
+        const { imgSrc }= this.getStates();
+        if (!preLoad || typeof preLoadGap !== "number" || preLoadGap < 1){
+            return;
+        }
+
+        const preloadImages = getPreloadImagArr(imgSrc, currentIndex, preLoadGap, infinite);
+        const Img = new Image();
+        let index = 0;
+        function callback(e: any){
+            index++;
+            if (index < preloadImages.length) {
+                Img.src = preloadImages[index];
+            }
+        }
+        Img.onload = (e) => {
+            this.setLoadSuccessStatus(Img.src);
+            callback(e);
+        };
+        Img.onerror = callback;
+        Img.src = preloadImages[0];  
+    }
+
+    // 在切换左右图片时,当被切换图片完成加载后,根据方向决定下一个预加载的图片
+    // 如: 1,2,3,4,5,6,7,8张图片
+    // 当 preLoadGap 为 2, 从第 5 张图片进行切换
+    // - 如果向 右 切换到第 6 张,则第 6 张图片加载动作结束后(无论加载成功 or 失败),会预先加载第 8 张;
+    // - 如果向 左 切换到第 4 张,则第 4 张图片加载动作结束后(无论加载成功 or 失败),会预先加载第 2 张;
+    // When switching the left and right pictures, when the switched picture is loaded, the next preloaded picture is determined according to the direction
+    // Such as: 1, 2, 3, 4, 5, 6, 7, 8 pictures
+    // When preLoadGap is 2, switch from the 5th picture
+    // - If you switch to the 6th image(direction is next), the 8th image will be preloaded after the 6th image is loaded (whether it succeeds or fails to load);
+    // - If you switch to the 4th image(direction is prev), the second image will be preloaded after the 4th image is loaded (whether it succeeds or fails to load);
+    preloadSingleImage = () => {
+        const { preLoad, preLoadGap, infinite } = this.getProps();
+        const { imgSrc, currentIndex, direction, imgLoadStatus } = this.getStates();
+        if (!preLoad || typeof preLoadGap !== "number" || preLoadGap < 1){
+            return;
+        }
+        // 根据方向决定preload那个index
+        // Determine the index of preload according to the direction
+        let preloadIndex = currentIndex + (direction === "prev" ? -1 : 1) * preLoadGap;
+        if (preloadIndex < 0 || preloadIndex >= imgSrc.length) {
+            if (infinite) {
+                preloadIndex = (preloadIndex + imgSrc.length) % imgSrc.length;
+            } else {
+                return;
+            }
+        }
+        // 如果图片没有加载成功过,则进行预加载
+        // If the image has not been loaded successfully, preload it
+        if (!imgLoadStatus[preloadIndex]) {
+            const Img = new Image();
+            Img.onload = (e) => {
+                this.setLoadSuccessStatus(imgSrc[preloadIndex]);
+            };
+            Img.src = imgSrc[preloadIndex];
+        }
+    }
+
+    setLoadSuccessStatus = (src: string) => {
+        const { imgLoadStatus } = this.getStates();
+        const status = { ...imgLoadStatus };
+        status[src] = true;
+        this.setState({
+            imgLoadStatus: status,
+        } as any);
+    }
+
+    onImageLoad = (src: string) => {
+        const { preloadAfterVisibleChange } = this.getStates();
+        this.setLoadSuccessStatus(src);
+        // 当 preview 中当前加载的图片加载完成后,
+        // 如果是在visible change之后的第一次加载,则启动加载该currentIndex左右preloadGap范围的图片
+        // 如果是非第一次加载,是在左右切换图片,则根据方向预先加载单张图片
+        // When the currently loaded image in Preview is loaded,
+        // - It is the first load after visible change, start loading the images in the preloadGap range around the currentIndex
+        // - It is not the first load, the image is switched left and right, and a single image is preloaded according to the direction
+        if (preloadAfterVisibleChange) {
+            this.preloadGapImage();
+            this.setState({
+                preloadAfterVisibleChange: false,
+            } as any);
+        } else {
+            this.preloadSingleImage();
+        }
+    }   
+}

+ 51 - 0
packages/semi-foundation/image/rtl.scss

@@ -0,0 +1,51 @@
+@import "./variables.scss";
+@import "./animation.scss";
+
+$module: #{$prefix}-image;
+
+.#{$prefix}-rtl,
+.#{$prefix}-portal-rtl {
+
+    .#{$module}-preview {
+
+        direction: rtl;
+
+        &-group {
+            direction: rtl;
+        }
+        
+        &-prev {
+            right: $spacing-image_preview_icon-x;
+            left: auto;
+            transform: $transform_rotate-image_preview_icon-rtl;
+        }
+    
+        &-next {
+            left: $spacing-image_preview_icon-x;
+            right: auto;
+            transform: $transform_rotate-image_preview_icon-rtl;
+        }
+
+        &-footer {
+
+            &-page {
+                display: flex;
+                direction: rtl;
+            }
+
+            &-gap {
+                margin-right: $spacing-image_preview_footer_gap-marginLeft;
+                margin-left: $spacing-image_preview_footer_gap_rtl-marginLeft;
+            }
+
+            .#{$prefix}-icon-chevron_left {
+                transform: $transform_rotate-image_preview_icon_rtl;
+            }
+
+            .#{$prefix}-icon-chevron_right {
+                transform: $transform_rotate-image_preview_icon_rtl;
+            }
+        }
+
+    }
+}

+ 88 - 0
packages/semi-foundation/image/utils.ts

@@ -0,0 +1,88 @@
+export const isTargetEmit = (event, targetClasses): boolean => {
+    // event.path usage is discouraged, use event.composedPath() as it's standard and is more future-proof
+    // path is the event-triggered bubbling path, which stores each node through which bubbling passes.
+    // path.length-4 is to remove elements above the root node, such as body, html, document, window
+    const path = event?.composedPath();
+    const isTarget = path?.slice(0, path.length - 4).some((node): boolean => {
+        if (node.className && typeof node.className === "string") {
+            return targetClasses.some(c => node.className.includes(c));
+        }
+        return false;
+    });
+    return isTarget;
+};
+
+export const downloadImage = (src: string, filename: string): void => {
+    const image = new Image();
+    image.src = src;
+    image.crossOrigin = "anonymous";
+    image.onload = (e): void => {
+        const eleLink = document.createElement("a");
+        eleLink.download = filename;
+        eleLink.style.display = "none";
+        eleLink.download = filename;
+        eleLink.href = src;
+        const canvas = document.createElement("canvas");
+        canvas.width = image.width;
+        canvas.height = image.height;
+        const context = canvas.getContext("2d");
+        context.drawImage(image, 0, 0, image.width, image.height);
+        eleLink.href = canvas.toDataURL("image/jpeg");
+        document.body.appendChild(eleLink);
+        eleLink.click();
+        document.body.removeChild(eleLink);
+    };
+};
+
+export const crossMerge = (leftArr = [], rightArr = []) => {
+    let newArr = [];
+    const leftLen = leftArr.length;
+    const rightLen = rightArr.length;
+    const crossLength = leftLen <= rightLen ? leftLen : rightLen;
+    (new Array(crossLength).fill(0)).forEach((item, index) => {
+        newArr.push(rightArr[index]);
+        newArr.push(leftArr[index]);
+    });
+    if (leftLen > rightLen) {
+        newArr = newArr.concat(leftArr.slice(rightLen, leftLen));
+    } else if (leftLen < rightLen) {
+        newArr = newArr.concat(rightArr.slice(leftLen, rightLen));
+    }
+    return newArr;
+};
+
+export const getPreloadImagArr = (imgSrc: string[], currentIndex: number, preLoadGap: number, infinite: boolean) => {
+    const beginIndex = currentIndex - preLoadGap;
+    const endIndex = currentIndex + preLoadGap;
+    const srcLength = imgSrc.length;
+    let leftArr = [];
+    let rightArr = [];
+    if ( preLoadGap >= Math.floor(srcLength / 2)) {
+        if (infinite) {
+            leftArr = imgSrc.concat(imgSrc).slice(beginIndex + srcLength < 0 ? 0 : beginIndex + srcLength, currentIndex + srcLength);
+            rightArr = imgSrc.concat(imgSrc).slice(currentIndex + 1, endIndex + 1 < 2 * srcLength ? endIndex + 1 : 2 * srcLength);
+        } else {
+            leftArr = imgSrc.slice(0, currentIndex);
+            rightArr = imgSrc.slice(currentIndex + 1, srcLength);
+        }
+    } else {
+        if (infinite) {
+            leftArr = imgSrc.concat(imgSrc).slice(beginIndex + srcLength, currentIndex + srcLength);
+            rightArr = imgSrc.concat(imgSrc).slice(currentIndex + 1, endIndex + 1);
+        } else {
+            if (beginIndex >= 0 && endIndex < srcLength) {
+                leftArr = imgSrc.slice(beginIndex, currentIndex);
+                rightArr = imgSrc.slice(currentIndex + 1, endIndex + 1);
+            } else if (beginIndex < 0) {
+                leftArr = imgSrc.slice(0, currentIndex);
+                rightArr = imgSrc.slice(currentIndex + 1, 2 * preLoadGap + 1);
+            } else {
+                rightArr = imgSrc.slice(currentIndex + 1, srcLength);
+                leftArr = imgSrc.slice(srcLength - 2 * preLoadGap - 1, currentIndex);
+            }
+        }
+    }
+    const result = crossMerge(leftArr.reverse(), rightArr);
+    const duplicateResult = Array.from(new Set(result));
+    return duplicateResult;
+};

+ 47 - 0
packages/semi-foundation/image/variables.scss

@@ -0,0 +1,47 @@
+$spacing-image_mask_info_text-marginTop: 8px; // 图像预览遮罩文字上外边距
+$spacing-image_preview_icon-x: 24px; // 图像预览中部左右调节icon与页面距离
+$spacing-image_preview_header-paddingY: 0; // 图像预览header部分上下内边距
+$spacing-image_preview_header-paddingX: 24px; // 图像预览header部分左右内边距
+$spacing-image_preview_footer-paddingY: 0; // 图像预览footer操作区部分上下内边距
+$spacing-image_preview_footer-paddingX: 16px; // 图像预览footer操作区部分左右内边距
+$spacing-image_preview_footer_page-marginY: 0; // 图像预览footer操作区图像页数据上下内边距
+$spacing-image_preview_footer_page-marginX: 12px; // 图像预览footer操作区图像页数据左右内边距
+$spacing-image_preview_footer_slider-paddingY: 0; // 图像预览footer操作区slider上下内边距
+$spacing-image_preview_footer_slider-paddingX: 16px; // 图像预览footer操作区slider左右内边距
+$spacing-image_preview_footer_slider_handle-marginTop: 8px; // 图像预览footer操作区slider的滑块上外边距
+$spacing-image_preview_footer_divider-marginY: 0;  // 图像预览footer操作区slider的分割线上下外边距
+$spacing-image_preview_footer_divider-marginX: 16px; // 图像预览footer操作区slider的分割线左右外边距
+$spacing-image_preview_footer_gap-marginLeft: 16px; // 图像预览footer操作区icon的左外边距
+$spacing-image_preview_footer_gap_rtl-marginLeft: 0; // 图像预览footer操作区在rtl模式下icon的左外边距
+
+$width-image_preview_footer_slider: 132px; // 图像预览footer操作区slider宽度
+$width-image_preview_footer_slider_handle: 16px; // 图像预览footer操作区滑块宽度
+$width-image_preview_icon: 40px; // 图像预览footer操作区icon宽度
+$width-image_preview_header_close: 30px; // 图像预览header部分的关闭热区宽度
+
+$height-image_preview_header: 60px; // 图像预览header部分高度
+$height-image_preview_footer: 48px; // 图像预览footer部分高度
+$height-image_preview_footer_slider: 2px; // 图像预览footer中slider高度
+$height-image_preview_footer_slider_handle: 16px; // 图像预览footer中slider的滑块高度
+$height-image_preview_icon: 40px; // 图像预览footer操作区icon高度
+$height-image_preview_header_close: 30px; // 图像预览header部分的关闭热区高度
+
+$radius-image: var(--semi-border-radius-small); // 图像圆角
+$radius-image_preview_footer: 6px; // 图像预览footer操作区圆角
+
+$color-image_mask-bg: var(--semi-color-overlay-bg); // 图像蒙层背景色
+$color-image_mask_info_text: var(--semi-color-white); // 图像蒙层文字颜色
+$color-image_status-bg: var(--semi-color-fill-0); // 图像加载失败背景颜色
+$color-image_status: var(--semi-color-disabled-text); // 图像状态加载失败 icon 颜色
+$color-image_preview-bg: var(--semi-color-overlay-bg); // 图像预览背景色
+$color-image_preview_icon: var(--semi-color-white); // 图像预览中部左右icon背景色
+$color-image_preview_header: var(--semi-color-white); // 图像预览header文字颜色
+$color-image_preview_footer_icon: var(--semi-color-white); // 图像预览footer中icon颜色
+$color-image_preview_footer_slider_rail: var(--semi-color-white); // 图像预览footer中slider滑轨颜色 
+// 以下几个颜色在明暗主题下一致,所以没有采用变量写法
+$color-image_preview_disabled: rgba(249, 249, 249, 0.35); // 图像预览禁用颜色
+$color-image_preview_icon-bg: rgba(0, 0, 0, 0.75); //图像预览中部icon背景色
+$color-image_header_close-bg: rgba(0, 0, 0, 0.75); //图像预览header的关闭icon的hover背景色
+$color-image_preview_footer-bg: rgba(0, 0, 0, 0.75); // 图像预览footer部分背景色
+$color-image-preview_divider-bg: rgba(255, 255, 255, .5); // 图像预览footer中的分割线背景色
+$color-image_preview_image_spin: #ccc; // 图像预览的加载状态颜色

+ 2 - 2
packages/semi-foundation/input/input.scss

@@ -1,6 +1,6 @@
 //@import '../theme/variables.scss';
-@import './variables.scss';
 @import "./animation.scss";
+@import './variables.scss';
 
 $module: #{$prefix}-input;
 
@@ -457,7 +457,7 @@ $module: #{$prefix}-input;
     background-color: transparent;
     box-sizing: border-box;
 
-    &[type="password"]::-ms-reveal, 
+    &[type="password"]::-ms-reveal,
     &[type="password"]::-ms-clear {
         display: none;
     }

+ 2 - 2
packages/semi-foundation/input/textarea.scss

@@ -1,5 +1,5 @@
-@import "./variables.scss";
 @import "./animation.scss";
+@import "./variables.scss";
 
 $module: #{$prefix}-input;
 
@@ -144,7 +144,7 @@ $module: #{$prefix}-input;
     cursor: text;
     box-sizing: border-box;
     color: $color-input_default-text-default;
-    
+
     &:hover {
         border-color: $color-textarea-border-hover;
     }

+ 0 - 1
packages/semi-foundation/inputNumber/inputNumber.scss

@@ -1,5 +1,4 @@
 @import "./animation.scss";
-@import '../input/variables.scss';
 @import './variables.scss';
 
 $module: #{$prefix}-input-number;

+ 3 - 3
packages/semi-foundation/inputNumber/variables.scss

@@ -1,9 +1,9 @@
 $height-inputNumber_button_default: $height-control-default; // 数字输入框高度 - 默认
 $height-inputNumber_button_large: $height-control-large; // 数字输入框高度 - 大
 $height-inputNumber_button_small: $height-control-small; // 数字输入框高度 - 小
-$height-inputNumber_button_inner_default: $height-input_default; // 隐藏步进器的数字输入框高度 - 默认
-$height-inputNumber_button_inner_large: $height-input_large; // 隐藏步进器的数字输入框高度 - 大
-$height-inputNumber_button_inner_small: $height-input_small; // 隐藏步进器的数字输入框高度 - 小
+$height-inputNumber_button_inner_default: $height-control-default - 2px; // 隐藏步进器的数字输入框高度 - 默认
+$height-inputNumber_button_inner_large: $height-control-large - 2px; // 隐藏步进器的数字输入框高度 - 大
+$height-inputNumber_button_inner_small: $height-control-small - 2px; // 隐藏步进器的数字输入框高度 - 小
 
 $width-inputNumber_button: 14px; // 步进器按钮宽度
 $radius-inputNumber: var(--semi-border-radius-small);

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

@@ -181,20 +181,13 @@ export default class NavigationFoundation<P = Record<string, any>, S = Record<st
         return [...willOpenKeys];
     }
 
-    getItemKey(item: string | number, keyPropName = 'itemKey') {
-        if (item && typeof item === 'object') {
-            return item[keyPropName];
-        }
-        return item;
-    }
-
-    getShouldOpenKeys(itemKeysMap: ItemKey2ParentKeysMap = {}, selectedKeys: string | number[] = []) {
+    getShouldOpenKeys(itemKeysMap: ItemKey2ParentKeysMap = {}, selectedKeys: string | number[]= []) {
         const willOpenKeySet = new Set();
 
         if (Array.isArray(selectedKeys) && selectedKeys.length) {
             selectedKeys.forEach(item => {
                 if (item) {
-                    const parentKeys = get(itemKeysMap, this.getItemKey(item));
+                    const parentKeys = get(itemKeysMap, item);
 
                     if (Array.isArray(parentKeys)) {
                         parentKeys.forEach(k => willOpenKeySet.add(k));

+ 1 - 1
packages/semi-foundation/navigation/navigation.scss

@@ -1,7 +1,7 @@
 //@import '../theme/variables.scss';
+@import "./animation.scss";
 @import "./variables.scss";
 @import "./mixin.scss";
-@import "./animation.scss";
 
 $module: #{$prefix}-navigation;
 

+ 2 - 2
packages/semi-foundation/navigation/variables.scss

@@ -46,9 +46,9 @@ $spacing-navigation_dropdown_item_nav_sub_title-paddingY: $spacing-tight; // 导
 $spacing-navigation_dropdown_item_nav_item-marginTop: 0; // 导航栏下拉菜单项顶部外边距
 $spacing-navigation_dropdown_item_nav_item-marginBottom: 0; // 导航栏下拉菜单项底部外边距
 $spacing-navigation_vertical_nav_item_last-marginBottom: 0; // 侧边导航栏下拉最后一个菜单项底部外边距
-$spacing-navigation_vertical_nav_header-paddingLeft:  ($width-navigation_container_collapsed - $spacing-base-tight - $width-navigation_border - $height-navigation_header_logo_collapsed) / 2; // 侧边导航栏 header 左侧内边距
+$spacing-navigation_vertical_nav_header-paddingLeft:  ($width-navigation_container_collapsed - $spacing-base-tight - $width-navigation_border - $height-navigation_header_logo_collapsed) * 0.5; // 侧边导航栏 header 左侧内边距
 $spacing-navigation_vertical_nav_header-paddingRight: $spacing-tight; // 侧边导航栏 header 右侧内边距
-$spacing-navigation_vertical_nav_header_collapsed-paddingLeft: ($width-navigation_container_collapsed - $spacing-base-tight - $width-navigation_border - $height-navigation_header_logo_collapsed) / 2; // 侧边导航栏收起后 header 左侧内边距
+$spacing-navigation_vertical_nav_header_collapsed-paddingLeft: ($width-navigation_container_collapsed - $spacing-base-tight - $width-navigation_border - $height-navigation_header_logo_collapsed) * 0.5; // 侧边导航栏收起后 header 左侧内边距
 $spacing-navigation_vertical_nav_header_collapsed-paddingRight: 0; // 侧边导航栏收起后 header 右侧内边距
 $spacing-navigation_vertical_footer-paddingLeft: $spacing-tight; // 侧边导航栏 footer 左侧内边距
 $spacing-navigation_vertical_footer-paddingRight: $spacing-tight; // 侧边导航栏 footer 右侧内边距

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.19.0",
+    "version": "2.20.0",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",
@@ -36,7 +36,6 @@
         "gulp-sass": "^5.0.0",
         "gulp-typescript": "^6.0.0-alpha.1",
         "merge2": "^1.4.1",
-        "sass": "1.45.0",
         "through2": "^4.0.2"
     }
 }

+ 1 - 1
packages/semi-foundation/pagination/pagination.scss

@@ -1,6 +1,6 @@
 //@import '../theme/variables.scss';
-@import "./variables.scss";
 @import "./animation.scss";
+@import "./variables.scss";
 
 $module: #{$prefix}-page;
 

Some files were not shown because too many files changed in this diff