Browse Source

Merge branch 'release' into feat/KeyboardShortCut

pointhalo 1 year ago
parent
commit
ebb58e7d29
100 changed files with 925 additions and 348 deletions
  1. 3 1
      .github/workflows/cypress.yml
  2. 1 1
      CONTRIBUTING.md
  3. 14 1
      content/basic/typography/index-en-US.md
  4. 14 1
      content/basic/typography/index.md
  5. 104 63
      content/input/colorpicker/index-en-US.md
  6. 37 26
      content/input/colorpicker/index.md
  7. 9 17
      content/input/datepicker/index.md
  8. 37 42
      content/input/treeselect/index-en-US.md
  9. 3 8
      content/input/treeselect/index.md
  10. 1 1
      content/navigation/tree/index-en-US.md
  11. 1 1
      content/navigation/tree/index.md
  12. 6 0
      content/plus/markdownrender/index-en-US.md
  13. 13 6
      content/plus/markdownrender/index.md
  14. 2 0
      content/show/image/index-en-US.md
  15. 2 0
      content/show/image/index.md
  16. 5 0
      content/show/table/index-en-US.md
  17. 6 0
      content/show/table/index.md
  18. 20 3
      content/start/changelog/index-en-US.md
  19. 24 4
      content/start/changelog/index.md
  20. 15 0
      cypress/e2e/datePicker.spec.js
  21. 1 1
      lerna.json
  22. 2 2
      package.json
  23. 3 3
      packages/semi-animation-react/package.json
  24. 1 1
      packages/semi-animation-styled/package.json
  25. 1 1
      packages/semi-animation/package.json
  26. 1 1
      packages/semi-eslint-plugin/package.json
  27. 1 1
      packages/semi-foundation/chat/chat.scss
  28. 13 1
      packages/semi-foundation/datePicker/datePicker.scss
  29. 3 4
      packages/semi-foundation/datePicker/monthsGridFoundation.ts
  30. 3 14
      packages/semi-foundation/input/foundation.ts
  31. 2 14
      packages/semi-foundation/input/textareaFoundation.ts
  32. 25 0
      packages/semi-foundation/input/util/truncateValue.ts
  33. 9 3
      packages/semi-foundation/markdownRender/foundation.ts
  34. 6 4
      packages/semi-foundation/overflowList/foundation.ts
  35. 2 2
      packages/semi-foundation/package.json
  36. 2 0
      packages/semi-foundation/table/animation.scss
  37. 17 0
      packages/semi-foundation/table/table.scss
  38. 1 0
      packages/semi-foundation/table/variables.scss
  39. 1 1
      packages/semi-icons-lab/package.json
  40. 1 1
      packages/semi-icons/package.json
  41. 1 1
      packages/semi-illustrations/package.json
  42. 2 2
      packages/semi-next/package.json
  43. 1 1
      packages/semi-rspack/package.json
  44. 1 1
      packages/semi-scss-compile/package.json
  45. 1 1
      packages/semi-theme-default/package.json
  46. 12 13
      packages/semi-ui/chat/index.tsx
  47. 17 1
      packages/semi-ui/datePicker/_story/DatePickerSlot/index.jsx
  48. 1 0
      packages/semi-ui/datePicker/_story/datePicker.stories.jsx
  49. 8 0
      packages/semi-ui/datePicker/_story/v2/FixedSelectedStatus.jsx
  50. 1 0
      packages/semi-ui/datePicker/_story/v2/index.js
  51. 27 13
      packages/semi-ui/datePicker/datePicker.tsx
  52. 2 1
      packages/semi-ui/image/_story/image.stories.tsx
  53. 2 2
      packages/semi-ui/image/interface.tsx
  54. 78 23
      packages/semi-ui/input/__test__/textArea.test.js
  55. 4 1
      packages/semi-ui/locale/interface.ts
  56. 3 0
      packages/semi-ui/locale/source/ar.ts
  57. 3 0
      packages/semi-ui/locale/source/de.ts
  58. 3 0
      packages/semi-ui/locale/source/en_GB.ts
  59. 3 0
      packages/semi-ui/locale/source/en_US.ts
  60. 3 0
      packages/semi-ui/locale/source/es.ts
  61. 3 0
      packages/semi-ui/locale/source/fr.ts
  62. 3 0
      packages/semi-ui/locale/source/id_ID.ts
  63. 3 0
      packages/semi-ui/locale/source/it.ts
  64. 3 0
      packages/semi-ui/locale/source/ja_JP.ts
  65. 3 0
      packages/semi-ui/locale/source/ko_KR.ts
  66. 3 0
      packages/semi-ui/locale/source/ms_MY.ts
  67. 3 0
      packages/semi-ui/locale/source/nl_NL.ts
  68. 3 0
      packages/semi-ui/locale/source/pl_PL.ts
  69. 3 0
      packages/semi-ui/locale/source/pt_BR.ts
  70. 3 0
      packages/semi-ui/locale/source/ro.ts
  71. 3 0
      packages/semi-ui/locale/source/ru_RU.ts
  72. 3 0
      packages/semi-ui/locale/source/sv_SE.ts
  73. 3 0
      packages/semi-ui/locale/source/th_TH.ts
  74. 5 1
      packages/semi-ui/locale/source/tr_TR.ts
  75. 3 0
      packages/semi-ui/locale/source/vi_VN.ts
  76. 3 0
      packages/semi-ui/locale/source/zh_CN.ts
  77. 3 0
      packages/semi-ui/locale/source/zh_TW.ts
  78. 45 0
      packages/semi-ui/markdownRender/__test__/markdown.test.js
  79. 1 1
      packages/semi-ui/markdownRender/components/table.tsx
  80. 3 1
      packages/semi-ui/markdownRender/index.tsx
  81. 2 3
      packages/semi-ui/navigation/_story/NumberItemKey/index.jsx
  82. 8 7
      packages/semi-ui/overflowList/index.tsx
  83. 6 6
      packages/semi-ui/package.json
  84. 4 1
      packages/semi-ui/table/Body/BaseRow.tsx
  85. 1 0
      packages/semi-ui/table/ColumnShape.ts
  86. 30 13
      packages/semi-ui/table/ColumnSorter.tsx
  87. 18 7
      packages/semi-ui/table/Table.tsx
  88. 37 10
      packages/semi-ui/table/TableHeaderRow.tsx
  89. 2 1
      packages/semi-ui/table/_story/table.stories.jsx
  90. 54 0
      packages/semi-ui/table/_story/v2/FixedIndent/index.tsx
  91. 1 0
      packages/semi-ui/table/_story/v2/index.js
  92. 2 1
      packages/semi-ui/table/interface.ts
  93. 12 1
      packages/semi-ui/table/utils.ts
  94. 12 0
      packages/semi-ui/tabs/_story/tabs.stories.jsx
  95. 1 1
      packages/semi-ui/tree/interface.ts
  96. 1 1
      packages/semi-ui/tree/treeContext.tsx
  97. 1 1
      packages/semi-ui/tree/treeNode.tsx
  98. 35 0
      packages/semi-ui/typography/__test__/typography.test.js
  99. 8 2
      packages/semi-ui/typography/copyable.tsx
  100. 3 1
      packages/semi-ui/typography/title.tsx

+ 3 - 1
.github/workflows/cypress.yml

@@ -30,7 +30,9 @@ jobs:
             - name: Checkout
               uses: actions/checkout@v3
             - name: Install global packages
-              run: npm i -g lerna@^6 yarn
+              run: 
+                  npm i -g lerna@^6
+                  corepack enable
             - name: Build storybook
               run: |
                   yarn bootstrap

+ 1 - 1
CONTRIBUTING.md

