瀏覽代碼

Merge branch 'main' into css-way

zhangyumei.0319 6 月之前
父節點
當前提交
3a3957ca27
共有 100 個文件被更改,包括 3200 次插入864 次删除
  1. 37 0
      .github/workflows/auto-reply-react19.yaml
  2. 11 4
      .github/workflows/publish.yml
  3. 1 1
      content/feedback/banner/index-en-US.md
  4. 1 1
      content/feedback/banner/index.md
  5. 1 1
      content/feedback/notification/index-en-US.md
  6. 1 1
      content/feedback/notification/index.md
  7. 1 1
      content/feedback/popconfirm/index-en-US.md
  8. 1 1
      content/feedback/popconfirm/index.md
  9. 1 1
      content/feedback/progress/index-en-US.md
  10. 1 1
      content/feedback/progress/index.md
  11. 1 1
      content/feedback/skeleton/index-en-US.md
  12. 1 1
      content/feedback/skeleton/index.md
  13. 1 1
      content/feedback/spin/index-en-US.md
  14. 1 1
      content/feedback/spin/index.md
  15. 1 1
      content/feedback/toast/index-en-US.md
  16. 1 1
      content/feedback/toast/index.md
  17. 0 1
      content/input/cascader/index-en-US.md
  18. 0 1
      content/input/cascader/index.md
  19. 0 10
      content/input/datepicker/index-en-US.md
  20. 0 12
      content/input/datepicker/index.md
  21. 4 4
      content/input/form/index-en-US.md
  22. 6 6
      content/input/form/index.md
  23. 194 1
      content/input/inputnumber/index-en-US.md
  24. 189 1
      content/input/inputnumber/index.md
  25. 0 35
      content/input/select/index-en-US.md
  26. 0 67
      content/input/select/index.md
  27. 0 13
      content/input/timepicker/index-en-US.md
  28. 0 12
      content/input/timepicker/index.md
  29. 6 6
      content/input/transfer/index-en-US.md
  30. 6 6
      content/input/transfer/index.md
  31. 0 1
      content/input/treeselect/index-en-US.md
  32. 0 1
      content/input/treeselect/index.md
  33. 1 0
      content/order.js
  34. 1 1
      content/other/configprovider/index-en-US.md
  35. 1 1
      content/other/configprovider/index.md
  36. 3 3
      content/other/locale/index-en-US.md
  37. 3 3
      content/other/locale/index.md
  38. 1 1
      content/plus/audioPlayer/index-en-US.md
  39. 1 1
      content/plus/audioPlayer/index.md
  40. 1 0
      content/plus/chat/index-en-US.md
  41. 1 0
      content/plus/chat/index.md
  42. 98 12
      content/plus/jsonviewer/index-en-US.md
  43. 101 15
      content/plus/jsonviewer/index.md
  44. 1 1
      content/show/chart/index-en-US.md
  45. 1 1
      content/show/chart/index.md
  46. 126 26
      content/show/cropper/index-en-US.md
  47. 127 30
      content/show/cropper/index.md
  48. 10 10
      content/show/table/index-en-US.md
  49. 329 64
      content/show/table/index.md
  50. 1 1
      content/show/tooltip/index-en-US.md
  51. 1 1
      content/show/tooltip/index.md
  52. 553 0
      content/show/userGuide/index-en-US.md
  53. 553 0
      content/show/userGuide/index.md
  54. 51 0
      content/start/changelog/index-en-US.md
  55. 209 158
      content/start/changelog/index.md
  56. 32 96
      content/start/design-to-code/index-en-US.md
  57. 34 101
      content/start/design-to-code/index.md
  58. 1 0
      content/start/overview/index.md
  59. 1 1
      content/start/tailwind/index-en-US.md
  60. 6 6
      content/start/tailwind/index.md
  61. 11 12
      cypress/e2e/jsonViewer.spec.js
  62. 16 16
      cypress/e2e/scrollList.spec.js
  63. 7 0
      cypress/e2e/tabs.spec.js
  64. 1 1
      lerna.json
  65. 3 3
      packages/semi-animation-react/package.json
  66. 1 1
      packages/semi-animation-styled/package.json
  67. 1 1
      packages/semi-animation/package.json
  68. 1 1
      packages/semi-eslint-plugin/package.json
  69. 3 0
      packages/semi-foundation/backtop/foundation.ts
  70. 12 6
      packages/semi-foundation/cascader/cascader.scss
  71. 19 2
      packages/semi-foundation/cascader/foundation.ts
  72. 0 4
      packages/semi-foundation/cascader/rtl.scss
  73. 1 1
      packages/semi-foundation/cascader/variables.scss
  74. 1 0
      packages/semi-foundation/chat/chat.scss
  75. 29 0
      packages/semi-foundation/chat/foundation.ts
  76. 2 2
      packages/semi-foundation/chat/inputboxFoundation.ts
  77. 83 0
      packages/semi-foundation/cropper/foundation.ts
  78. 4 5
      packages/semi-foundation/input/input.scss
  79. 0 4
      packages/semi-foundation/input/rtl.scss
  80. 2 2
      packages/semi-foundation/input/variables.scss
  81. 176 10
      packages/semi-foundation/inputNumber/foundation.ts
  82. 38 19
      packages/semi-foundation/jsonViewer/foundation.ts
  83. 3 1
      packages/semi-foundation/jsonViewer/jsonViewer.scss
  84. 10 5
      packages/semi-foundation/navigation/foundation.ts
  85. 1 2
      packages/semi-foundation/navigation/itemFoundation.ts
  86. 31 0
      packages/semi-foundation/navigation/navigation.scss
  87. 2 2
      packages/semi-foundation/navigation/variables.scss
  88. 3 3
      packages/semi-foundation/package.json
  89. 1 0
      packages/semi-foundation/scrollList/scrollList.scss
  90. 1 0
      packages/semi-foundation/scrollList/variables.scss
  91. 0 4
      packages/semi-foundation/select/rtl.scss
  92. 4 4
      packages/semi-foundation/select/select.scss
  93. 1 3
      packages/semi-foundation/select/variables.scss
  94. 1 1
      packages/semi-foundation/table/animation.scss
  95. 6 5
      packages/semi-foundation/tagInput/tagInput.scss
  96. 2 2
      packages/semi-foundation/tagInput/variables.scss
  97. 2 1
      packages/semi-foundation/timePicker/ComboxFoundation.ts
  98. 0 6
      packages/semi-foundation/treeSelect/rtl.scss
  99. 4 4
      packages/semi-foundation/treeSelect/treeSelect.scss
  100. 2 3
      packages/semi-foundation/treeSelect/variables.scss

+ 37 - 0
.github/workflows/auto-reply-react19.yaml

@@ -0,0 +1,37 @@
+name: Automatically reply to React19 related issues
+
+on:
+  issues:
+    types: [opened, edited, labeled]
+
+jobs:
+  auto-reply:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/github-script@v6
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            const issue = context.payload.issue;
+            
+            const keywords = ['react19', 'react 19'];
+            const hasKeyword = keywords.some(keyword => 
+              issue.title.toLowerCase().includes(keyword.toLowerCase())
+            );
+
+            const isLabeledReact19 = issue.labels.some(label => label.name === 'react19');
+            
+            const mentionedIssueRegex = /#(\d+)/g;
+            const mentionedIssues = [...issue.body.matchAll(mentionedIssueRegex)].map(match => match[1]);
+            const importantIssues = ['2743']; // 要监听的issue编号
+            const hasMentionedImportantIssue = mentionedIssues.some(num => importantIssues.includes(num));
+            
+            if (hasKeyword || hasMentionedImportantIssue || isLabeledReact19) {
+              github.rest.issues.createComment({
+                issue_number: issue.number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                body: `感谢您反馈 Semi 在 React 19 下使用的异常问题!我们团队预计将在 2025 年 Q3 进行 React 19 的全面适配与优化工作,目前处于收集问题阶段,您的反馈将在 React 19 适配完成后得到处理,详细信息可参考 [issue](https://github.com/DouyinFE/semi-design/issues/2743)。
+                 Thank you for your feedback on the abnormal issues when using Semi under React 19! Our team plans to fully adapt and optimize React 19 in Q3 2025. We are currently in the stage of collecting issues. Your feedback will be processed after the adaptation of React 19 is completed. For more information, please refer to [issue](https://github.com/DouyinFE/semi-design/issues/2743).`
+              });
+            }

+ 11 - 4
.github/workflows/publish.yml

@@ -12,9 +12,16 @@ jobs:
         name: 'publish a new version'
         runs-on: ubuntu-latest
         steps:
+            - name: gen token
+              run: |
+                  git config --global user.name 'semi-team'
+                  git config --global user.email '[email protected]'
+                  mkdir ~/.ssh
+                  echo $SEMI_TEAM_PRIVATE_KEY > ~/.ssh/ssh-ed25519
+                  echo $SEMI_TEAM_PRIVATE_KEY_PUB > ~/.ssh/ssh-ed25519.pub
+                  echo "Host\n  github.com\n  AddKeysToAgent yes\n  UseKeychain yes\n  IdentityFile ~/.ssh/id_ed25519" > ~/.ssh/config
+
             - uses: actions/checkout@v4
-              with:
-                  token: ${{ secrets.PAT }}
 
             - name: Use Node.js 20
               uses: actions/setup-node@v4
@@ -36,8 +43,8 @@ jobs:
 
             - name: publish
               run: |
-                  git config --global user.name 'semi-bot'
-                  git config --global user.email 'semi-[email protected].com'
+                  git config --global user.name 'semi-team'
+                  git config --global user.email 'semi-team@bytedance.com'
                   node scripts/sitemap_update.js
                   if [ -n "$(git status --porcelain)" ]; then
                     echo "there are changes";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -1970,7 +1970,6 @@ function Demo() {
 | filterSorter | Sort the filtered options                                                                                                                                                                                                                     | (first: CascaderData, second: CascaderData, inputValue: string) => number | - | 2.28.0 |
 | filterTreeNode | Set filter, the value of treeNodeFilterProp is used for searching, data parameter provided since v2.28.0                                                                                                                                      | ((inputValue: string, treeNodeString: string, data?: CascaderData) => boolean) \| boolean | false | - |
 | getPopupContainer | Specify the parent DOM, the drop-down box will be rendered into the DOM, the customization needs to set position: relative   This will change the DOM tree position, but not the view's rendering position.                                                                                                                   |() => HTMLElement|() => document.body|-|
-| insetLabel | Prefix alias, used mainly in Form                                                                                                                                                                                                             | ReactNode | - | 0.28.0 |
 | leafOnly | When multiple selections, the set value only includes leaf nodes, that is, the displayed Tag and onChange value parameters only include leaf nodes. Does not support dynamic switching                                                        | boolean | false | 2.2.0  |
 | loadData | Load data asynchronously and the return value should be a promise                                                                                                                                                                             | (selectOptions: CascaderData[]) => Promise< void > |-| 1.8.0|
 | max| In the case of multiple selections, the number of multiple selections is limited, and the onExceed callback will be triggered when max is exceeded                                                                                            | number |-|1.28.0|

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

@@ -1940,7 +1940,6 @@ function Demo() {
 | filterSorter         | 对筛选后的选项进行排序                                                                                                                                         | (first: CascaderData, second: CascaderData, inputValue: string) => number                 | -                              | 2.28.0 |
 | filterTreeNode       | 设置筛选,默认用 treeNodeFilterProp 的值作为要筛选的 TreeNode 的属性值, data 参数自 v2.28.0 开始提供                                                                           | ((inputValue: string, treeNodeString: string, data?: CascaderData) => boolean) \| boolean | false                          | -      |
 | getPopupContainer    | 指定父级 DOM,下拉框将会渲染至该 DOM 中,自定义需要设置 position: relative   这会改变浮层 DOM 树位置,但不会改变视图渲染位置。                                                                                               | () => HTMLElement                                                                         | () => document.body            | -      |
-| insetLabel           | 前缀标签别名,主要用于 Form                                                                                                                                    | ReactNode                                                                                 | -                              | 0.28.0 |
 | leafOnly             | 多选时设置 value 只包含叶子节点,即显示的 Tag 和 onChange 的 value 参数只包含叶子节点。不支持动态切换                                                                                   | boolean                                                                                   | false                          | 2.2.0  |
 | loadData             | 异步加载数据,需要返回一个Promise                                                                                                                                | (selectOptions: CascaderData[]) => Promise< void >                                        | -                              | 1.8.0  |
 | max                  | 多选时,限制多选选中的数量,超出 max 后将触发 onExceed 回调                                                                                                               | number                                                                                    | -                              | 1.28.0 |

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

@@ -48,16 +48,7 @@ function Demo() {
 }
 ```
 
-### With an Embedded Label
 
-```jsx live=true
-import React from 'react';
-import { DatePicker } from '@douyinfe/semi-ui';
-
-function Demo() {
-    return <DatePicker insetLabel="End date" style={{ width: 240 }} />;
-}
-```
 
 ### Multiple Date Selection
 
@@ -907,7 +898,6 @@ function Demo() {
 | inputReadOnly      | Is the text box readonly                                                                                                                                                                                                                      | boolean                                                                                                                                                                                                   | false                                                                                 |                           |
 | insetInput        | Whether the input box is embedded in the panel. InsetInputProps type supported after v2.29                                                                                                                                                    | boolean  \| <ApiType detail='{ placeholder?: { dateStart?: string; dateEnd?: string; timeStart?: string; timeEnd?: string } }'>InsetInputProps</ApiType>                                                                                                                                                                                                | false                                                                                 | **2.7.0**                          |
 | inputStyle         | Input box style                                                                                                                                                                                                                               | object                                                                                                                                                                                                    |                                                                                       |                           |
-| insetLabel         | Prefix label, lower priority than `prefix`                                                                                                                                                                                                    | string\|ReactNode                                                                                                                                                                                         |                                                                                       |                           |
 | max                | When multiple is set to true, the number of selected, non-pass or value is null\|undefined, unlimited.                                                                                                                                        | number                                                                                                                                                                                                    | -                                                                                     |                           |
 | multiple           | Whether you can choose multiple, only type = "date" is supported                                                                                                                                                                              | boolean                                                                                                                                                                                                   | false                                                                                 |                           |
 | needConfirm        | Do you need to "confirm selection", only `type= "dateTime"\| "dateTimeRange"` works.                                                                                                                                                          | boolean                                                                                                                                                                                                   |                                                                                       | **0.18.0**                |

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

@@ -45,17 +45,6 @@ function Demo() {
 }
 ```
 
-### 带内嵌标签
-
-```jsx live=true
-import React from 'react';
-import { DatePicker } from '@douyinfe/semi-ui';
-
-function Demo() {
-    return <DatePicker insetLabel="结束日期" style={{ width: 240 }} />;
-}
-```
-
 ### 多个日期选择
 
 将 `multiple` 设为 `true`,可以多选日期
@@ -858,7 +847,6 @@ function Demo() {
 | insetInput | 面板中是否嵌入输入框,InsetInputProps 类型 v2.29 支持                                                               | boolean \| <ApiType detail='{ placeholder?: { dateStart?: string; dateEnd?: string; timeStart?: string; timeEnd?: string } }'>InsetInputProps</ApiType>  | false | **2.7.0** |
 | inputReadOnly | 文本框是否 readonly                                                                                       | boolean | false |  |
 | inputStyle | 输入框样式                                                                                                | object |  |  |
-| insetLabel | 前缀标签,优先级低于 `prefix`                                                                                  | string\|ReactNode |  |  |
 | leftSlot   | 渲染左侧额外区域                                                                                             | ReactNode |         |  **2.65.0** |
 | max | multiple 为 true 时,多选的数目,不传或者值为 null\|undefined 的话无限制                                                 | number | - |  |
 | motion | 是否开启面板展开的动画                                                                                          | boolean | true |  |

+ 4 - 4
content/input/form/index-en-US.md

@@ -1095,7 +1095,7 @@ import { Form, Button } from '@douyinfe/semi-ui';
                     { required: true, message: 'required error' },
                     { type: 'string', message: 'type error' },
                     { validator: (rule, value) => value === 'semi', message: 'should be semi' },
-                    { validator: (rule, value) => value && value.startsWith('se'), message: 'should startsWith se' }
+                    { validator: (rule, value) => Boolean(value && value.startsWith('se')), message: 'should startsWith se' }
                 ]}
             />
             <Input
@@ -1106,7 +1106,7 @@ import { Form, Button } from '@douyinfe/semi-ui';
                     { required: true, message: 'required error' },
                     { type: 'string', message: 'type error' },
                     { validator: (rule, value) => value === 'semi', message: 'should be semi' },
-                    { validator: (rule, value) => value && value.startsWith('se'), message: 'should startsWith se' }
+                    { validator: (rule, value) => Boolean(value && value.startsWith('se')), message: 'should startsWith se' }
                 ]}
             />
             <Button htmlType='submit'>提交</Button>
@@ -2012,9 +2012,9 @@ const MyComponent = (props) => {
     };
     return (
         <div className='customField'>
-            <Input insetLabel='Name' value={name} onChange={v => handleChange(v, 'name')} style={{ width: 180, marginRight: 12 }} />
+            <Input prefix='Name' value={name} onChange={v => handleChange(v, 'name')} style={{ width: 180, marginRight: 12 }} />
             <Select
-                insetLabel='Role'
+                prefix='Role'
                 value={role}
                 onChange={v => handleChange(v, 'role')}
                 style={{ width: 200 }}

+ 6 - 6
content/input/form/index.md

@@ -558,12 +558,12 @@ class BasicDemo extends React.Component {
             <>
                 <div style={{ borderBottom: '1px solid var(--semi-color-border)', paddingBottom: 12 }}>
                     <Form.Label style={{ marginLeft: 10 }}>切换Label位置:</Form.Label>
-                    <Select onChange={this.changeLabelPos} value={labelPosition} style={{ width: 200 }} insetLabel='labelPosition'>
+                    <Select onChange={this.changeLabelPos} value={labelPosition} style={{ width: 200 }} prefix='labelPosition'>
                         <Select.Option value='top'>top</Select.Option>
                         <Select.Option value='left'>left</Select.Option>
                     </Select>
                     <Form.Label style={{ marginLeft: 10 }}>切换Label文本对齐方向:</Form.Label>
-                    <Select onChange={this.changeLabelAlign} value={labelAlign} style={{ width: 200 }} insetLabel='labelAlign'>
+                    <Select onChange={this.changeLabelAlign} value={labelAlign} style={{ width: 200 }} prefix='labelAlign'>
                         <Select.Option value='left'>left</Select.Option>
                         <Select.Option value='right'>right</Select.Option>
                     </Select>
@@ -1168,7 +1168,7 @@ import { Form, Button } from '@douyinfe/semi-ui';
                     { required: true, message: 'required error' },
                     { type: 'string', message: 'type error' },
                     { validator: (rule, value) => value === 'semi', message: 'should be semi' },
-                    { validator: (rule, value) => value && value.startsWith('se'), message: 'should startsWith se' }
+                    { validator: (rule, value) => Boolean(value && value.startsWith('se')), message: 'should startsWith se' }
                 ]}
             />
             <Input
@@ -1179,7 +1179,7 @@ import { Form, Button } from '@douyinfe/semi-ui';
                     { required: true, message: 'required error' },
                     { type: 'string', message: 'type error' },
                     { validator: (rule, value) => value === 'semi', message: 'should be semi' },
-                    { validator: (rule, value) => value && value.startsWith('se'), message: 'should startsWith se' }
+                    { validator: (rule, value) => Boolean(value && value.startsWith('se')), message: 'should startsWith se' }
                 ]}
             />
             <Button htmlType='submit'>提交</Button>
@@ -2019,9 +2019,9 @@ const MyComponent = (props) => {
     };
     return (
         <div className='customField'>
-            <Input insetLabel='名称' value={name} onChange={v => handleChange(v, 'name')} style={{ width: 180, marginRight: 12 }} />
+            <Input prefix='名称' value={name} onChange={v => handleChange(v, 'name')} style={{ width: 180, marginRight: 12 }} />
             <Select
-                insetLabel='角色'
+                prefix='角色'
                 value={role}
                 onChange={v => handleChange(v, 'role')}
                 style={{ width: 200 }}

+ 194 - 1
content/input/inputnumber/index-en-US.md

@@ -197,6 +197,196 @@ function Demo () {
 }
 ```
 
+### Currency Display
+Version 2.77.0 supports currency display. In internationalization mode, enable currency={true} and the component will automatically display the corresponding currency type according to localeCode. (Note that the component key value needs to be updated after switching the language type)
+
+```jsx live=true
+import React from 'react';
+import zh_CN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
+import en_GB from '@douyinfe/semi-ui/lib/es/locale/source/en_GB';
+import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
+import ko_KR from '@douyinfe/semi-ui/lib/es/locale/source/ko_KR';
+import ja_JP from '@douyinfe/semi-ui/lib/es/locale/source/ja_JP';
+import ar from '@douyinfe/semi-ui/lib/es/locale/source/ar';
+import vi_VN from '@douyinfe/semi-ui/lib/es/locale/source/vi_VN';
+import ru_RU from '@douyinfe/semi-ui/lib/es/locale/source/ru_RU';
+import id_ID from '@douyinfe/semi-ui/lib/es/locale/source/id_ID';
+import ms_MY from '@douyinfe/semi-ui/lib/es/locale/source/ms_MY';
+import th_TH from '@douyinfe/semi-ui/lib/es/locale/source/th_TH';
+import tr_TR from '@douyinfe/semi-ui/lib/es/locale/source/tr_TR';
+import pt_BR from '@douyinfe/semi-ui/lib/es/locale/source/pt_BR';
+import zh_TW from '@douyinfe/semi-ui/lib/es/locale/source/zh_TW';
+import sv_SE from '@douyinfe/semi-ui/lib/es/locale/source/sv_SE';
+import pl_PL from '@douyinfe/semi-ui/lib/es/locale/source/pl_PL';
+import nl_NL from '@douyinfe/semi-ui/lib/es/locale/source/nl_NL';
+import es from '@douyinfe/semi-ui/lib/es/locale/source/es';
+import it from '@douyinfe/semi-ui/lib/es/locale/source/it';
+import de from '@douyinfe/semi-ui/lib/es/locale/source/de';
+import fr from '@douyinfe/semi-ui/lib/es/locale/source/fr';
+import ro from '@douyinfe/semi-ui/lib/es/locale/source/ro';
+import { LocaleProvider, InputNumber, Select } from '@douyinfe/semi-ui';
+
+class I18nDemo extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            locale: zh_CN,
+            localeCode: 'zh_CN',
+        };
+        this.onLanguageChange = this.onLanguageChange.bind(this);
+    }
+
+    onLanguageChange(code) {
+        let language = {
+            'zh_CN': zh_CN,
+            'en_GB': en_GB,
+            'en_US': en_US,
+            'ko_KR': ko_KR,
+            'ja_JP': ja_JP,
+            'ar': ar,
+            'vi_VN': vi_VN,
+            'ru_RU': ru_RU,
+            'id_ID': id_ID,
+            'ms_MY': ms_MY,
+            'th_TH': th_TH,
+            'tr_TR': tr_TR,
+            'pt_BR': pt_BR,
+            'zh_TW': zh_TW,
+            'es': es,
+            'sv_SE': sv_SE,
+            'pl_PL': pl_PL,
+            'nl_NL': nl_NL,
+            de,
+            it,
+            fr,
+            ro
+        };
+        this.setState({ locale: language[code], localeCode: code });
+    }
+
+    render() {
+        const { locale, localeCode } = this.state;
+        return (
+            <>
+                <div style={{ paddingBottom: 20 }}>
+                    <Select onChange={this.onLanguageChange} insetLabel='切换语言' style={{ width: 250 }} defaultValue='zh_CN'>
+                        <Select.Option value='zh_CN'>Chinese</Select.Option>
+                        <Select.Option value='en_GB'>English</Select.Option>
+                        <Select.Option value='ja_JP'>Japanese</Select.Option>
+                        <Select.Option value='ko_KR'>Korean</Select.Option>
+                        <Select.Option value='ar'>Arabic</Select.Option>
+                        <Select.Option value='vi_VN'>Vietnamese</Select.Option>
+                        <Select.Option value='ru_RU'>Russian</Select.Option>
+                        <Select.Option value='id_ID'>Indonesian</Select.Option>
+                        <Select.Option value='ms_MY'>Malay</Select.Option>
+                        <Select.Option value='th_TH'>Thai</Select.Option>
+                        <Select.Option value='tr_TR'>Turkish</Select.Option>
+                        <Select.Option value='es'>Spanish</Select.Option>
+                        <Select.Option value='de'>German</Select.Option>
+                        <Select.Option value='it'>Italian</Select.Option>
+                        <Select.Option value='fr'>French</Select.Option>
+                        <Select.Option value='ro'>Romanian</Select.Option>
+                        <Select.Option value='sv_SE'>Swedish</Select.Option>
+                        <Select.Option value='pl_PL'>Polish</Select.Option>
+                        <Select.Option value='nl_NL'>Dutch</Select.Option>
+                    </Select>
+                </div>
+                <LocaleProvider locale={locale}>
+                    <InputNumber key={localeCode} currency={true} defaultValue={123456.78} />
+                </LocaleProvider>
+            </>
+        );
+    }
+}
+
+```
+You can also specify the currency to be displayed by manually passing localeCode and currency.
+
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 CNY</div>
+            <InputNumber localeCode="zh-CN" currency="CNY" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇪🇺 EUR</div>
+            <InputNumber localeCode="de-DE" currency="EUR" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇯🇵 JPY</div>
+            <InputNumber localeCode="ja-JP" currency="JPY" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇻🇳 VND</div>
+            <InputNumber localeCode="vi-VN" currency="VND" defaultValue={defaultValue} />
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+Supports three display modes: symbol, code, and name. It is controlled by the currencyDisplay property. The currency symbol is displayed by default. Set showCurrencySymbol to false to hide the display of currency symbol/code/name
+
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';    
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 CNY ➕ code</div>
+            <InputNumber currency="CNY" currencyDisplay="code" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ symbol</div>
+            <InputNumber currency="CNY" currencyDisplay="symbol" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ name</div>
+            <InputNumber currency="CNY" currencyDisplay="name" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>Hide display of currency symbols/codes/names</div>
+            <InputNumber currency="CNY" currencyDisplay="name" defaultValue={defaultValue} showCurrencySymbol={false}/>
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+
+Hide the display of currency symbols/codes/names, and display the currency symbol through the prefix/suffix
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';    
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 CNY ➕ code</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" prefix="CNY" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ symbol</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" prefix="¥" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ name</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" suffix="人民币" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+
 ## API Reference
 
 | Properties   | Instructions                                                                                    | type                              | Default   | Version    |
@@ -204,13 +394,15 @@ function Demo () {
 | autofocus    | Automatic access to focus                                                                       | boolean                           | false     |            |
 | className    | class name of InputNumber                                                               | string  | -      |
 | clearIcon    | Can be used to customize the clear button, valid when showClear is true                       | ReactNode                       |     | 2.25.0 |
+| currency | Currency type. In international mode, currency={true} is enabled. The component will automatically display the corresponding currency type according to the locale. You can also manually pass in localeCode and currency to specify the currency type to display. The optional values ​​of currency are `CNY`,`EUR`,`USD`, etc. | boolean\|string | false | **2.77.0** |
+| currencyDisplay | Currency display method. Optional values: symbol, code, name | string | symbol | **2.77.0** |
 | defaultValue | Default                                                                                         | number                            |           |            |
 | disabled     | Disabled status                                                                                 | boolean                           | false     |            |
 | formatter    | Specifies the format of the input box to display the value                                      | (value: number\|string) => string | -         |            |
 | hideButtons  | Hide the "up/down" button when passing `true`                                                   | boolean                           | false     | **1.0.0**  |
 | innerButtons  | Show the "up/down" button in input box when passing `true`                                 | boolean                           | false         | **1.5.0** |
-| insetLabel   | Prefix label, lower priority than `prefix`                                                      | string\|ReactNode                 |           |            |
 | keepFocus    | Keep the input box focused when you click the button                                        | boolean                 |     false               | **1.10.0** |
+| localeCode | Used to specify the country code in currency mode. Optional values ​​include `zh-CN`, `en-US`, `en-GB`, `ja-JP`, `ko-KR`, `ar`, `vi-VN`, `ru-RU`, `id-ID`, `ms-MY`, `th-TH`, `tr-TR`, `pt-BR`, `zh-TW`, `es`, `de`, `it`, `fr`, `ro`, `sv-SE`, `pl-PL`, `nl-NL`, etc. | string | - | **2.77.0** |
 | max          | Limit maximum value                                                                             | number                            | Infinity  |            |
 | min          | Limit minimum value                                                                             | number                            | -Infinity |            |
 | parser       | Specifies how to convert back number string from formatter and use them in conjunction with formatter | (value: string) => string         | -         |      |
@@ -221,6 +413,7 @@ function Demo () {
 | preventScroll | Indicates whether the browser should scroll the document to display the newly focused element, acting on the focus method inside the component, excluding the component passed in by the user | boolean |  |  |
 | shiftStep    | Step size for pressing the shift key, it can be a decimal. The default value was adjusted from 1 to 10 in v2.13                     | number                            | 10         | **1.5.0** |
 | showClear    | Do you show the clear button?                                                                   | boolean                           | false     | **0.35.0** |
+| showCurrencySymbol | Whether to display the currency symbol/code/name, only valid in currency mode | boolean | true | **2.77.0** |
 | size         | Enter box size, optional value: "default"\|"small"\|"large"                                     | string                            | 'default' |            |
 | step         | Each time you change the number of steps, it can be a decimal.                                  | number                            | 1         |            |
 | style        | Inline style of InputNumber                                                             | CSSProperties  | -      |

+ 189 - 1
content/input/inputnumber/index.md

@@ -171,6 +171,191 @@ function Demo () {
 }
 ```
 
+### 货币展示
+2.77.0 版本开始支持货币展示,国际化模式下通过 currency={true} 开启,组件会自动根据 localeCode 展示对应货币种类。(注意切换语言类型后需要更新组件 key 值)
+```jsx live=true
+import React from 'react';
+import zh_CN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
+import en_GB from '@douyinfe/semi-ui/lib/es/locale/source/en_GB';
+import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
+import ko_KR from '@douyinfe/semi-ui/lib/es/locale/source/ko_KR';
+import ja_JP from '@douyinfe/semi-ui/lib/es/locale/source/ja_JP';
+import ar from '@douyinfe/semi-ui/lib/es/locale/source/ar';
+import vi_VN from '@douyinfe/semi-ui/lib/es/locale/source/vi_VN';
+import ru_RU from '@douyinfe/semi-ui/lib/es/locale/source/ru_RU';
+import id_ID from '@douyinfe/semi-ui/lib/es/locale/source/id_ID';
+import ms_MY from '@douyinfe/semi-ui/lib/es/locale/source/ms_MY';
+import th_TH from '@douyinfe/semi-ui/lib/es/locale/source/th_TH';
+import tr_TR from '@douyinfe/semi-ui/lib/es/locale/source/tr_TR';
+import pt_BR from '@douyinfe/semi-ui/lib/es/locale/source/pt_BR';
+import zh_TW from '@douyinfe/semi-ui/lib/es/locale/source/zh_TW';
+import sv_SE from '@douyinfe/semi-ui/lib/es/locale/source/sv_SE';
+import pl_PL from '@douyinfe/semi-ui/lib/es/locale/source/pl_PL';
+import nl_NL from '@douyinfe/semi-ui/lib/es/locale/source/nl_NL';
+import es from '@douyinfe/semi-ui/lib/es/locale/source/es';
+import it from '@douyinfe/semi-ui/lib/es/locale/source/it';
+import de from '@douyinfe/semi-ui/lib/es/locale/source/de';
+import fr from '@douyinfe/semi-ui/lib/es/locale/source/fr';
+import ro from '@douyinfe/semi-ui/lib/es/locale/source/ro';
+import { LocaleProvider, InputNumber, Select } from '@douyinfe/semi-ui';
+
+class I18nDemo extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            locale: zh_CN,
+            localeCode: 'zh_CN',
+        };
+        this.onLanguageChange = this.onLanguageChange.bind(this);
+    }
+
+    onLanguageChange(code) {
+        let language = {
+            'zh_CN': zh_CN,
+            'en_GB': en_GB,
+            'en_US': en_US,
+            'ko_KR': ko_KR,
+            'ja_JP': ja_JP,
+            'ar': ar,
+            'vi_VN': vi_VN,
+            'ru_RU': ru_RU,
+            'id_ID': id_ID,
+            'ms_MY': ms_MY,
+            'th_TH': th_TH,
+            'tr_TR': tr_TR,
+            'pt_BR': pt_BR,
+            'zh_TW': zh_TW,
+            'es': es,
+            'sv_SE': sv_SE,
+            'pl_PL': pl_PL,
+            'nl_NL': nl_NL,
+            de,
+            it,
+            fr,
+            ro
+        };
+        this.setState({ locale: language[code], localeCode: code });
+    }
+
+    render() {
+        const { locale, localeCode } = this.state;
+        return (
+            <>
+                <div style={{ paddingBottom: 20 }}>
+                    <Select onChange={this.onLanguageChange} insetLabel='切换语言' style={{ width: 250 }} defaultValue='zh_CN'>
+                        <Select.Option value='zh_CN'>简体中文</Select.Option>
+                        <Select.Option value='en_US'>英语(美)</Select.Option>
+                        <Select.Option value='en_GB'>英语(英)</Select.Option>
+                        <Select.Option value='ja_JP'>日语</Select.Option>
+                        <Select.Option value='ko_KR'>韩语</Select.Option>
+                        <Select.Option value='ar'>阿拉伯语</Select.Option>
+                        <Select.Option value='vi_VN'>越南语</Select.Option>
+                        <Select.Option value='ru_RU'>俄罗斯语</Select.Option>
+                        <Select.Option value='id_ID'>印尼语</Select.Option>
+                        <Select.Option value='ms_MY'>马来语</Select.Option>
+                        <Select.Option value='th_TH'>泰语</Select.Option>
+                        <Select.Option value='tr_TR'>土耳其语</Select.Option>
+                        <Select.Option value='pt_BR'>葡萄牙语(巴西)</Select.Option>
+                        <Select.Option value='zh_TW'>繁体中文</Select.Option>
+                        <Select.Option value='es'>西班牙语</Select.Option>
+                        <Select.Option value='de'>德语</Select.Option>
+                        <Select.Option value='it'>意大利语</Select.Option>
+                        <Select.Option value='fr'>法语</Select.Option>
+                        <Select.Option value='ro'>罗马尼亚语</Select.Option>
+                        <Select.Option value='sv_SE'>瑞典语</Select.Option>
+                        <Select.Option value='pl_PL'>波兰语</Select.Option>
+                        <Select.Option value='nl_NL'>荷兰语</Select.Option>
+                    </Select>
+                </div>
+                <LocaleProvider locale={locale}>
+                    <InputNumber key={localeCode} currency={true} defaultValue={123456.78} />
+                </LocaleProvider>
+            </>
+        );
+    }
+}
+```
+也可以通过手动传 localeCode 和 currency 指定展示的货币种类
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 人民币</div>
+            <InputNumber localeCode="zh-CN" currency="CNY" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇪🇺 欧元</div>
+            <InputNumber localeCode="de-DE" currency="EUR" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇯🇵 日元</div>
+            <InputNumber localeCode="ja-JP" currency="JPY" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇻🇳 越南盾</div>
+            <InputNumber localeCode="vi-VN" currency="VND" defaultValue={defaultValue} />
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+支持 symbol、code、name 三种展示方式,通过 currencyDisplay 属性控制,默认以货币符号展示。showCurrencySymbol 设置为 false 隐藏货币符号/代码/名称的展示
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';    
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 CNY ➕ code</div>
+            <InputNumber currency="CNY" currencyDisplay="code" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ symbol</div>
+            <InputNumber currency="CNY" currencyDisplay="symbol" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ name</div>
+            <InputNumber currency="CNY" currencyDisplay="name" defaultValue={defaultValue} />
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+
+隐藏货币符号、代码或名称的展示,通过前后缀展示货币符号
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';    
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 CNY ➕ code</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" prefix="CNY" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ symbol</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" prefix="¥" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ name</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" suffix="人民币" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+
 ## API 参考
 
 | 属性         | 说明                                                           | 类型                              | 默认值    | 版本      |
@@ -178,13 +363,15 @@ function Demo () {
 | autofocus    | 自动获取焦点                                                   | boolean                           | false     |           |
 | className | 类名                                                               | string  | -      |
 | clearIcon | 可用于自定义清除按钮, showClear为true时有效 | ReactNode |   | 2.25.0|
+| currency | 货币种类,国际化模式下通过 currency={true} 开启,组件会自动根据 locale 展示对应货币种类, 也可以手动传入 localeCode 和 currency 指定展示的货币种类, currency 的可选值有 `CNY`,`EUR`,`USD`等| boolean\|string | false | **2.77.0** |
+| currencyDisplay | 货币展示方式,可选值:symbol、code、name | string | symbol | **2.77.0** |
 | defaultValue | 默认值                                                         | number                            |           |           |
 | disabled     | 禁用                                                           | boolean                           | false     |           |
 | formatter    | 指定输入框展示值的格式                                         | (value: number\|string) => string | -         |           |
 | hideButtons  | 为 `true` 时隐藏 “上/下” 按钮                                  | boolean                           | false     | **1.0.0** |
 | innerButtons | 为 `true` 时 “上/下” 按钮显示在输入框内部                                  | boolean                           | false     | **1.5.0** |
-| insetLabel   | 前缀标签,优先级低于 `prefix`                                  | string\|ReactNode                 |           |           |
 | keepFocus    | 点击按钮时保持输入框聚焦                                        | boolean                 |     false      |   **1.10.0**        |
+|  localeCode    | 货币模式下用于指定国家地区代码,可选值有 `zh-CN`, `en-US`, `en-GB`, `ja-JP`, `ko-KR`, `ar`, `vi-VN`, `ru-RU`, `id-ID`, `ms-MY`, `th-TH`, `tr-TR`, `pt-BR`, `zh-TW`, `es`, `de`, `it`, `fr`, `ro`, `sv-SE`, `pl-PL`, `nl-NL`等 | string                 |     -      |   **2.77.0**  |
 | max          | 限定最大值                                                     | number                            | Infinity  |           |
 | min          | 限定最小值                                                     | number                            | -Infinity |           |
 | parser       | 指定从 `formatter` 里转换回数字串的方式,和 `formatter` 搭配使用 | (str: string) => string           | -         |           |
@@ -195,6 +382,7 @@ function Demo () {
 | preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法 | boolean |  |  |
 | shiftStep    | 按住 shift 键每次改变步数,可以为小数,v2.13 默认值由 1 调整为 10                           | number                            | 10         | **1.5.0** |
 | showClear    | 是否显示清除按钮                                               | boolean                           | false     | **0.35.0**   |
+| showCurrencySymbol | 是否显示货币符号/代码/名称,仅货币模式下生效 | boolean | true | **2.77.0** |
 | size         | 输入框大小,可选值:"default"\|"small"\|"large"                | string                            | 'default' |           |
 | step         | 每次改变步数,可以为小数                                       | number                            | 1         |           |
 | style     | 样式                                                               | CSSProperties  | -      |

+ 0 - 35
content/input/select/index-en-US.md

@@ -297,40 +297,6 @@ import { IconVigoLogo, IconGift } from '@douyinfe/semi-icons';
 );
 ```
 
-### Select with inset label
-
-By setting`insetLabel`, you can set a label for Select, you can pass in string or ReactNode  
-When the incoming type is ReactNode, you need to handle the padding between the label and the text.
-
-```jsx live=true
-import React from 'react';
-import { Select } from '@douyinfe/semi-ui';
-
-() => {
-    const list = [
-        { value: 'douyin', label: 'Douyin' },
-        { value: 'capcut', label: 'Capcut' },
-        { value: 'xigua', label: 'BuzzVideo' },
-    ];
-    return (
-        <>
-            <Select style={{ width: 320 }} optionList={list} insetLabel="Application" defaultValue="douyin"></Select>
-            <br />
-            <br />
-            <Select
-                style={{ width: 320 }}
-                optionList={list}
-                insetLabel={
-                    <span style={{ marginRight: 0, marginLeft: 12, color: 'var(--semi-color-text-2)' }}>
-                        Application
-                    </span>
-                }
-            ></Select>
-        </>
-    );
-};
-```
-
 ### Additional items
 
 We have reserved two slots at the bottom of the pop-up layer, which you can use when you need to add a custom node to the pop-up layer.  
@@ -1441,7 +1407,6 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | inputProps | When filter is true, the additional configuration parameters of the input, please refer to the Input component for specific configurable properties (note: please do not pass in `value`, `ref`, `onChange`, `onFocus`, otherwise it will override Select related callbacks and affect component behavior)                                                         | object | | 2.2.0|
 | innerTopSlot | Render at the top of the pop-up layer, custom slot inside the optionList                                                                                                                                                                                                                                                                                           | ReactNode |  |
 | innerBottomSlot | Render at the bottom of the pop-up layer, custom slot inside the optionList                                                                                                                                                                                                                                                                                        | ReactNode |  |
-| insetLabel | Same to `prefix`, just an alias                                                                                                                                                                                                                                                                                                                                    | ReactNode |  |
 | loading | Does the drop-down list show the loading animation                                                                                                                                                                                                                                                                                                                 | boolean | false |
 | max | Maximum number of choices, effective only in multi-selection mode                                                                                                                                                                                                                                                                                                  | number |  |
 | maxTagCount | In multi-selection mode, when the option is beyond maxTag Count, the subsequent option is rendered in the form of + N                                                                                                                                                                                                                                              | number |  |

+ 0 - 67
content/input/select/index.md

@@ -321,72 +321,6 @@ import { IconVigoLogo, IconGift } from '@douyinfe/semi-icons';
 );
 ```
 
-### 内嵌标签
-
-通过设置`insetLabel`,你可以给 Select 设置 label,可以传入 string 或者 ReactNode  
-当传入类型为 ReactNode 时,注意要自行处理 label 与文本之间的间隔
-
-```jsx live=true
-import React, { useState } from 'react';
-import { Select } from '@douyinfe/semi-ui';
-
-() => {
-    const list = [
-        { value: 'douyin', label: '抖音' },
-        { value: 'ulikecam', label: '轻颜相机' },
-        { value: 'jianying', label: '剪映' },
-        { value: 'toutiao', label: '今日头条' },
-    ];
-    const colorList = ['red', 'light-blue', 'yellow', 'purple', 'pink', 'green'].map(color => {
-        return {
-            value: `rgba(var(--semi-${color}-4), 1)`,
-            label: (
-                <span
-                    style={{
-                        color: `rgba(var(--semi-${color}-4), 1)`,
-                    }}
-                >
-                    {`--semi-${color}-4`}
-                </span>
-            ),
-        };
-    });
-    const [colorVal, setColotVal] = useState('--semi-light-blue-3');
-    return (
-        <>
-            <Select style={{ width: 300 }} optionList={list} insetLabel="业务线" defaultValue="douyin"></Select>
-            <br />
-            <br />
-            <Select
-                style={{ width: 300 }}
-                optionList={colorList}
-                value={colorVal}
-                insetLabel={
-                    <div
-                        style={{
-                            marginLeft: 12,
-                            display: 'flex',
-                        }}
-                    >
-                        <div
-                            style={{
-                                display: 'block',
-                                width: 5,
-                                height: 5,
-                                border: 'solid 7px transparent',
-                                borderRadius: '50%',
-                                borderColor: 'rgba(var(--semi-light-blue-3), 1)',
-                            }}
-                        ></div>
-                        <span style={{ marginLeft: 4 }}>色值</span>
-                    </div>
-                }
-            ></Select>
-        </>
-    );
-};
-```
-
 ### 在顶部/底部渲染附加项
 
 我们在弹出层顶部、底部分别预留了插槽,当你需要在弹出层中添加自定义 node 时  
@@ -1501,7 +1435,6 @@ import { Select, Checkbox, Highlight } from '@douyinfe/semi-ui';
 | inputProps | filter 为 true 时, input 输入框的额外配置参数,具体可配置属性请参考 Input 组件(注意:请不要传入 value、ref、onChange、onFocus,否则会覆盖 Select 相关回调,影响组件行为)                   | object |  | 2.2.0 |
 | innerTopSlot | 渲染在弹出层顶部,在 optionList 内部的自定义 slot                                                                                                     | ReactNode |  |
 | innerBottomSlot | 渲染在弹出层底部,在 optionList 内部的自定义 slot                                                                                                     | ReactNode |  |
-| insetLabel | 同上,与 prefix 区别是 fontWeight 更大                                                                                                         | ReactNode |  |
 | loading | 下拉列表是否展示加载动画                                                                                                                          | boolean | false |
 | maxTagCount | 多选模式下,已选项超出 maxTagCount 时,后续选项会被渲染成+N 的形式                                                                                             | number |  |
 | max | 最多可选几项,仅在多选模式下生效                                                                                                                      | number |  |

+ 0 - 13
content/input/timepicker/index-en-US.md

@@ -42,18 +42,6 @@ function Demo() {
 }
 ```
 
-
-### With an Embedded Label
-
-```jsx live=true
-import React from 'react';
-import { TimePicker } from '@douyinfe/semi-ui';
-
-function Demo() {
-    return <TimePicker insetLabel="Time" />;
-}
-```
-
 ### Controlled Component
 
 When using `value` And not. `defaultValue` When used as a controlled component.`value` and `onChange` It needs to be used in conjunction.
@@ -311,7 +299,6 @@ function Demo(props = {}) {
 | hideDisabledOptions | Hide the option of forbidden selection                                                                                                                                                                                                        | boolean | false |
 | hourStep | Hour option interval                                                                                                                                                                                                                          | number | 1 |
 | inputReadOnly | Set the input box to read-only (avoid opening a virtual keyboard on a mobile device)                                                                                                                                                          | boolean | false |
-| insetLabel | Prefix label, lower priority than `prefix`                                                                                                                                                                                                    | string\|ReactNode |  |  |
 | minuteStep | Minute option interval                                                                                                                                                                                                                        | number | 1 |
 | motion | Whether to display the pop-up layer animation                                                                                                                                                                                                 | boolean | true |  |
 | open | Controlled property of whether the panel is open                                                                                                                                                                                              | boolean |  |

+ 0 - 12
content/input/timepicker/index.md

@@ -41,17 +41,6 @@ function Demo() {
 }
 ```
 
-### 带内嵌标签
-
-```jsx live=true
-import React from 'react';
-import { TimePicker } from '@douyinfe/semi-ui';
-
-function Demo() {
-    return <TimePicker insetLabel='时刻'/>;
-}
-```
-
 ### 受控组件
 
 当使用 `value` 而不是 `defaultValue` 时,作为受控组件使用。`value` 和 `onChange` 需要配合使用。
@@ -301,7 +290,6 @@ function Demo(props = {}) {
 | hideDisabledOptions   | 隐藏禁止选择的选项                                                                                                       | boolean                                                                           | false                                                             |            |
 | hourStep              | 小时选项间隔                                                                                                             | number                                                                            | 1                                                                 |            |
 | inputReadOnly         | 设置输入框为只读(避免在移动设备上打开虚拟键盘)                                                                         | boolean                                                                           | false                                                             |            |
-| insetLabel            | 前缀标签,优先级低于 `prefix`                                                                                            | string\|ReactNode                                                                 |                                                                   |            |
 | minuteStep            | 分钟选项间隔                                                                                                             | number                                                                            | 1                                                                 |            |
 | motion                | 是否展示弹出层动画                                                                                                       | boolean                                                                           | true                                                              |            |
 | open                  | 面板是否打开的受控属性                                                                                                   | boolean                                                                           |                                                                   |            |

+ 6 - 6
content/input/transfer/index-en-US.md

@@ -30,7 +30,7 @@ import { Transfer } from '@douyinfe/semi-ui';
             label: `Item ${i}`,
             value: i,
             disabled: false,
-            key: i,
+            key: `key-${i}`,
         };
     });
     return (
@@ -233,7 +233,7 @@ import { Transfer } from '@douyinfe/semi-ui';
             label: `Item ${i}`,
             value: i,
             disabled: false,
-            key: i,
+            key: `key-${i}`,
         };
     });
     return (
@@ -261,7 +261,7 @@ import { Transfer } from '@douyinfe/semi-ui';
             label: `Item ${i}`,
             value: i,
             disabled: false,
-            key: i,
+            key: `key-${i}`,
         };
     });
     return (
@@ -391,7 +391,7 @@ import { Transfer, Button } from '@douyinfe/semi-ui';
             label: `Item ${i}`,
             value: i,
             disabled: false,
-            key: i,
+            key: `key-${i}`,
         };
     });
 
@@ -480,7 +480,7 @@ class CustomRenderDemo extends React.Component {
                 label: `Hdl Store ${i}`,
                 value: i,
                 disabled: false,
-                key: i,
+                key: `key-${i}`,
             })),
         };
         this.renderSourcePanel = this.renderSourcePanel.bind(this);
@@ -741,7 +741,7 @@ class CustomRenderDragDemo extends React.Component {
                 label: `Hdl Store ${i}`,
                 value: i,
                 disabled: false,
-                key: i,
+                key: `key-${i}`,
             })),
         };
         this.renderSourcePanel = this.renderSourcePanel.bind(this);

+ 6 - 6
content/input/transfer/index.md

@@ -31,7 +31,7 @@ import { Transfer } from '@douyinfe/semi-ui';
             label: `选项名称 ${i}`,
             value: i,
             disabled: false,
-            key: i,
+            key: `key-${i}`,
         };
     });
     return (
@@ -236,7 +236,7 @@ import { Transfer } from '@douyinfe/semi-ui';
             label: `选项名称 ${i}`,
             value: i,
             disabled: false,
-            key: i,
+            key: `key-${i}`,
         };
     });
     return (
@@ -264,7 +264,7 @@ import { Transfer } from '@douyinfe/semi-ui';
             label: `选项名称 ${i}`,
             value: i,
             disabled: false,
-            key: i,
+            key: `key-${i}`,
         };
     });
     return (
@@ -394,7 +394,7 @@ import { Transfer, Button } from '@douyinfe/semi-ui';
             label: `选项名称 ${i}`,
             value: i,
             disabled: false,
-            key: i,
+            key: `key-${i}`,
         };
     });
 
@@ -483,7 +483,7 @@ class CustomRenderDemo extends React.Component {
                 label: `海底捞门店 ${i}`,
                 value: i,
                 disabled: false,
-                key: i,
+                key: `key-${i}`,
             })),
         };
         this.renderSourcePanel = this.renderSourcePanel.bind(this);
@@ -746,7 +746,7 @@ class CustomRenderDragDemo extends React.Component {
                 label: `海底捞门店 ${i}`,
                 value: i,
                 disabled: false,
-                key: i,
+                key: `key-${i}`,
             })),
         };
         this.renderSourcePanel = this.renderSourcePanel.bind(this);

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