@@ -15,7 +15,7 @@ Semi 团队会维护两个常驻分支:`main` 和 `release`,根据我们的[
 Semi Design 团队会认真对待每一个 Pull Request。我们会 review 并合并你的代码。也有可能对你的代码提出一些修改意见。
 
 要提交一个 Pull Request,请遵循以下步骤:
- - Node.js > v16
+ - Node.js > v20
  - Fork 项目并克隆下来
 ```bash
 git clone https://github.com/<your-username>/semi-design.git

+ 14 - 1
content/basic/typography/index-en-US.md

@@ -292,10 +292,11 @@ Copying of text can be supported by configuring the `copyable` property.
 When copyable is configured as true, the default copied content is children itself. Note that at this time, children only support string type.    
 When copyable is configured as object, you can specify the content copied to the clipboard through `copyable.content`, which is no longer strongly associated with children.   
 At this time, children will no longer limit the type, but `copyable.content` still needs to be a string.  
+You can use the `copyable.render` attribute to customize the copyable button render.
 
 ```jsx live=true
 import React from 'react';
-import { Typography, TextArea } from '@douyinfe/semi-ui';
+import { Typography, TextArea, Button } from '@douyinfe/semi-ui';
 import { IconSetting } from '@douyinfe/semi-icons';
 
 function Demo() {
@@ -308,6 +309,18 @@ function Demo() {
             <Paragraph copyable={{ onCopy: () => Toast.success({ content: 'Successfully copied.' }) }}>Click the right icon to copy.</Paragraph>
             Timestamp: <Numeral truncate="ceil" copyable underline>{new Date().getTime()/1000}s</Numeral>
             <Paragraph copyable={{ icon: <IconSetting style={{ color: 'var(--semi-color-link)' }}/> }}>Custom Copy Node</Paragraph>
+            <Paragraph copyable={{
+                content: 'Custom render!',
+                render: (copied, doCopy, config) => {
+                    return (
+                        <Button size="small" onClick={doCopy}>
+                            <span>{copied ? 'Copy success' : `Click to copy: ${config.content}`}</span>
+                        </Button>
+                    );
+                }
+            }}>
+                Custom Copy Render
+            </Paragraph>
             <br/>
             <br/>
             <Text type="secondary">Paste here: </Text>

+ 14 - 1
content/basic/typography/index.md

@@ -280,10 +280,11 @@ function Demo() {
 可通过配置 copyable 属性支持文本的复制。  
 当 copyable 配置为 true时,默认复制内容为 children 本身,注意,此时 children 只支持 string类型传入    
 当 copyable 配置为 object 时,可通过 `copyable.content` 指定复制至粘贴板的内容,与 children 不再强关联, 此时 children 将不再限定类型,但 `copyable.content` 仍需要为 string    
+可以通过 `copyable.render` 属性,自定义复制按钮的渲染逻辑
 
 ```jsx live=true
 import React from 'react';
-import { Typography, TextArea } from '@douyinfe/semi-ui';
+import { Typography, TextArea, Button } from '@douyinfe/semi-ui';
 import { IconSetting } from '@douyinfe/semi-icons';
 
 function Demo() {
@@ -296,6 +297,18 @@ function Demo() {
             <Paragraph copyable={{ onCopy: () => Toast.success({ content: '复制文本成功' }) }}>点击右边的图标复制文本。</Paragraph>
             时间戳: <Numeral truncate="ceil" copyable underline>{new Date().getTime()/1000}s</Numeral>
             <Paragraph copyable={{ icon: <IconSetting style={{ color: 'var(--semi-color-link)' }}/> }}>自定义复制节点</Paragraph>
+            <Paragraph copyable={{
+                content: 'Custom render!',
+                render: (copied, doCopy, config) => {
+                    return (
+                        <Button size="small" onClick={doCopy}>
+                            <span>{copied ? '复制成功' : `点击复制:${config.content}`}</span>
+                        </Button>
+                    );
+                }
+            }}>
+                自定义复制渲染
+            </Paragraph>
             <br/>
             <br/>
             <Text type="secondary">粘贴区域:</Text>

+ 104 - 63
content/input/colorpicker/index-en-US.md

@@ -7,47 +7,67 @@ icon: doc-colorPlatteNew
 brief: Quickly and easily select colors, and provide a dropper tool to pick colors
 ---
 
-
-
 ## Demos
 
 ### How to import
 
+ColorPicker component supported from v2.64.0
 
 ```jsx import
 import { ColorPicker } from '@douyinfe/semi-ui';
 ```
 
-
 ### Basic Use
 
 #### In portal
 
 ```jsx live=true
+import React from 'react';
 import { ColorPicker, Button } from '@douyinfe/semi-ui';
-function Demo(){
-    return <div>
-        <ColorPicker alpha={true} onChange={value=>{console.log(value)}} usePopover={true}/>
-        
-        <br/>
-        <div>自定义 trigger</div>
-
-        <ColorPicker alpha={true} onChange={value=>{console.log(value)}} usePopover={true}>
-            <Button> Trigger </Button>
-        </ColorPicker>
-        
-    </div>
+function Demo() {
+    return (
+        <div>
+            <ColorPicker
+                alpha={true}
+                onChange={value => {
+                    console.log(value);
+                }}
+                usePopover={true}
+            />
+
+            <br />
+            <div>自定义 trigger</div>
+
+            <ColorPicker
+                alpha={true}
+                onChange={value => {
+                    console.log(value);
+                }}
+                usePopover={true}
+            >
+                <Button> Trigger </Button>
+            </ColorPicker>
+        </div>
+    );
 }
-
 ```
 
 #### Normal display
+
 ```jsx live=true
 import { ColorPicker } from '@douyinfe/semi-ui';
-function Demo(){
-    return <ColorPicker alpha={true} onChange={value=>{console.log(value)}}/>
+import React from 'react';
+
+function Demo() {
+    return (
+        <ColorPicker
+            alpha={true}
+            onChange={value => {
+                console.log(value);
+            }}
+        />
+    );
 }
-
 ```
 
 ### Eyedropper Color Picker
@@ -58,13 +78,20 @@ Use `eyeDropper={true}` to enable the eyedropper function, which supports pickin
 To enable this function, the current web page must be deployed in a secure context such as HTTPS or localhost domain name, otherwise it will have no effect. The user's browser version must be Chromium > 95
 </Notice>
 
-
 ```jsx live=true
+import React from 'react';
 import { ColorPicker } from '@douyinfe/semi-ui';
-function Demo(){
-    return <ColorPicker alpha={true} eyeDropper={true} onChange={value=>{console.log(value)}}/>
+function Demo() {
+    return (
+        <ColorPicker
+            alpha={true}
+            eyeDropper={true}
+            onChange={value => {
+                console.log(value);
+            }}
+        />
+    );
 }
-
 ```
 
 ### Default Value
@@ -76,18 +103,21 @@ The defaultValue (uncontrolled) and value (controlled) you pass in should also b
 We provide a static tool function `colorStringToValue` on the component class to convert common color strings to this object, supporting direct passing of strings such as rgb(57,197,187) #39c5bb and hsv(176,71,77).
 
 ```jsx live=true
+import React from 'react';
 import { ColorPicker } from '@douyinfe/semi-ui';
-function Demo(){
-    return <div>
-        <ColorPicker 
-            defaultValue={ColorPicker.colorStringToValue("rgb(57,197,187)")}
-            onChange={(value)=>{
-            console.log(value)
-        }} className={""} alpha={true}/>
-    </div>
-
+function Demo() {
+    return (
+        <div>
+            <ColorPicker
+                defaultValue={ColorPicker.colorStringToValue('rgb(57,197,187)')}
+                onChange={value => {
+                    console.log(value);
+                }}
+                alpha={true}
+            />
+        </div>
+    );
 }
-
 ```
 
 ### Controlled
@@ -95,49 +125,60 @@ function Demo(){
 Controlled use by passing in value
 
 ```jsx live=true
+import React from 'react';
 import { ColorPicker } from '@douyinfe/semi-ui';
-function Demo(){
-    const [value,setValue] = useState(ColorPicker.colorStringToValue("#39c5bb"));
+function Demo() {
+    const [value, setValue] = useState(ColorPicker.colorStringToValue('#39c5bb'));
     console.log(value);
-    return <div>
-        <ColorPicker value={value} onChange={(value)=>{
-            setValue(value)
-        }} className={""} alpha={true}/>
-    </div>
-
+    return (
+        <div>
+            <ColorPicker
+                value={value}
+                onChange={value => {
+                    setValue(value);
+                }}
+                alpha={true}
+            />
+        </div>
+    );
 }
-
 ```
 
-
 ### Rendering additional elements at the top and bottom
 
 Use `topSlot` and `bottomSlot` to render additional elements at the top and bottom
 
 ```jsx live=true
+import React from 'react';
 import { ColorPicker } from '@douyinfe/semi-ui';
-function Demo(){
-    return <ColorPicker topSlot={<div>
-        TopSlot
-    </div>} bottomSlot={<div>Bottom Slot</div>} alpha={true} onChange={value=>{console.log(value)}}/>
+function Demo() {
+    return (
+        <ColorPicker
+            topSlot={<div>TopSlot</div>}
+            bottomSlot={<div>Bottom Slot</div>}
+            alpha={true}
+            onChange={value => {
+                console.log(value);
+            }}
+        />
+    );
 }
-
 ```
 
 ### API Table
 
-| Parameter | Description | Type | Default value |
-|---------------|------------|---------------|------|
-| onChange | User selected color callback | (value)=>void | - |
-| alpha | Whether to enable transparency selection | boolean | true |
-| bottomSlot | Bottom rendering additional elements | ReactNode | - |
-| className | Class name | string | - |
-| defaultFormat | Default format for manual input | rgba hex hsva | hex |
-| defaultValue | Default value | Object | - |
-| eyeDropper | Whether to enable the eyedropper color picker | boolean | true |
-| height | Height | number | 280 |
-| style | Style | CSSProperties | - |
-| topSlot | Top rendering additional elements | ReactNode | - |
-| width | Width | number | 280 |
-| usePopover | Whether to put in Popover rendering | boolean | false |
-| popoverProps | When placing a Popover, the props passed to the Popover | Popover Props | - |
+| Parameter     | Description                                             | Type          | Default value |
+| ------------- | ------------------------------------------------------- | ------------- | ------------- |
+| onChange      | User selected color callback                            | (value)=>void | -             |
+| alpha         | Whether to enable transparency selection                | boolean       | true          |
+| bottomSlot    | Bottom rendering additional elements                    | ReactNode     | -             |
+| className     | Class name                                              | string        | -             |
+| defaultFormat | Default format for manual input                         | rgba hex hsva | hex           |
+| defaultValue  | Default value                                           | Object        | -             |
+| eyeDropper    | Whether to enable the eyedropper color picker           | boolean       | true          |
+| height        | Height                                                  | number        | 280           |
+| style         | Style                                                   | CSSProperties | -             |
+| topSlot       | Top rendering additional elements                       | ReactNode     | -             |
+| width         | Width                                                   | number        | 280           |
+| usePopover    | Whether to put in Popover rendering                     | boolean       | false         |
+| popoverProps  | When placing a Popover, the props passed to the Popover | Popover Props | -             |

+ 37 - 26
content/input/colorpicker/index.md

@@ -13,6 +13,7 @@ brief: 快速便捷地选择颜色,并提供滴管工具取色
 
 ### 如何引入
 
+ColorPicker 从 v2.64.0 开始支持
 
 ```jsx import
 import { ColorPicker } from '@douyinfe/semi-ui';
@@ -24,28 +25,27 @@ import { ColorPicker } from '@douyinfe/semi-ui';
 #### 放在弹层
 
 ```jsx live=true
+import React from 'react';
 import { ColorPicker, Button } from '@douyinfe/semi-ui';
-function Demo(){
+function Demo() {
     return <div>
-        <ColorPicker alpha={true} onChange={value=>{console.log(value)}} usePopover={true}/>
-        
+        <ColorPicker alpha={true} onChange={value=>{console.log(value);}} usePopover={true}/>
         <br/>
         <div>自定义 trigger</div>
-
-        <ColorPicker alpha={true} onChange={value=>{console.log(value)}} usePopover={true}>
+        <ColorPicker alpha={true} onChange={value=>{console.log(value);}} usePopover={true}>
             <Button> Trigger </Button>
         </ColorPicker>
-        
-    </div>
+    </div>;
 }
 
 ```
 
 #### 正常展示
 ```jsx live=true
+import React from 'react';
 import { ColorPicker } from '@douyinfe/semi-ui';
-function Demo(){
-    return <ColorPicker alpha={true} onChange={value=>{console.log(value)}}/>
+function Demo() {
+    return <ColorPicker alpha={true} onChange={value=>{console.log(value);}}/>;
 }
 
 ```
@@ -60,9 +60,10 @@ function Demo(){
 
 
 ```jsx live=true
+import React from 'react';
 import { ColorPicker } from '@douyinfe/semi-ui';
-function Demo(){
-    return <ColorPicker alpha={true} eyeDropper={true} onChange={value=>{console.log(value)}}/>
+function Demo() {
+    return <ColorPicker alpha={true} eyeDropper={true} onChange={value=>{console.log(value);}}/>;
 }
 
 ```
@@ -76,14 +77,16 @@ function Demo(){
 
 ```jsx live=true
 import { ColorPicker } from '@douyinfe/semi-ui';
-function Demo(){
+import React from 'react';
+
+function Demo() {
     return <div>
         <ColorPicker 
             defaultValue={ColorPicker.colorStringToValue("rgb(57,197,187)")}
             onChange={(value)=>{
-            console.log(value)
-        }} className={""} alpha={true}/>
-    </div>
+                console.log(value);
+            }} className={""} alpha={true}/>
+    </div>;
 
 }
 
@@ -95,14 +98,18 @@ function Demo(){
 
 ```jsx live=true
 import { ColorPicker } from '@douyinfe/semi-ui';
-function Demo(){
-    const [value,setValue] = useState(ColorPicker.colorStringToValue("#39c5bb"));
-    console.log(value);
+import React from 'react';
+function Demo() {
+    const [value, setValue] = useState(ColorPicker.colorStringToValue("#39c5bb"));
     return <div>
-        <ColorPicker value={value} onChange={(value)=>{
-            setValue(value)
-        }} className={""} alpha={true}/>
-    </div>
+        <ColorPicker
+            value={value}
+            onChange={(value)=>{
+                setValue(value);
+            }}
+            alpha={true}
+        />
+    </div>;
 
 }
 
@@ -114,11 +121,15 @@ function Demo(){
 使用 `topSlot` 和 `bottomSlot` 在顶部和底部渲染额外元素
 
 ```jsx live=true
+import React from 'react';
 import { ColorPicker } from '@douyinfe/semi-ui';
-function Demo(){
-    return <ColorPicker topSlot={<div>
-        TopSlot
-    </div>} bottomSlot={<div>Bottom Slot</div>} alpha={true} onChange={value=>{console.log(value)}}/>
+function Demo() {
+    return <ColorPicker
+        topSlot={<div> TopSlot</div>}
+        bottomSlot={<div>Bottom Slot</div>}
+        alpha={true}
+        onChange={value=>{console.log(value);}}
+    />;
 }
 
 ```

+ 9 - 17
content/input/datepicker/index.md

@@ -27,7 +27,7 @@ import { DatePicker } from '@douyinfe/semi-ui';
 
 ### 小尺寸
 
-使用 density 可以控制日期面板的尺寸,`compact` 为小尺寸,`default` 为默认尺寸。v1.17.0 后支持。
+使用 density 可以控制日期面板的尺寸,`compact` 为小尺寸,`default` 为默认尺寸。
 
 ```jsx live=true
 import React from 'react';
@@ -186,8 +186,6 @@ function Demo() {
 
 ### 同步切换双面板月份
 
-version: >= 1.28.0
-
 在范围选择的场景中, 开启 `syncSwitchMonth` 则允许双面板同步切换。默认为 false。
 
 > Note:点击年份按钮也会同步切换两个面板,从滚轮里面切换年月不会同步切换面板,这保证了用户选择非固定间隔月份的能力。
@@ -208,8 +206,6 @@ import { DatePicker } from '@douyinfe/semi-ui';
 
 ### 切换面板日期的回调
 
-版本:>=1.28.0
-
 `onPanelChange` 回调函数会在面板的月份或年份切换改变时被调用。
 
 ```jsx live=true
@@ -228,7 +224,7 @@ import { DatePicker } from '@douyinfe/semi-ui';
 
 ### 周选择
 
-dateRange 搭配 startDateOffset 和 endDateOffset 可以进行单击范围选择,如周选择、双周选择。v1.10.0 后支持。
+dateRange 搭配 startDateOffset 和 endDateOffset 可以进行单击范围选择,如周选择、双周选择。
 
 ```jsx live=true
 import React from 'react';
@@ -280,8 +276,6 @@ function Demo() {
 
 ### 年月选择
 
-**版本:** >= 0.21.0
-
 将 `type` 设定为 `month`,可以进行年月选择。
 
 ```jsx live=true
@@ -306,8 +300,6 @@ import { DatePicker } from '@douyinfe/semi-ui';
 
 ### 确认日期时间选择
 
-**版本:** >= 0.18.0
-
 对于“日期时间”(type="dateTime")或“日期时间范围”(type="dateTimeRange")的选择,可以进行确认后才将值写入输入框内,你可以通过传递 needConfirm=true 来开启这种行为。
 
 同时支持 “确认”(onConfirm) 和 “取消”(onCancel) 两个按钮的点击回调。
@@ -364,7 +356,8 @@ import { DatePicker } from '@douyinfe/semi-ui';
 
 ### 渲染顶部/底部额外区域
 
-通过 `topSlot` 和 `bottomSlot` 可以自定义渲染顶部和底部额外区域。
+通过 `topSlot` 和 `bottomSlot` 可以自定义渲染顶部和底部额外区域     
+通过 `leftSlot` 和 `rightSlot` 可以自定义渲染左侧和右侧额外区域(v2.65.0后支持)
 
 ```jsx live=true
 import React, { useState, useMemo } from 'react';
@@ -641,8 +634,6 @@ import { DatePicker } from '@douyinfe/semi-ui';
 
 ### 自定义触发器
 
-**版本:**>=0.34.0
-
 默认情况下我们使用 `Input` 组件作为 `DatePicker` 组件的触发器,通过传递 `triggerRender` 方法你可以自定义这个触发器。
 
 自定义触发器是对触发器的完全自定义,默认的清除按钮将不生效,如果你需要清除功能,请自定义一个清除按钮。
@@ -735,8 +726,6 @@ function Demo() {
 
 ### 自定义日期显示内容
 
-**版本:**>=1.4.0
-
 `renderDate: (dayNumber: number, fullDate: string) => ReactNode`,自定义日期内容。
 
 -   `dayNumber`:当前日。如 `13`。
@@ -771,8 +760,6 @@ function Demo() {
 
 ### 自定义日期格子渲染
 
-**版本:**>=1.4.0
-
 `renderFullDate: (dayNumber: number, fullDate: string, dayStatus: object) => ReactNode`, 自定义日期格子的渲染内容。
 
 `dayStatus` 表示当前格子的状态,包括的 `key` 有:
@@ -872,6 +859,7 @@ function Demo() {
 | inputReadOnly | 文本框是否 readonly                                                                                       | boolean | false |  |
 | inputStyle | 输入框样式                                                                                                | object |  |  |
 | insetLabel | 前缀标签,优先级低于 `prefix`                                                                                  | string\|ReactNode |  |  |
+| leftSlot   | 渲染左侧额外区域                                                                                             | ReactNode |         |  **2.65.0** |
 | max | multiple 为 true 时,多选的数目,不传或者值为 null\|undefined 的话无限制                                                 | number | - |  |
 | motion | 是否开启面板展开的动画                                                                                          | boolean | true |  |
 | multiple | 是否可以选择多个,仅支持 type="date"                                                                             | boolean | false |  |
@@ -883,9 +871,13 @@ function Demo() {
 | presets | 日期时间快捷方式, start 和 end 在 v2.52 版本支持函数类型                                                                                            |  <ApiType detail='type PresetType = { start?: BaseValueType \| (() => BaseValueType); end?: BaseValueType \| (() => BaseValueType); text?: string }; type PresetsType = Array<PresetType \| (() => PresetType)>;'>Array</ApiType> | [] |  |
 | preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法                                                               | boolean |  |  |
 | presetPosition | 日期时间快捷方式面板位置, 可选值'left', 'right', 'top', 'bottom'                                                    | string |  'bottom' | **2.18.0** |
+| rangeSeparator | 自定义范围类型输入框的日期分隔符                                                                                     | string | '~' |  |
+| renderDate | 自定义日期显示内容                                                                                            | (dayNumber, fullDate) => ReactNode | - |  |
+| renderFullDate | 自定义显示日期格子内容                                                                                          | (dayNumber, fullDate, dayStatus) => ReactNode | - |  |
 | rangeSeparator | 自定义范围类型输入框的日期分隔符                                                                                     | string | '~' | |
 | renderDate | 自定义日期显示内容                                                                                            | (dayNumber, fullDate) => ReactNode | - |  |
 | renderFullDate | 自定义显示日期格子内容                                                                                          | (dayNumber, fullDate, dayStatus) => ReactNode | - |  |
+| rightSlot         | 渲染右侧额外区域                                                                                             | ReactNode |         | **2.65.0** |
 | showClear | 是否显示清除按钮                                                                                             | boolean | true |  |
 | size | 尺寸,可选值:"small", "default", "large"                                                                   | string | 'default' |  |
 | spacing | 浮层与 trigger 的距离                                                                                      | number | 4 |  |

+ 37 - 42
content/input/treeselect/index-en-US.md

@@ -76,7 +76,7 @@ import { TreeSelect } from '@douyinfe/semi-ui';
 ### Multi-choice
 
 You could use `multiple` to set mode to multi-choice. When all child items are selected, the parent item will be selected.  
-Use `leafOnly` (>= v0.32.0) if you prefer to render leaf nodes only and the corresponding params for onChange will also be leaf nodes values.  
+Use `leafOnly` if you prefer to render leaf nodes only and the corresponding params for onChange will also be leaf nodes values.  
 
 ```jsx live=true
 import React from 'react';
@@ -676,8 +676,6 @@ import { TreeSelect } from '@douyinfe/semi-ui';
 
 ### Disable Strictly
 
-version: >= 1.30.0
-
 You can use `disableStrictly` to enable strict disabling. After enabling strict disabling, when the node is disabled, the selected state cannot be changed through the relationship between the child or the parent.
 
 Take the following demo as an example, the node "China" is strictly disabled. Therefore, when we change the selected state of its parent node "Asia", it will not affect the selected state of the node "China".
@@ -743,8 +741,6 @@ import { TreeSelect } from '@douyinfe/semi-ui';
 
 Both `defaultExpandAll` and `expandAll` can set the default expanded/collapsed state of `TreeSelect`. The difference between the two is that `defaultExpandAll` only takes effect during initialization, while `expandAll` will take effect not only during initialization, but also when the data (`treeData`) is dynamically updated.
 
-Among them, `expandAll` is supported starting from 1.30.0.
-
 In the demo below, after `TreeData` is updated, `defaultExpandAll` becomes invalid, and `expandAll` still takes effect.
 
 ```jsx live=true
@@ -1409,86 +1405,85 @@ function Demo() {
 
 | Properties               | Instructions                                                                        | type                                                              | Default     | Version |
 | ------------------------ | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ----------- | ------- |
-| arrowIcon|Customize the right drop-down arrow Icon, when the showClear switch is turned on and there is currently a selected value, hover will give priority to the clear icon| ReactNode | | 1.15.0|
-|autoAdjustOverflow|Whether the pop-up layer automatically adjusts the direction when it is obscured (only vertical direction is supported for the time being, and the inserted parent is body)|boolean | true| 0.34.0|
-| autoExpandParent | Toggle whether to expand parent nodes automatically | boolean | false | 0.34.0 |
+| arrowIcon|Customize the right drop-down arrow Icon, when the showClear switch is turned on and there is currently a selected value, hover will give priority to the clear icon| ReactNode | | - |
+|autoAdjustOverflow|Whether the pop-up layer automatically adjusts the direction when it is obscured (only vertical direction is supported for the time being, and the inserted parent is body)|boolean | true| - |
+| autoExpandParent | Toggle whether to expand parent nodes automatically | boolean | false | - |
 | autoMergeValue | Sets the automerge value. Specifically, when enabled, when a parent node is selected, value will include that node and its children. (Works if leafOnly is false)| boolean | true | 2.61.0 | 
 | borderless        | borderless mode  >=2.33.0                                                                                                                                                                     | boolean                         |           |
 | checkRelation | In multiple, the relationship between the checked states of the nodes, optional: 'related'、'unRelated' | string | 'related' | 2.5.0 |
 | className                | Class name                                                                          | string                                                            | -           | -       |
 | clearIcon    | Can be used to customize the clear button, valid when showClear is true                       | ReactNode                |       | 2.25.0    |
-| clickToHide  | Whether to close the drop-down layer automatically when selecting, only works in single-selection mode  | boolean    | true | 1.5.0      |
+| clickToHide  | Whether to close the drop-down layer automatically when selecting, only works in single-selection mode  | boolean    | true | - |
 | clickTriggerToHide  | When the panel is open, whether to close the panel after clicking the Trigger  | boolean    | true | 2.32.0      |
-| defaultExpandAll    | Set whether to expand all nodes during initialization. And if the data (`treeData`) changes, this api cannot affect the expansion of the node. If you need this, you can use `expandAll`    | boolean                     | false   | 0.32.0 |
-| defaultExpandedKeys | Keys of default expanded nodes. Direct child nodes will be displayed. | string\[] | - | 0.32.0 |
-| defaultOpen | Toggle whether to open dropdown menu by default | boolean | false | 0.32.0 |
+| defaultExpandAll    | Set whether to expand all nodes during initialization. And if the data (`treeData`) changes, this api cannot affect the expansion of the node. If you need this, you can use `expandAll`    | boolean                     | false   | - |
+| defaultExpandedKeys | Keys of default expanded nodes. Direct child nodes will be displayed. | string\[] | - | - |
+| defaultOpen | Toggle whether to open dropdown menu by default | boolean | false | - |
 | defaultValue             | Default value         | <ApiType detail='string \| number \| TreeNodeData \| (string \| number \| TreeNodeData)[]'>ValueType</ApiType>   | -   | -     |
 | disabled                 | Disabled                                                                            | boolean                                                           | false       | -       |
-| disableStrictly | Disable Strictly | boolean | false | 1.30.0 |
+| disableStrictly | Disable Strictly | boolean | false | - |
 | dropdownClassName        | `className` property for dropDown                                                   | string                                                            | -           | -       |
 | dropdownMatchSelectWidth | Toggle if min-width of dropDown menu should be same as width of select box          | boolean                                                           | true        | -       |
 | dropdownMargin | Popup layer calculates the size of the safe area when the current direction overflows, used in scenes covered by fixed elements, more detail refer to [issue#549](https://github.com/DouyinFE/semi-design/issues/549), same as Tooltip margin | object\|number |  | 2.25.0 |
 | dropdownStyle            | Style for dropDown                                                                  | CSSProperties                                                            | -           | -       |
 | emptyContent             | Empty content when no data                                                          | ReactNode                                                         | `no result` | -       |
-| expandAction             | Expand logic, one of false, 'click', 'doubleClick'. Default is set to false, which means item will not be expanded on clicking except on expand icon    | boolean \| string   | false | 1.4.0        |
-| expandAll | Set whether to expand all nodes by default. If the data (`treeData`) changes, the default expansion will still be affected by this api | boolean | false | 1.30.0 |
-| expandedKeys        | (Controlled)Keys of expanded nodes. Direct child nodes will be displayed.  | string[]                    | -       | 0.32.0 |
+| expandAction             | Expand logic, one of false, 'click', 'doubleClick'. Default is set to false, which means item will not be expanded on clicking except on expand icon    | boolean \| string   | false | - |
+| expandAll | Set whether to expand all nodes by default. If the data (`treeData`) changes, the default expansion will still be affected by this api | boolean | false |- |
+| expandedKeys        | (Controlled)Keys of expanded nodes. Direct child nodes will be displayed.  | string[]                    | -       | - |
 | 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                                                         | -           | 0.28.0  |
-| labelEllipsis | Toggle whether to ellipsis label when overflow | boolean | false\|true(virtualized) | 1.8.0 |  
-| leafOnly | Toggle whether to display tags for leaf nodes only and for onChange callback params in multiple mode | boolean | false |0.32.0 |
-| loadData | Load data asynchronously and the return value should be a promise | (treeNode: TreeNodeData) => Promise |-| 1.32.0|
-| loadedKeys | (Controlled)Mark node as loaded, working with `loadData` | Set< string > | - | 1.32.0|
+| 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 |-| - |
+| loadedKeys | (Controlled)Mark node as loaded, working with `loadData` | Set< string > | - | -|
 | maxTagCount              | Maximum number of tags displayed                                                    | number                                                            | -           | -       |
 | motionExpand             | Toggle whether to turn on animation for expansion                                   | boolean                                                           | true        | -       |
 | multiple                 | Toggle whether in multi-choice mode                                                 | boolean                                                           | false       | -       |
-| optionListStyle          | Style for optionList                                                                | CSSProperties                                                     | -           | 1.8.0  |
-| outerBottomSlot          | Rendered at the bottom of the pop-up layer, custom slot level with optionList    | ReactNode  |  - | 1.1.0 |
-| outerTopSlot| Rendered at the top of the pop-up layer, custom slot level with optionList. If turn on filterTreeNode, it will replace search box as well. You could use static search method to customize instead. |  ReactNode  |  - | 1.9.0|
+| optionListStyle          | Style for optionList                                                                | CSSProperties                                                     | -           |- |
+| outerBottomSlot          | Rendered at the bottom of the pop-up layer, custom slot level with optionList    | ReactNode  |  - |- |
+| outerTopSlot| Rendered at the top of the pop-up layer, custom slot level with optionList. If turn on filterTreeNode, it will replace search box as well. You could use static search method to customize instead. |  ReactNode  |  - |- |
 | placeholder              | Placeholder for input box                                                           | string                                                            | -           | -       |
 | position                 | Pop-up position, optional values refer to Tooltip position     | string          | bottomLeft           | 2.25.0       |
-| prefix                   | Prefix                                                                              | ReactNode                                                         | -           | 0.28.0  |
+| prefix                   | Prefix                                                                              | ReactNode                                                         | -           | -  |
 | 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 |  |  |
-| renderFullLabel | Custom option render function, [Detailed Params and Usage](/en-US/navigation/tree#Advanced%20FullRender) | (obj) => ReactNode | 1.7.0 |
-| renderLabel | Custom label render function | <ApiType detail='(label: ReactNode, data: TreeNodeData) => ReactNode'>(label, data) => ReactNode</ApiType> | 1.6.0 | 
-| renderSelectedItem | render selected item | Function | - | 1.26.0 | 
+| renderFullLabel | Custom option render function, [Detailed Params and Usage](/en-US/navigation/tree#Advanced%20FullRender) | (obj) => ReactNode |- |
+| renderLabel | Custom label render function. The searchWord parameter is supported since 2.65.0 | <ApiType detail='(label: ReactNode, data: TreeNodeData, searchWord: string) => ReactNode'>(label, data, searchWord) => ReactNode</ApiType> | -| 
+| renderSelectedItem | render selected item | Function | - |- | 
 | restTagsPopoverProps | The configuration properties of the [Popover](/en-US/show/popover#API%20Reference)     | PopoverProps     | {}        | 2.22.0 |
-| searchAutoFocus        | Whether autofocus for search box           | boolean      | false           | 1.27.0       |
+| searchAutoFocus        | Whether autofocus for search box           | boolean      | false           |-|
 | searchPlaceholder        | Placeholder for search box                                                          | string                                                            | -           | -       |
-| searchPosition | Set the position of the search box, one of: `dropdown`、`trigger` | string | `dropdown` | 1.29.0 |
+| searchPosition | Set the position of the search box, one of: `dropdown`、`trigger` | string | `dropdown` | - |
 | showClear | When the value is not empty, whether the trigger displays the clear button | boolean | false |  |
-| showFilteredOnly | Toggle whether to displayed filtered result only in search mode | boolean | false | 0.32.0 |
+| showFilteredOnly | Toggle whether to displayed filtered result only in search mode | boolean | false | - |
 | showLine | The option in the options panel shows connecting lines | boolean | false | 2.50.0 |
 | showRestTagsPopover | When the number of tags exceeds maxTagCount and hover reaches +N, whether to display the remaining content through Popover | boolean | false | 2.22.0 |
-| showSearchClear | Toggle whether to support clear search box | boolean | true | 0.35.0 |
+| showSearchClear | Toggle whether to support clear search box | boolean | true | - |
 | size                     | Size for input box,one of `large`,`small`,`default`                              | string                                                            | `default`   | -       |
 | style                    | Inline style                                                            | CSSProperties             | -           | -       |
-| suffix                   | Suffix                                                                              | ReactNode                                                         | -           | 0.28.0  |
+| suffix                   | Suffix                                                                              | ReactNode                                                         | -           |  - |
 | treeData                 | Data for treeNodes                                                                  | TreeNodeData[]                                                  | \[]         | -       |
 | treeNodeFilterProp       | Property in a `TreeNodeData` used to search                                             | string                                                            | `label`     | -       |
 | treeNodeLabelProp        | Property in a `TreeNodeData` used to display                                            | string                                                            | `label`     | -       |
-| triggerRender | Method to create a custom trigger  | (props: TriggerRenderProps) => ReactNode | - | 0.34.0 |
-| validateStatus | Validate status,one of `warning`、`error`、 `default`, only affects the background color of the component | string | - | 0.32.0 |
+| triggerRender | Method to create a custom trigger  | (props: TriggerRenderProps) => ReactNode | - | - |
+| validateStatus | Validate status,one of `warning`、`error`、 `default`, only affects the background color of the component | string | - | - |
 | value                    | Value data of current item, used when TreeSelect is a controlled component     | <ApiType detail='string \| number \| TreeNodeData \| (string \| number \| TreeNodeData)[]'>ValueType</ApiType>    | -           | -       |
-| virtualize | Efficiently rendering large lists, refer to Tree - VirtualizeObj. Motion is disabled when tree is rendered as virtualized list. | object | - | 0.32.0 |
-| zIndex | zIndex for treeSelect dropDown menu | number | 1030 | 0.30.0 |
+| virtualize | Efficiently rendering large lists, refer to Tree - VirtualizeObj. Motion is disabled when tree is rendered as virtualized list. | object | - | - |
+| zIndex | zIndex for treeSelect dropDown menu | number | 1030 | - |
 | onBlur                 | Callback function when treeSelect blur | function(event)                            | -           | -       |
 | onFocus                 | Callback function when treeSelect focus  | function(event)                            | -           | -       |
 | onChange                 | Callback function when the tree node is selected, return the value property of data | Function                           | -           | -       |
-| onChangeWithObject        | Toggle whether to return all properties in an option as a return value. When set to true, onChange turn to Function(node, e)   | boolean                     | false   | 1.0.0 |
+| onChangeWithObject        | Toggle whether to return all properties in an option as a return value. When set to true, onChange turn to Function(node, e)   | boolean                     | false   | - |
 | onClear     | Callback triggered when clear button is clicked   | (e: Event) => void |  -  |   2.52.0  |
 | onExpand                 | Callback function when expand or collapse a node                                    | <ApiType detail='(expandedKeys:array, {expanded: bool, node}) => void'>(expandedKeys, object) => void</ApiType>             | -           | -       |
-| onLoad | Callback function when a node is loaded | <ApiType detail='(loadedKeys: Set<string\>, treeNode: TreeNodeData) => void'>(loadedKeys, treeNode) => void</ApiType> | - | 1.32.0|
+| onLoad | Callback function when a node is loaded | <ApiType detail='(loadedKeys: Set<string\>, treeNode: TreeNodeData) => void'>(loadedKeys, treeNode) => void</ApiType> | - |-|
 | onSearch                 | Callback function when search value changes. <br/>`filteredExpandedKeys` represents the key of the node expanded due to search or value/defaultValue, which can be used when expandedKeys is controlled<br/> **filteredExpandedKeys is supported in 2.6.0**. <br/>`filteredNodes` represents the nodes hit by the search. **filteredNodes is supported in 2.57.0**       | function(input: string, filteredExpandedKeys: string[], filteredNodes: TreeNodeData[])                                        | -           |     |
 | onSelect                 | Callback function when selected, return the key property of data                    | <ApiType detail='(selectedKey:string, selected: bool, selectedNode: TreeNodeData) => void'>(selectedKey, selected, selectedNode)=>void</ApiType>                      | -           | -       |
-| onVisibleChange     | A callback triggered when the pop-up layer is displayed/hidden   | function(isVisible:boolean) |     |   1.4.0  |
+| onVisibleChange     | A callback triggered when the pop-up layer is displayed/hidden   | function(isVisible:boolean) |     |  - |
 
 ### TreeNodeData
 
-> **Key for `TreeNodeData` is required and must be unique**, `label` can be duplicated. Before **v>=1.7.0** value is also required and must be unique.
-> After **v>=1.7.0**, value is not required. In this case, the value property in `onChange`, `value`, `defaultValue` and `onChangeWithObject` will point to key property.
+> **Key for `TreeNodeData` is required and must be unique**, `label` can be duplicated. Value is not required. In this case, the value property in `onChange`, `value`, `defaultValue` and `onChangeWithObject` will point to key property.
 > To ensure everything behave as expected, keep a consistency of whether to have value or not to have value.
 
 | Properties | Instructions| type              | Default |

+ 3 - 8
content/input/treeselect/index.md

@@ -67,7 +67,7 @@ import { TreeSelect } from '@douyinfe/semi-ui';
 ### 多选
 
 设置 `multiple`,可以进行多选。多选情况下所有子项都被选择时,自动勾选显示其父项。  
-通过 `leafOnly` (>= v0.32.0) 属性,可以设置只展示叶子节点,同时 onChange 的回调入参也会只有叶子节点的值。  
+通过 `leafOnly` 属性,可以设置只展示叶子节点,同时 onChange 的回调入参也会只有叶子节点的值。  
 
 ```jsx live=true
 import React from 'react';
@@ -571,8 +571,6 @@ import { TreeSelect } from '@douyinfe/semi-ui';
 
 `defaultExpandAll` 和 `expandAll` 均可以设置 `TreeSelect` 的默认展开/收起状态。二者的区别是,`defaultExpandAll` 只在初始化时生效,而 `expandAll` 不仅会在初始化时生效,当数据(`treeData`)发生动态更新时,`expandAll` 也仍然生效。
 
-其中,`expandAll` 是从 1.30.0 开始支持的。
-
 在下面的 demo 中,`TreeData` 更新后,`defaultExpandAll` 失效,`expandAll` 仍然生效。
 
 ```jsx live=true
@@ -709,8 +707,6 @@ import { TreeSelect } from '@douyinfe/semi-ui';
 
 ### 严格禁用
 
-version: >= 1.30.0
-
 可以使用 `disableStrictly` 来开启严格禁用。开启严格禁用后,当节点是 disabled 的时候,则不能通过子级或者父级的关系改变选中状态。
 
 以下面的 demo 为例,节点"中国"开启了严格禁用,因此,当我们改变其父节点"亚洲"的选中状态时,也不会影响到节点"中国"的选中状态。
@@ -1435,7 +1431,7 @@ function Demo() {
 | prefix | 前缀标签                                                                                                                                            | ReactNode | - |
 | preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法                                                                                 | boolean | - |
 | renderFullLabel | 完全自定义label的渲染函数,[入参及用法详见](/zh-CN/navigation/tree#高级定制)                                                                     | (obj) => ReactNode | - |
-| renderLabel | 自定义label的渲染函数,[入参及用法详见](/zh-CN/navigation/tree#自定义节点内容)                                                                        | <ApiType detail='(label:ReactNode, data:TreeNodeData) => ReactNode'>(label, data) => ReactNode</ApiType> | - |
+| renderLabel | 自定义label的渲染函数,searchWord 参数自 2.65.0 开始支持。[入参及用法详见](/zh-CN/navigation/tree#自定义节点内容)                                                                        | <ApiType detail='(label: ReactNode, data: TreeNodeData, searchWord: string) => ReactNode'>(label, data, searchWord) => ReactNode</ApiType> | - |
 | renderSelectedItem | 自定义渲染已选项                                                                                                                         | Function | - |
 | restTagsPopoverProps | Popover 的配置属性,可以控制 position、zIndex、trigger 等,具体参考[Popover](/zh-CN/show/popover#API%20%E5%8F%82%E8%80%83) 。v2.22.0后提供  | PopoverProps | {} |
 | searchAutoFocus | 搜索框自动聚焦                                                                                                                              | boolean | false |
@@ -1470,8 +1466,7 @@ function Demo() {
 
 ### TreeNodeData
 
-> __不同 `TreeNodeData` 的 key 值要求必填且唯一。__`label` 允许重复。**v>=1.7.0** 之前 value 值要求必须必填且唯一。
-> **v>=1.7.0** 之后 value 值非必填。此时 onChange, value, defaultValue 及 onChangeWithObject 中所取的 value 属性值将改为 key 值。
+> __不同 `TreeNodeData` 的 key 值要求必填且唯一。__`label` 允许重复。value 值非必填。此时 onChange, value, defaultValue 及 onChangeWithObject 中所取的 value 属性值将改为 key 值。
 > 为了保证行为的符合预期,treeData 中的 value 值或者全部不填写,或者全部填写且唯一,不建议混写。
 
 | 属性            | 说明         | 类型           | 默认值          |

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

@@ -2299,7 +2299,7 @@ import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSec
 | 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 |  |  |
 | renderDraggingNode | Custom render function to render html element of dragImg for dragging node | (nodeInstance: HTMLElement, node: TreeNodeData) => HTMLElement | - | 1.8.0 | 
 | renderFullLabel | Custom option render function | (data: object) => ReactNode | - | 1.7.0 | 
-| renderLabel | Custom label render function | (label: ReactNode, data: TreeNodeData) => ReactNode | - | 1.6.0 | 
+| renderLabel | Custom label render function. The searchWord parameter is supported since 2.65.0 | <ApiType detail='(label: ReactNode, data: TreeNodeData, searchWord: string) => ReactNode'>(label, data, searchWord) => ReactNode</ApiType> | - | 1.6.0 | 
 | searchClassName     | Classname property for search box  | string                      | -       | - |
 | searchPlaceholder   | Placeholder for search box         | string                      | -       | - |
 | searchRender | Custom method to render search input; hide search box if set to false(**V>=1.0.0**) | ((searchRenderProps: object) => ReactNode) \| false | - | 0.35.0 |

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

@@ -2314,7 +2314,7 @@ import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSec
 | preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法 | boolean |  |  |
 | renderDraggingNode | 自定义正在拖拽节点的 dragImg 的 Html 元素 | (nodeInstance: HTMLElement, node: TreeNodeData) => HTMLElement | - | 1.8.0 | 
 | renderFullLabel | 完全自定义label的渲染函数 | (data: object) => ReactNode | - | 1.7.0 | 
-| renderLabel | 自定义label的渲染函数 | (label: ReactNode, data: TreeNodeData) => ReactNode |- |  1.6.0 | 
+| renderLabel | 自定义label的渲染函数, searchWord 参数自 2.65.0 开始支持 | <ApiType detail='(label: ReactNode, data: TreeNodeData, searchWord: string) => ReactNode'>(label, data, searchWord) => ReactNode</ApiType> |- |  1.6.0 | 
 | searchClassName | 搜索框的 `className` 属性 | string | - | - |
 | searchPlaceholder | 搜索框默认文字 | string | - | - |
 | searchRender | 自定义搜索框的渲染方法,为 false 时可以隐藏组件的搜索框(**V>=1.0.0**) | ((searchRenderProps: object) => ReactNode) \| false | - | 0.35.0 |

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

@@ -158,6 +158,10 @@ Just write JSX directly in Markdown
 
 ```
 
+# Add plugins
+
+Support all RemarkPlugin and RehypePlugins plugins of MDXJS through `remarkPlugins` `rehypePlugins`, please refer to [MDXJS](https://mdxjs.com/docs/extending-mdx/) for details
+
 
 ### API
 
@@ -167,6 +171,8 @@ Just write JSX directly in Markdown
 | components | Used to override Markdown elements and add custom components | Record<string, JSXElementConstructor> | - |
 | format | The incoming raw type, whether it is pure Markdown | 'md'\|'mdx' | 'mdx' |
 | raw | plain text in Markdown or MDX | string | - |
+| remarkPlugins | custom Remark Plugin          | Remark Plugin Array                | - |
+| rehypePlugins | custom Rehype Plugin          | Rehype Plugin Array               | - |
 | style | style | CSSProperties | - |
 
 ## Design Token

+ 13 - 6
content/plus/markdownrender/index.md

@@ -175,16 +175,23 @@ function Demo() {
 
 ```
 
+# 添加插件
+
+通过 `remarkPlugins` `rehypePlugins` 支持 MDXJS 的所有 RemarkPlugin 和 RehypePlugins 插件,详情请参考 [MDXJS](https://mdxjs.com/docs/extending-mdx/)
+
+
 
 ### API
 
-| 属性         | 说明                         | 类型                                    | 默认值   |
-|------------|----------------------------|---------------------------------------|-------|
-| className | 类名    | string | -   |
+| 属性         | 说明                         | 类型                                   | 默认值   |
+|------------|----------------------------|--------------------------------------|-------|
+| className | 类名                         | string                               | -   |
 | components | 用于覆盖 Markdown 元素,也可添加自定义组件 | Record<string, JSXElementConstructor> | -     |
-| format     | 传入的 raw 类型,是否是纯 Markdown   | 'md'\|'mdx'                           | 'mdx' |
-| raw        | Markdown 或 MDX 的纯文本        | string                                | -     |
-| style | 样式 | CSSProperties | - |
+| format     | 传入的 raw 类型,是否是纯 Markdown   | 'md'\|'mdx'                          | 'mdx' |
+| raw        | Markdown 或 MDX 的纯文本        | string                               | -     |
+| remarkPlugins | 自定义 Remark Plugin          | Remark Plugin Array                | - |
+| rehypePlugins | 自定义 Rehype Plugin          | Rehype Plugin Array               | - |
+| style | 样式                         | CSSProperties                        | - |
 
 ## 设计变量
 

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

@@ -482,6 +482,8 @@ import { Image, ImagePreview } from '@douyinfe/semi-ui';
 | width            | Image display width                  | number            | - | |
 | setDownloadName  | Set the name of the downloaded image | (src: string) => string | - | 2.40.0 |
 
+Other attributes same as [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes)。Other attributes will be transmitted to the underlying img node.
+
 ### ImagePreview
 
 | Properties       | Instructions                                                                                                                                                             | Type            | Default | Version |

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

@@ -483,6 +483,8 @@ import { Image, ImagePreview } from '@douyinfe/semi-ui';
 | width             | 图片显示宽度                             | number            | - | |
 | setDownloadName   | 设置图片下载名称                         | (src: string) => string | - | 2.40.0 |
 
+其他支持的属性同 [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes)。其他属性将透传至底层的 img 节点。
+
 ### ImagePreview
 
 | 属性               | 说明                                                                                                                                               | 类型              | 默认值 | 版本 |

+ 5 - 0
content/show/table/index-en-US.md

@@ -1198,6 +1198,10 @@ render(App);
 
 When sorter is a function type, the sortOrder status can be obtained through the third parameter of the function. The function type is `(a?: RecordType, b?: RecordType, sortOrder?: 'ascend' | 'descend') => number`. Supported by version v2.47.
 
+You can control whether to display the sorting tip through the `showSortTip` attribute. It is supported since v2.65 and defaults to `false`. When the tip is turned on, when there is only sorting function, the sorting prompt will be displayed when the mouse is moved to the table header; in other cases, the sorting prompt will be displayed only when the mouse is moved to the sorting icon.
+
+**Note**: When using the `sortOrder` attribute for controlled sorting, since the next sort order cannot be predicted, `showSortTip` does not take effect and the prompt will not be displayed.
+
 ```jsx live=true noInline=true dir="column"
 import React from 'react';
 import { Table, Avatar } from '@douyinfe/semi-ui';
@@ -5418,6 +5422,7 @@ import { Table } from '@douyinfe/semi-ui';
 | renderFilterDropdown | Custom filter dropdown panel, for usage details, see [Custom Filter Rendering](#Custom-Filter-Rendering) | (props?: RenderFilterDropdownProps) => React.ReactNode; | - | **2.52.0** |
 | renderFilterDropdownItem | Customize the rendering method of each filter item. For usage details, see [Custom Filter Item Rendering](#Custom-Filter-Item-Rendering) | ({ value: any, text: any, onChange: Function, level: number, ...otherProps }) => ReactNode | - | **1.1.0** |
 | resize | Whether to enable resize mode, this property will take effect only after Table resizable is enabled | boolean |  | **2.42.0** |
+| showSortTip | Whether to display sorting tips, If sortOrder is set and sorting is controlled, this parameter will not take effect | boolean | false | **2.65.0** |
 | sortChildrenRecord | Whether to sort child data locally | boolean |  | **0.29.0** |
 | sortOrder | The controlled property of the sorting, the sorting of this control column can be set to 'ascend'\|'descended '\|false | boolean | false |
 | sorter | Sorting function, local sorting uses a function (refer to the compareFunction of Array.sort), requiring a server-side sorting can be set to true. **An independent dataIndex must be set for the sort column, and an independent key must be set for each data item in the dataSource** | boolean\|(r1: RecordType, r2: RecordType, sortOrder: 'ascend' \| 'descend') => number | true |

+ 6 - 0
content/show/table/index.md

@@ -1302,6 +1302,10 @@ render(App);
 
 sorter 为函数类型时,可以通过函数的第三个参数获取 sortOrder 状态。函数类型为 `(a?: RecordType, b?: RecordType, sortOrder?: 'ascend' | 'descend') => number`。v2.47 版本支持。
 
+可通过 `showSortTip` 属性控制是否展示排序提示,自 v2.65 版本支持,默认为 `false`。当开启提示后,当仅有排序功能时候,鼠标移动至表头时,会展示排序提示;其他情况下,仅鼠标移动至排序图标时,会展示排序提示。
+
+**注**:在使用 `sortOrder` 属性受控排序时,由于无法预测下一个排序顺序,因此 `showSortTip` 不生效,不会展示提示。
+
 ```jsx live=true noInline=true dir="column"
 import React from 'react';
 import { Table, Avatar } from '@douyinfe/semi-ui';
@@ -1338,6 +1342,7 @@ function App() {
                     return 0; // 保持原来的顺序
                 }
             },
+            showSortTip: true,
             render: text => text ? `${text} KB` : '未知',
         },
         {
@@ -5545,6 +5550,7 @@ 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** |
 | 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 |

+ 20 - 3
content/start/changelog/index-en-US.md

@@ -17,12 +17,29 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
 ---
 
 
-#### 🎉 2.64.0 (2024-08-12)
+#### 🎉 2.65.0-beta.0 (2024-08-20)
 - 【Feat】
-  - Added the ColorPicker component, which allows users to quickly select colors and supports eyedropper screen color selection. [#2218](https://github.com/DouyinFE/semi-design/pull/2218)
+  - MarkdownRender now supports RemarkPlugin and RehypePlugins plugins. [#2433](https://github.com/DouyinFE/semi-design/pull/2433)
+  - The renderLabel API of Tree and TreeSelect has added a searchWord parameter to expose the currently input value in the search box. [#2412](https://github.com/DouyinFE/semi-design/pull/2412)
+  - Optimize the sorting interaction of Table. When there is only sorting function, click on the entire table header column to trigger sorting. Column supports the showSortTooltip API to set whether to display the tooltip, and the default is true [#2413](https://github.com/DouyinFE/semi-design/pull/2413)
+  - Datepicker supports leftSlot and rightSlot. [@LuyangFE](https://github.com/LuyangFE) [#2409](https://github.com/DouyinFE/semi-design/pull/2409)
+  - Typograph support custom render copy trigger [@sylingd](https://github.com/sylingd) [#2408](https://github.com/DouyinFE/semi-design/pull/2408)
+- 【Perf】
+  - Optimized the judgment times of getValueLength for Input and TextArea. [#2432](https://github.com/DouyinFE/semi-design/pull/2432)
+- 【Chore】
+  - The Image component interface supports native img element attributes. [#2427](https://github.com/DouyinFE/semi-design/pull/2427)
 - 【Fix】
-  - Fix Tooltip triggerDOM not defined in some case  [commit](https://github.com/DouyinFE/semi-design/commit/05878dd7b7c20f2e924f8e0b3cf71ad0eaa3aaf3)
+  - Fixed the type error after sending a message when the messages in Chat are an empty array. [#2411](https://github.com/DouyinFE/semi-design/pull/2411)
+  - Fixed the incorrect problem of Table aria-level when tree data is empty. [#2359](https://github.com/DouyinFE/semi-design/issues/2359)
+  - Fixed the incorrect disabling of arrows in Collapse Tabs when quickly clicking left and right arrows. [#2415](https://github.com/DouyinFE/semi-design/issues/2415)
+  - Fixed the problem that when showStopGenerate of Chat component is true and the status of a message is error, the stop button is displayed. [#2422](https://github.com/DouyinFE/semi-design/pull/2422)
+  - Fixed the problem that in Cascader, after searching and in multiple selection scenarios, the position of the pop-up layer is not recalculated, resulting in a long panel being blocked. [#2417](https://github.com/DouyinFE/semi-design/pull/2417)
+  - Fixed the problem that in Cascader's multiple selection scenario, when unselecting by clicking the close icon of an existing option in the trigger, the position of the pop-up layer is not recalculated. [#2417](https://github.com/DouyinFE/semi-design/pull/2417)
+  - Fixed the loss of the selected state color after clicking the selected date twice in DatePicker. [#2389](https://github.com/DouyinFE/semi-design/pull/2389)
 
+#### 🎉 2.64.0 (2024-08-12)
+- 【Fix】
+  - Fix Tooltip triggerDOM not defined in some case  [commit](https://github.com/DouyinFE/semi-design/commit/05878dd7b7c20f2e924f8e0b3cf71ad0eaa3aaf3)
 
 #### 🎉 2.64.0-beta.0 (2024-08-05)
 - 【Feat】

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

@@ -14,16 +14,36 @@ Semi 版本号遵循 **Semver** 规范(主版本号-次版本号-修订版本
 -   不同版本间的详细关系,可查阅 [FAQ](/zh-CN/start/faq)
 
 
-#### 🎉 2.64.0 (2024-08-12)
+#### 🎉 2.65.0-beta.0 (2024-08-20)
 - 【Feat】
-  - 新增 颜色选择器 ColorPicker 组件,用户快速选择颜色,支持滴管屏幕取色 [#2218](https://github.com/DouyinFE/semi-design/pull/2218)
+  - 优化 Table 的排序交互,仅有排序功能时,支持点击整个表头column触发排序。Column 支持 showSortTooltip API支持设置是否显示 tooltip,默认为 true [#2413](https://github.com/DouyinFE/semi-design/pull/2413)
+  - MarkdownRender 支持 RemarkPlugin 和 RehypePlugins 插件 [#2433](https://github.com/DouyinFE/semi-design/pull/2433)
+  - Tree、TreeSelect 的 renderLabel API 增加 searchWord 参数,用于透出当前搜索框输入值 [#2412](https://github.com/DouyinFE/semi-design/pull/2412)
+  - Datepicker 支持 leftSlot、rightSlot [@LuyangFE](https://github.com/LuyangFE) [#2409](https://github.com/DouyinFE/semi-design/pull/2409) 
+  - Typograph 组件支持自定义复制区域渲染 [@sylingd](https://github.com/sylingd) [#2408](https://github.com/DouyinFE/semi-design/pull/2408)
+- 【Perf】
+  - 优化 Input、TextArea getValueLength 判断次数 [#2432](https://github.com/DouyinFE/semi-design/pull/2432)
+- 【Chore】
+  - Image 组件 interface 支持原生 img 元素属性 [#2427](https://github.com/DouyinFE/semi-design/pull/2427)
 - 【Fix】
-  - 修复 Tooltip triggerDOM 特殊场景下未定义的问题  [commit](https://github.com/DouyinFE/semi-design/commit/05878dd7b7c20f2e924f8e0b3cf71ad0eaa3aaf3)
+  - 修复 Chat 中消息为空数组时,发送消息后的类型错误 [#2411](https://github.com/DouyinFE/semi-design/pull/2411)
+  - 修复 Table aria-level 在树形数据为空时错误的问题  [#2359](https://github.com/DouyinFE/semi-design/issues/2359)
+  - 修复 Table 树形数据为空且 expandIcon 为 false时缩进错误的问题  [#2425](https://github.com/DouyinFE/semi-design/issues/2425)
+  - 修复 Collapse Tabs 在快速点击左右箭头情况下造成的箭头禁用情况不正确问题 [#2415](https://github.com/DouyinFE/semi-design/issues/2415)
+  - 修复 Chat 组件在 showStopGenerate 为 true 时,消息的 status 为 error 会展示停止按钮问题 [#2422](https://github.com/DouyinFE/semi-design/pull/2422)
+  - 修复 Cascader 搜索后以及多选,弹出层的位置未重新计算,导致内容较长的面板被遮挡问题 [#2417](https://github.com/DouyinFE/semi-design/pull/2417)
+  - 修复 Cascader 多选场景,通过点击 trigger 中已选项的关闭 icon 取消选中,弹出层位置未重新计算问题 [#2417](https://github.com/DouyinFE/semi-design/pull/2417)
+  - 修复 DatePicker 点击选择的日期两次后,选中态颜色丢失 [#2389](https://github.com/DouyinFE/semi-design/pull/2389)
+
+#### 🎉 2.64.0 (2024-08-12)
+- 【Fix】
+  - 修复 Tooltip triggerDOM 特殊场景下未定义的问题 [commit](https://github.com/DouyinFE/semi-design/commit/05878dd7b7c20f2e924f8e0b3cf71ad0eaa3aaf3)
 
 #### 🎉 2.64.0-beta.0 (2024-08-05)
+- 【New Component】
+  - 新增 颜色选择器 ColorPicker 组件,用户快速选择颜色,支持滴管屏幕取色 [#2218](https://github.com/DouyinFE/semi-design/pull/2218)
 - 【Feat】
     - Calendar 日视图中起止时间完全相同的事件支持并排显示,不互相遮盖 [#2393](https://github.com/DouyinFE/semi-design/pull/2393)
-    - 新增颜色选择器 ColorPicker 组件,用户快速选择颜色,支持滴管屏幕取色
 - 【Fix】
     - 修复鼠标滚轮缩放图片后,拖动了图片,再次缩放后会重置回中心位置的问题 [@l123wx](https://github.com/l123wx) [#2293](https://github.com/DouyinFE/semi-design/pull/2293)
     - 修复 Modal 在 SSR 时 document 不存在的问题 (影响范围 2.62.0~2.63.0) [#2395](https://github.com/DouyinFE/semi-design/pull/2395)

+ 15 - 0
cypress/e2e/datePicker.spec.js

@@ -859,6 +859,21 @@ describe('DatePicker', () => {
         cy.get('.semi-input').eq(1).should('have.value', '2024-02-26');
     });
 
+    it('fixed selected status bug when double click', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=datepicker--fixed-selected-status&viewMode=story');
+        cy.get('.semi-input').eq(0).click();
+        cy.get('.semi-datepicker-month-grid-left .semi-datepicker-day').contains('15')
+            .then($day => {
+                $day.trigger('click');
+            });
+        cy.get('.semi-datepicker-day-selected').contains("15");
+        cy.get('.semi-datepicker-month-grid-left .semi-datepicker-day').contains('15')
+            .then($day => {
+                $day.trigger('click');
+            });
+        cy.get('.semi-datepicker-day-selected').contains("15");
+    })
+      
     it('fixed selected is not update when close panel', () => {
         cy.visit('http://localhost:6006/iframe.html?id=datepicker--fixed-controlled&viewMode=story');
         cy.get('.semi-input').eq(1).click();

+ 1 - 1
lerna.json

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

+ 2 - 2
package.json

@@ -43,12 +43,12 @@
         "coverage:merge": "npx istanbul-combine -d test/merged -p detail -r lcov -r json cypress/coverage/coverage-final.json test/coverage/coverage-final.json"
     },
     "engines": {
-        "node": ">= 16.0.0"
+        "node": ">= 20.0.0"
     },
     "dependencies": {
         "@dnd-kit/modifiers": "^7.0.0",
         "@douyinfe/semi-site-banner": "^0.1.5",
-        "@douyinfe/semi-site-doc-style": "0.0.4",
+        "@douyinfe/semi-site-doc-style": "0.0.5",
         "@douyinfe/semi-site-header": "^0.0.29",
         "@douyinfe/semi-site-markdown-blocks": "^0.0.18",
         "@mdx-js/mdx": "1.6.22",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation-react",
-    "version": "2.64.0",
+    "version": "2.65.0-beta.0",
     "description": "motion library for semi-ui-react",
     "keywords": [
         "motion",
@@ -25,8 +25,8 @@
         "prepublishOnly": "npm run build:lib"
     },
     "dependencies": {
-        "@douyinfe/semi-animation": "2.64.0",
-        "@douyinfe/semi-animation-styled": "2.64.0",
+        "@douyinfe/semi-animation": "2.65.0-beta.0",
+        "@douyinfe/semi-animation-styled": "2.65.0-beta.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.64.0",
+    "version": "2.65.0-beta.0",
     "description": "semi styled animation",
     "keywords": [
         "semi",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation",
-    "version": "2.64.0",
+    "version": "2.65.0-beta.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.64.0",
+    "version": "2.65.0-beta.0",
     "description": "semi ui eslint plugin",
     "keywords": [
         "semi",

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

@@ -119,7 +119,7 @@ $module: #{$prefix}-chat;
         display: flex;
         flex-direction: row;
         margin-top: $spacing-chat_chatBox-marginY;
-        margin-Bottom: $spacing-chat_chatBox-marginY;
+        margin-bottom: $spacing-chat_chatBox-marginY;
         column-gap: $spacing-chat_chatBox-columnGap;
 
         &:hover {

+ 13 - 1
packages/semi-foundation/datePicker/datePicker.scss

@@ -6,7 +6,11 @@ $module-list: #{$prefix}-scrolllist;
 
 .#{$module} {
     box-sizing: border-box;
-    display: inline-block;
+    display: inline-flex;
+
+    &-container {
+        display: flex;
+    }
 
     .#{$module-list}-body {
         .#{$module-list}-item {
@@ -820,6 +824,14 @@ $module-list: #{$prefix}-scrolllist;
     &-topSlot {
         border-bottom: $width-datepicker_slot-border solid $color-datepicker_border-bg-default;
     }
+    
+    &-leftSlot {
+        border-right: $width-datepicker_slot-border solid $color-datepicker_border-bg-default;
+    }
+
+    &-rightSlot {
+        border-left: $width-datepicker_slot-border solid $color-datepicker_border-bg-default;
+    }
 
     &-bottomSlot {
         border-top: $width-datepicker_slot-border solid $color-datepicker_border-bg-default;

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

@@ -613,11 +613,11 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
         const multiple = this._isMultiple();
         const { selected } = this.getStates();
         const monthDetail = this._getPanelDetail(panelType);
-        const newSelected = new Set(multiple ? [...selected] : []);
+        const newSelected = new Set<string>(multiple ? [...selected] : []);
 
         const { fullDate } = day;
         const time = monthDetail.pickerDate;
-        const dateStr = type === 'dateTime' ? this._mergeDateAndTime(fullDate, time) : fullDate;
+        const dateStr = fullDate;
 
         if (!multiple) {
             newSelected.add(dateStr);
@@ -631,9 +631,8 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
             }
         }
 
-        const dateFormat = this.getValidDateFormat();
         // When passed to the upper layer, it is converted into a Date object to ensure that the input parameter format of initFormDefaultValue is consistent
-        const newSelectedDates = [...newSelected].map(_dateStr => compatibleParse(_dateStr, dateFormat, undefined, dateFnsLocale));
+        const newSelectedDates = [...newSelected].map(_dateStr => type === 'dateTime' ? this._mergeDateAndTime(_dateStr, time) : compatibleParse(_dateStr, strings.FORMAT_FULL_DATE, undefined, dateFnsLocale));
 
         this.handleShowDateAndTime(panelType, time);
 

+ 3 - 14
packages/semi-foundation/input/foundation.ts

@@ -3,6 +3,7 @@ import { strings } from './constants';
 import { noop, set, isNumber, isString, isFunction } from 'lodash';
 
 import { ENTER_KEY } from './../utils/keyCode';
+import truncateValue from './util/truncateValue';
 
 export interface InputDefaultAdapter {
     notifyChange: noopFunction;
@@ -112,6 +113,7 @@ class InputFoundation extends BaseFoundation<InputAdapter> {
                 return value;
             }
         }
+        return value;
     }
 
     /**
@@ -122,20 +124,7 @@ class InputFoundation extends BaseFoundation<InputAdapter> {
      */
     handleTruncateValue(value: any, maxLength: number) {
         const { getValueLength } = this._adapter.getProps();
-        if (isFunction(getValueLength)) {
-            let truncatedValue = '';
-            for (let i = 1, len = value.length; i <= len; i++) {
-                const currentValue = value.slice(0, i);
-                if (getValueLength(currentValue) > maxLength) {
-                    return truncatedValue;
-                } else {
-                    truncatedValue = currentValue;
-                }
-            }
-            return truncatedValue;
-        } else {
-            return value.slice(0, maxLength);
-        }
+        return truncateValue({ value, maxLength, getValueLength });
     }
 
     handleClear(e: any) {

+ 2 - 14
packages/semi-foundation/input/textareaFoundation.ts

@@ -7,6 +7,7 @@ import {
 } from 'lodash';
 import calculateNodeHeight from './util/calculateNodeHeight';
 import getSizingData from './util/getSizingData';
+import truncateValue from './util/truncateValue';
 
 export interface TextAreaDefaultAdapter {
     notifyChange: noopFunction;
@@ -124,20 +125,7 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdapter>
      */
     handleTruncateValue(value: string, maxLength: number) {
         const { getValueLength } = this._adapter.getProps();
-        if (isFunction(getValueLength)) {
-            let truncatedValue = '';
-            for (let i = 1, len = value.length; i <= len; i++) {
-                const currentValue = value.slice(0, i);
-                if (getValueLength(currentValue) > maxLength) {
-                    return truncatedValue;
-                } else {
-                    truncatedValue = currentValue;
-                }
-            }
-            return truncatedValue;
-        } else {
-            return value.slice(0, maxLength);
-        }
+        return truncateValue({ value, maxLength, getValueLength });
     }
 
     handleFocus(e: any) {

+ 25 - 0
packages/semi-foundation/input/util/truncateValue.ts

@@ -0,0 +1,25 @@
+import { isFunction } from 'lodash';
+
+export default function truncateValue(options: {
+    value: string;
+    maxLength: number;
+    getValueLength?: (value: string) => number
+}): string {
+    const { value, maxLength, getValueLength } = options;
+    if (isFunction(getValueLength)) {
+        let left = 0;
+        let right = value.length;
+        while (left < right) {
+            const mid = left + Math.floor((right - left) / 2);
+            const currentValue = value.slice(0, mid + 1);
+            if (getValueLength(currentValue) > maxLength) {
+                right = mid;
+            } else {
+                left = mid + 1;
+            }
+        }
+        return value.slice(0, left);
+    } else {
+        return value.slice(0, maxLength);
+    }
+}

+ 9 - 3
packages/semi-foundation/markdownRender/foundation.ts

@@ -2,6 +2,7 @@ import BaseFoundation, { DefaultAdapter } from '../base/foundation';
 import { CompileOptions, evaluate, compile, EvaluateOptions, evaluateSync, RunOptions } from '@mdx-js/mdx';
 import { MDXProps } from 'mdx/types';
 import remarkGfm from 'remark-gfm';
+import { type PluggableList } from "@mdx-js/mdx/lib/core";
 export interface MarkdownRenderAdapter <P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
     getRuntime: () => any
 
@@ -13,7 +14,9 @@ export interface MarkdownRenderAdapter <P = Record<string, any>, S = Record<stri
 export interface MarkdownRenderBaseProps{
     raw: string;
     components: MDXProps['components'];
-    format: "md"|"mdx"
+    format: "md"|"mdx";
+    remarkPlugins?: PluggableList;
+    rehypePlugins?: PluggableList
 }
 
 
@@ -26,11 +29,14 @@ class MarkdownRenderFoundation extends BaseFoundation<MarkdownRenderAdapter> {
     private getOptions = ()=>{
         return {
             evaluateOptions: {
-                remarkPlugins: [remarkGfm],
+                remarkPlugins: [remarkGfm, ...(this.getProp("remarkPlugins") ?? [])],
+                rehypePlugins: this.getProp("rehypePlugins") ?? [],
                 format: this.getProp("format")
             },
             compileOptions: {
-                format: this.getProp("format")
+                format: this.getProp("format"),
+                remarkPlugins: [remarkGfm, ...(this.getProp("remarkPlugins") ?? [])],
+                rehypePlugins: this.getProp("rehypePlugins") ?? [],
             },
             runOptions: {
             }

+ 6 - 4
packages/semi-foundation/overflowList/foundation.ts

@@ -1,6 +1,6 @@
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
 import { strings } from './constants';
-import { noop, get } from 'lodash';
+import { get, cloneDeep } from 'lodash';
 import copy from 'fast-copy';
 
 const Boundary = strings.BOUNDARY_MAP;
@@ -33,13 +33,15 @@ class OverflowListFoundation extends BaseFoundation<OverflowListAdapter> {
             return overflow;
         }
 
-        const visibleStateArr = items.map(({ key }: { key: string }) => Boolean(visibleState.get(key)));
+        const cloneItems = copy(items);
+
+        const visibleStateArr = cloneItems.map(({ key }: { key: string }) => Boolean(visibleState.get(key)));
         const visibleStart = visibleStateArr.indexOf(true);
         const visibleEnd = visibleStateArr.lastIndexOf(true);
 
         const overflowList = [];
-        overflowList[0] = visibleStart >= 0 ? items.slice(0, visibleStart) : [];
-        overflowList[1] = visibleEnd >= 0 ? items.slice(visibleEnd + 1, items.length) : items;
+        overflowList[0] = visibleStart >= 0 ? cloneItems.slice(0, visibleStart) : [];
+        overflowList[1] = visibleEnd >= 0 ? cloneItems.slice(visibleEnd + 1, cloneItems.length) : cloneItems;
         return overflowList;
     }
 

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

@@ -1,13 +1,13 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.64.0",
+    "version": "2.65.0-beta.0",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",
         "prepublishOnly": "npm run build:lib"
     },
     "dependencies": {
-        "@douyinfe/semi-animation": "2.64.0",
+        "@douyinfe/semi-animation": "2.65.0-beta.0",
         "@mdx-js/mdx": "^3.0.1",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",

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

@@ -1,3 +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_function-table_row-head-bg: linear; // 表格-行头-背景颜色-过渡曲线

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

@@ -140,6 +140,23 @@ $module: #{$prefix}-table;
                 // word-break: break-all;
                 // word-wrap: break-word;
                 position: relative;
+                transition: background-color $transition_duration-table_row-head-bg $transition_function-table_row-head-bg;
+
+                &.#{$module}-row-head-clickSort {
+                    cursor: pointer;
+                    &:hover {
+                        background: $color-table_th-clickSort-bg-hover;
+                        
+                        &.#{$module}-cell-fixed {
+                            &-left,
+                            &-right {
+                                &::before {
+                                    background-color: transparent;
+                                }
+                            }
+                        }
+                    }
+                }
 
                 &.#{$module}-cell-fixed {
                     &-left,

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

@@ -57,6 +57,7 @@ $color-table_shadow-border-default: var(--semi-color-border); // 表格拟阴影
 $color-table_th-bg-default: var(--semi-color-bg-1); // 表头背景色
 $color-table_th-border-default: var(--semi-color-border); // 表头底部分割线颜色
 $color-table_th-text-default: var(--semi-color-text-2); // 表头文字颜色
+$color-table_th-clickSort-bg-hover: var(--semi-color-fill-0); //点击表头触发排序背景色 - 悬浮
 
 $color-table_pl-bg-default: transparent;
 $color-table_body-bg-default: var(--semi-color-bg-1); // 表格背景颜色 - 默认

+ 1 - 1
packages/semi-icons-lab/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-icons-lab",
-  "version": "2.64.0",
+  "version": "2.65.0-beta.0",
   "description": "semi icons lab",
   "keywords": [
     "semi",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-icons",
-    "version": "2.64.0",
+    "version": "2.65.0-beta.0",
     "description": "semi icons",
     "keywords": [
         "semi",

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

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

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

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

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-rspack-plugin",
-    "version": "2.64.0",
+    "version": "2.65.0-beta.0",
     "description": "",
     "homepage": "",
     "license": "MIT",

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

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

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

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

+ 12 - 13
packages/semi-ui/chat/index.tsx

@@ -191,7 +191,7 @@ class Chat extends BaseComponent<ChatProps, ChatState> {
         const { chats, hints } = nextProps;
         const newState = {} as any;
         if (chats !== prevState.chats) {
-            newState.chats = chats;
+            newState.chats = chats ?? [];
         }
         if (hints !== prevState.cacheHints) {
             newState.cacheHints = hints;
@@ -212,19 +212,18 @@ class Chat extends BaseComponent<ChatProps, ChatState> {
         const { wheelScroll } = this.state;
         let shouldScroll = false;
         if (newChats !== oldChats) {
-            const newLastChat = newChats[newChats.length - 1];
-            const oldLastChat = oldChats[oldChats.length - 1];
-            if (newChats.length > oldChats.length) {
-                if (newLastChat.id !== oldLastChat.id) {
+            if (Array.isArray(newChats) && Array.isArray(oldChats)) {
+                const newLastChat = newChats[newChats.length - 1];
+                const oldLastChat = oldChats[oldChats.length - 1];
+                if (newChats.length > oldChats.length) {
+                    if (oldChats.length === 0 || newLastChat.id !== oldLastChat.id) {
+                        shouldScroll = true;
+                    }
+                } else if (newChats.length === oldChats.length &&
+                    (newLastChat.status !== 'complete' || newLastChat.status !== oldLastChat.status)
+                ) {
                     shouldScroll = true;
                 }
-            } else if (newChats.length === oldChats.length &&
-        (
-            newLastChat.status !== 'complete' ||
-          newLastChat.status !== oldLastChat.status
-        )
-            ) {
-                shouldScroll = true;
             }
         }
         if (newHints !== cacheHints) {
@@ -283,7 +282,7 @@ class Chat extends BaseComponent<ChatProps, ChatState> {
         const lastChat = chats.length > 0 && chats[chats.length - 1];
         let disableSend = false;
         if (lastChat && showStopGenerate) {
-            const lastChatOnGoing = lastChat.status && [MESSAGE_STATUS.LOADING, MESSAGE_STATUS.INCOMPLETE].includes(lastChat.status);
+            const lastChatOnGoing = lastChat?.status && [MESSAGE_STATUS.LOADING, MESSAGE_STATUS.INCOMPLETE].includes(lastChat?.status);
             disableSend = lastChatOnGoing;
             showStopGenerate && (showStopGenerateFlag = lastChatOnGoing);
         }

+ 17 - 1
packages/semi-ui/datePicker/_story/DatePickerSlot/index.jsx

@@ -44,6 +44,22 @@ export default function Demo() {
         );
     };
 
+    const LeftSlot = function (props) {
+        const { style } = props;
+        return (
+            <div>LeftSlot</div>
+        );
+    };
+
+    const RightSlot = function (props) {
+        const { style } = props;
+        return (
+            <Space style={{ padding: '12px 20px', ...style }}>
+                <div>RightSlot</div>
+            </Space>
+        );
+    };
+
     const MonthBottomSlot = function (props) {
         const { style } = props;
         return (
@@ -60,7 +76,7 @@ export default function Demo() {
     return (
         <div>
             <span>topSlot</span>
-            <DatePicker topSlot={<TopSlot />} disabledDate={disabledDate} value={date} onChange={handleDateChange} />
+            <DatePicker topSlot={<TopSlot />} leftSlot={<LeftSlot />} rightSlot={<RightSlot/>} disabledDate={disabledDate} value={date} onChange={handleDateChange} />
             <br />
             <br />
             <span>bottomSlot</span>

+ 1 - 0
packages/semi-ui/datePicker/_story/datePicker.stories.jsx

@@ -73,6 +73,7 @@ export {
     FixNeedConfirmControlled,
     FixedNaN,
     PresetsFunctionType,
+    FixedSelectedStatus,
     FixedControlled
 } from './v2';
 

+ 8 - 0
packages/semi-ui/datePicker/_story/v2/FixedSelectedStatus.jsx

@@ -0,0 +1,8 @@
+import React from 'react';
+import { DatePicker } from '@douyinfe/semi-ui';
+
+const App = () => (
+    <DatePicker type="dateTime" defaultPickerValue="2022-07-01" />
+);
+
+export default App;

+ 1 - 0
packages/semi-ui/datePicker/_story/v2/index.js

@@ -30,4 +30,5 @@ export { default as AutoSplitInput } from './AutoSplitInput';
 export { default as FixNeedConfirmControlled } from './FixNeedConfirmControlled';
 export { default as PresetsFunctionType } from './PresetsFunctionType';
 export { default as FixedNaN } from './FixedNaN';
+export { default as FixedSelectedStatus } from './FixedSelectedStatus';
 export { default as FixedControlled } from './FixedControlled';

+ 27 - 13
packages/semi-ui/datePicker/datePicker.tsx

@@ -44,6 +44,7 @@ export interface DatePickerProps extends DatePickerFoundationProps {
     insetLabelId?: string;
     prefix?: React.ReactNode;
     topSlot?: React.ReactNode;
+    rightSlot?: React.ReactNode;
     renderDate?: (dayNumber?: number, fullDate?: string) => React.ReactNode;
     renderFullDate?: (dayNumber?: number, fullDate?: string, dayStatus?: DayStatusType) => React.ReactNode;
     triggerRender?: (props: DatePickerProps) => React.ReactNode;
@@ -63,6 +64,7 @@ export interface DatePickerProps extends DatePickerFoundationProps {
     onPresetClick?: (item: PresetType, e: React.MouseEvent<HTMLDivElement>) => void;
     onClickOutSide?: () => void;
     locale?: Locale['DatePicker'];
+    leftSlot?: React.ReactNode;
     dateFnsLocale?: Locale['dateFnsLocale'];
     yearAndMonthOpts?: ScrollItemProps<any>;
     dropdownMargin?: PopoverProps['margin']
@@ -747,7 +749,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
     };
 
     renderPanel = (locale: Locale['DatePicker'], localeCode: string, dateFnsLocale: Locale['dateFnsLocale']) => {
-        const { dropdownClassName, dropdownStyle, density, topSlot, bottomSlot, presetPosition, type } = this.props;
+        const { dropdownClassName, dropdownStyle, density, topSlot, bottomSlot, presetPosition, type, leftSlot, rightSlot } = this.props;
         const wrapCls = classnames(
             cssClasses.PREFIX,
             {
@@ -759,20 +761,32 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
 
         return (
             <div ref={this.panelRef} className={wrapCls} style={dropdownStyle} x-type={type}>
-                {topSlot && (
-                    <div className={`${cssClasses.PREFIX}-topSlot`} x-semi-prop="topSlot">
-                        {topSlot}
+                {leftSlot && (
+                    <div className={`${cssClasses.PREFIX}-leftSlot`} x-semi-prop="leftSlot">
+                        {leftSlot}
                     </div>
                 )}
-                {/* todo: monthRange does not support presetPosition temporarily */}
-                {presetPosition === "top" && type !== 'monthRange' && this.renderQuickControls()}
-                {this.adapter.typeIsYearOrMonth()
-                    ? this.renderYearMonthPanel(locale, localeCode)
-                    : this.renderMonthGrid(locale, localeCode, dateFnsLocale)}
-                {presetPosition === "bottom" && type !== 'monthRange' && this.renderQuickControls()}
-                {bottomSlot && (
-                    <div className={`${cssClasses.PREFIX}-bottomSlot`} x-semi-prop="bottomSlot">
-                        {bottomSlot}
+                <div>
+                    {topSlot && (
+                        <div className={`${cssClasses.PREFIX}-topSlot`} x-semi-prop="topSlot">
+                            {topSlot}
+                        </div>
+                    )}
+                    {/* todo: monthRange does not support presetPosition temporarily */}
+                    {presetPosition === "top" && type !== 'monthRange' && this.renderQuickControls()}
+                    {this.adapter.typeIsYearOrMonth()
+                        ? this.renderYearMonthPanel(locale, localeCode)
+                        : this.renderMonthGrid(locale, localeCode, dateFnsLocale)}
+                    {presetPosition === "bottom" && type !== 'monthRange' && this.renderQuickControls()}
+                    {bottomSlot && (
+                        <div className={`${cssClasses.PREFIX}-bottomSlot`} x-semi-prop="bottomSlot">
+                            {bottomSlot}
+                        </div>
+                    )}
+                </div>
+                {rightSlot && (
+                    <div className={`${cssClasses.PREFIX}-rightSlot`} x-semi-prop="rightSlot">
+                        {rightSlot}
                     </div>
                 )}
                 {this.renderFooter(locale, localeCode)}

+ 2 - 1
packages/semi-ui/image/_story/image.stories.tsx

@@ -68,9 +68,10 @@ export const BasicPreview = () => {
                 {srcList1.map((src, index) => {
                     return (
                         <Image 
-                            key={index}
+                            key={`${index}`}
                             src={src}
                             width={200}
+                            loading='lazy'
                             alt={`lamp${index + 1}`}
                             data-test={'data-test'}
                             onClick={()=>{}}

+ 2 - 2
packages/semi-ui/image/interface.tsx

@@ -11,7 +11,7 @@ export interface ImageStates {
     previewVisible: boolean
 }
 
-export interface ImageProps extends BaseProps {
+export interface ImageProps extends BaseProps, Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'onLoad' | 'onError'> {
     src?: string;
     width?: string | number;
     height?: string | number;
@@ -190,7 +190,7 @@ export interface PreviewImageStates {
     width: number;
     height: number;
     translate: ImageTranslate;
-    currZoom: number;
+    currZoom: number
 }
 
 export interface DragDirection {

+ 78 - 23
packages/semi-ui/input/__test__/textArea.test.js

@@ -1,22 +1,22 @@
 import TextArea from '../textarea';
 import Icon from '../../icons/index';
 import { BASE_CLASS_PREFIX } from '../../../semi-foundation/base/constants';
+import truncateValue from '../../../semi-foundation/input/util/truncateValue';
 import GraphemeSplitter from 'grapheme-splitter';
 import { isString } from 'lodash';
 
 function getValueLength(str) {
-  if (isString(str)) {
-    const splitter = new GraphemeSplitter();
-    return splitter.countGraphemes(str);
-  } else {
-    return -1;
-  }
+    if (isString(str)) {
+        const splitter = new GraphemeSplitter();
+        return splitter.countGraphemes(str);
+    } else {
+        return -1;
+    }
 }
 
 describe('TextArea', () => {
-
     it('TextArea with custom className & style', () => {
-        const wrapper = mount(<TextArea className='test' style={{ color: 'red' }} />);
+        const wrapper = mount(<TextArea className="test" style={{ color: 'red' }} />);
         expect(wrapper.hasClass('test')).toEqual(true);
         expect(wrapper.find('div.test')).toHaveStyle('color', 'red');
     });
@@ -39,7 +39,7 @@ describe('TextArea', () => {
         textArea.find('textarea').simulate('change', event);
         expect(spyOnChange.calledOnce).toBe(true);
         expect(spyOnChange.calledWithMatch(textAreaValue)).toBe(true);
-    })
+    });
 
     it('TextArea show maxCount', () => {
         const textarea = mount(<TextArea maxCount={10} />);
@@ -54,21 +54,24 @@ describe('TextArea', () => {
         const textarea = mount(<TextArea placeholder={placeholderText} />);
         let textareaDom = textarea.find('textarea');
         expect(textareaDom.props().placeholder).toEqual(placeholderText);
-    })
+    });
 
     it('TextArea disabled', () => {
         const textarea = mount(<TextArea disabled />);
         let textareaDom = textarea.find(`textarea.${BASE_CLASS_PREFIX}-input-textarea-disabled`);
         expect(textareaDom.props().disabled).toEqual(true);
-    })
+    });
 
     it('TextArea showClear / onClear', () => {
-        const spyOnClear = sinon.spy(()=>{});
-        const textarea = mount(<TextArea showClear defaultValue='123' onClear={spyOnClear}/>);
-        textarea.simulate('mouseEnter', {}).find(`.${BASE_CLASS_PREFIX}-input-clearbtn`).simulate('click');
+        const spyOnClear = sinon.spy(() => {});
+        const textarea = mount(<TextArea showClear defaultValue="123" onClear={spyOnClear} />);
+        textarea
+            .simulate('mouseEnter', {})
+            .find(`.${BASE_CLASS_PREFIX}-input-clearbtn`)
+            .simulate('click');
         expect(spyOnClear.calledOnce).toBe(true);
         expect(textarea.find(`.${BASE_CLASS_PREFIX}-input-textarea`).getDOMNode().textContent).toEqual('');
-    })
+    });
 
     // TODO
     // it('TextArea autosize', () => {
@@ -95,7 +98,7 @@ describe('TextArea', () => {
             console.log(e);
         };
         let spyOnChange = sinon.spy(onChange);
-        const textArea = mount(<TextArea onChange={spyOnChange} value='semi' />);
+        const textArea = mount(<TextArea onChange={spyOnChange} value="semi" />);
         const textareaDom = textArea.find('textarea');
         expect(textareaDom.instance().value).toEqual('semi');
         let newValue = 'vita lemon';
@@ -125,10 +128,12 @@ describe('TextArea', () => {
         let event1 = { target: { value: inputValue1 } };
 
         let onChange = value => {
-        console.log(value);
+            console.log(value);
         };
         let spyOnChange = sinon.spy(onChange);
-        const textArea = mount(<TextArea onChange={spyOnChange} minLength={minLength} getValueLength={getValueLength} />);
+        const textArea = mount(
+            <TextArea onChange={spyOnChange} minLength={minLength} getValueLength={getValueLength} />
+        );
         const textAreaDom = textArea.find('textarea');
 
         textAreaDom.simulate('change', event);
@@ -138,7 +143,7 @@ describe('TextArea', () => {
 
         textAreaDom.simulate('change', event1);
         expect(spyOnChange.calledWithMatch(textAreaDom)).toBe(true);
-        expect(textAreaDom.instance().minLength).toEqual(minLength)
+        expect(textAreaDom.instance().minLength).toEqual(minLength);
     });
 
     it('test maxLength + truncateValue', () => {
@@ -149,7 +154,9 @@ describe('TextArea', () => {
             };
 
             let spyOnChange = sinon.spy(onChange);
-            const textArea = mount(<TextArea onChange={spyOnChange} maxLength={maxLength} getValueLength={getValueLength} />);
+            const textArea = mount(
+                <TextArea onChange={spyOnChange} maxLength={maxLength} getValueLength={getValueLength} />
+            );
             const textAreaDom = textArea.find('textarea');
             textAreaDom.simulate('change', event);
             expect(spyOnChange.calledOnce).toBe(true);
@@ -157,7 +164,7 @@ describe('TextArea', () => {
         }
 
         const testCases = [
-        // 自定义valueLength
+            // 自定义valueLength
             ['Semi', 5, getValueLength, 'Semi'],
             ['Semi Design', 4, getValueLength, 'Semi'],
             ['💖💖💖💖💖💖💖💖💖💖👨👩👧👦', 10, getValueLength, '💖💖💖💖💖💖💖💖💖💖'],
@@ -168,5 +175,53 @@ describe('TextArea', () => {
         for (let [value, length, fc, result] of testCases) {
             expect(truncateValue(value, length, fc)).toBe(result);
         }
-  })
-})
+    });
+
+    it('test truncateValue', () => {
+        expect(truncateValue({ value: 'Semi Design', getValueLength, maxLength: 4 })).toBe('Semi');
+        expect(truncateValue({ value: 'Semi', getValueLength, maxLength: 4 })).toBe('Semi');
+        expect(truncateValue({ value: 'Se', getValueLength, maxLength: 1 })).toBe('S');
+        expect(truncateValue({ value: 'S', getValueLength, maxLength: 2 })).toBe('S');
+        expect(truncateValue({ value: '', getValueLength, maxLength: 2 })).toBe('');
+
+        expect(truncateValue({ value: '💖💖💖💖💖', getValueLength, maxLength: 4 })).toBe('💖💖💖💖');
+        expect(truncateValue({ value: '💖💖💖💖', getValueLength, maxLength: 4 })).toBe('💖💖💖💖');
+        expect(truncateValue({ value: '💖', getValueLength, maxLength: 1 })).toBe('💖');
+    });
+
+    it('test truncateValue function call time', () => {
+        function truncateValue(inputValue, maxLength) {
+            let event = { target: { value: inputValue } };
+
+            let spyTruncateValue = sinon.spy((str) => {
+                console.log('call getValueLength', str);
+                if (isString(str)) {
+                    const splitter = new GraphemeSplitter();
+                    return splitter.countGraphemes(str);
+                } else {
+                    return 0;
+                }
+            });
+            
+            const textArea = mount(
+                <TextArea maxLength={maxLength} getValueLength={spyTruncateValue} />
+            );
+            const textAreaDom = textArea.find('textarea');
+            textAreaDom.simulate('change', event);
+            // 超出判断一次,截断判断 LogN 次
+            const expectedValue = 1 + Math.ceil(Math.log2(inputValue.length));
+            console.log('expectedValue', expectedValue);
+            expect(spyTruncateValue.callCount).toBeLessThanOrEqual(expectedValue);
+            return textAreaDom.instance().value;
+        }
+
+        const testCases = [
+            ['Semi Design', 4],
+            [Array.from({ length: 1000 }).fill('👨‍👩‍👧‍👦').join(''), 500],
+        ];
+
+        for (let [value, length, expectedCalcTimes] of testCases) {
+            truncateValue(value, length, expectedCalcTimes);
+        }
+    });
+});

+ 4 - 1
packages/semi-ui/locale/interface.ts

@@ -99,7 +99,10 @@ export interface Locale {
     };
     Table: {
         emptyText: string;
-        pageText: string
+        pageText: string;
+        descend: string;
+        ascend: string;
+        cancelSort: string
     };
     Select: {
         emptyText: string;

+ 3 - 0
packages/semi-ui/locale/source/ar.ts

@@ -101,6 +101,9 @@ const local: Locale = {
     Table: {
         emptyText: 'لا نتيجة',
         pageText: 'عرض ${currentStart} إلى ${currentEnd} من ${total}',
+        descend: 'انقر للهبوط',
+        ascend: 'انقر للصعود',
+        cancelSort: 'إلغاء الترتيب',
     },
     Select: {
         emptyText: 'لا نتيجة',

+ 3 - 0
packages/semi-ui/locale/source/de.ts

@@ -101,6 +101,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Kein Ergebnis',
         pageText: 'Anzeigen ${currentStart} bis ${currentEnd} von ${total}',
+        descend: 'Klicken, um absteigend zu sortieren',
+        ascend: 'Klicken, um aufsteigend zu sortieren',
+        cancelSort: 'Sortierung abbrechen',
     },
     Select: {
         emptyText: 'Kein Ergebnis',

+ 3 - 0
packages/semi-ui/locale/source/en_GB.ts

@@ -101,6 +101,9 @@ const local: Locale = {
     Table: {
         emptyText: 'No Result',
         pageText: 'Showing ${currentStart} to ${currentEnd} of ${total}',
+        descend: 'Click to descend',
+        ascend: 'Click to ascend',
+        cancelSort: 'Cancel sorting',
     },
     Select: {
         emptyText: 'No Result',

+ 3 - 0
packages/semi-ui/locale/source/en_US.ts

@@ -101,6 +101,9 @@ const local: Locale = {
     Table: {
         emptyText: 'No Result',
         pageText: 'Showing ${currentStart} to ${currentEnd} of ${total}',
+        descend: 'Click to descend',
+        ascend: 'Click to ascend',
+        cancelSort: 'Cancel sorting',
     },
     Select: {
         emptyText: 'No Result',

+ 3 - 0
packages/semi-ui/locale/source/es.ts

@@ -106,6 +106,9 @@ const locale: Locale = {
     Table: {
         emptyText: 'Sin resultados',
         pageText: 'Mostrando del ${currentStart} al ${currentEnd} de ${total}',
+        descend: 'Hacer clic para descender',
+        ascend: 'Hacer clic para ascender',
+        cancelSort: 'Cancelar ordenación',
     },
     Select: {
         emptyText: 'Sin resultados',

+ 3 - 0
packages/semi-ui/locale/source/fr.ts

@@ -101,6 +101,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Aucun Résultat',
         pageText: 'Montrant ${currentStart} to ${currentEnd} of ${total}',
+        descend: 'Cliquez pour descendre',
+        ascend: 'Cliquez pour monter',
+        cancelSort: 'Annuler le tri',
     },
     Select: {
         emptyText: 'Aucun Résultat',

+ 3 - 0
packages/semi-ui/locale/source/id_ID.ts

@@ -101,6 +101,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Tidak ada Hasil',
         pageText: 'Tampilkan halaman ${currentStart} sampai ${currentEnd} dari ${total}',
+        descend: 'Klik untuk menurun',
+        ascend: 'Klik untuk menaik',
+        cancelSort: 'Batalkan penyortiran',
     },
     Select: {
         emptyText: 'Tidak ada Hasil',

+ 3 - 0
packages/semi-ui/locale/source/it.ts

@@ -101,6 +101,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Nessun risultato',
         pageText: 'Mostra ${currentStart} a ${currentEnd} di ${total}',
+        descend: 'Clicca per discendere',
+        ascend: 'Clicca per ascendere',
+        cancelSort: 'Annulla ordinamento',
     },
     Select: {
         emptyText: 'Nessun risultato',

+ 3 - 0
packages/semi-ui/locale/source/ja_JP.ts

@@ -102,6 +102,9 @@ const local: Locale = {
     Table: {
         emptyText: 'データがありません',
         pageText: '第 ${currentStart} 条から第 ${currentEnd} 条まで表示します。計 ${total} 条',
+        descend: 'クリックして降順',
+        ascend: 'クリックして昇順',
+        cancelSort: 'ソートのキャンセル',
     },
     Select: {
         emptyText: 'データがありません',

+ 3 - 0
packages/semi-ui/locale/source/ko_KR.ts

@@ -102,6 +102,9 @@ const local: Locale = {
     Table: {
         emptyText: '결과 없음',
         pageText: '${total} 중 ${currentStart}-${currentEnd}',
+        descend: '내림차순을 보려면 클릭하세요',
+        ascend: '오름차순을 보려면 클릭하세요',
+        cancelSort: '정렬 취소',
     },
     Select: {
         emptyText: '결과 없음',

+ 3 - 0
packages/semi-ui/locale/source/ms_MY.ts

@@ -101,6 +101,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Tiada kandungan',
         pageText: 'Papar halaman ${currentStart} hingga ${currentEnd} daripada ${total}',
+        descend: 'Klik untuk menurun',
+        ascend: 'Klik untuk menaik',
+        cancelSort: 'Batal mengurutkan',
     },
     Select: {
         emptyText: 'Tiada kandungan',

+ 3 - 0
packages/semi-ui/locale/source/nl_NL.ts

@@ -108,6 +108,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Geen resultaten gevonden',
         pageText: '${currentStart} tot ${currentEnd} van ${total} wordt weergegeven',
+        descend: 'Klik om af te dalen',
+        ascend: 'Klik om op te stijgen',
+        cancelSort: 'Sorteren annuleren',
     },
     Select: {
         emptyText: 'Geen resultaten gevonden',

+ 3 - 0
packages/semi-ui/locale/source/pl_PL.ts

@@ -109,6 +109,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Nie znaleziono żadnych wyników',
         pageText: 'Wyświetlanie od ${currentStart} do ${currentEnd} z ${total}',
+        descend: 'Kliknij, aby sortować malejąco',
+        ascend: 'Kliknij, aby sortować rosnąco',
+        cancelSort: 'Anuluj sortowanie',
     },
     Select: {
         emptyText: 'Nie znaleziono żadnych wyników',

+ 3 - 0
packages/semi-ui/locale/source/pt_BR.ts

@@ -109,6 +109,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Não há dados',
         pageText: 'Mostrando ${currentStart} - ${currentEnd} ,de ${total}',
+        descend: 'Clique para descrescer',
+        ascend: 'Clique para crescer',
+        cancelSort: 'Cancelar classificação',
     },
     Select: {
         emptyText: 'Não há dados',

+ 3 - 0
packages/semi-ui/locale/source/ro.ts

@@ -101,6 +101,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Nici un rezultat',
         pageText: 'Arată ${currentStart} la ${currentEnd} de ${total}',
+        descend: 'Faceți clic pentru a coborî',
+        ascend: 'Faceți clic pentru a urca',
+        cancelSort: 'Anulați sortarea',
     },
     Select: {
         emptyText: 'Nici un rezultat',

+ 3 - 0
packages/semi-ui/locale/source/ru_RU.ts

@@ -104,6 +104,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Нет результата',
         pageText: 'Отображение ${currentStart} до ${currentEnd} из ${total}',
+        descend: 'Щелкните, чтобы упорядочить по убыванию',
+        ascend: 'Щелкните, чтобы упорядочить по возрастанию',
+        cancelSort: 'Отменить сортировку',
     },
     Select: {
         emptyText: 'Нет результата',

+ 3 - 0
packages/semi-ui/locale/source/sv_SE.ts

@@ -106,6 +106,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Inga resultat hittades',
         pageText: 'Visar ${currentStart} till ${currentEnd} av ${total}',
+        descend: 'Klicka för att sortera fallande',
+        ascend: 'Klicka för att sortera stigande',
+        cancelSort: 'Avbryt sortering',
     },
     Select: {
         emptyText: 'Inga resultat hittades',

+ 3 - 0
packages/semi-ui/locale/source/th_TH.ts

@@ -105,6 +105,9 @@ const local: Locale = {
     Table: {
         emptyText: 'ไม่มีข้อมูล',
         pageText: 'แสดงรายการ ${currentStart} - ${currentEnd} จาก ${total}',
+        descend: 'คลิกเพื่อเรียงจากมากไปหาน้อย',
+        ascend: 'คลิกเพื่อเรียงจากน้อยไปหามาก',
+        cancelSort: 'ยกเลิกการเรียงลำดับ',
     },
     Select: {
         emptyText: 'ไม่มีข้อมูล',

+ 5 - 1
packages/semi-ui/locale/source/tr_TR.ts

@@ -107,7 +107,11 @@ const local: Locale = {
     {
         emptyText: 'Henüz veri yok',
         pageText:
-            '${currentStart} öğesini görüntüle - ${currentEnd} öğe, toplam ${total} öğe '
+            '${currentStart} öğesini görüntüle - ${currentEnd} öğe, toplam ${total} öğe ',
+        descend: 'Azalan sıralama için tıklayın',
+        ascend: 'Artan sıralama için tıklayın',
+        cancelSort: 'Sıralamayı iptal et',
+    
     },
     Select: { emptyText: 'Henüz veri yok', createText: 'Oluştur' },
     Cascader: { emptyText: 'Henüz veri yok' },

+ 3 - 0
packages/semi-ui/locale/source/vi_VN.ts

@@ -104,6 +104,9 @@ const local: Locale = {
     Table: {
         emptyText: 'Không kết quả',
         pageText: 'Hiển thị ${currentStart} đến ${currentEnd} trong tổng số ${total}',
+        descend: 'Nhấp để sắp xếp giảm dần',
+        ascend: 'Nhấp để sắp xếp tăng dần',
+        cancelSort: 'Hủy sắp xếp',
     },
     Select: {
         emptyText: 'Không kết quả',

+ 3 - 0
packages/semi-ui/locale/source/zh_CN.ts

@@ -102,6 +102,9 @@ const local: Locale = {
     Table: {
         emptyText: '暂无数据',
         pageText: '显示第 ${currentStart} 条-第 ${currentEnd} 条,共 ${total} 条',
+        descend: '点击降序',
+        ascend: '点击升序',
+        cancelSort: '取消排序',
     },
     Select: {
         emptyText: '暂无数据',

+ 3 - 0
packages/semi-ui/locale/source/zh_TW.ts

@@ -102,6 +102,9 @@ const local: Locale = {
     Table: {
         emptyText: '暫無數據',
         pageText: '顯示第 ${currentStart} 條-第 ${currentEnd} 條,共 ${total} 條',
+        descend: '點擊降序',
+        ascend: '點擊升序',
+        cancelSort: '取消排序',
     },
     Select: {
         emptyText: '暫無數據',

+ 45 - 0
packages/semi-ui/markdownRender/__test__/markdown.test.js

@@ -0,0 +1,45 @@
+import MarkdownRender from '../index'
+import React from 'react';
+import { mount } from 'enzyme';
+import { BASE_CLASS_PREFIX } from '@douyinfe/semi-foundation/base/constants';
+
+
+describe(`MarkdownRender`, () => {
+    it(`test table render`, async () => {
+        const content = `
+        | Name | Brand | Count | Price |
+        | - | :- | -: | :-: |
+        | Book | Semi | 10 | ¥100 |
+        | Pen | Semi Design | 20 | ¥200 |
+        `;
+
+        const render = mount(
+            <MarkdownRender raw={content} />
+        );
+
+        // check if has table container
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-container`)).toEqual(true);
+        // check if has table head & body
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-thead`)).toEqual(true);
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-tbody`)).toEqual(true);
+        // check has row is two
+        expect(render.find(`.${BASE_CLASS_PREFIX}-table-tbody .${BASE_CLASS_PREFIX}-table-row`).length).toBe(2);
+    });
+
+    it(`test table only header`, async () => {
+        const content = `
+        | Title | Name | Count | Price |
+        | - | :- | -: | :-: |
+        `;
+
+        const render = mount(
+            <MarkdownRender raw={content} />
+        );
+
+        // check if has table container
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-container`)).toEqual(true);
+        // check if has table head & body
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-thead`)).toEqual(true);
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-tbody`)).toEqual(true);
+    });
+});

+ 1 - 1
packages/semi-ui/markdownRender/components/table.tsx

@@ -19,7 +19,7 @@ const table = (props: PropsWithChildren<TableProps>)=>{
         let item: Record<string, string> = {
             key: String(i)
         };
-        dataFiber[i].props.children.forEach((child, index)=>{
+        dataFiber[i]?.props.children.forEach((child, index)=>{
             item[titles[index]] = child?.props?.children ?? "";
         });
         tableDataSource.push(item);

+ 3 - 1
packages/semi-ui/markdownRender/index.tsx

@@ -38,7 +38,9 @@ class MarkdownRender extends BaseComponent<MarkdownRenderProps, MarkdownRenderSt
         style: PropTypes.object,
         format: PropTypes.string,
         components: PropTypes.any,
-        raw: PropTypes.string
+        raw: PropTypes.string,
+        remarkPlugins: PropTypes.arrayOf(PropTypes.object),
+        rehypePlugins: PropTypes.arrayOf(PropTypes.object),
     }
 
     static __SemiComponentName__ = "MarkdownRender";

+ 2 - 3
packages/semi-ui/navigation/_story/NumberItemKey/index.jsx

@@ -1,7 +1,6 @@
-import React from 'react';
+import React, { useState } from 'react';
 import { Nav } from '@douyinfe/semi-ui';
-import { IconSemiLogo } from '@douyinfe/semi-icons';
-import { IconDescriptions, IconIntro, IconTree, IconAvatar, IconTreeSelect, IconTabs } from '@douyinfe/semi-icons-lab';
+import { IconDescriptions, IconTree, IconAvatar } from '@douyinfe/semi-icons-lab';
 
 export default function NumberItemKey() {
     const [openKeys, setOpenKeys] = useState([]);

+ 8 - 7
packages/semi-ui/overflowList/index.tsx

@@ -10,7 +10,8 @@ import IntersectionObserver from './intersectionObserver';
 import OverflowListFoundation, { OverflowListAdapter } from '@douyinfe/semi-foundation/overflowList/foundation';
 
 import '@douyinfe/semi-foundation/overflowList/overflowList.scss';
-import { cloneDeep, getDefaultPropsFromGlobalConfig } from '../_utils';
+import { getDefaultPropsFromGlobalConfig } from '../_utils';
+import copy from 'fast-copy';
 
 const prefixCls = cssClasses.PREFIX;
 const Boundary = strings.BOUNDARY_MAP;
@@ -20,7 +21,7 @@ const RenderMode = strings.MODE_MAP;
 export type { ReactIntersectionObserverProps } from './intersectionObserver';
 export type OverflowItem = Record<string, any>;
 
-type Key = string|number
+type Key = string | number
 
 export interface OverflowListProps {
     className?: string;
@@ -38,7 +39,7 @@ export interface OverflowListProps {
     wrapperStyle?: CSSProperties;
     itemKey?: Key | ((item: OverflowItem) => Key);
     onVisibleStateChange?: (visibleState: Map<string, boolean>) => void;
-    overflowRenderDirection?: "both"|"start"|'end' // used in tabs, not exposed to user
+    overflowRenderDirection?: "both" | "start" | 'end' // used in tabs, not exposed to user
 }
 
 export interface OverflowListState {
@@ -134,8 +135,8 @@ class OverflowList extends BaseComponent<OverflowListProps, OverflowListState> {
                 }
 
                 const isCollapseFromStart = props.collapseFrom === Boundary.START;
-                const visible = isCollapseFromStart ? cloneDeep(props.items).reverse().slice(0, maxCount) : props.items.slice(0, maxCount);
-                const overflow = isCollapseFromStart ? cloneDeep(props.items).reverse().slice(maxCount) : props.items.slice(maxCount);
+                const visible = isCollapseFromStart ? copy(props.items).reverse().slice(0, maxCount) : props.items.slice(0, maxCount);
+                const overflow = isCollapseFromStart ? copy(props.items).reverse().slice(maxCount) : props.items.slice(maxCount);
                 newState.visible = visible;
                 newState.overflow = overflow;
                 newState.maxCount = maxCount;
@@ -150,7 +151,7 @@ class OverflowList extends BaseComponent<OverflowListProps, OverflowListState> {
         return {
             ...super.adapter,
             updateVisibleState: (visibleState): void => {
-                this.setState({ visibleState }, ()=>{
+                this.setState({ visibleState }, () => {
                     this.props.onVisibleStateChange?.(visibleState);
                 });
             },
@@ -266,7 +267,7 @@ class OverflowList extends BaseComponent<OverflowListProps, OverflowListState> {
         }
         const inner =
             renderMode === RenderMode.SCROLL ?
-                (()=>{
+                (() => {
                     const list = [<div
                         className={cls(wrapperClassName, `${prefixCls}-scroll-wrapper`)}
                         ref={(ref): void => {

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.64.0",
+    "version": "2.65.0-beta.0",
     "description": "A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.",
     "main": "lib/cjs/index.js",
     "module": "lib/es/index.js",
@@ -20,11 +20,11 @@
         "@dnd-kit/core": "^6.0.8",
         "@dnd-kit/sortable": "^7.0.2",
         "@dnd-kit/utilities": "^3.2.1",
-        "@douyinfe/semi-animation": "2.64.0",
-        "@douyinfe/semi-animation-react": "2.64.0",
-        "@douyinfe/semi-foundation": "2.64.0",
-        "@douyinfe/semi-icons": "2.64.0",
-        "@douyinfe/semi-illustrations": "2.64.0",
+        "@douyinfe/semi-animation": "2.65.0-beta.0",
+        "@douyinfe/semi-animation-react": "2.65.0-beta.0",
+        "@douyinfe/semi-foundation": "2.65.0-beta.0",
+        "@douyinfe/semi-icons": "2.65.0-beta.0",
+        "@douyinfe/semi-illustrations": "2.65.0-beta.0",
         "@douyinfe/semi-theme-default": "2.61.0",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",

+ 4 - 1
packages/semi-ui/table/Body/BaseRow.tsx

@@ -245,8 +245,11 @@ export default class TableRow extends BaseComponent<BaseRowProps, Record<string,
                 // Only the first data row will be indented
                 if (level != null && columnIndex === firstIndex) {
                     expandableProps.indent = level;
+                    const isBool = typeof expandIcon === 'boolean';
+                    const hasExpandIcon = expandIcon !== false || !isBool && expandIcon !== null;
 
-                    if (!expandableRow && hideExpandedColumn) {
+                    // 如果 expandIcon 为空,不需要 indent
+                    if (!expandableRow && hideExpandedColumn && hasExpandIcon) {
                         expandableProps.indent = level + 1;
                     }
                 }

+ 1 - 0
packages/semi-ui/table/ColumnShape.ts

@@ -30,4 +30,5 @@ export default {
     title: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
     useFullRender: PropTypes.bool,
     width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+    showSortTip: PropTypes.bool,
 };

+ 30 - 13
packages/semi-ui/table/ColumnSorter.tsx

@@ -6,8 +6,11 @@ import { IconCaretup, IconCaretdown } from '@douyinfe/semi-icons';
 
 import { cssClasses, strings } from '@douyinfe/semi-foundation/table/constants';
 
-import { SortIcon, SortOrder } from './interface';
+import { SortIcon, SortOrder, TableLocale } from './interface';
+import Tooltip from '../tooltip';
 import isEnterPress from '@douyinfe/semi-foundation/utils/isEnterPress';
+import { getNextSortOrder } from './utils';
+import LocaleConsumer from '../locale/localeConsumer';
 
 export interface ColumnSorterProps {
     className?: string;
@@ -16,7 +19,8 @@ export interface ColumnSorterProps {
     prefixCls?: string;
     sortOrder?: SortOrder;
     title?: React.ReactNode;
-    sortIcon?: SortIcon
+    sortIcon?: SortIcon;
+    showTooltip?: boolean
 }
 
 export default class ColumnSorter extends PureComponent<ColumnSorterProps> {
@@ -27,16 +31,18 @@ export default class ColumnSorter extends PureComponent<ColumnSorterProps> {
         prefixCls: PropTypes.string,
         sortOrder: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
         sortIcon: PropTypes.func,
+        showTooltip: PropTypes.bool,
     };
 
     static defaultProps = {
         prefixCls: cssClasses.PREFIX,
         onClick: noop,
         sortOrder: false,
+        showTooltip: false
     };
 
     render() {
-        const { prefixCls, onClick, sortOrder, style, title, sortIcon } = this.props;
+        const { prefixCls, onClick, sortOrder, style, title, sortIcon, showTooltip } = this.props;
 
         const iconBtnSize = 'default';
 
@@ -60,16 +66,27 @@ export default class ColumnSorter extends PureComponent<ColumnSorterProps> {
             if (typeof sortIcon === 'function') {
                 return sortIcon({ sortOrder });
             } else {
-                return (
-                    <div style={style} className={`${prefixCls}-column-sorter`}>
-                        <span className={`${upCls}`}>
-                            <IconCaretup size={iconBtnSize} />
-                        </span>
-                        <span className={`${downCls}`}>
-                            <IconCaretdown size={iconBtnSize} />
-                        </span>
-                    </div>
-                );
+                const node = (<div style={style} className={`${prefixCls}-column-sorter`}>
+                    <span className={`${upCls}`}>
+                        <IconCaretup size={iconBtnSize} />
+                    </span>
+                    <span className={`${downCls}`}>
+                        <IconCaretdown size={iconBtnSize} />
+                    </span>
+                </div>);
+                if (showTooltip) {
+                    let content = getNextSortOrder(sortOrder);
+                    return (<LocaleConsumer 
+                        componentName="Table" 
+                    >
+                        {(locale: TableLocale, localeCode: string) => (
+                            <Tooltip content={locale[content]}>
+                                {node}
+                            </Tooltip>
+                        )}
+                    </LocaleConsumer>);
+                }
+                return node;
             }
         };
 

+ 18 - 7
packages/semi-ui/table/Table.tsx

@@ -1008,8 +1008,15 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
     addFnsInColumn = (column: ColumnProps = {}) => {
         const { prefixCls } = this.props;
         if (column && (column.sorter || column.filters || column.onFilter || column.useFullRender)) {
+            let hasSorter = typeof column.sorter === 'function' || column.sorter === true;
+            let hasFilter = (Array.isArray(column.filters) && column.filters.length) ||
+                isValidElement(column.filterDropdown) ||
+                typeof column.renderFilterDropdown === 'function';
             let hasSorterOrFilter = false;
+            const sortOrderNotControlled = !('sortOrder' in column);
+            const showSortTip = sortOrderNotControlled && column.showSortTip === true;
             const { dataIndex, title: rawTitle, useFullRender } = column;
+            const clickColumnToSorter = hasSorter && !hasFilter && !Boolean(useFullRender);
             const curQuery = this.foundation.getQuery(dataIndex);
             const titleMap: ColumnTitleProps = {};
             const titleArr = [];
@@ -1032,7 +1039,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
                     {rawTitle as React.ReactNode}
                 </span>
             );
-            if (typeof column.sorter === 'function' || column.sorter === true) {
+            if (hasSorter) {
                 // In order to increase the click hot area of ​​sorting, when sorting is required & useFullRender is false,
                 // both the title and sorting areas are used as the click hot area for sorting。
                 const sorter = (
@@ -1040,8 +1047,9 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
                         key={strings.DEFAULT_KEY_COLUMN_SORTER}
                         sortOrder={sortOrder}
                         sortIcon={column.sortIcon}
-                        onClick={e => this.foundation.handleSort(column, e)}
+                        onClick={useFullRender || hasFilter ? e => this.foundation.handleSort(column, e) : null}
                         title={TitleNode}
+                        showTooltip={!clickColumnToSorter && showSortTip}
                     />
                 );
                 useFullRender && (titleMap.sorter = sorter);
@@ -1054,11 +1062,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
             const stateFilteredValue = get(curQuery, 'filteredValue');
             const defaultFilteredValue = get(curQuery, 'defaultFilteredValue');
             const filteredValue = stateFilteredValue ? stateFilteredValue : defaultFilteredValue;
-            if (
-                (Array.isArray(column.filters) && column.filters.length) ||
-                isValidElement(column.filterDropdown) ||
-                typeof column.renderFilterDropdown === 'function'
-            ) {
+            if (hasFilter) {
 
                 const filter = (
                     <ColumnFilter
@@ -1086,6 +1090,13 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
                 );
 
             column = { ...column, title: newTitle };
+            if (clickColumnToSorter) {
+                column.clickToSort = e => {
+                    this.foundation.handleSort(column, e);
+                };
+                column.sortOrder = sortOrder;
+                column.showSortTip = showSortTip;
+            }
         }
 
         return column;

+ 37 - 10
packages/semi-ui/table/TableHeaderRow.tsx

@@ -15,8 +15,11 @@ import {
 } from '@douyinfe/semi-foundation/table/utils';
 import BaseComponent from '../_base/baseComponent';
 import TableContext, { TableContextProps } from './table-context';
-import { TableComponents, OnHeaderRow, Fixed } from './interface';
+import { TableComponents, OnHeaderRow, Fixed, TableLocale } from './interface';
 import type { TableHeaderCell } from './TableHeader';
+import Tooltip from '../tooltip';
+import LocaleConsumer from '../locale/localeConsumer';
+import { getNextSortOrder } from './utils';
 
 export interface TableHeaderRowProps {
     components?: TableComponents;
@@ -149,6 +152,7 @@ export default class TableHeaderRow extends BaseComponent<TableHeaderRowProps, R
                     [`${prefixCls}-cell-fixed-right`]: fixedRight,
                     [`${prefixCls}-cell-fixed-right-first`]: fixedRightFirst,
                     [`${prefixCls}-row-head-ellipsis`]: column.ellipsis,
+                    [`${prefixCls}-row-head-clickSort`]: column.clickToSort
                 }
             );
 
@@ -190,17 +194,40 @@ export default class TableHeaderRow extends BaseComponent<TableHeaderRowProps, R
             if (rowSpan === 0 || colSpan === 0) {
                 return null;
             }
+            
+            if (typeof column.clickToSort === 'function') {
+                if (props.onClick) {
+                    props.onClick = (e: any) => {
+                        props.onClick(e);
+                        column.clickToSort(e);
+                    };
+                } else {
+                    props.onClick = column.clickToSort;
+                }
+            }
 
-            return (
-                // @ts-ignore  no need to do complex ts type checking and qualification
-                <HeaderCell
-                    role="columnheader"
-                    aria-colindex={cellIndex + 1}
-                    {...props}
-                    style={cellStyle}
+            const headerCellNode = (<HeaderCell
+                role="columnheader"
+                aria-colindex={cellIndex + 1}
+                {...props}
+                style={cellStyle}
+                key={column.key || column.dataIndex || cellIndex}
+            />);
+            if (typeof column.clickToSort === 'function' && column.showSortTip === true) {
+                let content = getNextSortOrder(column.sortOrder);
+                return (<LocaleConsumer 
+                    componentName="Table" 
                     key={column.key || column.dataIndex || cellIndex}
-                />
-            );
+                >
+                    {(locale: TableLocale, localeCode: string) => (
+                        <Tooltip content={locale[content]}>
+                            {headerCellNode}
+                        </Tooltip>
+                    )}
+                </LocaleConsumer>);
+            }
+
+            return headerCellNode;
         });
 
         return (

+ 2 - 1
packages/semi-ui/table/_story/table.stories.jsx

@@ -118,7 +118,8 @@ export {
     DndKitDrag,
     FixedOnGroupedRowClassName,
     FixedVirtualizedRef,
-    RowSelectionOnCell
+    RowSelectionOnCell,
+    FixedIndent
 } from './v2';
 export { default as FixSelectAll325 } from './Demos/rowSelection';
 

+ 54 - 0
packages/semi-ui/table/_story/v2/FixedIndent/index.tsx

@@ -0,0 +1,54 @@
+import React from 'react';
+import { Table } from '@douyinfe/semi-ui';
+
+export default function App() {
+    const columns = [
+        {
+            title: 'Key',
+            dataIndex: 'dataKey',
+            key: 'dataKey',
+        },
+        {
+            title: '名称',
+            dataIndex: 'name',
+            key: 'name',
+            width: 200,
+        },
+        {
+            title: '数据类型',
+            dataIndex: 'type',
+            key: 'type',
+            width: 400,
+        },
+    ];
+    const data = [
+        {
+            key: 99,
+            dataKey: 99,
+            name: 'row 99',
+            children: [],
+        },
+        {
+            key: 2,
+            dataKey: 'text_info',
+            name: '文本信息',
+            type: 'Object 对象',
+            description: '视频的元信息',
+            default: '无',
+            children: [
+                {
+                    key: 21,
+                    dataKey: 'title',
+                    name: '视频标题',
+                    type: 'String 字符串',
+                    description: '视频的标题',
+                    default: '无',
+                },
+            ],
+        },
+    ];
+    return <div>
+        <Table expandIcon={false} defaultExpandAllRows columns={columns} dataSource={data} />
+        <Table defaultExpandAllRows columns={columns} dataSource={data} />
+    </div>;
+}

+ 1 - 0
packages/semi-ui/table/_story/v2/index.js

@@ -38,3 +38,4 @@ export { default as DndKitDrag } from './DndKitDrag';
 export { default as FixedOnGroupedRowClassName } from './FixedOnGroupedRowClassName';
 export { default as FixedVirtualizedRef } from './FixedVirtualizedRef';
 export { default as RowSelectionOnCell } from './RowSelectionOnCell';
+export { default as FixedIndent } from './FixedIndent';

+ 2 - 1
packages/semi-ui/table/interface.ts

@@ -116,7 +116,8 @@ export interface ColumnProps<RecordType extends Record<string, any> = any> {
     onFilterDropdownVisibleChange?: OnFilterDropdownVisibleChange;
     onHeaderCell?: OnHeaderCell<RecordType>;
     ellipsis?: BaseEllipsis;
-    resize?: boolean
+    resize?: boolean;
+    showSortTip?: boolean
 }
 
 export type Align = BaseAlign;

+ 12 - 1
packages/semi-ui/table/utils.ts

@@ -1,6 +1,6 @@
 import { merge, clone as lodashClone, find, map } from 'lodash';
 import Logger from '@douyinfe/semi-foundation/utils/Logger';
-import { numbers } from '@douyinfe/semi-foundation/table/constants';
+import { numbers, strings } from '@douyinfe/semi-foundation/table/constants';
 import { cloneDeep } from '../_utils';
 import { TableComponents, Virtualized } from './interface';
 import { getColumnKey } from '@douyinfe/semi-foundation/table/utils';
@@ -147,4 +147,15 @@ export function mergeColumns(oldColumns: any[] = [], newColumns: any[] = [], key
     return finalColumns;
 }
 
+export function getNextSortOrder(sortOrder: string | boolean) {
+    switch (sortOrder) {
+        case strings.SORT_DIRECTIONS[0]:
+            return strings.SORT_DIRECTIONS[1];
+        case strings.SORT_DIRECTIONS[1]:
+            return 'cancelSort';
+        default: 
+            return strings.SORT_DIRECTIONS[0];
+    }
+}
+
 export { cloneDeep };

+ 12 - 0
packages/semi-ui/tabs/_story/tabs.stories.jsx

@@ -1058,4 +1058,16 @@ export const ShowRestInDropdownDemo = () => {
       ))}
     </Tabs>
   )
+}
+
+export const Fix2415 = () => {
+  return (
+     <Tabs style={{ width: 250, margin: '20px' }} type="card" collapsible>
+        {[10,2324325324324,1111].map(i => (
+            <TabPane tab={`Tab-${i}`} itemKey={`Tab-${i}`} key={i}>
+                Content of card tab {i}.Quickly click the right arrow and observe that the arrow is disabled correctly.
+            </TabPane>   
+        ))}    
+    </Tabs>
+  )
 }

+ 1 - 1
packages/semi-ui/tree/interface.ts

@@ -84,7 +84,7 @@ export interface TreeProps extends BasicTreeProps {
     onSelect?: (selectedKey: string, selected: boolean, selectedNode: TreeNodeData) => void;
     renderDraggingNode?: (nodeInstance: HTMLElement, node: TreeNodeData) => HTMLElement;
     renderFullLabel?: (renderFullLabelProps: RenderFullLabelProps) => ReactNode;
-    renderLabel?: (label?: ReactNode, treeNode?: TreeNodeData) => ReactNode;
+    renderLabel?: (label?: ReactNode, treeNode?: TreeNodeData, searchWord?: string) => ReactNode;
     autoMergeValue?: boolean
 }
 export interface OptionProps {

+ 1 - 1
packages/semi-ui/tree/treeContext.tsx

@@ -36,7 +36,7 @@ export interface TreeContextValue {
     isSearching?: boolean;
     loadData?: (treeNode?: TreeNodeData) => Promise<void>;
     onNodeLoad?: (data: TreeNodeData) => Promise<unknown>;
-    renderLabel?: (label?: ReactNode, treeNode?: TreeNodeData) => ReactNode;
+    renderLabel?: (label?: ReactNode, treeNode?: TreeNodeData, searchWord?: string) => ReactNode;
     draggable?: boolean;
     renderFullLabel?: (renderFullLabelProps: RenderFullLabelProps) => React.ReactNode;
     dragOverNodeKey?: string | string[];

+ 1 - 1
packages/semi-ui/tree/treeNode.tsx

@@ -299,7 +299,7 @@ export default class TreeNode extends PureComponent<TreeNodeProps, TreeNodeState
         const { renderLabel } = this.context;
         const { label, keyword, data, filtered, treeNodeFilterProp } = this.props;
         if (isFunction(renderLabel)) {
-            return renderLabel(label, data);
+            return renderLabel(label, data, keyword);
         } else if (isString(label) && filtered && keyword) {
             return getHighLightTextHTML({
                 sourceString: label,

+ 35 - 0
packages/semi-ui/typography/__test__/typography.test.js

@@ -124,4 +124,39 @@ describe(`Typography`, () => {
         expect(typographyParagraph.find('.semi-typography').children().at(0).text()).toEqual('Key: code');
     });
 
+    it('custom copy render', () => {
+        const { Text } = Typography;
+        const code = 'code';
+
+        const typographyParagraph = mount(
+            <Text
+                style={{ marginTop: 6, color: 'var(--semi-color-text-2)' }}
+                ellipsis={{ showTooltip: { opts: { style: { wordBreak: 'break-word' } } } }}
+                copyable={{
+                    content: code,
+                    render: (copied, doCopy, config) => {
+                        return (
+                            <span className="test-copy-button" onClick={doCopy}>
+                                <span className="test-copied">{String(copied)}</span>
+                                <span className="test-copy-content">{config.content}</span>
+                            </span>
+                        );
+                    }
+                }}
+            >
+                Key: {code}
+            </Text>
+        );
+
+        // test basic render
+        expect(typographyParagraph.find('.test-copied').text()).toEqual('false');
+        expect(typographyParagraph.find('.test-copy-content').text()).toEqual(code);
+
+        // test copy
+        const trigger = typographyParagraph.find('.test-copy-button');
+        expect(trigger.length).toEqual(1);
+        trigger.at(0).simulate('click');
+        expect(typographyParagraph.find('.test-copied').text()).toEqual('true');
+    });
+
 });

+ 8 - 2
packages/semi-ui/typography/copyable.tsx

@@ -10,6 +10,7 @@ import { IconCopy, IconTick } from '@douyinfe/semi-icons';
 import { BaseProps } from '../_base/baseComponent';
 import { Locale } from '../locale/interface';
 import isEnterPress from '@douyinfe/semi-foundation/utils/isEnterPress';
+import { CopyableConfig } from './title';
 
 const prefixCls = cssClasses.PREFIX;
 
@@ -20,7 +21,8 @@ export interface CopyableProps extends BaseProps {
     forwardRef?: React.RefObject<any>;
     successTip?: React.ReactNode;
     icon?: React.ReactNode;
-    onCopy?: (e: React.MouseEvent, content: string, res: boolean) => void
+    onCopy?: (e: React.MouseEvent, content: string, res: boolean) => void;
+    render?: (copied: boolean, doCopy: (e: React.MouseEvent) => void, configs: CopyableConfig) => React.ReactNode
 }
 interface CopyableState {
     copied: boolean;
@@ -133,13 +135,17 @@ export class Copyable extends React.PureComponent<CopyableProps, CopyableState>
     }
 
     render() {
-        const { style, className, forwardRef, copyTip } = this.props;
+        const { style, className, forwardRef, copyTip, render } = this.props;
         const { copied } = this.state;
         const finalCls = cls(className, {
             [`${prefixCls}-action-copy`]: !copied,
             [`${prefixCls}-action-copied`]: copied,
         });
 
+        if (render) {
+            return render(copied, this.copy, this.props);
+        }
+
         return (
             <LocaleConsumer componentName="Typography">
                 {(locale: Locale['Typography']) => (

+ 3 - 1
packages/semi-ui/typography/title.tsx

@@ -13,7 +13,9 @@ export interface CopyableConfig {
     successTip?: React.ReactNode;
     icon?: React.ReactNode;
 
-    onCopy?(e: React.MouseEvent, content: string, res: boolean): void
+    onCopy?: (e: React.MouseEvent, content: string, res: boolean) => void;
+
+    render?: (copied: boolean, doCopy: (e: React.MouseEvent) => void, configs: CopyableConfig) => React.ReactNode
 }
 
 export type LinkType = React.AnchorHTMLAttributes<HTMLAnchorElement> | boolean;

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