@@ -1433,7 +1433,6 @@ function Demo() {
 | keyMaps | Customize the key, label, and value fields in the node | object |  - | 2.47.0 |
 | filterTreeNode           | Toggle whether searchable or pass in a function to customize search behavior, data parameter provided since v2.28.0 | boolean\| <ApiType detail='(inputValue: string, treeNodeString: string, data?: TreeNodeData) => boolean'>Function</ApiType> | false       | -       |
 | getPopupContainer        | Container to render pop-up, you need to set 'position: relative`  This will change the DOM tree position, but not the view's rendering position.                                                    | function():HTMLElement                                            | -           | -       |
-| insetLabel               | Prefix alias,used mainly in Form                                                   | ReactNode                                                         | -           | - |
 | labelEllipsis | Toggle whether to ellipsis label when overflow | boolean | false\|true(virtualized) | - |  
 | leafOnly | Toggle whether to display tags for leaf nodes only and for onChange callback params in multiple mode | boolean | false | - |
 | loadData | Load data asynchronously and the return value should be a promise | (treeNode: TreeNodeData) => Promise |-| - |

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

@@ -1416,7 +1416,6 @@ function Demo() {
 | keyMaps | 自定义节点中 key、label、value 的字段。v2.47.0后提供                                                                                                | object |  - |
 | filterTreeNode | 是否根据输入项进行筛选,默认用 `treeNodeFilterProp` 的值作为要筛选的 `TreeNodeData` 的属性值, data 参数自 v2.28.0 开始提供                         | boolean\| <ApiType detail='(inputValue: string, treeNodeString: string, data?: TreeNodeData) => boolean'>Function</ApiType> | false |
 | getPopupContainer  | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative` 这会改变浮层 DOM 树位置,但不会改变视图渲染位置。                                                                                       | function():HTMLElement | - |
-| insetLabel | 前缀标签别名,主要用于 Form                                                                                                                     | ReactNode | - |
 | labelEllipsis | 是否开启label的超出省略,默认虚拟化状态下开启                                                                                                   | boolean | false\|true(虚拟化) | 
 | leafOnly | 多选模式下是否开启 onChange 回调入参及展示标签只有叶子节点                                                                                            | boolean | false |
 | loadData | 异步加载数据,需要返回一个Promise                                                                                                                 | (treeNode: TreeNodeData) => Promise |- |

+ 1 - 0
content/order.js

@@ -78,6 +78,7 @@ const order = [
     'tag',
     'timeline',
     'tooltip',
+    'userGuide',
     'chart',
     'banner',
     'notification',

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

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

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

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

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 89
+order: 90
 category: Other
 title: LocaleProvider
 subTitle: LocaleProvider
@@ -342,7 +342,7 @@ class I18nDemo extends React.Component {
                             style={{ width: 300, margin: 10 }}
                             treeData={treeData}
                             filterTreeNode
-                            insetLabel='Cascader'
+                            prefix='Cascader'
                         />
                     </div>
                     <h5>DatePicker</h5>
@@ -418,7 +418,7 @@ class I18nDemo extends React.Component {
         return (
             <>
                 <div style={{ borderBottom: '1px solid var(--semi-color-border)', paddingBottom: 20 }}>
-                    <Select onChange={this.onLanguageChange} insetLabel='Switch Language' style={{ width: 250 }} defaultValue='en_GB'>
+                    <Select onChange={this.onLanguageChange} prefix='Switch Language' style={{ width: 250 }} defaultValue='en_GB'>
                         <Select.Option value='zh_CN'>Chinese</Select.Option>
                         <Select.Option value='en_GB'>English</Select.Option>
                         <Select.Option value='ja_JP'>Japanese</Select.Option>

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 89
+order: 90
 category: 其他
 title:  LocaleProvider 多语言
 icon: doc-i18n
@@ -348,7 +348,7 @@ class I18nDemo extends React.Component {
                             style={{ width: 300, margin: 10 }}
                             treeData={treeData}
                             filterTreeNode
-                            insetLabel='Cascader'
+                            prefix='Cascader'
                         />
                     </div>
                     <h5>DatePicker</h5>
@@ -424,7 +424,7 @@ class I18nDemo extends React.Component {
         return (
             <>
                 <div style={{ borderBottom: '1px solid var(--semi-color-border)', paddingBottom: 20 }}>
-                    <Select onChange={this.onLanguageChange} insetLabel='切换语言' style={{ width: 250 }} defaultValue='zh_CN'>
+                    <Select onChange={this.onLanguageChange} prefix='切换语言' style={{ width: 250 }} defaultValue='zh_CN'>
                         <Select.Option value='zh_CN'>简体中文</Select.Option>
                         <Select.Option value='en_US'>英语(美)</Select.Option>
                         <Select.Option value='en_GB'>英语(英)</Select.Option>

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

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 91
+order: 92
 category: Plus
 title: AudioPlayer
 icon: doc-audioplayer

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

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 91
+order: 92
 category: Plus
 title: AudioPlayer 音频播放器
 icon: doc-audioplayer

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

@@ -1611,6 +1611,7 @@ render(DefaultChat);
 | chats | Controlled conversation list | Message | - |
 | className | Custom class name | string | - |
 | customMarkDownComponents | custom markdown render, transparently passed to MarkdownRender for conversation content rendering | MDXProps\['components'\]| - |
+| enableUpload | Whether to enable uploading, supported since v2.76.0, supports boolean type and object type. When the boolean type is passed in, the upload behavior of dragging, clicking the upload button, and pasting in the input box will be controlled at the same time. When the object is passed in, it can be set separately. When the object type is passed in, the unset items default to true. | boolean \| { pasteUpload: boolean, clickUpload: boolean, dragUpload: boolean } | true |
 | hints | prompt information | string | - |
 | hintCls | hint style | string | - |
 | hintStyle | hint style | CSSProperties | - |

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

@@ -1614,6 +1614,7 @@ render(DefaultChat);
 | chats | 受控对话列表 | Message | - |
 | className | 自定义类名 | string | - |
 | customMarkDownComponents | 自定义 markdown render, 透传给对话内容渲染的 MarkdownRender | MDXProps\['components'\]| - |
+| enableUpload | 是否启用上传, 自 v2.76.0 支持,支持 boolean 类型及对象类型,传入 boolean 类型将同时控制拖拽,点击上传按钮,在输入框中粘贴的上传行为,传入对象可分别设置,传入对象类型时未设置的项默认为 true | boolean \| { pasteUpload: boolean, clickUpload: boolean, dragUpload: boolean } | true |
 | hints | 提示信息 | string | - |
 | hintCls | 提示区最外层样式类名 | string | - |
 | hintStyle | 提示区最外层样式 | CSSProperties | - |

+ 98 - 12
content/plus/jsonviewer/index-en-US.md

@@ -49,7 +49,7 @@ class SimpleJsonViewer extends React.Component {
     render() {
         return (
             <div style={{ marginBottom: 16 }}>
-                <JsonViewer height={100} width={400} value={data} />
+                <JsonViewer height={100} width={700} value={data} />
             </div>
         );
     }
@@ -74,13 +74,13 @@ class SimpleJsonViewerWithLineHeight extends React.Component {
         return (
             <div>
                 <div style={{ marginBottom: 20 }}>
-                    <JsonViewer height={100} width={320} value={data} options={{ lineHeight: 20 }} />
+                    <JsonViewer height={100} width={700} value={data} options={{ lineHeight: 20 }} />
                 </div>
                 <div style={{ marginBottom: 20 }}>
-                    <JsonViewer height={120} width={320} value={data} options={{ lineHeight: 24 }} />
+                    <JsonViewer height={120} width={700} value={data} options={{ lineHeight: 24 }} />
                 </div>
                 <div style={{ marginBottom: 20 }}>
-                    <JsonViewer height={120} width={320} value={data} options={{ lineHeight: 26 }} />
+                    <JsonViewer height={120} width={700} value={data} options={{ lineHeight: 26 }} />
                 </div>
             </div>
         );
@@ -106,7 +106,7 @@ class SimpleJsonViewerWithAutoWrap extends React.Component {
     render() {
         return (
             <div style={{ marginBottom: 16 }}>
-                <JsonViewer height={120} width={800} value={data} options={{ autoWrap: true }} />
+                <JsonViewer height={120} width={700} value={data} options={{ autoWrap: true }} />
             </div>
         );
     }
@@ -139,7 +139,7 @@ function FormatJsonComponent() {
                 <JsonViewer
                     ref={jsonviewerRef}
                     height={100}
-                    width={400}
+                    width={700}
                     value={data}
                     options={{ formatOptions: { tabSize: 4, insertSpaces: true, eol: '\n' } }}
                 />
@@ -151,6 +151,78 @@ function FormatJsonComponent() {
 render(FormatJsonComponent);
 ```
 
+### Custom Render Rules
+
+By configuring the `options.customRenderRule` parameter, you can customize how JSON content is rendered (Note: only works in read-only mode).
+
+`customRenderRule` is an array of rules, where each rule contains two properties:
+- `match`: Matching condition, can be one of three types:
+  - String: Exact match
+  - Regular expression: Match by regex
+  - Function: Custom matching logic, with signature `(value: string, pathChain: string) => boolean`
+    - `value`: Value to match (key or value from JSON key-value pairs, as strings since internal processing only filters quotes)
+    - `path`: Current matching path, format is `root.key1.key2.key3[0].key4`
+- `render`: Custom render function, with signature `(content: string) => React.ReactNode`
+  - `content`: Matched content. For string values, includes double quotes (e.g., `"name"`, `"Semi"`)
+
+```jsx live=true dir="column" noInline=true
+import React, { useRef } from 'react';
+import { JsonViewer, Button, Rating, Popover, Tag, Image } from '@douyinfe/semi-ui';
+const data = `{
+  "name": "Semi",
+  "version": "2.7.4",
+  "rating": 5,
+  "tags": ["design", "react", "ui"],
+  "image": "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg"
+}`;
+function CustomRenderJsonComponent() {
+    const jsonviewerRef = useRef();
+    const customRenderRule = [
+        {
+            match: 'Semi',
+            render: (content) => {
+                return <Popover showArrow content={'I am a custom render'} trigger='hover'><span>{content}</span></Popover>;
+            }
+        },
+        {
+            match: (value)=> value == 5,
+            render: (content) => {
+                return <Rating defaultValue={content} size={10} disabled/>;
+            }
+        },
+        {
+            match: (value, path)=> path === 'root.tags[0]' || path === 'root.tags[1]' || path === 'root.tags[2]',
+            render: (content) => {
+                return <Tag size='small' shape='circle'>{content}</Tag>;
+            }
+        },
+        {
+            match: new RegExp('^http'),
+            render: (content) => {
+                // content is original string with quotes, need to remove quotes for valid URL
+                return <Popover showArrow content={<Image width={100} height={100} src={content.replace(/^"|"$/g, '')} />} trigger='hover'><span>{content}</span></Popover>;
+            }
+        }
+    ];
+    return (
+        <div>
+            <div style={{ marginBottom: 16, marginTop: 16 }}>
+                <JsonViewer
+                    ref={jsonviewerRef}
+                    height={200}
+                    width={600}
+                    value={data}
+                    showSearch={false}
+                    options={{ formatOptions: { tabSize: 4, insertSpaces: true, eol: '\n' }, customRenderRule, readOnly: true, autoWrap: true }}
+                />
+            </div>
+        </div>
+    );
+}
+
+render(CustomRenderJsonComponent);
+```
+
 ## API Reference
 
 ### JsonViewer
@@ -168,12 +240,13 @@ render(FormatJsonComponent);
 
 ### JsonViewerOptions
 
-| Attribute     | Description                             | Type              | Default |
-| ------------- | --------------------------------------- | ----------------- | ------- |
-| lineHeight    | Height of each line of content, unit:px | number            | 20      |
-| autoWrap      | Whether to wrap lines automatically.    | boolean           | true    |
-| readOnly      | Whether to be read-only.    | boolean           | false    |
-| formatOptions | Content format setting                  | FormattingOptions | -       |
+| Attribute     | Description                             | Type              | Default | Version |
+| ------------- | --------------------------------------- | ----------------- | ------- | ------- |
+| lineHeight    | Height of each line of content, unit:px | number            | 20      | -       |
+| autoWrap      | Whether to wrap lines automatically.    | boolean           | true    | -       |
+| readOnly      | Whether to be read-only.    | boolean           | false    | -       |
+| customRenderRule | Custom render rules | CustomRenderRule[] | -       | 2.74.0  |
+| formatOptions | Content format setting                  | FormattingOptions | -       | -       |
 
 ### FormattingOptions
 
@@ -183,6 +256,13 @@ render(FormatJsonComponent);
 | insertSpaces | Whether to use spaces for indentation | boolean | true    |
 | eol          | Line break character                  | string  | '\n'    |
 
+### CustomRenderRule
+
+| Attribute | Description | Type | Default |
+| --- | --- | --- | --- |
+| match | Matching rule | string \| RegExp \| (value: string, path: string) => boolean | - |
+| render | Render function | (content: string) => React.ReactNode | - |
+
 ## Methods
 
 Methods bound to the component instance can be called via `ref` to achieve certain special interactions.
@@ -191,6 +271,12 @@ Methods bound to the component instance can be called via `ref` to achieve certa
 | ---------- | ---------------------- |
 | getValue() | Get current value      |
 | format()   | Format current content |
+| search(searchText: string, caseSensitive?: boolean, wholeWord?: boolean, regex?: boolean) | Search for text with optional parameters |
+| getSearchResults() | Get current search results |
+| prevSearch(step?: number) | Navigate to previous search result, with optional step size |
+| nextSearch(step?: number) | Navigate to next search result, with optional step size |
+| replace(replaceText: string) | Replace current search match |
+| replaceAll(replaceText: string) | Replace all search matches |
 
 ### Performance
 

+ 101 - 15
content/plus/jsonviewer/index.md

@@ -45,7 +45,7 @@ class SimpleJsonViewer extends React.Component {
     render() {
         return (
             <div style={{ marginBottom: 16 }}>
-                <JsonViewer height={100} width={400} value={data} />
+                <JsonViewer height={100} width={700} value={data} />
             </div>
         );
     }
@@ -70,13 +70,13 @@ class SimpleJsonViewerWithLineHeight extends React.Component {
         return (
             <div>
                 <div style={{ marginBottom: 12, overflow: 'hidden' }}>
-                    <JsonViewer height={100} width={320} value={data} options={{ lineHeight: 20 }} />
+                    <JsonViewer height={100} width={700} value={data} options={{ lineHeight: 20 }} />
                 </div>
                 <div style={{ marginBottom: 12, overflow: 'hidden' }}>
-                    <JsonViewer height={120} width={320} value={data} options={{ lineHeight: 24 }} />
+                    <JsonViewer height={120} width={700} value={data} options={{ lineHeight: 24 }} />
                 </div>
                 <div style={{ marginBottom: 12, overflow: 'hidden' }}>
-                    <JsonViewer height={120} width={320} value={data} options={{ lineHeight: 26 }} />
+                    <JsonViewer height={120} width={700} value={data} options={{ lineHeight: 26 }} />
                 </div>
             </div>
         );
@@ -102,7 +102,7 @@ class SimpleJsonViewerWithAutoWrap extends React.Component {
     render() {
         return (
             <div style={{ marginBottom: 16 }}>
-                <JsonViewer height={120} width={800} value={data} options={{ autoWrap: true }} />
+                <JsonViewer height={120} width={700} value={data} options={{ autoWrap: true }} />
             </div>
         );
     }
@@ -135,7 +135,7 @@ function FormatJsonComponent() {
                 <JsonViewer
                     ref={jsonviewerRef}
                     height={100}
-                    width={400}
+                    width={700}
                     value={data}
                     options={{ formatOptions: { tabSize: 4, insertSpaces: true, eol: '\n' } }}
                 />
@@ -147,6 +147,78 @@ function FormatJsonComponent() {
 render(FormatJsonComponent);
 ```
 
+### 自定义渲染规则
+
+通过配置 `options.customRenderRule` 参数,你可以自定义 JSON 内容的渲染方式(注意:仅在只读模式下生效)。
+
+`customRenderRule` 是一个规则数组,每条规则包含两个属性:
+- `match`: 匹配条件,可以是以下三种类型之一:
+  - 字符串:精确匹配
+  - 正则表达式:按正则匹配
+  - 函数:自定义匹配逻辑,函数签名为 `(value: string, path: string) => boolean`
+    - `value`: 待匹配的值(为Json字符串的键值对的键或者值,由于内部处理注入时仅过滤引号,因此类型全部为string)
+    - `path`: 当前匹配到的路径,格式为 `root.key1.key2.key3[0].key4`
+- `render`: 自定义渲染函数,函数签名为 `(content: string) => React.ReactNode`
+  - `content`: 匹配到的内容。如果是字符串类型的值,将包含双引号(如 `"name"`,`"Semi"`)
+
+```jsx live=true dir="column" noInline=true
+import React, { useRef } from 'react';
+import { JsonViewer, Button, Rating, Popover, Tag, Image } from '@douyinfe/semi-ui';
+const data = `{
+  "name": "Semi",
+  "version": "2.7.4",
+  "rating": 5,
+  "tags": ["design", "react", "ui"],
+  "image": "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg"
+}`;
+function CustomRenderJsonComponent() {
+    const jsonviewerRef = useRef();
+    const customRenderRule = [
+        {
+            match: 'Semi',
+            render: (content) => {
+                return <Popover showArrow content={'我是用户自定义的渲染'} trigger='hover'><span>{content}</span></Popover>;
+            }
+        },
+        {
+            match: (value)=> value == 5,
+            render: (content) => {
+                return <Rating defaultValue={content} size={10} disabled/>;
+            }
+        },
+        {
+            match: (value, path)=> path === 'root.tags[0]' || path === 'root.tags[1]' || path === 'root.tags[2]',
+            render: (content) => {
+                return <Tag size='small' shape='circle'>{content}</Tag>;
+            }
+        },
+        {
+            match: new RegExp('^http'),
+            render: (content) => {
+                // content 为原始字符串,包含引号,因此需要去除引号才可以作为合法的url
+                return <Popover showArrow content={<Image width={100} height={100} src={content.replace(/^"|"$/g, '')} />} trigger='hover'><span>{content}</span></Popover>;
+            }
+        }
+    ];
+    return (
+        <div>
+            <div style={{ marginBottom: 16, marginTop: 16 }}>
+                <JsonViewer
+                    ref={jsonviewerRef}
+                    height={200}
+                    width={600}
+                    value={data}
+                    showSearch={false}
+                    options={{ formatOptions: { tabSize: 4, insertSpaces: true, eol: '\n' }, customRenderRule, readOnly: true, autoWrap: true }}
+                />
+            </div>
+        </div>
+    );
+}
+
+render(CustomRenderJsonComponent);
+```
+
 
 ## API 参考
 
@@ -155,22 +227,29 @@ render(FormatJsonComponent);
 | 属性                | 说明                                             | 类型                              | 默认值    |
 |-------------------|------------------------------------------------|---------------------------------|--------------|
 | value             | 展示内容                                    | string                                  | -  |
-| height            | 高度                                     | number                                  | -  |
-| width             | 宽度                                     | number                                  | -  |
+| height            | 高度                                     | number \| string                                  | -  |
+| width             | 宽度                                     | number \| string                                 | -  |
 | className         | 类名                           | string                                  | -   |
 | style             | 内联样式                           | object                                  | -   |
 | showSearch        | 是否显示搜索Icon                           | boolean                                  | true   |
-| options           | 格式化配置                                | JsonViewerOptions                       | -   |
+| options           | 编辑器配置                                | JsonViewerOptions                       | -   |
 | onChange          | 内容变化回调                           | (value: string) => void                  | -   |
 
 ### JsonViewerOptions
 
+| 属性                | 说明                                          | 类型                              | 默认值    | 版本
+|-------------------|------------------------------------------------|---------------------------------|-----------|---------|
+| lineHeight        | 行高                                    | number                          | 20  | - |
+| autoWrap        | 是否自动换行                             | boolean                            | true  | - |
+| readOnly        | 是否只读                             | boolean                            | false  | - |
+| customRenderRule | 自定义渲染规则                             | CustomRenderRule[]               |  -  | 2.74.0 |
+| formatOptions     | 格式化配置                               | FormattingOptions                |  -  | - |
+
+### CustomRenderRule
 | 属性                | 说明                                          | 类型                              | 默认值    |
 |-------------------|------------------------------------------------|---------------------------------|-----------|
-| lineHeight        | 行高                                    | number                          | 20  |
-| autoWrap        | 是否自动换行                             | boolean                            | true  |
-| readOnly        | 是否只读                             | boolean                            | false  |
-| formatOptions     | 格式化配置                               | FormattingOptions                |  -  |
+| match             | 匹配规则                                   | string \| RegExp \| (value: string, path: string) => boolean | -  |
+| render            | 渲染函数                                   | (content: string) => React.ReactNode | -  |
 
 ### FormattingOptions
 
@@ -182,12 +261,19 @@ render(FormatJsonComponent);
 
 ## Methods
 
-绑定在组件实例上的方法,可以通过 ref 调用实现某些特殊交互
+可以通过 `ref` 调用组件实例上绑定的方法,实现某些特殊交互
 
 | 名称    | 描述     |
 |---------|--------|
 | getValue()  | 获取当前值 |
-| format() | 格式化 |
+| format() | 格式化当前内容 |
+| search(searchText: string, caseSensitive?: boolean, wholeWord?: boolean, regex?: boolean) | 搜索文本,可选参数控制大小写敏感、全词匹配和正则表达式 |
+| getSearchResults() | 获取当前搜索结果 |
+| prevSearch(step?: number) | 导航到上一个搜索结果,可选步长参数 |
+| nextSearch(step?: number) | 导航到下一个搜索结果,可选步长参数 |
+| replace(replaceText: string) | 替换当前搜索匹配项 |
+| replaceAll(replaceText: string) | 替换所有搜索匹配项 |
+
 
 
 ### Performance 

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

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

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

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

+ 126 - 26
content/show/cropper/index-en-US.md

@@ -27,6 +27,7 @@ Use `sr` to set the cropped image; use `shape` to set the shape of the cropping
 
 ```jsx live=true dir=column noInline=true
 import { Cropper, Button, RadioGroup, Radio } from '@douyinfe/semi-ui';
+import React, { useState, useRef, useCallback } from 'react';
 
 const containerStyle = {
   width: 550,
@@ -36,13 +37,12 @@ const containerStyle = {
 
 function Demo() {
     const ref = useRef(null);
-  const [shape, setShape] = useState('rect');
+    const [shape, setShape] = useState('rect');
+    const [cropperUrl, setCropperUrl] = useState('');
 
     const onButtonClick = useCallback(() => {
-        const value = ref.current.getCropperCanvas();
-        const previewContainer = document.getElementById('previewContainer');
-        previewContainer.innerHTML = '';
-        previewContainer.appendChild(value);
+        const canvas = ref.current.getCropperCanvas();
+        setCropperUrl(canvas.toDataURL());
     }, []);
 
     const onShapeChange = useCallback((e) => {
@@ -62,7 +62,8 @@ function Demo() {
             shape={shape}
         />
         <Button onClick={onButtonClick}>Get Cropped Image</Button>
-        <div id='previewContainer'/>
+         <br/><br/>
+        {cropperUrl && <img src={cropperUrl} style={{height: 400}}/>}
     </>;
 }
 
@@ -79,6 +80,7 @@ When setting `aspectRatio`, the crop box ratio is fixed, and the crop box will c
 
 ```jsx live=true dir=column noInline=true
 import { Cropper, Button, RadioGroup, Radio } from '@douyinfe/semi-ui';
+import React, { useState, useRef, useCallback } from 'react';
 
 const containerStyle = {
   width: 550,
@@ -88,13 +90,12 @@ const containerStyle = {
 
 function Demo() {
     const ref = useRef(null);
-    const shape = useState('rect');
+    const [cropperUrl, setCropperUrl] = useState('');
 
     const onButtonClick = useCallback(() => {
-        const value = ref.current.getCropperCanvas();
-        const previewContainer = document.getElementById('previewContainer-aspect');
-        previewContainer.innerHTML = '';
-        previewContainer.appendChild(value);
+      const canvas = ref.current.getCropperCanvas();
+      const url = canvas.toDataURL();
+      setCropperUrl(url);
     }, []);
 
     return <>
@@ -105,7 +106,8 @@ function Demo() {
             style={containerStyle}
         />
         <Button onClick={onButtonClick}>Get Cropped Image</Button>
-        <div id='previewContainer-aspect' />
+         <br /><br />
+        {cropperUrl && <img src={cropperUrl} style={{height: 400}}/>}
     </>;
 }
 
@@ -118,6 +120,7 @@ Control image rotation and zoom through `rotate` and `zoom`, and get the latest
 
 ```jsx live=true dir=column noInline=true
 import { Cropper, Button, Slider } from '@douyinfe/semi-ui';
+import React, { useState, useRef, useCallback } from 'react';
 
 const containerStyle = {
   width: 550,
@@ -137,6 +140,7 @@ function Demo() {
   const [rotate, setRotate] = useState(0);
   const [zoom, setZoom] = useState(1);
   const ref = useRef();
+  const [cropperUrl, setCropperUrl] = useState('');
 
   const onZoomChange = useCallback((value) => {
     setZoom(value);
@@ -147,10 +151,8 @@ function Demo() {
   }, []);
 
   const onButtonClick = useCallback(() => {
-    const value = ref.current.getCropperCanvas();
-    const previewContainer = document.getElementById('previewContainer-control');
-    previewContainer.innerHTML = '';
-    previewContainer.appendChild(value);
+    const canvas = ref.current.getCropperCanvas();
+    setCropperUrl(canvas.toDataURL());
   }, []);
 
   return (
@@ -187,11 +189,8 @@ function Demo() {
            </div>
            <br />
            <Button onClick={onButtonClick}>Get Cropped Image</Button>
-           <br />
-           <div >
-            <div id='previewContainer-control'
-            />
-          </div>
+           <br /><br />
+          {cropperUrl && <img src={cropperUrl} style={{height: 400}}/>}
       </div>
   );
 };
@@ -205,6 +204,7 @@ The crop box style can be customized through `cropperBoxStyle`, `cropperBoxClass
 
 ```jsx live=true dir=column noInline=true
 import { Cropper, Button, Switch } from '@douyinfe/semi-ui';
+import React, { useState, useRef, useCallback } from 'react';
 
 const containerStyle = {
   width: 550,
@@ -221,12 +221,11 @@ const centerStyle = {
 
 function Demo() {
     const ref = useRef(null);
+    const [cropperUrl, setCropperUrl] = useState('');
 
     const onButtonClick = useCallback(() => {
-        const value = ref.current.getCropperCanvas();
-        const previewContainer = document.getElementById('previewContainer-cropperBox');
-        previewContainer.innerHTML = '';
-        previewContainer.appendChild(value);
+        const canvas = ref.current.getCropperCanvas();
+        setCropperUrl(canvas.toDataURL());
     }, []);
 
     return <>
@@ -239,13 +238,113 @@ function Demo() {
             showResizeBox={false}
         />
         <Button onClick={onButtonClick}>Get Cropped Image</Button>
-        <div id='previewContainer-cropperBox'/>
+        <br /><br />
+        {cropperUrl && <img src={cropperUrl} style={{height: 400}}/>}
     </>;
 }
 
 render(<Demo />)
 ```
 
+### 实时预览裁切效果
+
+通过 `preview` 指定预览容器,实时预览裁切效果。
+
+```jsx live=true dir=column noInline=true
+import { Cropper, Button, RadioGroup, Radio } from '@douyinfe/semi-ui';
+import React, { useState, useRef, useCallback } from 'react';
+
+const containerStyle = {
+  width: 550,
+  height: 300,
+  margin: 20,
+}
+
+const actionStyle = {
+  marginTop: 20,
+  display: 'flex',
+  alignItems: 'center',
+  justifyContent: 'center',
+  width: 'fit-content'
+}
+
+function Demo() {
+  const [rotate, setRotate] = useState(0);
+  const [zoom, setZoom] = useState(1);
+  const [cropperData, setCropperUrl ] = useState('');
+  const ref = useRef();
+
+  const onZoomChange = useCallback((value) => {
+    setZoom(value);
+  })
+
+  const onSliderChange = useCallback((value) => {
+    setRotate(value);
+  }, []);
+
+  const onButtonClick = useCallback(() => {
+    const canvas = ref.current.getCropperCanvas();
+    const url = canvas.toDataURL();
+    setCropperUrl(url);
+  }, []);
+
+  const preview = useCallback(() => {
+    const previewContainer = document.getElementById('previewWrapper');
+    return previewContainer;
+  }, []);
+
+  return (
+      <div >
+           <Cropper 
+              ref={ref} 
+              src={"https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg"}
+              style={containerStyle}
+              rotate={rotate}
+              zoom={zoom}
+              onZoomChange={onZoomChange}
+              preview={preview}
+           />
+           <div style={actionStyle} >
+            <span>旋转</span>
+            <Slider
+              style={{ width: 500}}
+              value={rotate}
+              step={1}
+              min={-360}
+              max={360}
+              onChange={onSliderChange}
+            />
+           </div>
+           <div style={actionStyle} >
+            <span>缩放</span>
+            <Slider
+              style={{ width: 500}}
+              value={zoom}
+              step={0.1}
+              min={0.1}
+              max={3}
+              onChange={onZoomChange}
+            />
+           </div>
+           <br />
+           <div style={{ display: 'flex', }}>
+              <div style={{ width: '50%', flexGrow: 1}}>
+                <strong>实时预览</strong>
+                <div id='previewWrapper' style={{height: 300, marginTop: 8}}/>
+              </div>
+              <div style={{width: '50%', flexGrow: 1, paddingLeft: 10 }}>
+                <Button onClick={onButtonClick}>裁切</Button>
+                <br /><br />
+                <img src={cropperData} style={{ width: '90%'}} />
+              </div>
+           </div>
+      </div>
+  );
+};
+
+render(<Demo />)
+```
+
 ### API
 
 | PROPERTIES | INSTRUCTIONS | TYPE | DEFAULT |
@@ -260,6 +359,7 @@ render(<Demo />)
 | maxZoom | Maximum zoom factor | number | 3 |
 | minZoom | Minimum zoom factor | number | 0.1 |
 | onZoomChange | Callback during zoom transformation | (zoom: number) => void | - |
+| preview | The container of the preview image | () => HTMLElement | - |
 | rotate | rotation angle | number | - |
 | shape | Crop box shape | 'rect' \| 'round' \| 'roundRect' | 'rect' |
 | src | The address of the cropped image | string | - |

+ 127 - 30
content/show/cropper/index.md

@@ -30,6 +30,7 @@ import { Cropper } from '@douyinfe/semi-ui';
 
 ```jsx live=true dir=column noInline=true
 import { Cropper, Button, RadioGroup, Radio } from '@douyinfe/semi-ui';
+import React, { useState, useRef, useCallback } from 'react';
 
 const containerStyle = {
   width: 550,
@@ -39,13 +40,12 @@ const containerStyle = {
 
 function Demo() {
     const ref = useRef(null);
-  const [shape, setShape] = useState('rect');
+    const [shape, setShape] = useState('rect');
+    const [cropperUrl, setCropperUrl] = useState('');
 
     const onButtonClick = useCallback(() => {
-        const value = ref.current.getCropperCanvas();
-        const previewContainer = document.getElementById('previewContainer');
-        previewContainer.innerHTML = '';
-        previewContainer.appendChild(value);
+        const canvas = ref.current.getCropperCanvas();
+        setCropperUrl(canvas.toDataURL());
     }, []);
 
     const onShapeChange = useCallback((e) => {
@@ -65,7 +65,8 @@ function Demo() {
             shape={shape}
         />
         <Button onClick={onButtonClick}>裁切</Button>
-        <div id='previewContainer'/>
+        <br/><br/>
+        {cropperUrl && <img src={cropperUrl} style={{height: 400}}/>}
     </>;
 }
 
@@ -82,6 +83,7 @@ render(<Demo />)
 
 ```jsx live=true dir=column noInline=true
 import { Cropper, Button, RadioGroup, Radio } from '@douyinfe/semi-ui';
+import React, { useState, useRef, useCallback } from 'react';
 
 const containerStyle = {
   width: 550,
@@ -91,13 +93,11 @@ const containerStyle = {
 
 function Demo() {
     const ref = useRef(null);
-    const shape = useState('rect');
+    const [cropperUrl, setCropperUrl] = useState('');
 
     const onButtonClick = useCallback(() => {
-        const value = ref.current.getCropperCanvas();
-        const previewContainer = document.getElementById('previewContainer-aspect');
-        previewContainer.innerHTML = '';
-        previewContainer.appendChild(value);
+        const canvas = ref.current.getCropperCanvas();
+        setCropperUrl(canvas.toDataURL());
     }, []);
 
     return <>
@@ -108,7 +108,8 @@ function Demo() {
             style={containerStyle}
         />
         <Button onClick={onButtonClick}>裁切</Button>
-        <div id='previewContainer-aspect' />
+        <br /><br />
+        {cropperUrl && <img src={cropperUrl} style={{height: 400}}/>}
     </>;
 }
 
@@ -121,6 +122,7 @@ render(<Demo />)
 
 ```jsx live=true dir=column noInline=true
 import { Cropper, Button, Slider } from '@douyinfe/semi-ui';
+import React, { useState, useRef, useCallback } from 'react';
 
 const containerStyle = {
   width: 550,
@@ -140,6 +142,7 @@ function Demo() {
   const [rotate, setRotate] = useState(0);
   const [zoom, setZoom] = useState(1);
   const ref = useRef();
+  const [cropperUrl, setCropperUrl] = useState('');
 
   const onZoomChange = useCallback((value) => {
     setZoom(value);
@@ -150,10 +153,8 @@ function Demo() {
   }, []);
 
   const onButtonClick = useCallback(() => {
-    const value = ref.current.getCropperCanvas();
-    const previewContainer = document.getElementById('previewContainer-control');
-    previewContainer.innerHTML = '';
-    previewContainer.appendChild(value);
+    const canvas = ref.current.getCropperCanvas();
+    setCropperUrl(canvas.toDataURL());
   }, []);
 
   return (
@@ -167,7 +168,7 @@ function Demo() {
               onZoomChange={onZoomChange}
            />
            <div style={actionStyle} >
-            <span>Rotate</span>
+            <span>旋转</span>
             <Slider
               style={{ width: 500}}
               value={rotate}
@@ -178,7 +179,7 @@ function Demo() {
             />
            </div>
            <div style={actionStyle} >
-            <span>Zoom</span>
+            <span>缩放</span>
             <Slider
               style={{ width: 500}}
               value={zoom}
@@ -190,13 +191,8 @@ function Demo() {
            </div>
            <br />
            <Button onClick={onButtonClick}>裁切</Button>
-           <br />
-           <div 
-            // style={{ background: 'pink' }} 
-           >
-            <div id='previewContainer-control'
-            />
-          </div>
+          <br /><br />
+          {cropperUrl && <img src={cropperUrl} style={{height: 400}}/>}
       </div>
   );
 };
@@ -210,6 +206,7 @@ render(<Demo />)
 
 ```jsx live=true dir=column noInline=true
 import { Cropper, Button, Switch } from '@douyinfe/semi-ui';
+import React, { useState, useRef, useCallback } from 'react';
 
 const containerStyle = {
   width: 550,
@@ -226,12 +223,11 @@ const centerStyle = {
 
 function Demo() {
     const ref = useRef(null);
+    const [cropperUrl, setCropperUrl] = useState('');
 
     const onButtonClick = useCallback(() => {
-        const value = ref.current.getCropperCanvas();
-        const previewContainer = document.getElementById('previewContainer-cropperBox');
-        previewContainer.innerHTML = '';
-        previewContainer.appendChild(value);
+        const canvas = ref.current.getCropperCanvas();
+        setCropperUrl(canvas.toDataURL());
     }, []);
 
     return <>
@@ -244,13 +240,113 @@ function Demo() {
             showResizeBox={false}
         />
         <Button onClick={onButtonClick}>裁切</Button>
-        <div id='previewContainer-cropperBox'/>
+        <br /><br />
+        {cropperUrl && <img src={cropperUrl} style={{height: 400}}/>}
     </>;
 }
 
 render(<Demo />)
 ```
 
+### 实时预览裁切效果
+
+通过 `preview` 指定预览容器,实时预览裁切效果。
+
+```jsx live=true dir=column noInline=true
+import { Cropper, Button, RadioGroup, Radio } from '@douyinfe/semi-ui';
+import React, { useState, useRef, useCallback } from 'react';
+
+const containerStyle = {
+  width: 550,
+  height: 300,
+  margin: 20,
+}
+
+const actionStyle = {
+  marginTop: 20,
+  display: 'flex',
+  alignItems: 'center',
+  justifyContent: 'center',
+  width: 'fit-content'
+}
+
+function Demo() {
+  const [rotate, setRotate] = useState(0);
+  const [zoom, setZoom] = useState(1);
+  const [cropperUrl, setCropperUrl ] = useState('');
+  const ref = useRef();
+
+  const onZoomChange = useCallback((value) => {
+    setZoom(value);
+  })
+
+  const onSliderChange = useCallback((value) => {
+    setRotate(value);
+  }, []);
+
+  const onButtonClick = useCallback(() => {
+    const canvas = ref.current.getCropperCanvas();
+    const url = canvas.toDataURL();
+    setCropperUrl(url);
+  }, []);
+
+  const preview = useCallback(() => {
+    const previewContainer = document.getElementById('previewWrapper');
+    return previewContainer;
+  }, []);
+
+  return (
+      <div >
+           <Cropper 
+              ref={ref} 
+              src={"https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/abstract.jpg"}
+              style={containerStyle}
+              rotate={rotate}
+              zoom={zoom}
+              onZoomChange={onZoomChange}
+              preview={preview}
+           />
+           <div style={actionStyle} >
+            <span>旋转</span>
+            <Slider
+              style={{ width: 500}}
+              value={rotate}
+              step={1}
+              min={-360}
+              max={360}
+              onChange={onSliderChange}
+            />
+           </div>
+           <div style={actionStyle} >
+            <span>缩放</span>
+            <Slider
+              style={{ width: 500}}
+              value={zoom}
+              step={0.1}
+              min={0.1}
+              max={3}
+              onChange={onZoomChange}
+            />
+           </div>
+           <br />
+           <div style={{ display: 'flex', }}>
+              <div style={{ width: '50%', flexGrow: 1}}>
+                <strong>实时预览</strong>
+                <div id='previewWrapper' style={{height: 300, marginTop: 8}}/>
+              </div>
+              <div style={{width: '50%', flexGrow: 1, paddingLeft: 10 }}>
+                <Button onClick={onButtonClick}>裁切</Button>
+                <br /><br />
+                <img src={cropperUrl} style={{ width: '90%'}} />
+              </div>
+           </div>
+      </div>
+  );
+};
+
+render(<Demo />)
+```
+
 ### API
 
 | 属性 | 说明 | 类型 | 默认值 |
@@ -265,6 +361,7 @@ render(<Demo />)
 | maxZoom | 最大缩放倍数 | number | 3 |
 | minZoom | 最小缩放倍数 | number | 0.1 |
 | onZoomChange | 缩放回调 | (zoom: number) => void | - |
+| preview | 指定预览容器 | () => HTMLElement | - |
 | rotate | 旋转角度 | number | - |
 | shape | 裁切框形状 | 'rect' \| 'round' \| 'roundRect' | 'rect' |
 | src | 图片地址 | string | - |

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

@@ -1465,7 +1465,7 @@ You can call `setTempFilteredValue` to store the filter value when the user ente
 The reason for setting `tempFilteredValue` is that in scenarios where temporary filtered values need to be stored, there is no need to declare a state to save this temporary filtered value.
 
 ```typescript
-type RenderFilterDropdown = (props?: RenderFilterDropdownProps) => React.ReactNode;
+type RenderFilterDropdown = (props: RenderFilterDropdownProps) => React.ReactNode;
 interface RenderFilterDropdownProps {
      /** Temporary filter value, the initial value is `filteredValue` or `defaultFilteredValue` */
      tempFilteredValue: any[];
@@ -1850,21 +1850,21 @@ const expandData = {
     '0': [
         { key: 'DAU', value: '1,480,000' },
         { key: 'Day7 Retention Ratio', value: '98%' },
-        { key: 'Security Level', value: '3级' },
+        { key: 'Security Level', value: '3 级' },
         { key: 'Vertical label', value: <Tag style={{ margin: 0 }}>Designer</Tag> },
         { key: 'Certification', value: 'No Verified' },
     ],
     '1': [
         { key: 'DAU', value: '2,480,000' },
         { key: 'Day7 Retention Ratio', value: '90%' },
-        { key: 'Security Level', value: '1级' },
+        { key: 'Security Level', value: '1 级' },
         { key: 'Vertical label', value: <Tag style={{ margin: 0 }}>Template</Tag> },
         { key: 'Certification', value: 'Verified' },
     ],
     '2': [
         { key: 'DAU', value: '2,920,000' },
         { key: 'Day7 Retention Ratio', value: '98%' },
-        { key: 'Security Level', value: '2级' },
+        { key: 'Security Level', value: '2 级' },
         { key: 'Vertical label', value: <Tag style={{ margin: 0 }}>Docs</Tag> },
         { key: 'Certification', value: 'Verified' },
     ],
@@ -1996,21 +1996,21 @@ const expandData = {
     '0': [
         { key: 'DAU', value: '1,480,000' },
         { key: 'Day7 Retention Ratio', value: '98%' },
-        { key: 'Security Level', value: '3级' },
+        { key: 'Security Level', value: '3 级' },
         { key: 'Vertical label', value: <Tag style={{ margin: 0 }}>Designer</Tag> },
         { key: 'Certification', value: 'No Verified' },
     ],
     '1': [
         { key: 'DAU', value: '2,480,000' },
         { key: 'Day7 Retention Ratio', value: '90%' },
-        { key: 'Security Level', value: '1级' },
+        { key: 'Security Level', value: '1 级' },
         { key: 'Vertical label', value: <Tag style={{ margin: 0 }}>Template</Tag> },
         { key: 'Certification', value: 'Verified' },
     ],
     '2': [
         { key: 'DAU', value: '2,920,000' },
         { key: 'Day7 Retention Ratio', value: '98%' },
-        { key: 'Security Level', value: '2级' },
+        { key: 'Security Level', value: '2 级' },
         { key: 'Vertical label', value: <Tag style={{ margin: 0 }}>Docs</Tag> },
         { key: 'Certification', value: 'Verified' },
     ],
@@ -2143,21 +2143,21 @@ const expandData = {
     '0': [
         { key: 'DAU', value: '1,480,000' },
         { key: 'Day7 Retention Ratio', value: '98%' },
-        { key: 'Security Level', value: '3级' },
+        { key: 'Security Level', value: '3 级' },
         { key: 'Vertical label', value: <Tag style={{ margin: 0 }}>Designer</Tag> },
         { key: 'Certification', value: 'No Verified' },
     ],
     '1': [
         { key: 'DAU', value: '2,480,000' },
         { key: 'Day7 Retention Ratio', value: '90%' },
-        { key: 'Security Level', value: '1级' },
+        { key: 'Security Level', value: '1 级' },
         { key: 'Vertical label', value: <Tag style={{ margin: 0 }}>Template</Tag> },
         { key: 'Certification', value: 'Verified' },
     ],
     '2': [
         { key: 'DAU', value: '2,920,000' },
         { key: 'Day7 Retention Ratio', value: '98%' },
-        { key: 'Security Level', value: '2级' },
+        { key: 'Security Level', value: '2 级' },
         { key: 'Vertical label', value: <Tag style={{ margin: 0 }}>Docs</Tag> },
         { key: 'Certification', value: 'Verified' },
     ],

+ 329 - 64
content/show/table/index.md

@@ -17,7 +17,7 @@ brief: 表格用于呈现结构化的数据内容,通常会伴随提供对数
 
 ```jsx import
 import React from 'react';
-import { Table } from '@douyinfe/semi-ui';
+import { Table, Tag } from '@douyinfe/semi-ui';
 
 function App() {
     const columns = [
@@ -83,8 +83,8 @@ function App() {
 
 ```jsx live=true noInline=true dir="column"
 import React from 'react';
-import { Table, Avatar } from '@douyinfe/semi-ui';
-import { IconMore } from '@douyinfe/semi-icons';
+import { Table, Avatar, Tag } from '@douyinfe/semi-ui';
+import { IconMore, IconTickCircle, IconComment, IconClear } from '@douyinfe/semi-icons';
 
 function App() {
     const columns = [
@@ -119,7 +119,7 @@ function App() {
                     wait: { color: 'cyan', prefixIcon: <IconComment />, text: '待评审' },
                 };
                 const tagProps = tagConfig[text];
-                return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>
+                return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>;
             }
         },
         {
@@ -149,7 +149,7 @@ function App() {
         },
     ];
     const data = [
-         {
+        {
             key: '1',
             name: 'Semi Design 设计稿.fig',
             nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
@@ -285,8 +285,8 @@ render(App);
 
 ```jsx live=true noInline=true dir="column"
 import React from 'react';
-import { Table, Avatar } from '@douyinfe/semi-ui';
-import { IconMore } from '@douyinfe/semi-icons';
+import { Table, Avatar, Tag } from '@douyinfe/semi-ui';
+import { IconMore, IconTickCircle, IconComment, IconClear } from '@douyinfe/semi-icons';
 
 function App() {
     const [selectedKeys, setSelectedKeys] = useState([]);
@@ -324,7 +324,7 @@ function App() {
                     wait: { color: 'cyan', prefixIcon: <IconComment />, text: '待评审' },
                 };
                 const tagProps = tagConfig[text];
-                return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>
+                return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>;
             }
         },
         {
@@ -453,8 +453,8 @@ render(App);
 
 ```jsx live=true noInline=true dir="column"
 import React from 'react';
-import { Table, Avatar, Button, Empty, Typography } from '@douyinfe/semi-ui';
-import { IconDelete } from '@douyinfe/semi-icons';
+import { Table, Avatar, Button, Empty, Typography, Tag } from '@douyinfe/semi-ui';
+import { IconMore, IconTickCircle, IconComment, IconClear, IconDelete } from '@douyinfe/semi-icons';
 import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
 const { Text } = Typography;
 
@@ -491,7 +491,7 @@ const raw = [
     },
     {
         key: '4',
-        name: 'Semi D2C 设计文档可能也有点长所以也会显示Tooltip',
+        name: 'Semi D2C 设计文档可能也有点长所以也会显示 Tooltip',
         nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
         size: '34KB',
         status: 'success',
@@ -557,7 +557,7 @@ function App() {
                     wait: { color: 'cyan', prefixIcon: <IconComment />, text: '待评审' },
                 };
                 const tagProps = tagConfig[text];
-                return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>
+                return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>;
             }
         },
         {
@@ -626,7 +626,8 @@ render(App);
 
 ```jsx live=true noInline=true dir="column"
 import React, { useState, useMemo } from 'react';
-import { Table, Avatar } from '@douyinfe/semi-ui';
+import { Table, Avatar, Tag } from '@douyinfe/semi-ui';
+import { IconMore, IconTickCircle, IconComment, IconClear, IconDelete } from '@douyinfe/semi-icons';
 import * as dateFns from 'date-fns';
 
 const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
@@ -671,7 +672,7 @@ const columns = [
                 wait: { color: 'cyan', prefixIcon: <IconComment />, text: '待评审' },
             };
             const tagProps = tagConfig[text] || {};
-            return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>
+            return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>;
         }
     },
     {
@@ -757,7 +758,8 @@ render(App);
 
 ```jsx live=true noInline=true dir="column"
 import React, { useState, useMemo } from 'react';
-import { Table, Avatar } from '@douyinfe/semi-ui';
+import { Table, Avatar, Tag } from '@douyinfe/semi-ui';
+import { IconMore, IconTickCircle, IconComment, IconClear, IconDelete } from '@douyinfe/semi-icons';
 import * as dateFns from 'date-fns';
 
 const DAY = 24 * 60 * 60 * 1000;
@@ -805,7 +807,7 @@ const columns = [
                 wait: { color: 'cyan', prefixIcon: <IconComment />, text: '待评审' },
             };
             const tagProps = tagConfig[text] || {};
-            return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>
+            return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>;
         }
     },
     {
@@ -902,9 +904,9 @@ render(App);
 
 可以通过设置 column 的 `fixed` 属性以及 `scroll.x` 来进行列固定,通过设置 `scroll.y` 来进行表头固定。
 
-如果是固定值,设置为 >=所有固定列宽之和+所有表格列宽之和 的数值。
+如果是固定值,设置为 >=所有固定列宽之和 + 所有表格列宽之和 的数值。
 
-> -   建议指定 `scroll.x` 为大于表格宽度的**固定值**或百分比。 如果是固定值,设置为 `>=所有固定列宽之和+所有表格列宽之和` 的数值。
+> -   建议指定 `scroll.x` 为大于表格宽度的**固定值**或百分比。如果是固定值,设置为 `>=所有固定列宽之和+所有表格列宽之和` 的数值。
 > -   若列头与内容不对齐或出现列重复或者固定列失效的情况,请指定固定列的宽度 `width`,若指定宽度后仍不生效,请尝试建议留一列不设宽度以适应弹性布局,或者检查是否有超长连续字段破坏布局。
 > -   请确保表格内部的所有元素在渲染后不会对单元格的高度造成影响(例如含有未加载完成的图片等),这种情况下请给定子元素一个确定的高度,以此确保左右固定列单元格不会错乱。
 
@@ -1181,7 +1183,8 @@ render(App);
 
 ```jsx live=true noInline=true dir="column"
 import React, { useState, useMemo } from 'react';
-import { Table, Avatar } from '@douyinfe/semi-ui';
+import { Table, Avatar, Tag } from '@douyinfe/semi-ui';
+import { IconMore, IconTickCircle, IconComment, IconClear, IconDelete } from '@douyinfe/semi-icons';
 import * as dateFns from 'date-fns';
 
 const DAY = 24 * 60 * 60 * 1000;
@@ -1229,7 +1232,7 @@ const columns = [
                 wait: { color: 'cyan', prefixIcon: <IconComment />, text: '待评审' },
             };
             const tagProps = tagConfig[text];
-            return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>
+            return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>;
         }
     },
     {
@@ -1283,7 +1286,7 @@ function App() {
                 name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                 owner: isSemiDesign ? '姜鹏志' : '郝宣',
                 size: randomNumber,
-                status:  isSemiDesign ? 'success' : 'wait',
+                status: isSemiDesign ? 'success' : 'wait',
                 updateTime: new Date().valueOf() + randomNumber * DAY,
                 avatarBg: isSemiDesign ? 'grey' : 'red',
             });
@@ -1394,7 +1397,7 @@ function App() {
         },
         {
             key: '3',
-            name: '设计文档3',
+            name: '设计文档 3',
             nameIconSrc: docIconUrl,
             size: 1,
             owner: 'Zoey Edwards',
@@ -1403,7 +1406,7 @@ function App() {
         },
         {
             key: '4',
-            name: '设计文档4',
+            name: '设计文档 4',
             nameIconSrc: docIconUrl,
             size: 5,
             owner: 'Zoey Edwards',
@@ -1412,7 +1415,7 @@ function App() {
         },
         {
             key: '5',
-            name: '设计文档5',
+            name: '设计文档 5',
             nameIconSrc: docIconUrl,
             size: undefined,
             owner: 'Zoey Edwards',
@@ -1421,7 +1424,7 @@ function App() {
         },
         {
             key: '6',
-            name: '设计文档6',
+            name: '设计文档 6',
             nameIconSrc: docIconUrl,
             size: 2,
             owner: 'Zoey Edwards',
@@ -1442,7 +1445,8 @@ render(App);
 
 ```jsx live=true noInline=true dir="column"
 import React, { useState, useEffect, useRef } from 'react';
-import { Table, Avatar, Input, Space } from '@douyinfe/semi-ui';
+import { Table, Avatar, Input, Space, Tag } from '@douyinfe/semi-ui';
+import { IconMore, IconTickCircle, IconComment, IconClear, IconDelete } from '@douyinfe/semi-icons';
 import * as dateFns from 'date-fns';
 
 function App() {
@@ -1517,7 +1521,7 @@ function App() {
                     wait: { color: 'cyan', prefixIcon: <IconComment />, text: '待评审' },
                 };
                 const tagProps = tagConfig[text];
-                return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>
+                return <Tag shape='circle' {...tagProps} style={{ userSelect: 'text' }}>{tagProps.text}</Tag>;
             }
         },
         {
@@ -1554,7 +1558,7 @@ function App() {
                 name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 首页${i}.fig`,
                 owner: isSemiDesign ? '姜鹏志' : '郝宣',
                 size: randomNumber,
-                status:  isSemiDesign ? 'success' : 'wait',
+                status: isSemiDesign ? 'success' : 'wait',
                 updateTime: new Date('2024-01-25').valueOf() + randomNumber * DAY,
                 avatarBg: isSemiDesign ? 'grey' : 'red',
             });
@@ -1582,7 +1586,7 @@ render(App);
 设置 `tempFilteredValue` 的原因是在需要存储临时筛选值的场景,不需要自己声明一个 state 保存这个临时筛选值。
 
 ```typescript
-type RenderFilterDropdown = (props?: RenderFilterDropdownProps) => React.ReactNode;
+type RenderFilterDropdown = (props: RenderFilterDropdownProps) => React.ReactNode;
 interface RenderFilterDropdownProps {
     /** 临时筛选值,初始值为 `filteredValue` 或 `defaultFilteredValue`  */
     tempFilteredValue: any[];
@@ -1641,8 +1645,8 @@ function App() {
                     <Space vertical align='start' style={{ padding: 8 }}>
                         <Input ref={inputRef} value={tempFilteredValue[0]} onChange={handleChange}/>
                         <Space>
-                            <Button onClick={() => confirm({ closeDropdown: true })}>筛选+关闭</Button>
-                            <Button onClick={() => clear({ closeDropdown: true })}>清除+关闭</Button>
+                            <Button onClick={() => confirm({ closeDropdown: true })}>筛选 + 关闭</Button>
+                            <Button onClick={() => clear({ closeDropdown: true })}>清除 + 关闭</Button>
                             <Button onClick={() => close()}>直接关闭</Button>
                         </Space>
                     </Space>
@@ -1871,7 +1875,7 @@ render(App);
 ### 可以展开的表格
 
 <Notice type="primary" title="注意事项">
-    <div>1. 自 0.27.0版本后,展开按钮会默认与第一列文案渲染在同一个单元格内,你可以通过往 Table 传入 hideExpandedColumn=false 将展开按钮单独作为一列渲染;</div>
+    <div>1. 自 0.27.0 版本后,展开按钮会默认与第一列文案渲染在同一个单元格内,你可以通过往 Table 传入 hideExpandedColumn=false 将展开按钮单独作为一列渲染;</div>
     <div>2. 请务必为每行数据提供一个与其他行值不同的 key,或者使用 rowKey 参数指定一个作为主键的属性名。</div>
 </Notice>
 
@@ -1965,22 +1969,22 @@ const data = [
 const expandData = {
     '0': [
         { key: '实际用户数量', value: '1,480,000' },
-        { key: '7天留存', value: '98%' },
-        { key: '安全等级', value: '3级' },
+        { key: '7 天留存', value: '98%' },
+        { key: '安全等级', value: '3 级' },
         { key: '垂类标签', value: <Tag style={{ margin: 0 }}>设计</Tag> },
         { key: '认证状态', value: '未认证' },
     ],
     '1': [
         { key: '实际用户数量', value: '2,480,000' },
-        { key: '7天留存', value: '90%' },
-        { key: '安全等级', value: '1级' },
+        { key: '7 天留存', value: '90%' },
+        { key: '安全等级', value: '1 级' },
         { key: '垂类标签', value: <Tag style={{ margin: 0 }}>模板</Tag> },
         { key: '认证状态', value: '已认证' },
     ],
     '2': [
         { key: '实际用户数量', value: '2,920,000' },
-        { key: '7天留存', value: '98%' },
-        { key: '安全等级', value: '2级' },
+        { key: '7 天留存', value: '98%' },
+        { key: '安全等级', value: '2 级' },
         { key: '垂类标签', value: <Tag style={{ margin: 0 }}>文档</Tag> },
         { key: '认证状态', value: '已认证' },
     ],
@@ -2111,22 +2115,22 @@ const data = [
 const expandData = {
     '0': [
         { key: '实际用户数量', value: '1,480,000' },
-        { key: '7天留存', value: '98%' },
-        { key: '安全等级', value: '3级' },
+        { key: '7 天留存', value: '98%' },
+        { key: '安全等级', value: '3 级' },
         { key: '垂类标签', value: <Tag style={{ margin: 0 }}>设计</Tag> },
         { key: '认证状态', value: '未认证' },
     ],
     '1': [
         { key: '实际用户数量', value: '2,480,000' },
-        { key: '7天留存', value: '90%' },
-        { key: '安全等级', value: '1级' },
+        { key: '7 天留存', value: '90%' },
+        { key: '安全等级', value: '1 级' },
         { key: '垂类标签', value: <Tag style={{ margin: 0 }}>模板</Tag> },
         { key: '认证状态', value: '已认证' },
     ],
     '2': [
         { key: '实际用户数量', value: '2,920,000' },
-        { key: '7天留存', value: '98%' },
-        { key: '安全等级', value: '2级' },
+        { key: '7 天留存', value: '98%' },
+        { key: '安全等级', value: '2 级' },
         { key: '垂类标签', value: <Tag style={{ margin: 0 }}>文档</Tag> },
         { key: '认证状态', value: '已认证' },
     ],
@@ -2258,22 +2262,22 @@ const data = [
 const expandData = {
     '0': [
         { key: '实际用户数量', value: '1,480,000' },
-        { key: '7天留存', value: '98%' },
-        { key: '安全等级', value: '3级' },
+        { key: '7 天留存', value: '98%' },
+        { key: '安全等级', value: '3 级' },
         { key: '垂类标签', value: <Tag style={{ margin: 0 }}>设计</Tag> },
         { key: '认证状态', value: '未认证' },
     ],
     '1': [
         { key: '实际用户数量', value: '2,480,000' },
-        { key: '7天留存', value: '90%' },
-        { key: '安全等级', value: '1级' },
+        { key: '7 天留存', value: '90%' },
+        { key: '安全等级', value: '1 级' },
         { key: '垂类标签', value: <Tag style={{ margin: 0 }}>模板</Tag> },
         { key: '认证状态', value: '已认证' },
     ],
     '2': [
         { key: '实际用户数量', value: '2,920,000' },
-        { key: '7天留存', value: '98%' },
-        { key: '安全等级', value: '2级' },
+        { key: '7 天留存', value: '98%' },
+        { key: '安全等级', value: '2 级' },
         { key: '垂类标签', value: <Tag style={{ margin: 0 }}>文档</Tag> },
         { key: '认证状态', value: '已认证' },
     ],
@@ -3133,6 +3137,264 @@ function App() {
 render(App);
 ```
 
+### 实现表头样式定制
+可以通过 Column.onHeaderCell 返回特定 style 或 className,定制表头的样式  
+如下例子,通过传入 backgroundColor 改变了表头背景色
+
+```jsx live=true noInline=true dir="column"
+import React from "react";
+import { Table, Avatar } from "@douyinfe/semi-ui";
+import { IconMore } from "@douyinfe/semi-icons";
+
+function App() {
+    const onHeaderCell = () => ({
+        style: {
+            backgroundColor: "var(--semi-color-fill-0)",
+        },
+    });
+    const columns = [
+        {
+            title: "标题",
+            dataIndex: "name",
+            width: 280,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar
+                            size="small"
+                            shape="square"
+                            src={record.nameIconSrc}
+                            style={{ marginRight: 12 }}
+                        ></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+        },
+        {
+            title: "大小",
+            width: 100,
+            dataIndex: "size",
+        },
+        {
+            title: "所有者",
+            dataIndex: "owner",
+            width: 200,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar
+                            size="small"
+                            color={record.avatarBg}
+                            style={{ marginRight: 4 }}
+                        >
+                            {typeof text === "string" && text.slice(0, 1)}
+                        </Avatar>
+                        {text}
+                    </div>
+                );
+            },
+        },
+        {
+            title: "更新日期",
+            width: 300,
+            dataIndex: "updateTime",
+        },
+        {
+            title: "",
+            dataIndex: "operate",
+            render: () => {
+                return <IconMore />;
+            },
+        },
+    ];
+
+    columns.forEach((item) => (item.onHeaderCell = onHeaderCell));
+    const data = [
+        {
+            key: "1",
+            name: "Semi Design 设计稿.fig",
+            nameIconSrc:
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png",
+            size: "2M",
+            owner: "姜鹏志",
+            updateTime: "2020-02-02 05:13",
+            avatarBg: "grey",
+        },
+        {
+            key: "2",
+            name: "Semi Design 分享演示文稿",
+            nameIconSrc:
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png",
+            size: "2M",
+            owner: "郝宣",
+            updateTime: "2020-01-17 05:31",
+            avatarBg: "red",
+        },
+        {
+            key: "3",
+            name: "设计文档",
+            nameIconSrc:
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png",
+            size: "34KB",
+            owner: "Zoey Edwards",
+            updateTime: "2020-01-26 11:01",
+            avatarBg: "light-blue",
+        },
+    ];
+
+    return (
+        <>
+            <Table
+                columns={columns}
+                dataSource={data}
+                pagination={false}
+            />
+        </>
+    );
+}
+
+render(App);
+```
+
+
+
+### 实现单元格 Hover 样式定制
+
+Table 默认为整行配置 Hover 样式,如果你需要修改相关样式可以通过 CSS 覆盖的方式自行实现。  
+如下例子,通过 CSS 覆盖,将可 Hover 的背景色或者由行高亮改为 Cell 单元格高亮  
+```css
+.component-table-demo-cell-hover-custom {
+  .semi-table-tbody {
+    // remove origin style of row hover
+    .semi-table-row:hover {
+      .semi-table-row-cell {
+        background-color: transparent;
+        background-image: none;
+        &.semi-table-cell-fixed-left::before {
+          background-color: transparent;
+        }
+      }
+      // add style of single cell
+      .semi-table-row-cell:hover {
+        background-color: rgba(var(--semi-light-green-1), 1);
+      }
+    }
+  }
+}
+```
+
+
+```jsx live=true noInline=true dir="column"
+import React from "react";
+import { Table, Avatar } from "@douyinfe/semi-ui";
+import { IconMore } from "@douyinfe/semi-icons";
+
+function App() {
+    const columns = [
+        {
+            title: "标题",
+            dataIndex: "name",
+            // fixed: true,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar
+                            size="small"
+                            shape="square"
+                            src={record.nameIconSrc}
+                            style={{ marginRight: 12 }}
+                        ></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+        },
+        {
+            title: "大小",
+            width: 100,
+            dataIndex: "size",
+        },
+        {
+            title: "所有者",
+            dataIndex: "owner",
+            width: 200,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar
+                            size="small"
+                            color={record.avatarBg}
+                            style={{ marginRight: 4 }}
+                        >
+                            {typeof text === "string" && text.slice(0, 1)}
+                        </Avatar>
+                        {text}
+                    </div>
+                );
+            },
+        },
+        {
+            title: "更新日期",
+            width: 300,
+            dataIndex: "updateTime",
+        },
+        {
+            title: "",
+            dataIndex: "operate",
+            render: () => {
+                return <IconMore />;
+            },
+        },
+    ];
+    const data = [
+        {
+            key: "1",
+            name: "Semi Design 设计稿.fig",
+            nameIconSrc:
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png",
+            size: "2M",
+            owner: "姜鹏志",
+            updateTime: "2020-02-02 05:13",
+            avatarBg: "grey",
+        },
+        {
+            key: "2",
+            name: "Semi Design 分享演示文稿",
+            nameIconSrc:
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png",
+            size: "2M",
+            owner: "郝宣",
+            updateTime: "2020-01-17 05:31",
+            avatarBg: "red",
+        },
+        {
+            key: "3",
+            name: "设计文档",
+            nameIconSrc:
+        "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png",
+            size: "34KB",
+            owner: "Zoey Edwards",
+            updateTime: "2020-01-26 11:01",
+            avatarBg: "light-blue",
+        },
+    ];
+
+    return (
+        <>
+            <Table
+                columns={columns}
+                dataSource={data}
+                className="component-table-demo-cell-hover-custom"
+                pagination={false}
+            />
+        </>
+    );
+}
+
+render(App);
+```
+
 ### 单元格缩略
 
 使用 `ellipsis` 可以让单元格自动实现缩略效果。v2.34.0 支持。
@@ -3249,7 +3511,7 @@ function App() {
 render(App);
 ```
 
-设置 `ellipsis.showTitle` 为 false 可以隐藏默认原生的 HTML title。 配合 `column.render` 可以自定义内容提示。
+设置 `ellipsis.showTitle` 为 false 可以隐藏默认原生的 HTML title。配合 `column.render` 可以自定义内容提示。
 
 ```jsx live=true noInline=true dir="column"
 import React from 'react';
@@ -3386,6 +3648,7 @@ render(App);
 ```jsx live=true noInline=true dir="column"
 import React, { useMemo } from 'react';
 import { Table, Avatar } from '@douyinfe/semi-ui';
+import { IconMore, IconTickCircle, IconComment, IconClear, IconDelete } from '@douyinfe/semi-icons';
 import * as dateFns from 'date-fns';
 
 const DAY = 24 * 60 * 60 * 1000;
@@ -3931,12 +4194,12 @@ render(Demo);
 
 虚拟化可用于需要渲染大规模数据的场景,通过配置 `virtualized` 参数来开启这个功能。需要注意的是:
 
--   必须传递 `scroll.y`(number) 与 `style.width`(number);
+-   必须传递 `scroll.y`(number)与 `style.width`(number);
 -   需要传递每行的高度 `virtualized.itemSize`(不传时普通行高默认为 `56`,组头行高默认为 `56`),可以为如下类型:
     -   `number`
     -   `(index, { sectionRow?: boolean, expandedRow?: boolean }) => number`
 -   表格分组虚拟化需要版本 >= `0.37.0`
--   Semi Table 底层借助了 `react-window` 的能力来实现虚拟化,因此 `react-window` `VariableSizeList` 所支持的其他参数也可以通过 `virtualized`(object)传入,例如 `overscanCount`
+-   Semi Table 底层借助了 `react-window` 的能力来实现虚拟化,因此 `react-window` `VariableSizeList` 所支持的其他参数也可以通过 `virtualized`(object) 传入,例如 `overscanCount`
 -   如果需要使用 `VariableSizeList` 的 API,可以传入`getVirtualizedListRef` 获取对应 ref,需要版本 >= `1.20`
 
 以下为渲染 1000 条数据的示例。
@@ -4599,11 +4862,11 @@ class App extends React.Component {
             let pagination = checked
                 ? false
                 : {
-                      currentPage: 1,
-                      pageSize: 8,
-                      total: data.length,
-                      onPageChange: page => this.setPage(page),
-                  };
+                    currentPage: 1,
+                    pageSize: 8,
+                    total: data.length,
+                    onPageChange: page => this.setPage(page),
+                };
 
             this.setState({ pagination });
         };
@@ -4836,7 +5099,7 @@ const columns = [
         ],
         onFilter: (value, record) => record.name.includes(value),
         useFullRender: true,
-        // 此处从render的第四个形参中解构出 展开按钮、选择按钮、文本等内容
+        // 此处从 render 的第四个形参中解构出 展开按钮、选择按钮、文本等内容
         render: (text, record, index, { expandIcon, selection, indentText }) => {
             return (
                 <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
@@ -5366,6 +5629,8 @@ function App() {
 render(App);
 ```
 
+
+
 ## API 参考
 
 ## Table
@@ -5533,7 +5798,7 @@ import { Table } from '@douyinfe/semi-ui';
 | align | 设置列的对齐方式,在 RTL 时会自动切换 | 'left' \| 'right' \| 'center' | 'left' |
 | className | 列样式名 | string |  |
 | children | 表头合并时用于子列的设置 | Column[] |  |
-| colSpan | 表头列合并,设置为 0 时,不渲染 | number |  |
+| colSpan | 表头列合并设置为 0 时,不渲染 | number |  |
 | dataIndex | 列数据在数据项中对应的 key,使用排序或筛选时必传,且需要保持不重复 | string |  |
 | defaultFilteredValue | 筛选的默认值,值为已筛选的 value 数组 | any[] |  | **2.5.0** |
 | defaultSortOrder | 排序的默认值,可设置为 'ascend'\|'descend'\|false | boolean\| string | false | **1.31.0** |
@@ -5552,14 +5817,14 @@ import { Table } from '@douyinfe/semi-ui';
 | renderFilterDropdown | 自定义筛选器 dropdown 面板,用法详见[自定义筛选器](#自定义筛选器) | (props?: RenderFilterDropdownProps) => React.ReactNode; | - | **2.52.0** |
 | renderFilterDropdownItem | 自定义每个筛选项渲染方式,用法详见[自定义筛选项渲染](#自定义筛选项渲染) | ({ value: any, text: any, onChange: Function, level: number, ...otherProps }) => ReactNode | - | **1.1.0** |
 | resize | 是否开启 resize 模式,只有 Table resizable 开启后此属性才会生效 | boolean |  | **2.42.0** |
-| showSortTip | 是否展示排序提示, 如果设置了 sortOrder,排序受控,则该参数不会生效 | boolean | false | **2.65.0** |
+| showSortTip | 是否展示排序提示如果设置了 sortOrder,排序受控,则该参数不会生效 | boolean | false | **2.65.0** |
 | sortChildrenRecord | 是否对子级数据进行本地排序 | boolean |  | **0.29.0** |
 | sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 'ascend'\|'descend'\|false | boolean\| string | false |
-| sorter | 排序函数,本地排序使用一个函数(参考 Array.sort 的 compareFunction),需要服务端排序可设为 true。**必须给排序列设置一个独立的 dataIndex,必须为 dataSource 里面的每条数据项设置独立的 key** | boolean\|(r1: RecordType, r2: RecordType, sortOrder: 'ascend' \| 'descend') => number | true |
+| sorter | 排序函数,本地排序使用一个函数 (参考 Array.sort 的 compareFunction),需要服务端排序可设为 true。**必须给排序列设置一个独立的 dataIndex,必须为 dataSource 里面的每条数据项设置独立的 key** | boolean\|(r1: RecordType, r2: RecordType, sortOrder: 'ascend' \| 'descend') => number | true |
 | sortIcon | 自定义 sort 图标,返回的节点控制了整个排序按钮,包含升序和降序。需根据 sortOrder 控制高亮行为 | (props: { sortOrder }) => ReactNode | | **2.50.0** |
 | shouldCellUpdate | 自定义控制单元格是否渲染。默认 cell 会深对比 props 和 nextProps 是否变化,来决定是否渲染单元格。如果你的 props 中的 record 比较复杂,建议使用 `shouldCellUpdate` 接管单元格的渲染。 | (props: TableCellProps, prevProps: TableCellProps) => boolean | | **2.71.0** |
 | title | 列头显示文字。传入 function 时,title 将使用函数的返回值;传入其他类型,将会和 sorter、filter 进行聚合。需要搭配 useFullRender 获取函数类型中的 filter 等参数 | ReactNode\|({ filter: ReactNode, sorter: ReactNode, selection: ReactNode }) => ReactNode |  | Function 类型需要**0.34.0** |
-| useFullRender | 是否完全自定义渲染,用法详见[完全自定义渲染](#完全自定义渲染), 开启此功能会造成一定的性能损耗 | boolean | false | **0.34.0** |
+| useFullRender | 是否完全自定义渲染,用法详见[完全自定义渲染](#完全自定义渲染),开启此功能会造成一定的性能损耗 | boolean | false | **0.34.0** |
 | width | 列宽度 | string \| number |  |
 | onCell | 设置单元格属性 | (record: RecordType, rowIndex: number) => object |  |
 | onFilter | 本地模式下,确定筛选的运行函数。**必须给筛选列设置一个独立的 dataIndex,必须为 dataSource 里面的每条数据项设置独立的 key** | (filteredValue: any, record: RecordType) => boolean |  |
@@ -5768,7 +6033,7 @@ function Demo() {
 
     涉及到单个 cell 需要控制样式的,可以通过 column.onHeaderCell、column.onCell 控制。
 
--   **为何 rowSelection onChange 的第一个参数缓存了之前选中的 keys ?**
+-   **为何 rowSelection onChange 的第一个参数缓存了之前选中的 keys?**
 
     这么做为了在分页受控时,在第一页选中数据后,去第二页选择数据,回到第一页后选择的 row keys 丢失的场景。如果不想用缓存的 keys,可以从当前 dataSource 过滤一遍,或者使用 rowSelection onChange 的第二个参数。
 

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

@@ -354,7 +354,7 @@ import { Popconfirm, Tooltip, Button } from '@douyinfe/semi-ui';
 | stopPropagation | Whether to prevent click events on the bomb layer from bubbling                                                                                                                                                             | boolean | false | **0.34.0** |
 | transformFromCenter | Whether to transform from the horizontal or vertical center of the element of the package, this parameter affects only the `tansform-origin 'of the dynamic effect transformation and generally does not need to be changed | boolean | true |
 | trigger | Timing of triggering display, optional value: `hover`/`focus`/`click`/`custom`                                                                                                                                              | string | 'hover' |  |
-| visible | Whether to show the pop-up layer                                                                                                                                                                                            | boolean |  |  |
+| visible | Whether to show the pop-up layer, need to be used with trigger='custom'                                                                                                                                                                                          | boolean |  |  |
 | wrapperClassName | When children are disabled or children are multiple elements, the outer layer will wrap a layer of span elements, and the api is used to set the style class name of this span                                              | string |  | 1.32.0 |
 | wrapperId | The id of the wrapper node of the popup layer. The aria attribute of the trigger points to this id.                                                                                                                         | string |  | 2.11.0  |
 | zIndex              | Bullet levels.                                                                                                                                                                                                              | number                      | 1060                |            |

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

@@ -390,7 +390,7 @@ function Demo() {
 | stopPropagation | 是否阻止弹层上的点击事件冒泡                                                                                                                                       | boolean | false |  |
 | transformFromCenter | 是否从包裹的元素水平或垂直中心处变换,该参数仅影响动效变换的 `transform-origin`,一般无需改动                                                                                             | boolean | true |  |
 | trigger | 触发展示的时机,可选值:`hover` / `focus` / `click` / `custom` / `contextMenu` (v2.42后提供)                                                                                                   | string | 'hover' |  |
-| visible | 是否展示弹出层                                                                                                                                              | boolean |  |  |
+| visible | 是否展示弹出层, 需配合 trigger='custom' 使用                                                                                                                                            | boolean |  |  |
 | wrapperClassName | 当 children 为 disabled ,或者 children 为多个元素时,外层将会包裹一层 span 元素,该 api 用于设置此 span 的样式类名                                                                    | string |  |  |
 | wrapperId | 弹出层 wrapper 节点的 id,trigger 的 aria 属性指向此 id,若不设置组件会随机生成一个 id                                                                                          | string |  | 2.11.0  |
 | zIndex | 弹层层级                                                                                                                                                 | number | 1060 |  |

+ 553 - 0
content/show/userGuide/index-en-US.md

@@ -0,0 +1,553 @@
+---
+localeCode: en-US
+order: 80
+category: Show
+title: UserGuide
+icon: doc-userGuide
+brief: Used to guide new users through pages
+showNew: true
+---
+
+
+## Demos
+
+### How to import
+
+```jsx import
+import { UserGuide } from '@douyinfe/semi-ui';
+```
+
+### Basic Usage
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'basic-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'basic-demo-2'}> Default Tag </Tag>
+                <Button id={'basic-demo-3'}>Confirm</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#basic-demo-1'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#basic-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#basic-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    console.log('Current guide step', current);
+                }}
+                onNext={(current) => {
+                    console.log('Next guide step');
+                }}
+                onPrev={(current) => {
+                    console.log('Previous guide step');
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### Theme
+`popup` bubble card mode provides two themes `default` and `primary`, set by the `theme` property.
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'theme-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'theme-demo-2'}> Default Tag </Tag>
+                <Button id={'theme-demo-3'}>Confirm</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                theme="primary"
+                steps={[
+                    {
+                        target: document.querySelector('#theme-demo-1'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#theme-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#theme-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### Popup position
+`popup` bubble card mode provides 12 positions, optional values are `top`, `topLeft`, `topRight`, `left`, `leftTop`, `leftBottom`, `right`, `rightTop`, `rightBottom`, `bottom`, `bottomLeft`, `bottomRight`, and can be set by the `showArrow` property to display the arrow.
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button id={'position-demo'} onClick={showDialog}>Start Guide</Button>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                        position: 'top',
+                    },
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: 'New Position',
+                        description: 'This is Right Position',
+                        position: 'right',
+                    },
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: 'Hide Arrow',
+                        description: 'We hide the arrow',
+                        position: 'bottom',
+                        showArrow: false,
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### Set the size of the highlight area
+Set by the `spotlightPadding` property.
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'padding-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'padding-demo-2'}> Default Tag </Tag>
+                <Button id={'padding-demo-3'}>Confirm</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                spotlightPadding={10}
+                steps={[
+                    {
+                        target: document.querySelector('#padding-demo-1'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                    },
+                    {
+                        target: document.querySelector('#padding-demo-2'),
+                        title: 'New Padding',
+                        description: 'This is 10px padding',
+                    },
+                    {
+                        target: document.querySelector('#padding-demo-3'),
+                        title: 'Change Padding',
+                        spotlightPadding: 15,
+                        description: 'We change the Padding to 15px',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### Customize the button
+Set by the `nextButtonProps` and `prevButtonProps` properties.
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'button-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'button-demo-2'}> Default Tag </Tag>
+                <Button id={'button-demo-3'}>Confirm</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                nextButtonProps={{
+                    children: 'Next',
+                }}
+                prevButtonProps={{
+                    children: 'Prev',
+                    theme: 'borderless',
+                }}
+                finishText="I know!"
+                steps={[
+                    {
+                        target: document.querySelector('#button-demo-1'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                    },
+                    {
+                        target: document.querySelector('#button-demo-2'),
+                        title: 'New Button Style',
+                        description: 'Button text is Next',
+                    },
+                    {
+                        target: document.querySelector('#button-demo-3'),
+                        title: 'New finish button text',
+                        description: 'Button text is I know',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### Controlled
+Set by the `current` property.
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const [current, setCurrent] = useState(0);
+
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'controlled-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'controlled-demo-2'}> Default Tag </Tag>
+                <Button id={'controlled-demo-3'}>Confirm</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                current={current}
+                steps={[
+                    {
+                        target: document.querySelector('#controlled-demo-1'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#controlled-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#controlled-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    setCurrent(current);
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    setCurrent(0);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    setCurrent(0);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+
+### Modal guide
+Set by the `mode` property.
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch, Image, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <UserGuide
+                mode="modal"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        title: 'Welcome to Semi DSM!',
+                        description: <div>You can start from the published theme, or choose {<Typography.Text strong>Create Now</Typography.Text>} to create a new theme</div>,
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_welcome.png"
+                        />,
+                        position: 'bottom',
+                    },
+                    {
+                        title: 'High-available color palette',
+                        description: 'After selecting the main color, our color algorithm will generate a high-available color palette for you',
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_console.png"
+                        />,
+                        position: 'bottom',
+                    },
+                    {
+                        title: 'Customize freely',
+                        description: 'Start customizing your design system!',
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_palette.png" 
+                        />,
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    console.log('Current guide step', current);
+                }}
+                onNext={(current) => {
+                    console.log('Next guide step');
+                }}
+                onPrev={(current) => {
+                    console.log('Previous guide step');
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### No mask
+Set by the `mask` property.
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch, Image } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button id={'mask-demo'} onClick={showDialog}>Start Guide</Button>
+            <UserGuide
+                mode="popup"
+                mask={false}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#mask-demo'),
+                        title: 'No Mask',
+                        description: 'Hello ByteDancer!',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+## API Reference
+
+---
+| Properties | Instructions | Type | Default | Version |
+| --- | --- | --- | --- | --- |
+| className | Custom class name | string | - | |
+| current | Current step index | number | 0 | |
+| finishText | Text of the last step completion button | string | '完成' | |
+| mask | Whether to display the mask | boolean | true | |
+| mode | Guide mode, optional values: `popup` (bubble card) or `modal` (modal) | string | popup | |
+| nextButtonProps | The properties of the next button | ButtonProps | {} | |
+| onChange | Callback when the step changes | function(current: number) | () => void | |
+| onFinish | Callback when all steps are completed | function() | () => void | |
+| onNext | Callback when the next button is clicked | function(current: number) | () => void | |
+| onPrev | Callback when the previous button is clicked | function(current: number) | () => void | |
+| onSkip | Callback when the skip button is clicked | function() | () => void | |
+| position | The position of the pop-up layer relative to the target element, optional values: `top`, `topLeft`, `topRight`, `left`, `leftTop`, `leftBottom`, `right`, `rightTop`, `rightBottom`, `bottom`, `bottomLeft`, `bottomRight` | string | bottom | |
+| prevButtonProps | The properties of the previous button | ButtonProps | {} | |
+| showPrevButton | Whether to display the previous button | boolean | true | |
+| showSkipButton | Whether to display the skip button | boolean | true | |
+| spotlightPadding | The inner padding of the highlight area, in pixels | number | - | |
+| steps | Guide step configuration, required | StepItem[] | [] | |
+| style | Custom style | React.CSSProperties | - | |
+| theme | Theme style, optional values: `default` or `primary` | string | default | |
+| visible | Whether to display the guide | boolean | false | |
+| getPopupContainer | Specify the parent DOM, the pop-up layer will be rendered to the DOM | () => HTMLElement | - | |
+| zIndex | Pop-up layer level | number | 1030 | |
+
+### Steps.Step
+| Properties | Instructions | Type | Default | Version |
+| --- | --- | --- | --- | --- |
+| className | Custom class name | string | - | |
+| cover | The cover image of the step | ReactNode | - | |
+| target | The target element, the highlight area will focus on this element | (() => Element) \| Element | - | |
+| title | Step title | string \| ReactNode | - | |
+| description | Step description | ReactNode | - | |
+| mask | Whether to display the mask of this step, it will override the global configuration | boolean | - | |
+| showArrow | Whether to display the arrow (only valid when mode=`popup`) | boolean | true | | 
+| spotlightPadding | The inner padding of the highlight area of this step, it will override the global configuration | number | - | |
+| theme | The theme of this step, it will override the global configuration | `default` \| `primary` | - | |
+| position | The position of the pop-up layer of this step, it will override the global configuration | Position | - | |
+
+## Design Tokens
+
+<DesignToken/>
+

+ 553 - 0
content/show/userGuide/index.md

@@ -0,0 +1,553 @@
+---
+localeCode: zh-CN
+order: 80
+category: 展示类
+title:  UserGuide 用户引导
+icon: doc-userGuide
+brief: 用于页面对新用户进行功能引导
+showNew: true
+---
+
+
+## 代码演示
+
+### 如何引入
+
+```jsx import
+import { UserGuide } from '@douyinfe/semi-ui';
+```
+
+### 基本用法
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'basic-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'basic-demo-2'}> Default Tag </Tag>
+                <Button id={'basic-demo-3'}>确定</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#basic-demo-1'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#basic-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#basic-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    console.log('当前引导步骤', current);
+                }}
+                onNext={(current) => {
+                    console.log('下一步引导');
+                }}
+                onPrev={(current) => {
+                    console.log('上一步引导');
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 主题
+`popup` 气泡卡片模式下提供两种主题 `default` 和 `primary`,通过 `theme` 属性设置。
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'theme-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'theme-demo-2'}> Default Tag </Tag>
+                <Button id={'theme-demo-3'}>确定</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                theme="primary"
+                steps={[
+                    {
+                        target: document.querySelector('#theme-demo-1'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#theme-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#theme-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 气泡卡片弹出位置
+`popup` 气泡卡片模式下提供 12 种弹出位置,可选值有`top`, `topLeft`, `topRight`, `left`, `leftTop`, `leftBottom`, `right`, `rightTop`, `rightBottom`, `bottom`, `bottomLeft`, `bottomRight`,还可以通过 `showArrow` 属性设置是否显示箭头。
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button id={'position-demo'} onClick={showDialog}>开始引导</Button>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                        position: 'top',
+                    },
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: 'New Position',
+                        description: 'This is Right Position',
+                        position: 'right',
+                    },
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: 'Hide Arrow',
+                        description: 'We hide the arrow',
+                        position: 'bottom',
+                        showArrow: false,
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 设置高亮区域大小
+通过 `spotlightPadding` 属性设置。
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'padding-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'padding-demo-2'}> Default Tag </Tag>
+                <Button id={'padding-demo-3'}>确定</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                spotlightPadding={10}
+                steps={[
+                    {
+                        target: document.querySelector('#padding-demo-1'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                    },
+                    {
+                        target: document.querySelector('#padding-demo-2'),
+                        title: 'New Padding',
+                        description: 'This is 10px padding',
+                    },
+                    {
+                        target: document.querySelector('#padding-demo-3'),
+                        title: 'Change Padding',
+                        spotlightPadding: 15,
+                        description: 'We change the Padding to 15px',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 定制按钮
+通过 `nextButtonProps` 和 `prevButtonProps` 属性设置按钮的样式。
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'button-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'button-demo-2'}> Default Tag </Tag>
+                <Button id={'button-demo-3'}>确定</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                nextButtonProps={{
+                    children: 'Next',
+                }}
+                prevButtonProps={{
+                    children: 'Prev',
+                    theme: 'borderless',
+                }}
+                finishText="我知道啦!"
+                steps={[
+                    {
+                        target: document.querySelector('#button-demo-1'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                    },
+                    {
+                        target: document.querySelector('#button-demo-2'),
+                        title: 'New Button Style',
+                        description: 'Button text is Next',
+                    },
+                    {
+                        target: document.querySelector('#button-demo-3'),
+                        title: 'New finish button text',
+                        description: 'Button text is I know',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 受控
+通过 `current` 属性设置当前引导步骤。
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const [current, setCurrent] = useState(0);
+
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'controlled-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'controlled-demo-2'}> Default Tag </Tag>
+                <Button id={'controlled-demo-3'}>确定</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                current={current}
+                steps={[
+                    {
+                        target: document.querySelector('#controlled-demo-1'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#controlled-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#controlled-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    setCurrent(current);
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    setCurrent(0);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    setCurrent(0);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+
+### 弹窗式引导
+通过 `mode` 属性设置为 `modal` 开启弹窗式引导。
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch, Image, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <UserGuide
+                mode="modal"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        title: '欢迎使用 Semi DSM!',
+                        description: <div>你可以从已发布的主题出发,或者选择{<Typography.Text strong>立即创造</Typography.Text>}来创造一个新的主题</div>,
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_welcome.png"
+                        />,
+                        position: 'bottom',
+                    },
+                    {
+                        title: '高可用的色盘',
+                        description: '选取主色后,我们的颜色算法会为你生成一套高可用的色盘',
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_console.png"
+                        />,
+                        position: 'bottom',
+                    },
+                    {
+                        title: '自由定制',
+                        description: '开始定制属于你的设计系统吧!',
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_palette.png" 
+                        />,
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    console.log('当前引导步骤', current);
+                }}
+                onNext={(current) => {
+                    console.log('下一步引导');
+                }}
+                onPrev={(current) => {
+                    console.log('上一步引导');
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 无遮罩
+通过 `mask` 属性设置为 `false` 开启无遮罩引导。
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch, Image } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button id={'mask-demo'} onClick={showDialog}>开始引导</Button>
+            <UserGuide
+                mode="popup"
+                mask={false}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#mask-demo'),
+                        title: 'No Mask',
+                        description: 'Hello ByteDancer!',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+## API 参考
+
+---
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| className | 自定义类名 | string | - | |
+| current | 当前步骤的索引 | number | 0 | |
+| finishText | 最后一步完成按钮的文本 | string | '完成' | |
+| mask | 是否显示蒙层 | boolean | true | |
+| mode | 引导模式,可选值:`popup`(气泡卡片)或 `modal`(弹窗式) | string | popup | |
+| nextButtonProps | 下一步按钮的属性 | ButtonProps | {} | |
+| onChange | 步骤改变时的回调 | function(current: number) | () => void | |
+| onFinish | 完成所有步骤时的回调 | function() | () => void | |
+| onNext | 点击下一步按钮时的回调 | function(current: number) | () => void | |
+| onPrev | 点击上一步按钮时的回调 | function(current: number) | () => void | |
+| onSkip | 点击跳过按钮时的回调 | function() | () => void | |
+| position | 弹出层相对于目标元素的位置,可选值:`top`, `topLeft`, `topRight`, `left`, `leftTop`, `leftBottom`, `right`, `rightTop`, `rightBottom`, `bottom`, `bottomLeft`, `bottomRight` | string | bottom | |
+| prevButtonProps | 上一步按钮的属性 | ButtonProps | {} | |
+| showPrevButton | 是否显示上一步按钮 | boolean | true | |
+| showSkipButton | 是否显示跳过按钮 | boolean | true | |
+| spotlightPadding | 高亮区域的内边距,单位为像素 | number | - | |
+| steps | 引导步骤配置,必填 | StepItem[] | [] | |
+| style | 自定义样式 | React.CSSProperties | - | |
+| theme | 主题样式,可选值:`default` 或 `primary` | string | default | |
+| visible | 是否显示引导 | boolean | false | |
+| getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中 | () => HTMLElement | - | |
+| zIndex | 弹层层级 | number | 1030 | |
+
+### Steps.Step
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| className | 步骤的自定义类名 | string | - | |
+| cover | 步骤的封面图 | ReactNode | - | |
+| target | 目标元素,高亮区域会聚焦到这个元素上 | (() => Element) \| Element | - | |
+| title | 步骤标题 | string \| ReactNode | - | |
+| description | 步骤描述 | ReactNode | - | |
+| mask | 是否显示此步骤的蒙层,会覆盖全局配置 | boolean | - | |
+| showArrow | 是否显示箭头(仅在 mode=`popup` 时有效) | boolean | true | |
+| spotlightPadding | 此步骤高亮区域区域的内边距,会覆盖全局配置 | number | - | |
+| theme | 此步骤的主题,会覆盖全局配置 | `default` \| `primary` | - | |
+| position | 此步骤弹出层的位置,会覆盖全局配置 | Position | - | |
+
+## 设计变量
+
+<DesignToken/>
+

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

@@ -121,6 +121,57 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
     - Fixed the problem that  Form Label lost padding right(effect version v2.23.1) [#1258](https://github.com/DouyinFE/semi-design/pull/1258)
     - The Switch component Design Token is updated, adding `$spacing-switch_knob-left`; `$motion-switch_unchecked-translateX` is corrected to more semantically `$spacing-switch_unchecked-translateX` [#1267](https://github.com/DouyinFE/semi-design/pull/1267)
 
+#### 🎉 2.78.0 (2025-04-08)
+- 【Fix】
+    - A fallback operation to avoid TypeError when target does not exist in BackTop component
+    - Fixed the issue that markdownRenderProps did not take effect when parsing text in Chat when the content of the message is an array
+    - fix the issue that Collapse Tabs cannot scroll activeTab into the viewport when first loaded
+- 【Style】
+    - Fixed the problem that the header cells with sorting in Table flicker when switching between light and dark
+- 【Design Token】
+    - ScrollList adds $color-scrollList_header-bg to control header color
+
+#### 🎉 2.78.0-beta.0 (2025-04-01)
+- 【Feat】
+    - Cropper adds preview API to support real-time preview of cropping effects  [#2783](https://github.com/DouyinFE/semi-design/issues/2783)
+#### 🎉 2.77.0 (2025-03-25)
+- 【Fix】
+    - Fixed the issue of click-through when the UserGuide is in the bubble mode [#2764](https://github.com/DouyinFE/semi-design/pull/2764)
+    - Fixed the problem that the icon in the header of the Navigation is not centered in the collapsed mode.  [#2675](https://github.com/DouyinFE/semi-design/issues/2675)
+    - Fixed the problem that the search box of JsonViewer does not support i18n multilingual adaptation. [#2766](https://github.com/DouyinFE/semi-design/pull/2766)
+    - Fixed the problem of long text folding when the auto-wrap function of JsonViewer is not enabled, and fixed the issue of line number display. [#2756](https://github.com/DouyinFE/semi-design/pull/2756)
+
+#### 🎉 2.77.0-beta.0 (2025-03-18)
+- 【New Component】
+    - support new component UserGuide
+- 【Feat】
+    - InputNumber supports currency mode
+- 【Fix】
+    - fix MarkdownRender table duplicate column elements when header containing more than two components or bold texts [@ByteLan](https://github.com/ByteLan)
+    - Correct locale codes for Netherlands, Poland and Sweden (nl_NL -> nl-NL, pl_PL -> pl-PL, sv_SE -> sv-SE)
+
+#### 🎉 2.76.1 (2025-03-17)
+- 【Style】
+    - Style: For input type components, the styles of insetLabel and prefix remain consistent. Removed unnecessary tokens related to insetLabel, Added $font-cascader_prefix_suffix_fontWeight, $spacing-input_prefix_suffix-marginX, $font-input_prefix_suffix-fontWeight, $font-select_prefix_suffix-fontWeight, $spacing-tagInput_prefix_suffix-marginX, $font-tagInput_prefix_suffix-fontWeight, $font-treeSelect_prefix_suffix_fontWeight to manage prefix and suffix weights and margins.[#2752](https://github.com/DouyinFE/semi-design/issues/2752)
+
+#### 🎉 2.76.0 (2025-03-07)
+- 【Fix】
+    - Fix JsonViewer type error issue [@anjiazhuyouxing](https://github.com/anjiazhuyouxing) [#2748](https://github.com/DouyinFE/semi-design/pull/2748)
+
+#### 🎉 2.76.0-beta.0 (2025-03-04)
+- 【Feat】
+    - Chat supports enableUpload API to support users to set upload behavior  [#2735](https://github.com/DouyinFE/semi-design/issues/2735)
+    - Allow RadioGroup options passing addonStyles/addonClassName/addonId/extraId option to Radio component [@SaltyfishEd](https://github.com/SaltyfishEd)
+    - JsonViewer adds custom rendering function [@anjiazhuyouxing](https://github.com/anjiazhuyouxing)
+- 【Fix】
+    - Nav adds a new selector style to provide hover style for renderWrapper scenes
+    - adjust jsonviewer search box popup positioning [@anjiazhuyouxing](https://github.com/anjiazhuyouxing)
+    - fix when TimePicker does not fill in the default value, users in different time zones open the template and select different default values
+    - fix the problem that Nav.item does not prompt ts error when passing non-recommended attribute Children
+- 【Style】
+    - Style: Fixed the issue of incorrect spacing between action buttons in the Chat component's dialog box (affected versions 2.71.1-2.75.0)
+    - Modify Cascader's default empty data display style to be consistent with TreeSelect/Select  [#2703](https://github.com/DouyinFE/semi-design/issues/2703)
+
 #### 🎉 2.75.0 (2025-02-21)
 - 【Design Token】
     - Select adds $color-select_prefix_suffix_text-default, Cascader adds $color-cascader_prefix_suffix_text-default, and TreeSelect adds $color-treeSelect_prefix_text-default to control the prefix and suffix colors. In addition, keep the font-size and font-weight settings of the prefix and suffix consistent with the settings of insetLabal (**Note: There are changes in styles before and after modification**)  [#2721](https://github.com/DouyinFE/semi-design/issues/2721)

File diff suppressed because it is too large
+ 209 - 158
content/start/changelog/index.md


+ 32 - 96
content/start/design-to-code/index-en-US.md

@@ -9,88 +9,28 @@ order: 4
 
 ## Introduction
 
-Design to code (D2C) is a design draft conversion code tool provided by `Semi Design`, which supports one-click recognition of layer layout + Semi components in Figma pages, restores design drafts at the pixel level, translates them into JSX and CSS codes, and quickly previews them.
-No need to start from 0.
-
-From now, you can let the tool take care of UI restoration, focus more on your business logic.
-
-## Abilities
-
-<div>
-     <Row gutter={[20, 80]}>
-         <Col span={8}>
-             <FeatureCard title='Support basic UI layout transcode'>
-                 Support flex layout, absolute layout
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Support Semi components'>
-                Support the identification of Semi components and Semi Icons, covering 28+ components in form and table scenarios
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Support exporting different stack codes' >
-                 Multiple code style output:React + Scss, React + Tailwind and JSON Schema
-             </FeatureCard>
-         </Col>
-     </Row>
-</div>
-<div>
-     <Row gutter={[20, 20]}>
-         <Col span={8}>
-             <FeatureCard title='Support to identify other theme components'>
-                 Support for identifying components behind custom themes
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Nested components and custom content recognition'>
-                 Recognize what the designer has modified to identify it as a ReactNode
-             </FeatureCard>
-         </Col>
-     </Row>
-</div>
-
-## How to use
-
-![](https://lf3-files.qingfuwucdn.net/obj/inspirecloud-file/baas/tt38q7/1aaf72252f553443_1676606724044.png)
-
-Please move directly to <a href="https://semi.design/code" target="_blank">semi.design/code</a>
-## Application scenario
-
-<div>
-     <Row gutter={[20, 20]}>
-         <Col span={8}>
-             <FeatureCard title='Basic page transcode' >
-                Restore the layout of the basic page, suitable for landing page rapid development
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Card transcode' >
-                One-click to restore the card, no need to care about the card layout
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Form page transcode' >
-                Use Semi Variants to build the operation table page, D2C helps you automatically identify the table column content
-             </FeatureCard>
-         </Col>
-     </Row>
-     <Row gutter={[20, 20]}>
-         <Col span={8}>
-             <FeatureCard title='Form page transcode' >
-                Use Semi Variants to build an operation form page, and restore the Semi Form form with one click
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Access to custom build platform'>
-                Customize consumption JSON Schema and create template code suitable for your project
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard />
-         </Col>
-     </Row>
-</div>
+D2C is the abbreviation of Design to Code, which means converting design drafts into code. As an auxiliary tool for frontend engineers, it can effectively improve the efficiency of design draft restoration and reduce the cost of manual Html/CSS coding.
+
+Thanks to the continuous enhancement of the native capabilities of the Figma platform ecosystem (such as Variant, DevMode, CodeConnect, etc.), the usability of D2C tools in the production environment has been greatly improved in recent years. They can support the access of complete design systems and achieve component - level identification and code output.  
+At the same time, with the help of AI models, it is possible to effectively rewrite the template style code translated from Figma to supplement business logic.  
+Semi D2C provides out-of-the-box design to code abilities: it supports one-click identification of layer layouts and design system components on Figma pages, achieves pixel perfect reproduction of design drafts, and translation into React JSX and CSS code. In addition, it also provides rich expansion capabilities. Based on a flexible plugin system, it can quickly create team specific design and R&D collaboration tools without starting from scratch.
+
+With the help of D2C, you can delegate the UI restoration work to the tool and focus more on implementing business logic.
+
+<DesignToCodeFeature /> 
+
+## Invocation Methods
+
+We provide multiple forms of invocation methods:  
+
+**Figma Plugin**: Quickly launched through Figma DevMode. You can directly obtain the corresponding code by clicking on the layer, and it supports the output of different code formats. It also supports freely consuming the Abstract Syntax Tree (AST) through a custom plugin and customizing the code generation results.  
+
+**OpenApi**: An open Http service that provides the ability to parse the corresponding code based on the Figma URL. It can be used to integrate D2C into business processes, such as LowCode building platforms and the construction of MCP services, etc. (Available internally in ByteDance. This capability is not yet provided in the community version.)  
+
+**NodeSdk**: It has strong customization and can be used to encapsulate the team's private D2C capabilities, such as CLI/HTTP services/VS Code plugins (Available internally in ByteDance. This capability is not yet provided in the community version.)  
+
+For more detailed usage instructions, you can visit <a href="/code" target="_blank"> the D2C Official Website</a> for reference.
+
 
 ## Examples
 
@@ -104,22 +44,18 @@ Here is a link to the Figma example mockup and its corresponding Codesandbox tra
 | <a href='https://www.figma.com/file/TlLeWouyImYUexTmhdLiIn/D2C-Getting-Start-Demo%EF%BC%88Figma-Community%EF%BC%89?node-id=419%3A128959&t=PMnGQ3VQIoGQZZPl-4' target="_blank"><img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/semi-linker/semi-code-site/Simple Table.png' style={{ width:  400 }} /></a> | Module with Semi Table Components                                                                 | Can be used to quickly identify table columns, create Table                                         | <a href='https://codesandbox.io/s/happy-browser-dt34sr' target="_blank">Link</a>             |
 | <a href='https://www.figma.com/file/TlLeWouyImYUexTmhdLiIn/D2C-Getting-Start-Demo?node-id=1%3A276' target="_blank" rel="noreferrer noopener"><img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/semi-linker/d2c-landing-example.png' style={{ width:  400 }} /></a>                                          | Full landing page                                                                                 | Can be used to quickly restore layout and content                                                   | <a href='https://codesandbox.io/s/cvhhqt' target="_blank" rel="noreferrer noopener">Link</a> |
 
-## Comparisons
-
-### More in line with the designer's usage habits
-
-We investigated the common C2D tools in the industry in 2021~2022. They all draw the component as an instance on the canvas. Whether it is creating an instance or switching variants, it needs to be operated within the plug-in.
-
-However, in the actual design process, designers are not used to opening a plugin to create and update components. The solution provided by Semi is that designers continue to use variants in the native way of Figma, which is consistent with the original design process.
-
-### Powerful Design Components
 
-There are some tools in the industry that support converting arbitrary component libraries into design components, but these components currently do not have auto layout capabilities and cannot meet the standards available to designers. In addition, their components do not use design variables, which makes it difficult for designers to customize this set of components.
 
-### Component identification does not depend on annotations
+## Differences from Other Technical Approaches
+Generally speaking, in addition to relying on Figma To Code/Sketch To Code on specific design platforms for design to code conversion, there is also Image to Code. Code generation based on AI models can also be considered a variant of Image to Code.
 
-Semi focuses on supporting Semi components. The Semi D2C solution does not require R&D to manually label components. If you have custom requirements, you can also use labeling to identify layers as components. There are some tools in the industry that provide powerful component labeling capabilities, but since there is no component library provided by default, it is necessary to manually label components and component properties in actual use.
+- Image To Code:
+    - The traditional Image To Code approach relies on methods such as YOLO to cut image elements and then identify them. This type of technical approach has poor recognition of similar elements (for example, when Select and TreeSelect have similar styles, the recognition may be confused). At the same time, it is difficult to further optimize the restoration degree of the design draft after it reaches a certain threshold.
+    - After the emergence of large-scale language models (LLMs) in the field of AI, relying on vllm such as GPT-4V can also achieve the toCode restoration of simple design drafts. However, this path is still difficult to support a specific design system (or a derivative of a specific design system + theme customization) and restore it based on specific design specifications. At the same time, there are many minor differences in aspects such as spacing, alignment, and element recognition. For designers, the acceptance cost will increase significantly. For scenarios with high requirements for Pixel Perfect visual restoration effects, its practicality remains poor. The improvement costs for these cases are still high at present.
 
-### Transcoding supports flex layout
+- Figma To Code:
+    - In 2023/2024, mainstream D2C products in the industry are basically based on this technical approach. Since it belongs to the mutual conversion of structured information, it has a great advantage in recognition accuracy in cases where Image to Code cannot solve problems, such as design systems and similar elements. Semi's D2C is also based on this.
+    - Different from other D2C solution providers, we are also the maintainers of the design system ourselves. We pay more attention to the connection with specific design systems. The D2C we provide natively supports component - level transcoding recognition of the Design System and also supports third - party design systems. Based on the Code To Design capability, it has been integrated with Figma's Variant. We also update various functions on the Figma platform more promptly.
+    - We are combining the above two approaches, integrating their advantages to achieve generation closer to practical applications. First, generate basic code based on Figma To Code, and then combine image information. With the help of AI models, rewrite the basic code more reasonably. In the internal version of the D2C plugin of ByteDance, we provide a Quality mode. Based on Doubao/Deepseek, the following out - of - the - box capabilities are integrated: more intelligent sub - component splitting, implementation of list loop structures based on map render, semantic classname, and addition of jsx -> tsx type declarations.
 
-We have researched some common D2C tools on the market, and some of them have relatively good flex layout support, but there are certain problems in some details. Semi supports reverting Figma's auto layout to flex layout. In addition, if certain rules are met between layers, it will also be automatically recognized as a flex layout.
+**D2C + AI is the technical approach that can best balance the design restoration degree and the high availability of generated code at the current stage (D2C + AI = 🚀 Pixel Perfect + 🔩 highly maintainable code)** 

+ 34 - 101
content/start/design-to-code/index.md

@@ -8,91 +8,29 @@ order: 4
 
 ## 简介
 
-Design to code(简称D2C) 是 Semi Design 提供的设计稿转代码功能,支持一键识别 Figma 页面中图层布局 + Semi 组件,像素级还原设计稿,转译为 JSX 和 CSS 代码,快捷预览,
-无需从 0 开发。
-
-从此,你可以将 UI 还原的工作交给工具,更专注于实现业务逻辑。
-
-## 基础能力
-
-<div>
-    <Row gutter={[20, 60]}>
-        <Col span={8}>
-            <FeatureCard title='基础 UI 布局还原' >
-                支持 flex 布局、absolute 布局
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='精准的Semi 组件识别' >
-                已覆盖表单和表格场景 28+ 组件
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='支持输出不同技术栈代码' >
-                多种代码风格输出: React + Scss、React + Tailwind 和 JSON Schema
-            </FeatureCard>
-        </Col>
-    </Row>
-    <Row gutter={[20, 20]}>
-        <Col span={8}>
-            <FeatureCard title='支持识别其他主题' >
-                支持识别自定义主题后的组件
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='嵌套组件和自定义内容识别' >
-                识别设计师修改后的内容,将其识别为 ReactNode
-            </FeatureCard>
-        </Col>
-    </Row>
-</div>
-
-## 如何使用
-
-
-![](https://lf3-files.qingfuwucdn.net/obj/inspirecloud-file/baas/tt38q7/82069cd816533f91_1676604095341.png)
-
-更详细的使用说明,可访问 <a href="/code" target="_blank">https://semi.design/code</a> 查阅
-
-
-## 使用场景
-
-<div>
-    <Row gutter={[20, 20]}>
-        <Col span={8}>
-            <FeatureCard title='基础页面转码' >
-                将基础页面的布局进行还原,适合落地页快速开发
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='卡片转码' >
-                一键将复杂样式的卡片进行还原,无需关心卡片布局
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='表格页转码' >
-                利用 Semi Variants 搭建运营表格页面,D2C 帮助你自动识别表格列内容
-            </FeatureCard>
-        </Col>
-    </Row>
-    <Row gutter={[20, 20]}>
-        <Col span={8}>
-            <FeatureCard title='表单页转码' >
-                利用 Semi Variants 搭建运营表单页,一键将 Semi Form 表单进行还原
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='接入自定义搭建平台' >
-                自定义消费 JSON Schema,创建适合你项目的模板代码
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard />
-        </Col>
-    </Row>
-</div>
-
-## 实际示例
+D2C 为 Design to Code 的缩写,即设计稿转代码。作为前端工程师的辅助工具,能有效加速设计稿还原的效率,降低人工编写 Html / CSS 的成本。
+
+得益于 Figma 平台生态原生能力(Variant 变体、DevMode、CodeConnect 等)的不断增强,D2C 类工具近些年在生产环境下的可用性已经有了非常大的提升,可以支撑完整的设计系统接入,做到组件级的识别与代码输出。
+同时借助 AI 大模型,可以有效对 Figma 转译出的模板样式代码做进一步改写,实现业务逻辑补充。
+
+Semi D2C 提供开箱即用的设计稿转代码:支持一键识别 Figma 页面中图层布局 + 设计系统组件,像素级还原设计稿,转译为 React JSX 和 CSS 代码。此外还提供了丰富的扩展能力,基于自定义插件系统快速打造团队专属的设计研发协作工具,无需从 0 开发,
+
+借助 D2C,你可以将 UI 还原的工作交给工具,更专注于实现业务逻辑。
+
+<DesignToCodeFeature /> 
+
+## 调用方式
+
+我们提供了多种形态的调用方式:
+
+- Figma 插件:通过 Figma DevMode 快速启动,点击图层直接获取对应代码,支持不同代码格式 Output。支持通过自定义插件自由消费 AST,自定义出码结果
+- OpenApi:开放式 Http 服务,提供基于 Figma URL 解析出对应代码的能力,可用于将 D2C 集成到业务流程,如 LowCode 搭建平台,构建 MCP 服务等(字节内部可用,社区版本暂未提供该能力)
+- NodeSdk:定制性强,可用于封装团队私有的 D2C 能力,如 CLI/HTTP 服务/VS Code 插件(字节内部可用,社区版本暂未提供该能力)
+
+更详细的使用说明,可访问 <a href="/code" target="_blank">D2C 官网</a> 查阅
+
+
+## 示例
 
 我们准备了一些 Figma 示例设计稿,以及使用 Semi Figma 插件实际转译的代码 Codesandbox 链接。   
 
@@ -107,26 +45,21 @@ Design to code(简称D2C) 是 Semi Design 提供的设计稿转代码功能
 | <a href='https://www.figma.com/file/TlLeWouyImYUexTmhdLiIn/D2C-Getting-Start-Demo%EF%BC%88Figma-Community%EF%BC%89?node-id=419%3A128959&t=PMnGQ3VQIoGQZZPl-4' target="_blank"><img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/semi-linker/semi-code-site/Simple Table.png' style={{ width:  400 }} /></a> | 含 Semi Table 组件                  | 可用于快速识别表格列、创建 Table                     | <a href='https://codesandbox.io/s/happy-browser-dt34sr' target="_blank">Link</a>             |
 | <a href='https://www.figma.com/file/TlLeWouyImYUexTmhdLiIn/D2C-Getting-Start-Demo?node-id=1%3A276' target="_blank" rel="noreferrer noopener"><img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/semi-linker/d2c-landing-example.png' style={{ width:  400 }} /></a>                                          | 整页 Landing Page                   | 可用于快速还原布局及内容                            | <a href='https://codesandbox.io/s/cvhhqt' target="_blank" rel="noreferrer noopener">Link</a> |
 
-## 与其他方案的差异
-
-### 更加符合设计师使用习惯
-
-我们在 2021 ~ 2022 年调研了业界常见的 C2D 工具,它们将组件作为一个实例绘制在画布上,无论是创建实例还是切换变体都需要在插件内操作。
-
-然而,在实际的设计流程中,设计师可能并不习惯打开一个插件创建和更新组件。Semi 提供的方案允许设计师继续通过 Figma 原生的方式使用变体,与原有的设计流程一致。
-
-### 可用的设计组件
-
-业界有一些工具支持任意的组件库转为设计组件,但是这些组件目前不具有 auto layout 能力,无法达到设计师可用的标准。另外它们的组件也没有使用设计变量,这导致设计师很难通过 design token 自定义这些组件库。
 
-### 组件识别不依赖标注
+## 与其他技术路线的差异
+通常而言,设计转代码除了依赖于特定设计平台的 Figma To Code / Sketch To Code 外,还有 Image to Code,基于多模态 AI 大模型的 Code 生成 也可以认为是 Image to Code 的一种变体。
 
-Semi D2C 方案提供了精准到组件级 props 的识别能力,并且不需要研发手动对组件进行标注。同时对于自定义组件,我们也额外提供了标注方式将图层识别为组件。业界有一些工具提供了强大的组件标注能力,但由于没有默认提供一套组件库,所以在实际的使用中需要人工将组件以及组件属性进行标注,整体的使用成本较高。
+- Image to Code:
+    - 传统的 ImageToCode 路线依赖 yolo 等手段对图片元素进行切割,然后识别。这类技术路线对相似元素的识别不佳(例如 Select 与 TreeSelect 在样式相似的情况下,识别可能会混淆),同时设计稿还原度在达到一定阈值后很难再优化
+    - 大模型时代,依赖 LLM 多模态能力如 GPT-4V 等手段亦能做到对简单设计稿的 toCode 还原,但该路径目前依然很难承载一个特定的设计系统(或者一个特定设计系统 + 主题定制的衍生体),并基于特定的设计规范去还原。同时对于间距、对齐、元素识别等存在非常多边边角角的 diff,对设计师来说,验收成本会剧增,对于 Pixel Perfect 视觉还原效果要求高的场景而言,实用性依然不佳。针对这些 case 的改善成本,目前依然居高不下
 
-### 更好的 Flex 布局支持
+- Figma to Code:  
+    - 23/24年业界主流的 D2C 产品基本都是基于该技术路线,它由于属于结构化信息的互相转换,所以对设计系统,相似元素等 Image to Code 无法解决的 case 里,**识别精准度上会有非常大的优势**。Semi 的 D2C 亦基于此。
+    - 与其他 D2C 方案提供者不同,我们本身亦是设计系统的维护者,会更注重与特定设计系统的联通,**提供的 D2C 原生支持 Design System 组件级转码识别,也支持第三方设计系统**,基于 Code To Design 能力 跟 Figma 的 Variant 做了打通。对 Figma 平台上各类功能的更新也更及时
+    - 我们正在将上述两个路线做结合,结合两者优点去做更靠近实际应用的生成,先基于 Figma To Code 生成基础代码,再结合 Image 图像信息,借助多模态 AI 大模型将基础代码做更合理的改写,在字节跳动内部版本的 D2C 插件中,我们提供了 Quality 模式,基于 Doubao / Deepseek 集成以下开箱即用的能力:更智能的子组件拆分、列表循环结构基于 map render 实现、classname 语义化、jsx -> tsx 类型声明补充。  
 
-市场上常见的 D2C 工具,有一些具有比较好的 Flex 布局能力支持,但在一些细节上有一定问题。Semi 支持将 Figma 的 auto layout 布局还原为 Flex 布局。另外,如果图层间符合一定规则,也将自动识别为 Flex 布局。
+**D2C + AI 是当下阶段能最好兼顾设计还原度 + 生成代码高可用性的技术路线(D2C + AI  =  🚀 Pixel Perfect + 🔩 高可维护代码)**
 
 
 ## 更多说明
-更多关于Figma 插件的安装,使用,标注自定义组件,Figma 设计变体的使用,D2C的使用受限等细节信息,请查阅 https://semi.design/code
+更多关于 Figma 插件的安装,使用,标注自定义组件,Figma 设计变体的使用,D2C 的使用受限等细节信息,请查阅 https://semi.design/code

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

@@ -94,6 +94,7 @@ Table 表格,
 Tag 标签,
 Timeline 时间轴,
 Tooltip 工具提示,
+UserGuide 用户引导,
 VChart 图表
 ```
 

+ 1 - 1
content/start/tailwind/index-en-US.md

@@ -12,7 +12,7 @@ brief: Use TailwindCSS and Semi more elegantly
 
 <br/>
 
-This page will provide best practices for some issues encountered when using atomic style libraries such as TailwindCSS with Semi.
+This page will provide best practices for some issues encountered when using atomic style libraries such as TailwindCSS (only v3, Tailwind V4 is not supported yet) with Semi.
 
 These problems are often encountered when other component libraries are used with Tailwind, but Semi provides official solutions. It is recommended to follow the instructions in this article to configure the project correctly.
 

+ 6 - 6
content/start/tailwind/index.md

@@ -12,7 +12,7 @@ brief: 更优雅地使用 TailwindCSS 与 Semi
 
 <br/>
 
-本页将提供 TailwindCSS 等原子类样式库与 Semi 共同使用时遇到的一些问题的最佳实践。
+本页将提供 TailwindCSS (只支持 v3,tailwind V4 暂未支持) 等原子类样式库与 Semi 共同使用时遇到的一些问题的最佳实践。
 
 这些问题在其他组件库与 Tailwind 共同使用时候也会经常遇到,但 Semi 提供了官方解决方案,建议按照本文说明,正确配置项目。
 
@@ -39,7 +39,7 @@ Semi 不依赖任何第三方样式库,没有安装 Tailwind 一样可以运
 如果是 1,则会出现 Tailwind 在添加某些原子类时,如果组件样式已经定义了某个 css 属性,原子类的优先级比 Semi 优先级低,此时原子类失效。
 例如在 1 的前提下,对 Button 组件设置 padding,会出现失效的情况。
 
-如果是2,因为 Tailwind 优先级较高,其对浏览器默认样式覆盖的 Preflight 会同时覆盖掉 Semi 的样式。
+如果是 2,因为 Tailwind 优先级较高,其对浏览器默认样式覆盖的 Preflight 会同时覆盖掉 Semi 的样式。
 例如在 2 的前提下,light 的 Button 的背景色会被覆盖为 transparent,导致样式表现异常。
 
 
@@ -52,7 +52,7 @@ yarn add -D @douyinfe/semi-webpack-plugin
 ```
 ** 2. 在项目中的配置文件中 **
 
-- webpack 用户: 在webpack.config.js 引入Semi webpack 插件并开启 cssLayer
+- webpack 用户:在 webpack.config.js 引入 Semi webpack 插件并开启 cssLayer
 
 ```js
 const SemiPlugin = require('@douyinfe/semi-webpack-plugin').default;
@@ -69,7 +69,7 @@ module.exports = {
 };
 
 ```
-- rspack 用户: 在 rspack.config.js  引入Semi webpack 插件并开启 cssLayer
+- rspack 用户:在 rspack.config.js  引入 Semi webpack 插件并开启 cssLayer
 
 ```js
 const {SemiRspackPlugin} = require('@douyinfe/semi-rspack-plugin');
@@ -127,7 +127,7 @@ CSS Layer 要求浏览器版本高于 Chromium 99 <a target="_blank" href="https
 ```css
 @layer tailwind-base,semi,tailwind-components,tailwind-utils;
 ```
-上述 CSS 的含义为, base (含 Preflight)优先级最低,Semi 次之,用户设置的原子类样式(padding-[xxx] 等)优先级最高,这样即可解决上面遇到的问题。
+上述 CSS 的含义为,base(含 Preflight)优先级最低,Semi 次之,用户设置的原子类样式(padding-[xxx] 等)优先级最高,这样即可解决上面遇到的问题。
 
 
 ### 2.解决在 Tailwind 原子类中使用 Semi Token 的问题 (可选)
@@ -137,7 +137,7 @@ Tailwind 支持用户配置自己的 Token 来实现主题。同时 Semi 也提
 
 Semi 提供了 Tailwind 的主题配置文件,用于将 Semi 的 Token 映射为原子类 Token,上述需求可以直接给 span 设置 `text-semi-color-text-0` 即可。
 
-在 Tailwind 配置中(即 `tainwind.config.js`)配置以下内容即可:
+在 Tailwind 配置中 (即 `tainwind.config.js`) 配置以下内容即可:
 
 ```js
 module.export = {

+ 11 - 12
cypress/e2e/jsonViewer.spec.js

@@ -77,23 +77,23 @@ describe('jsonViewer', () => {
         typeTextAtPosition(2, 7, `:`);
         typeTextAtPosition(2, 8, `1`);
         typeTextAtPosition(2, 9, `,`);
-        cy.get('.lines-content').children().eq(1).children().should('have.length', 5);
+        cy.get('.lines-content').children().eq(1).children().children().should('have.length', 5);
 
 
         // undo redo
         undo(1);
-        cy.get('.lines-content').children().eq(1).children().should('have.length', 4);
+        cy.get('.lines-content').children().eq(1).children().children().should('have.length', 4);
         redo(1);
-        cy.get('.lines-content').children().eq(1).children().should('have.length', 5);
+        cy.get('.lines-content').children().eq(1).children().children().should('have.length', 5);
         undo(8);
-        cy.get('.lines-content').children().eq(1).children().should('have.length', 6);
+        cy.get('.lines-content').children().eq(1).children().children().should('have.length', 6);
 
         //del
         typeTextAtPosition(2, 1, `{backspace}`);
-        cy.get('.lines-content').children().eq(0).children().should('have.length', 7);
+        cy.get('.lines-content').children().eq(0).children().children().should('have.length', 7);
         undo(1);
-        cy.get('.lines-content').children().eq(0).children().should('have.length', 1);
-        cy.get('.lines-content').children().eq(1).children().should('have.length', 6);
+        cy.get('.lines-content').children().eq(0).children().children().should('have.length', 1);
+        cy.get('.lines-content').children().eq(1).children().children().should('have.length', 6);
 
         // cut
         // typeTextAtPosition(2, 1, `{meta+x}`);
@@ -103,19 +103,19 @@ describe('jsonViewer', () => {
 
         //complete
         typeTextAtPosition(14, 4, '{enter}');
-        cy.get('.lines-content').children().eq(14).children().should('have.length', 1);
+        cy.get('.lines-content').children().eq(14).children().children().should('have.length', 1);
         typeTextAtPosition(15, 4, `c`);
         cy.get('.semi-json-viewer-complete-suggestions-container').children().should('have.length', 2);
         cy.get('.lines-content').type('{enter}');
         cy.get('.semi-json-viewer-complete-container').should('have.css', 'display', 'none');
-        cy.get('.lines-content').children().eq(14).children().should('have.length', 2);
+        cy.get('.lines-content').children().eq(14).children().children().should('have.length', 2);
         typeTextAtPosition(15, 11, `:`);
         cy.get('.semi-json-viewer-complete-container').should('have.css', 'display', 'block');
         cy.get('.semi-json-viewer-complete-suggestions-container').children().should('have.length', 2);
         cy.get('.lines-content').type('{enter}');
         cy.get('.semi-json-viewer-complete-container').should('have.css', 'display', 'none');
         typeTextAtPosition(15, 19, `,{enter}`);
-        cy.get('.lines-content').children().eq(14).children().should('have.length', 5);
+        cy.get('.lines-content').children().eq(14).children().children().should('have.length', 5);
         typeTextAtPosition(16, 4, `a`);
         cy.get('.semi-json-viewer-complete-suggestions-container').children().should('have.length', 2);
         typeTextAtPosition(16, 5, `{rightArrow}`);
@@ -127,7 +127,7 @@ describe('jsonViewer', () => {
         typeTextAtPosition(16, 9, `:`);
         cy.get('.lines-content').type('{enter}');
         cy.get('.semi-json-viewer-complete-container').should('have.css', 'display', 'none');
-        cy.get('.lines-content').children().eq(15).children().should('have.length', 4);
+        cy.get('.lines-content').children().eq(15).children().children().should('have.length', 4);
 
         //search
         cy.get('.semi-json-viewer-search-bar-trigger').click();
@@ -146,7 +146,6 @@ describe('jsonViewer', () => {
         cy.get('.semi-json-viewer-search-bar').children().eq(0).clear();
 
         //replace
-        cy.scrollTo('right');
         cy.get('.semi-json-viewer-search-bar').children().eq(0).type('a');
         cy.get('.semi-json-viewer-replace-bar').children().eq(0).type('x');
         cy.get('.semi-json-viewer-search-result').then(($el) => {

+ 16 - 16
cypress/e2e/scrollList.spec.js

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

+ 7 - 0
cypress/e2e/tabs.spec.js

@@ -107,4 +107,11 @@ describe('tabs', () => {
         cy.get('.semi-button').eq(1).trigger('mouseover');
         cy.get('.semi-dropdown-content .semi-dropdown-item').should('not.exist');
     });
+
+    it.skip('activeTab is within the viewport after collapse Tabs did mount', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=tabs--collapse-scroll-into-view-demo&args=&viewMode=story');
+
+        cy.wait(1000);
+        cy.get('.semi-tabs-tab').contains('Tab-9').should('exist').and('be.visible');
+    });
 });

+ 1 - 1
lerna.json

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

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

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

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

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

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation",
-    "version": "2.74.0",
+    "version": "2.78.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.74.0",
+    "version": "2.78.0",
     "description": "semi ui eslint plugin",
     "keywords": [
         "semi",

+ 3 - 0
packages/semi-foundation/backtop/foundation.ts

@@ -22,6 +22,9 @@ export default class BackTopFoundation extends BaseFoundation<BackTopAdapter> {
     init() {
         const { target } = this.getProps();
         const targetNode = target();
+        if (!targetNode) {
+            return;
+        }
         targetNode.addEventListener('scroll', this.handleScroll);
         this.handleScroll();
     }

+ 12 - 6
packages/semi-foundation/cascader/cascader.scss

@@ -246,7 +246,7 @@ $module: #{$prefix}-cascader;
 
         &-text {
             margin: 0 $spacing-cascader_text-marginX;
-            font-weight: $font-weight-bold;
+            font-weight: $font-cascader_prefix_suffix_fontWeight;
             @include font-size-regular;
             color: $color-cascader_prefix_suffix_text-default;
         }
@@ -259,10 +259,10 @@ $module: #{$prefix}-cascader;
 
     &-inset-label {
         display: inline;
-        margin-right: $spacing-cascader_label-marginRight;
-        font-weight: $font-weight-bold;
+        margin: 0 $spacing-cascader_text-marginX;
+        font-weight: $font-cascader_prefix_suffix_fontWeight;
         @include font-size-regular;
-        color: $color-cascader_label-text-default;
+        color: $color-cascader_prefix_suffix_text-default;
         flex-shrink: 0;
         white-space: nowrap;
     }
@@ -315,16 +315,20 @@ $module: #{$prefix}-cascader;
         border-bottom: $width-cascader_search-border solid $color-cascader_search-border-default;
     }
 
-    .#{$module}-option-empty {
+    .#{$module}-option-lists .#{$module}-option-empty {
         @include font-size-regular;
         border-radius: $radius-cascader_option_empty;
-        min-width: $width-cascader_option;
+        // min-width: $width-cascader_option;
         color: $color-cascader_option_empty-text-default;
         margin: 0;
         padding: $spacing-cascader_option_empty-paddingY $spacing-cascader_option_empty-paddingX;
         user-select: none;
         text-align: center;
         cursor: not-allowed;
+
+        &:hover {
+            background-color: transparent;
+        }
     }
 }
 
@@ -381,6 +385,8 @@ $module: #{$prefix}-cascader;
 
     &-empty {
         height: auto;
+        justify-content: center;
+        cursor: not-allowed;
     }
 
     ul,

+ 19 - 2
packages/semi-foundation/cascader/foundation.ts

@@ -1,4 +1,4 @@
-import { isEqual, get, difference, isUndefined, assign, isEmpty, isNumber, includes, isFunction, isObject } from 'lodash';
+import { isEqual, get, difference, isUndefined, assign, isEmpty, isNumber, includes, isFunction, isObject, isString } from 'lodash';
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
 import {
     findAncestorKeys,
@@ -186,6 +186,7 @@ export interface BasicCascaderProps {
 }
 
 export interface BasicCascaderInnerData {
+    emptyContentMinWidth: number;
     isOpen: boolean;
     rePosKey: number;
     keyEntities: BasicEntities;
@@ -239,7 +240,9 @@ export interface CascaderAdapter extends DefaultAdapter<BasicCascaderProps, Basi
     updateLoadingKeyRefValue: (keys: Set<string>) => void;
     getLoadingKeyRefValue: () => Set<string>;
     updateLoadedKeyRefValue: (keys: Set<string>) => void;
-    getLoadedKeyRefValue: () => Set<string>
+    getLoadedKeyRefValue: () => Set<string>;
+    setEmptyContentMinWidth: (minWidth: number) => void;
+    getTriggerWidth: () => number;
 }
 
 export default class CascaderFoundation extends BaseFoundation<CascaderAdapter, BasicCascaderProps, BasicCascaderInnerData> {
@@ -259,6 +262,19 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
         }
     }
 
+    _setEmptyContentMinWidth() {
+        const { style } = this.getProps();
+        let width;
+        if (style && isNumber(style.width)) {
+            width = style.width;
+        } else if (style && isString(style.width) && !style.width.includes('%')) {
+            width = style.width;
+        } else {
+            width = this._adapter.getTriggerWidth();
+        }
+        this._adapter.setEmptyContentMinWidth(width);
+    }
+
     handleKeyDown = (e: any) => {
         if (e.key === ESC_KEY) {
             const isOpen = this.getState('isOpen');
@@ -531,6 +547,7 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
         }
         this._adapter.notifyDropdownVisibleChange(true);
         this._adapter.registerClickOutsideHandler(e => this.close(e));
+        this._setEmptyContentMinWidth();
     }
 
     reCalcActiveKeys() {

+ 0 - 4
packages/semi-foundation/cascader/rtl.scss

@@ -5,10 +5,6 @@ $module: #{$prefix}-cascader;
     .#{$module} {
         direction: rtl;
 
-        &-inset-label {
-            margin-right:  $spacing-cascader_label-marginRight;
-            margin-left:  $spacing-cascader_label-marginRight;
-        }
 
         &.#{$module}-with-prefix {
             .#{$module}-selection {

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

@@ -84,6 +84,7 @@ $font-cascader_select-fontWeight: $font-weight-bold; // 级联选择菜单项字
 $font-cascader-fontWeight: $font-weight-regular; // 级联选择菜单项字重 - 未选中
 $font-cascader_label-fontWeight: 600;
 $font-cascader_selection_n-fontSize: $font-size-small; // 超出 maxTagCount 后,+n 的文字尺寸
+$font-cascader_prefix_suffix_fontWeight: $font-weight-bold; // 级联选择 prefix/suffix 文字字重
 
 $width-cascader-border: 1px; // 级联选择触发器描边宽度
 $width-cascader_option_list-border: 1px; // 级联选择各级菜单分割线宽度
@@ -107,7 +108,6 @@ $height-cascader_selection_wrapper_large: 38px;  //级联选择单选搜索时
 
 $spacing-cascader_text-marginX: $spacing-base-tight; // 级联选择 prefix/suffix 文字水平内间距
 $spacing-cascader_icon-marginX: $spacing-tight; // 级联选择 prefix/suffix 图标水平内间距
-$spacing-cascader_label-marginRight: $spacing-base-tight; // 级联选择 prefix/suffix 图标水平内间距
 $spacing-cascader_label_checkbox-marginRight: $spacing-tight; // 级联选择 checkbox 的右间距
 $spacing-cascader_selection_n-paddingX: $spacing-tight; // 级联选择 +N 的水平间距
 

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

@@ -164,6 +164,7 @@ $module: #{$prefix}-chat;
             &-btn {
                 &.#{$prefix}-button {
                     height: fit-content;
+                    width: fit-content;
                 }
                 
                 &.#{$prefix}-button.#{$prefix}-button-with-icon-only {

+ 29 - 0
packages/semi-foundation/chat/foundation.ts

@@ -36,6 +36,12 @@ export interface Message {
     [x: string]: any
 }
 
+export interface EnableUploadProps {
+    pasteUpload?: boolean;
+    dragUpload?: boolean;
+    clickUpload?: boolean
+}
+
 export interface ChatAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
     getContainerRef: () => HTMLDivElement;
     setWheelScroll: (flag: boolean) => void;
@@ -312,5 +318,28 @@ export default class ChatFoundation <P = Record<string, any>, S = Record<string,
             this._adapter.setUploadAreaVisible(false);
         });
     }
+
+    getUploadProps = (uploadProps?: boolean | EnableUploadProps) => {
+        if (Object.prototype.toString.call(uploadProps) === '[object Object]') {
+            const { dragUpload = true, clickUpload = true, pasteUpload = true } = uploadProps as EnableUploadProps;
+            return {
+                dragUpload: dragUpload,
+                clickUpload: clickUpload,
+                pasteUpload: pasteUpload
+            };
+        } else if (typeof uploadProps === 'boolean') {
+            return {
+                dragUpload: uploadProps,
+                clickUpload: uploadProps,
+                pasteUpload: uploadProps
+            };
+        } else {
+            return {
+                dragUpload: true,
+                clickUpload: true,
+                pasteUpload: true
+            };
+        }
+    }
 }
 

+ 2 - 2
packages/semi-foundation/chat/inputboxFoundation.ts

@@ -78,9 +78,9 @@ export default class InputBoxFoundation <P = Record<string, any>, S = Record<str
 
     onPaste = (e: any) => {
         const items = e.clipboardData?.items;
-        const { manualUpload } = this.getProps();
+        const { manualUpload, pasteUpload } = this.getProps();
         let files = [];
-        if (items) {
+        if (pasteUpload && items) {
             for (const it of items) {
                 const file = it.getAsFile();
                 file && files.push(it.getAsFile());

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

@@ -60,6 +60,12 @@ export default class CropperFoundation <P = Record<string, any>, S = Record<stri
     rangeX: [number, number];
     rangeY: [number, number];
     initial: boolean;
+    previewImg: HTMLImageElement;
+    previewContainer: HTMLElement;
+    previewContainerInitSize: {
+        width: number;
+        height: number
+    };
     
     constructor(adapter: CropperAdapter<P, S>) {
         super({ ...adapter });
@@ -74,6 +80,8 @@ export default class CropperFoundation <P = Record<string, any>, S = Record<stri
         this.rangeX = null;
         this.rangeY = null;
         this.initial = false;
+        this.previewImg = null;
+        this.previewContainer = null;
     }
 
     init() {
@@ -88,6 +96,7 @@ export default class CropperFoundation <P = Record<string, any>, S = Record<stri
     destroy() {
         this.unBindMoveEvent();
         this.unBindResizeEvent();
+        this.removePreview();
     }
 
     getImgDataWhenResize = (ratio: number) => {
@@ -228,6 +237,80 @@ export default class CropperFoundation <P = Record<string, any>, S = Record<stri
             cropperBox: newCropperBoxState,
             loaded: true,
         } as any);
+
+        this.renderPreview();
+    }
+
+    renderPreview = () => {
+        const { preview, src } = this.getProps();
+        const previewNode = preview?.();
+        if (!previewNode) {
+            return;
+        }
+        const img = document.createElement('img');
+        this.previewImg = img;
+        this.previewContainer = previewNode;
+        img.src = src;
+        previewNode.appendChild(img);
+        this.previewContainer.style.overflow = 'hidden';
+        // 记录预览容器初始宽高
+        const { width: previewWidth, height: previewHeight } = previewNode.getBoundingClientRect();
+        this.previewContainerInitSize = {
+            width: previewWidth,
+            height: previewHeight,
+        };
+    }
+
+    updatePreview = (props: {
+        width: number;
+        height: number;
+        translateX: number;
+        translateY: number;
+        rotate: number
+    }) => {
+        if (!this.previewImg) {
+            return;
+        }
+        const { cropperBox } = this.getStates();
+        let zoom = 1;
+        const { width: containerWidth, height: containerHeight } = this.previewContainerInitSize;
+        let previewWidth = containerWidth;
+        let previewHeight = containerHeight;
+        if (previewWidth < previewHeight) {
+            zoom = containerWidth / cropperBox.width;
+            let tempHeight = zoom * cropperBox.height;
+            if (tempHeight > containerHeight) {
+                zoom = containerHeight / cropperBox.height;
+                previewWidth = zoom * cropperBox.width;
+            } else {
+                previewHeight = tempHeight;
+            }
+        } else {
+            zoom = containerHeight / cropperBox.height;
+            let tempWidth = zoom * cropperBox.width;
+            if (tempWidth > containerWidth) {
+                zoom = containerWidth / cropperBox.width;
+                previewHeight = zoom * cropperBox.height;
+            } else {
+                previewWidth = tempWidth;
+            }
+        }
+        const { width, height, translateX, translateY, rotate } = props;
+        // Set the image style
+        this.previewImg.style.width = `${width * zoom}px`;
+        this.previewImg.style.height = `${height * zoom}px`;
+        this.previewImg.style.transform = `translate(${translateX * zoom}px, ${translateY * zoom}px) rotate(${rotate}deg)`;
+        this.previewImg.style.transformOrigin = 'center';
+        // set preview container size
+        this.previewContainer.style.width = `${previewWidth}px`;
+        this.previewContainer.style.height = `${previewHeight}px`;
+    }
+
+    removePreview = () => {
+        if (this.previewImg && this.previewContainer) {
+            this.previewContainer.removeChild(this.previewImg);
+            this.previewImg = null;
+        }
     }
 
     handleWheel = (e: any) => {

+ 4 - 5
packages/semi-foundation/input/input.scss

@@ -502,9 +502,8 @@ $module: #{$prefix}-input;
     // }
 
     &-inset-label {
-        margin-right: $spacing-input_insetLabel-marginRight;
-        font-weight: $font-input_insetLabel-fontWeight;
-        @include font-size-regular;
+        margin: 0 $spacing-input_prefix_suffix-marginX;
+        font-weight: $font-input_prefix_suffix-fontWeight;
         color: $color-input_prefix-text-default;
         flex-shrink: 0;
         white-space: nowrap;
@@ -515,9 +514,9 @@ $module: #{$prefix}-input;
         @include all-center;
 
         &-text {
-            margin: 0 $spacing-base-tight;
+            margin: 0 $spacing-input_prefix_suffix-marginX;
             color: $color-input_prefix-text-default;
-            font-weight: $font-weight-bold;
+            font-weight: $font-input_prefix_suffix-fontWeight;
             white-space: nowrap;
         }
 

+ 0 - 4
packages/semi-foundation/input/rtl.scss

@@ -27,10 +27,6 @@ $module: #{$prefix}-input;
         padding-left: $spacing-input-paddingRight;
         padding-right: $spacing-input-paddingLeft;
 
-        &-inset-label {
-            margin-right: auto;
-            margin-left: $spacing-input_insetLabel-marginRight;
-        }
 
         &-clearbtn + &-suffix {
             & + .#{$module}-suffix-text {

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

@@ -88,7 +88,7 @@ $radius-input_wrapper: var(--semi-border-radius-small); // 输入框圆角大小
 $spacing-input_icon-marginLeft: -$spacing-base-tight; // 输入框图标左侧内边距
 $spacing-input-paddingLeft: $spacing-base-tight; // 输入光标距离容器的左侧内边距
 $spacing-input-paddingRight: $spacing-base-tight; // 输入文字距离容器的右侧内边距
-$spacing-input_insetLabel-marginRight: $spacing-base-tight;
+$spacing-input_prefix_suffix-marginX: $spacing-base-tight; // prefix/suffix 水平外边距
 $spacing-input_prefix_icon-marginY: 0; // prefix 图标垂直内边距
 $spacing-input_prefix_icon-marginX: $spacing-tight; // prefix 图标水平内边距
 $spacing-input_clearBtn_withSuffix-marginLeft: -$spacing-base-tight; // 清空按钮左侧内边距
@@ -96,7 +96,7 @@ $spacing-input_prepend-paddingY: 0; // 前置标签垂直内边距
 $spacing-input_prepend-paddingX: $spacing-base-tight; // 前置标签水平内边距
 $spacing-input_group_withTopLabel-marginTop: $spacing-base;
 $spacing-input_group_withTopLabel-marginBottom: $spacing-base;
-$font-input_insetLabel-fontWeight: 600; // prefix 文字字重
+$font-input_prefix_suffix-fontWeight: $font-weight-bold; // prefix/suffix 文字字重
 
 $spacing-textarea-paddingY: 5px; // 多行文本垂直内边距
 $spacing-textarea-paddingX: $spacing-base-tight; // 多行文本水平内边距

+ 176 - 10
packages/semi-foundation/inputNumber/foundation.ts

@@ -23,7 +23,8 @@ export interface InputNumberAdapter extends DefaultAdapter {
     restoreCursor: (str?: string) => boolean;
     fixCaret: (start: number, end: number) => void;
     setClickUpOrDown: (clicked: boolean) => void;
-    updateStates: (states: BaseInputNumberState, callback?: () => void) => void
+    updateStates: (states: BaseInputNumberState, callback?: () => void) => void;
+    getInputCharacter: (index: number) => string
 }
 
 export interface BaseInputNumberState {
@@ -38,8 +39,13 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
     _interval: any;
     _timerHasRegistered: boolean;
     _timer: any;
+    _decimalPointSymbol: string = undefined;
+    _currencySymbol: string = '';
 
     init() {
+        if (this._isCurrency()) {
+            this._setCurrencySymbol();
+        }
         this._setInitValue();
     }
 
@@ -53,6 +59,16 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         return this._isControlledComponent('value');
     }
 
+    _isCurrency() {
+        const { currency } = this.getProps();
+        return currency === true || (typeof currency === 'string' && currency.trim() !== '');
+    }
+
+    _getFinalCurrency() {
+        const { currency } = this.getProps();
+        return currency === true ? this.getProp('defaultCurrency') : currency;
+    }
+
     _doInput(v = '', event: any = null, updateCb: any = null) {
         let notifyVal = v;
         let number = v;
@@ -122,6 +138,28 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         }
     }
 
+    _setCurrencySymbol() {
+        const { localeCode, currencyDisplay } = this.getProps();
+        const parts = new Intl.NumberFormat(localeCode, {
+            style: 'currency',
+            currency: this._getFinalCurrency() || this.getCurrencyByLocaleCode(),
+            currencyDisplay
+        }).formatToParts(1234.5);
+
+        for (const part of parts) {
+            if (part.type === 'decimal') {
+                this._decimalPointSymbol = part.value;
+                console.log('this._decimalPointSymbol: ', this._decimalPointSymbol);
+            }
+            // if (part.type === 'group') {
+            //     groupSeparator = part.value;
+            // }
+            if (part.type === 'currency') {
+                this._currencySymbol = part.value;
+            }
+        }
+    }
+
     handleInputFocus(e: any) {
         const value = this.getState('value');
 
@@ -198,7 +236,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
             this._adapter.setNumber(num);
         }
 
-        this._adapter.setValue(this.isControlled() ? formattedNum : this.doFormat(valueAfterParser as unknown as number, false), () => {
+        this._adapter.setValue(this.isControlled() && !this._isCurrency() ? formattedNum : this.doFormat(valueAfterParser as unknown as number, false), () => {
             this._adapter.restoreCursor();
         });
 
@@ -242,7 +280,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
                 numHasChanged = true;
             }
 
-            const currentFormattedNum = this.doFormat(currentNumber, true);
+            const currentFormattedNum = this.doFormat(currentNumber, true, true);
 
             if (currentFormattedNum !== currentValue) {
                 willSetVal = currentFormattedNum;
@@ -366,14 +404,15 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         const { defaultValue, value } = this.getProps();
 
         const propsValue = this._isControlledComponent('value') ? value : defaultValue;
-        const tmpNumber = this.doParse(toString(propsValue), false, true, true);
+
+        const tmpNumber = this.doParse(this._isCurrency() ? propsValue : toString(propsValue), false, true, true);
 
         let number = null;
         if (typeof tmpNumber === 'number' && !isNaN(tmpNumber)) {
             number = tmpNumber;
         }
 
-        const formattedValue = typeof number === 'number' ? this.doFormat(number, true) : '';
+        const formattedValue = typeof number === 'number' ? this.doFormat(number, true, true) : '';
 
         this._adapter.setNumber(number);
         this._adapter.setValue(formattedValue);
@@ -417,7 +456,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
 
         // console.log('scale: ', scale, 'curNum: ', curNum);
 
-        return this.doFormat(curNum, true);
+        return this.doFormat(curNum, true, true);
     }
 
     minus(step?: number, event?: any): string {
@@ -448,19 +487,43 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         return toString(num);
     }
 
+    formatCurrency(value: number | string) {
+        const { localeCode, minimumFractionDigits, precision, maximumFractionDigits, currencyDisplay, showCurrencySymbol } = this.getProps();
+
+        let formattedValue = value;
+        if (typeof value === 'string' && Number.isNaN(Number(value))) {
+            formattedValue = this.parseInternationalCurrency(value);
+        }
+
+        const formatter = new Intl.NumberFormat(localeCode, {
+            style: 'currency',
+            currency: this._getFinalCurrency() || this.getCurrencyByLocaleCode(),
+            currencyDisplay: currencyDisplay,
+            minimumFractionDigits: minimumFractionDigits || precision || undefined,
+            maximumFractionDigits: maximumFractionDigits || precision || undefined,
+        });
+
+        const formatted = formatter.format(Number(formattedValue)); 
+        return showCurrencySymbol ? formatted : formatted.replace(this._currencySymbol, '').trim();
+    }
+
     /**
      * format number to string
      * @param {string|number} value
      * @param {boolean} needAdjustPrec
      * @returns {string}
      */
-    doFormat(value: string | number = 0, needAdjustPrec = true): string {
+    doFormat(value: string | number = 0, needAdjustPrec = true, needAdjustCurrency = false): string {
         // if (typeof value === 'string') {
         //     return value;
         // }
+        const { formatter } = this.getProps();
         let str;
-        const formatter = this.getProp('formatter');
-        if (needAdjustPrec) {
+
+        // AdjustCurrency conversion is done only in blur situation, otherwise it is just converted to normal string
+        if (this._isCurrency() && needAdjustCurrency) {
+            str = this.formatCurrency(value);
+        } else if (needAdjustPrec) {
             str = this._adjustPrec(value);
         } else {
             str = toString(value);
@@ -487,6 +550,22 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         return current;
     }
 
+    // 将货币模式的货币转化为纯数字
+    // Convert currency in currency mode to pure numbers
+    // eg:¥123456.78 to 123456.78
+    // eg:123456.78 to 123456.78
+    parseInternationalCurrency(currencyString: string) {
+        let cleaned = currencyString
+            .replace(this._currencySymbol, '')
+            .replace(new RegExp(`[^\\d${this._decimalPointSymbol}\\-]`, 'g'), '');
+
+        // Convert the localized decimal point to the standard decimal point
+        if (this._decimalPointSymbol && this._decimalPointSymbol !== '.') {
+            cleaned = cleaned.replace(this._decimalPointSymbol, '.');
+        }
+        return parseFloat(cleaned);
+    }
+      
     /**
      * parse to number
      * @param {string|number} value
@@ -496,6 +575,11 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
      * @returns {number}
      */
     doParse(value: string | number, needCheckPrec = true, needAdjustPrec = false, needAdjustMaxMin = false) {
+
+        if (this._isCurrency() && typeof value === 'string') {
+            value = this.parseInternationalCurrency(value);
+        }
+
         if (typeof value === 'number') {
             if (needAdjustMaxMin) {
                 value = this.fetchMinOrMax(value);
@@ -557,7 +641,11 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
             return value;
         }
         if (typeof value === 'string') {
-            const parser = this.getProp('parser');
+            const { parser } = this.getProps();
+
+            if (this._isCurrency()) {
+                value = this.parseInternationalCurrency(value);
+            }
 
             if (typeof parser === 'function') {
                 value = parser(value);
@@ -630,6 +718,84 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
     updateStates(states: BaseInputNumberState, callback?: () => void) {
         this._adapter.updateStates(states, callback);
     }
+
+    /**
+     * Get currency by locale code
+     * @param {string} localeCode
+     * @returns {string}
+     */
+    getCurrencyByLocaleCode() {
+        const { localeCode } = this.getProps();
+
+        // Mapping table of region codes to currency codes
+        const localeToCurrency: Record<string, string> = {
+            // Asia
+            'zh-CN': 'CNY', // China
+            'zh-HK': 'HKD', // Hong Kong
+            'zh-TW': 'TWD', // Taiwan
+            'ja-JP': 'JPY', // Japan
+            'ko-KR': 'KRW', // Korea
+            'th-TH': 'THB', // Thailand
+            'vi-VN': 'VND', // Vietnam
+            'ms-MY': 'MYR', // Malaysia
+            'id-ID': 'IDR', // Indonesia
+            'hi-IN': 'INR', // India
+            'ar-SA': 'SAR', // Saudi Arabia
+        
+            // Europe
+            'en-GB': 'GBP', // United Kingdom
+            'de-DE': 'EUR', // Germany
+            'fr-FR': 'EUR', // France
+            'it-IT': 'EUR', // Italy
+            'es-ES': 'EUR', // Spain
+            'pt-PT': 'EUR', // Portugal
+            'ru-RU': 'RUB', // 俄罗斯
+        
+            // North America
+            'en-US': 'USD', // United States
+            'en-CA': 'CAD', // Canada
+            'es-MX': 'MXN', // Mexico
+        
+            // South America
+            'pt-BR': 'BRL', // Brazil
+            'es-AR': 'ARS', // Argentina
+        
+            // Oceania
+            'en-AU': 'AUD', // Australia
+            'en-NZ': 'NZD', // New Zealand
+        
+            // Africa
+            'en-ZA': 'ZAR', // South Africa
+            'ar-EG': 'EGP', // Egypt
+        };
+    
+        // Try to match the full region code directly
+        if (localeToCurrency[localeCode]) {
+            return localeToCurrency[localeCode];
+        }
+    
+        // If no direct match, try to match the language part (the first two characters)
+        const languageCode = localeCode.split('-')[0];
+        const fallbackMap: Record<string, string> = {
+            'en': 'USD', // English defaults to USD
+            'zh': 'CNY', // Chinese defaults to CNY
+            'es': 'EUR', // Spanish defaults to EUR
+            'fr': 'EUR', // French defaults to EUR
+            'de': 'EUR', // German defaults to EUR
+            'it': 'EUR', // Italian defaults to EUR
+            'ja': 'JPY', // Japanese defaults to JPY
+            'ko': 'KRW', // Korean defaults to KRW
+            'ru': 'RUB', // Russian defaults to RUB
+            'ar': 'SAR', // Arabic defaults to SAR
+        };
+    
+        if (fallbackMap[languageCode]) {
+            return fallbackMap[languageCode];
+        }
+    
+        // If no match, return USD as the default value
+        return 'USD';
+    }
 }
 
 export default InputNumberFoundation;

+ 38 - 19
packages/semi-foundation/jsonViewer/foundation.ts

@@ -1,15 +1,15 @@
+import { JsonViewer, JsonViewerOptions, CustomRenderRule } from '@douyinfe/semi-json-viewer-core';
+import BaseFoundation, { DefaultAdapter } from '../base/foundation';
 
-import { JsonViewer, JsonViewerOptions } from '@douyinfe/semi-json-viewer-core';
-import BaseFoundation, { DefaultAdapter, noopFunction } from '../base/foundation';
-
-export type { JsonViewerOptions };
+export type { JsonViewerOptions, CustomRenderRule };
 export interface JsonViewerAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
     getEditorRef: () => HTMLElement;
     getSearchRef: () => HTMLInputElement;
     notifyChange: (value: string) => void;
     notifyHover: (value: string, el: HTMLElement) => HTMLElement | undefined;
     setSearchOptions: (key: string) => void;
-    showSearchBar: () => void
+    showSearchBar: () => void;
+    notifyCustomRender: (customRenderMap: Map<HTMLElement, any>) => void
 }
 
 class JsonViewerFoundation extends BaseFoundation<JsonViewerAdapter> {
@@ -23,6 +23,9 @@ class JsonViewerFoundation extends BaseFoundation<JsonViewerAdapter> {
         const props = this.getProps();
         const editorRef = this._adapter.getEditorRef();
         this.jsonViewer = new JsonViewer(editorRef, props.value, props.options);
+        this.jsonViewer.emitter.on('customRender', (e) => {
+            this._adapter.notifyCustomRender(e.customRenderMap);
+        });
         this.jsonViewer.layout();
         this.jsonViewer.emitter.on('contentChanged', (e) => {
             this._adapter.notifyChange(this.jsonViewer?.getModel().getValue());
@@ -30,26 +33,38 @@ class JsonViewerFoundation extends BaseFoundation<JsonViewerAdapter> {
                 this.search(this._adapter.getSearchRef().value);
             }
         });
-        this.jsonViewer.emitter.on('hoverNode', (e) => {
-            const el = this._adapter.notifyHover(e.value, e.target);
-            if (el) {
-                this.jsonViewer.emitter.emit('renderHoverNode', { el });
-            }
-        });
+
     }
 
-    search(searchText: string) {
-        const state = this.getState('searchOptions');
-        const { caseSensitive, wholeWord, regex } = state;
-        this.jsonViewer?.getSearchWidget().search(searchText, caseSensitive, wholeWord, regex);
+    search(searchText: string, caseSensitive?: boolean, wholeWord?: boolean, regex?: boolean) {
+        let options;
+        if (caseSensitive !== undefined || wholeWord !== undefined || regex !== undefined) {
+            options = {
+                caseSensitive: caseSensitive ?? false,
+                wholeWord: wholeWord ?? false,
+                regex: regex ?? false
+            };
+        } else {
+            options = this.getState('searchOptions');
+        }
+        const { caseSensitive: cs, wholeWord: ww, regex: rx } = options;
+        this.jsonViewer?.getSearchWidget().search(searchText, cs, ww, rx);
     }
 
-    prevSearch() {
-        this.jsonViewer?.getSearchWidget().navigateResults(-1);
+    prevSearch(step?: number) {
+        if (step === undefined) {
+            this.jsonViewer?.getSearchWidget().navigateResults(-1);
+        } else {
+            this.jsonViewer?.getSearchWidget().navigateResults(-step);
+        }
     }
 
-    nextSearch() {
-        this.jsonViewer?.getSearchWidget().navigateResults(1);
+    nextSearch(step?: number) {
+        if (step === undefined) {
+            this.jsonViewer?.getSearchWidget().navigateResults(1);
+        } else {
+            this.jsonViewer?.getSearchWidget().navigateResults(step);
+        }
     }
 
     replace(replaceText: string) {
@@ -73,6 +88,10 @@ class JsonViewerFoundation extends BaseFoundation<JsonViewerAdapter> {
     showSearchBar() {
         this._adapter.showSearchBar();
     }
+
+    getSearchResults() {
+        return this.jsonViewer?.getSearchWidget().searchResults;
+    }
 }
 
 export default JsonViewerFoundation;

+ 3 - 1
packages/semi-foundation/jsonViewer/jsonViewer.scss

@@ -83,6 +83,8 @@ $module: #{$prefix}-json-viewer;
         text-align: center;
         width: 50px;
         user-select: none;
+        word-wrap: normal !important;
+        overflow-wrap: normal !important;
     }
 
     &-content-container {
@@ -111,7 +113,7 @@ $module: #{$prefix}-json-viewer;
         align-items: flex-start;
         gap: 8px;
         &-input {
-            width: 200px;
+            width: 200px !important;
             flex-shrink: 0;
         }
         .#{$prefix}-button-group {

+ 10 - 5
packages/semi-foundation/navigation/foundation.ts

@@ -80,6 +80,11 @@ export default class NavigationFoundation<P = Record<string, any>, S = Record<st
                     }
                     if (itemKey) {
                         keysMap[itemKey] = [...parentKeys];
+                        // Children is not a recommended usage and may cause some bug-like performance, but some users have already used it, so here we only delete the ts definition instead of deleting the actual code
+                        // children 并不是我们推荐的用法,可能会导致一些像 bug的表现,但是有些用户已经用了,所以此处仅作删除 ts 定义而非删除实际代码的操作
+                        // refer https://github.com/DouyinFE/semi-design/issues/2710
+                        // @ts-ignore  
+                        const itemChildren = item.props?.children;
 
                         if (Array.isArray(item.items) && item.items.length) {
                             NavigationFoundation.buildItemKeysMap(
@@ -87,11 +92,11 @@ export default class NavigationFoundation<P = Record<string, any>, S = Record<st
                                 keysMap,
                                 [...parentKeys, itemKey],
                                 keyPropName
-                            );
-                        } else if (item.props && item.props.children) {
-                            const children = Array.isArray(item.props.children)
-                                ? item.props.children
-                                : [item.props.children];
+                            );  
+                        } else if (itemChildren) { 
+                            const children = Array.isArray(itemChildren) 
+                                ? itemChildren
+                                : [itemChildren];
                             NavigationFoundation.buildItemKeysMap(
                                 children,
                                 keysMap,

+ 1 - 2
packages/semi-foundation/navigation/itemFoundation.ts

@@ -13,8 +13,7 @@ export interface ItemProps {
     isSubNav?: boolean;
     link?: string;
     linkOptions?: Record<string, any>;
-    disabled?: boolean;
-    children?: any
+    disabled?: boolean
 }
 
 export type ItemKey = string | number;

+ 31 - 0
packages/semi-foundation/navigation/navigation.scss

@@ -483,6 +483,37 @@ $module: #{$prefix}-navigation;
                 }
             }
         }
+
+        // 兼容 renderWrapper 的场景,详见 issue: https://github.com/DouyinFE/semi-design/issues/2690
+        &-list  .#{$module}-sub-wrap {
+            & > .#{$module}-sub-title {
+
+                &-disabled {
+                    @include item-disabled;
+                }
+
+                &:hover {
+                    &:not(.#{$module}-sub-title-selected) {
+                        @include item-hover;
+                    }
+
+                    &.#{$module}-sub-title-selected {
+                        @include item-hover-selected;
+                    }
+                }
+
+                &:hover {
+                    &.#{$module}-sub-title-disabled {
+                        &:not(.#{$module}-sub-title-selected) {
+                            @include item-disabled;
+                        }
+                        &.#{$module}-sub-title-selected {
+                            @include item-disabled-selected;
+                        }
+                    }
+                }
+            }
+        }
     }
 
     .#{$module}-item:last-of-type {

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

@@ -47,9 +47,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) * 0.5; // 侧边导航栏 header 左侧内边距
+$spacing-navigation_vertical_nav_header-paddingLeft:  ($width-navigation_container_collapsed - $spacing-navigation_collapsed-paddingX * 2 - $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) * 0.5; // 侧边导航栏收起后 header 左侧内边距
+$spacing-navigation_vertical_nav_header_collapsed-paddingLeft: ($width-navigation_container_collapsed - $spacing-navigation_collapsed-paddingX * 2 - $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 右侧内边距

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

@@ -1,14 +1,14 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.74.0",
+    "version": "2.78.0",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",
         "prepublishOnly": "npm run build:lib"
     },
     "dependencies": {
-        "@douyinfe/semi-animation": "2.74.0",
-        "@douyinfe/semi-json-viewer-core": "2.74.0",
+        "@douyinfe/semi-animation": "2.78.0",
+        "@douyinfe/semi-json-viewer-core": "2.78.0",
         "@mdx-js/mdx": "^3.0.1",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",

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

@@ -22,6 +22,7 @@ $module: #{$prefix}-scrolllist;
     &-header {
         text-align: center;
         padding: $spacing-scrollList_header-paddingY $spacing-scrollList_header-paddingX;
+        background: $color-scrollList_header-bg;
 
         &-title {
             padding: $spacing-scrollList_header_title-paddingY $spacing-scrollList_header_title-paddingX;

+ 1 - 0
packages/semi-foundation/scrollList/variables.scss

@@ -1,6 +1,7 @@
 // Color
 $color-scrollList-bg: var(--semi-color-bg-3); // 滚动列表背景色
 $color-scrollList-border: var(--semi-color-border); // 滚动列表描边颜色
+$color-scrollList_header-bg: transparent; // 滚动列表 header 背景色
 $color-scrollList_header-title: var(--semi-color-text-0); // 滚动列表标题颜色
 $color-scrollList_item-bg: transparent; // 滚动列表选项背景色
 $color-scrollList_item-text: var(--semi-color-text-0); // 滚动列表选项文字颜色

+ 0 - 4
packages/semi-foundation/select/rtl.scss

@@ -40,10 +40,6 @@ $module: #{$prefix}-select;
             }
         }
 
-        &-inset-label {
-            margin-left: $spacing-select_insetLabel-marginRight;
-        }
-
         &-create-tips {
             margin-right: 0;
             margin-left: $spacing-select_create_tips-marginRight;

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

@@ -311,7 +311,7 @@ $overflowList: #{$prefix}-overflow-list;
             margin: $spacing-select_prefix_suffix_text-marginY $spacing-select_prefix_suffix_text-marginX;
             color: $color-select_prefix_suffix_text-default;
             @include font-size-regular;
-            font-weight: $font-weight-bold;
+            font-weight: $font-select_prefix_suffix-fontWeight;
         }
 
         &-icon {
@@ -339,10 +339,10 @@ $overflowList: #{$prefix}-overflow-list;
     }
 
     &-inset-label {
-        margin-right: $spacing-base-tight;
-        font-weight: $font-select_inset_label-fontWeight;
+        margin: $spacing-select_prefix_suffix_text-marginY $spacing-select_prefix_suffix_text-marginX;
+        font-weight: $font-select_prefix_suffix-fontWeight;
         @include font-size-regular;
-        color: $color-select_inset_label-text;
+        color: $color-select_prefix_suffix_text-default;
         flex-shrink: 0;
         white-space: nowrap;
     }

+ 1 - 3
packages/semi-foundation/select/variables.scss

@@ -55,7 +55,6 @@ $color-select_option_disabled-bg: transparent; // 禁用选择器菜单选项背
 $color-select_option-icon-active: var(--semi-color-text-2); // 禁用选择器菜单选项图标颜色 - 选中态
 $color-select_option-border-default: var(--semi-color-border); // 分组选择器菜单项描边颜色
 $color-select-option-bg-selected: transparent; // 选择器菜单选项背景颜色 - 选中态
-$color-select_inset_label-text: var(--semi-color-text-2); // 分组选择器菜单项辅助文本颜色
 $color-select_create_tips-text: var(--semi-color-text-2); // 分组选择器菜单项提示文本颜色
 $color-select_group-text: var(--semi-color-text-2); // 分组选择器菜单项分组标题文本颜色
 
@@ -109,7 +108,6 @@ $spacing-select_tag-marginTop: $spacing-super-tight - 1px; // 多项选择器标
 $spacing-select_tag-marginRight: $spacing-extra-tight; // 多项选择器标签右侧外边距
 $spacing-select_tag-marginBottom: $spacing-super-tight - 1px; // 多项选择器标签底部外边距
 $spacing-select_selection-marginLeft: $spacing-base-tight; // 选择器内容区左侧外边距
-$spacing-select_insetLabel-marginRight: $spacing-base-tight; // 选择器内嵌标签右侧外边距
 
 $spacing-select_option_list-paddingTop: $spacing-extra-tight; // 选择器内容区顶部内边距
 $spacing-select_option_list-paddingRight: 0px; // 选择器内容区右侧内边距
@@ -127,8 +125,8 @@ $radius-select_option: 0px; // 选择器待选项圆角
 
 // Font
 $font-select-fontWeight: $font-weight-regular; // 选择器文本字重
-$font-select_inset_label-fontWeight: 600; // 选择器内嵌标签文本字重
 $font-select_keyword-fontWeight: 600; // 选择器搜索结果命关键词中文本字重
+$font-select_prefix_suffix-fontWeight: $font-weight-bold; // 选择器输入框前后缀文本字重
 
 // Other
 $opacity-select_selection_text_inactive: 0.4;

+ 1 - 1
packages/semi-foundation/table/animation.scss

@@ -1,5 +1,5 @@
 $transition_duration-table_body-bg: var(--semi-transition_duration-none); // 表格-背景颜色-动画持续时间
 $transition_function-table_body-bg: var(--semi-transition_function-easeOut); // 表格-背景颜色-过渡曲线
 $transition_delay-table_body-bg: 0ms; // 表格-背景颜色-延迟时间
-$transition_duration-table_row-head-bg: 0.1s; // 表格-行头-背景颜色-动画持续时间
+$transition_duration-table_row-head-bg: var(--semi-transition_duration-none); // 表格-行头-背景颜色-动画持续时间
 $transition_function-table_row-head-bg: linear; // 表格-行头-背景颜色-过渡曲线

+ 6 - 5
packages/semi-foundation/tagInput/tagInput.scss

@@ -256,10 +256,10 @@ $module: #{$prefix}-tagInput;
     }
 
     &-inset-label {
-        margin-right: $spacing-tagInput_insetLabel-marginRight;
-        font-weight: $font-tagInput_insetLabel-fontWeight;
+        margin: 0 $spacing-tagInput_prefix_suffix-marginX;
+        font-weight: $font-tagInput_prefix_suffix-fontWeight;
         @include font-size-regular;
-        color: $color-tagInput_prefix-text-default;
+        color: $color-tagInput_prefix-default;
         flex-shrink: 0;
         white-space: nowrap;
     }
@@ -268,9 +268,10 @@ $module: #{$prefix}-tagInput;
     &-suffix {
         @include all-center;
         &-text {
-            margin: 0 $spacing-base-tight;
-            font-weight: 600;
+            margin: 0 $spacing-tagInput_prefix_suffix-marginX;
+            font-weight: $font-tagInput_prefix_suffix-fontWeight;
             white-space: nowrap;
+            @include font-size-regular;
         }
         &-icon {
             color: $color-tagInput-icon-default;

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

@@ -37,7 +37,7 @@ $spacing-tagInput_large-Y: 3px; // 大尺寸标签输入框标签顶部外边距
 $spacing-tagInput_wrapper_n_paddingX: $spacing-tight; //标签输入框标签容器水平内边距
 $spacing-tagInput_drag_handler-marginRight: 4px; // 拖拽handler icon的右外边距 
 $spacing-tagInput_tag_icon_paddingLeft: 4px; // tag中有handler icon时tag的左内边距
-$spacing-tagInput_insetLabel-marginRight: $spacing-base-tight; // Form容器中标签输入框内嵌Label的右边距
+$spacing-tagInput_prefix_suffix-marginX: $spacing-base-tight; // 标签输入框 prefix/suffix 水平外边距
 
 $height-tagInput-large: $height-control-large; // 大尺寸标签输入框高度
 $height-tagInput-default: $height-control-default; // 默认尺寸标签输入框高度
@@ -56,4 +56,4 @@ $radius-tagInput: var(--semi-border-radius-small); // 标签输入框圆角
 
 $z-tagInput_drag_item_move: 2000 !default; // 标签输入框中正在拖拽元素的z-index
 
-$font-tagInput_insetLabel-fontWeight: 600; // prefix 文字字重
+$font-tagInput_prefix_suffix-fontWeight: $font-weight-bold; // 标签输入框 prefix/suffix 文字字重

+ 2 - 1
packages/semi-foundation/timePicker/ComboxFoundation.ts

@@ -145,7 +145,8 @@ class ComboboxFoundation extends BaseFoundation<DefaultAdapter> {
      */
 
     createDateDefault() {
-        return new Date(parseInt(String(Date.now() / DAY), 10) * DAY - 8 * HOUR);
+        const now = new Date();
+        return new Date(now.getFullYear(), now.getMonth(), now.getDate());
     }
 }
 

+ 0 - 6
packages/semi-foundation/treeSelect/rtl.scss

@@ -40,12 +40,6 @@ $module: #{$prefix}-tree-select;
                 }
             }
         }
-    
-        &-inset-label {
-            margin-right: 0;
-            margin-left: $spacing-treeSelect_insertLabel-marginRight;
-        }
-    
         &-prefix,
         &-suffix {
             &.#{$module}-with-suffix {

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

@@ -297,10 +297,10 @@ $module: #{$prefix}-tree-select;
 
     &-inset-label {
         display: inline;
-        margin-right: $spacing-treeSelect_insertLabel-marginRight;
-        font-weight: $font-treeSelect_insertLabel-fontWeight;
+        margin: $spacing-treeSelect_prefix_text-marginY $spacing-treeSelect_prefix_text-marginX;
+        font-weight: $font-treeSelect_prefix_suffix_fontWeight;
         @include font-size-regular;
-        color: $color-treeSelect_insertLabel-text-default;
+        color: $color-treeSelect_prefix_suffix_text-default;
         flex-shrink: 0;
         white-space: nowrap;
     }
@@ -338,7 +338,7 @@ $module: #{$prefix}-tree-select;
 
         &-text {
             color: $color-treeSelect_prefix_suffix_text-default;
-            font-weight: $font-weight-bold;
+            font-weight: $font-treeSelect_prefix_suffix_fontWeight;
             @include font-size-regular;
             margin: $spacing-treeSelect_prefix_text-marginY $spacing-treeSelect_prefix_text-marginX;
         }

+ 2 - 3
packages/semi-foundation/treeSelect/variables.scss

@@ -63,6 +63,8 @@ $font-treeSelect_small-lineHeight: $height-treeSelect_small; // 小尺寸树选
 $height-treeSelect_large: $height-control-large; // 大尺寸树选择器选择器文本行高
 $font-treeSelect_large-lineHeight: $height-treeSelect_large; // 大尺寸树选择器选择器文本行高
 
+$font-treeSelect_prefix_suffix_fontWeight: $font-weight-bold; // 树选择器前缀后缀文本字重
+
 $spacing-treeSelect_selection_tagInput_wrapper-paddingX: $spacing-extra-tight; // 树选择器多选标签容器水平内边距
 $spacing-treeSelect_selection_tagInput_empty-marginLeft: $spacing-extra-tight; // 树选择器多选标签容器为空时左侧外边距
 $spacing-treeSelect_selection_tagInput_notEmpty-marginLeft: - $spacing-extra-tight; // 树选择器多选标签容器不为空时左侧外边距
@@ -75,7 +77,6 @@ $spacing-treeSelect_prefix_text-marginY: 0px; // 树选择器前缀文本垂直
 $spacing-treeSelect_prefix_text-marginX: $spacing-base-tight; // 树选择器前缀文本水平外边距
 $spacing-treeSelect_prefix_icon-marginY: 0px; // 树选择器前缀图标垂直外边距
 $spacing-treeSelect_prefix_icon-marginX: $spacing-tight; // 树选择器前缀图标水平外边距
-$spacing-treeSelect_insertLabel-marginRight: $spacing-base-tight; // 树选择器前缀标签右侧外边距
 $spacing-treeSelect_selection_withSuffix-paddingRight: 0px; // 树选择器有后缀时内容右侧内边距
 $spacing-treeSelect_search_wrapper-paddingX: 12px; // 树选择器搜索框容器水平内边距
 $spacing-treeSelect_search_wrapper-paddingY: 8px; // 树选择器搜索框容器垂直内边距
@@ -83,8 +84,6 @@ $spacing-treeSelect_search_wrapper-paddingY: 8px; // 树选择器搜索框容器
 $color-treeSelect_selection_TriggerSearchItem_disabled-text-default: var(--semi-color-disabled-text); // 带搜索的树选择器触发器占位文本默认颜色
 $color-treeSelect_inputTrigger-border-default: none; // 带搜索的树选择器触发器描边颜色
 $color-treeSelect_inputTrigger-bg-default: transparent; // 带搜索的树选择器触发器背景颜色
-$color-treeSelect_insertLabel-text-default: var(--semi-color-text-2); // 带搜索的树前缀标签文本颜色
-$font-treeSelect_insertLabel-fontWeight: 600; // 带搜索的树前缀标签文本字重
 $color-treeSelect_prefix_suffix_text-default: var(--semi-color-text-2); // 带搜索的树选择器前后缀文本颜色
 
 $width-treeSelect_arrow: 32px; // 树选择器展开箭头宽度

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