浏览代码

Merge branch 'release' into feat_aweme

pointhalo 1 年之前
父节点
当前提交
b601db236a
共有 67 个文件被更改,包括 2069 次插入571 次删除
  1. 4 1
      .eslintrc.js
  2. 1 1
      content/input/datepicker/index-en-US.md
  3. 1 1
      content/input/datepicker/index.md
  4. 1 0
      content/input/treeselect/index-en-US.md
  5. 1 0
      content/input/treeselect/index.md
  6. 292 0
      content/show/table/index-en-US.md
  7. 290 0
      content/show/table/index.md
  8. 20 0
      content/start/changelog/index-en-US.md
  9. 21 0
      content/start/changelog/index.md
  10. 22 0
      cypress/e2e/datePicker.spec.js
  11. 33 0
      cypress/e2e/table.spec.js
  12. 11 0
      cypress/e2e/textarea.spec.js
  13. 1 1
      lerna.json
  14. 3 3
      packages/semi-animation-react/package.json
  15. 1 1
      packages/semi-animation-styled/package.json
  16. 1 1
      packages/semi-animation/package.json
  17. 1 1
      packages/semi-eslint-plugin/package.json
  18. 14 22
      packages/semi-foundation/datePicker/foundation.ts
  19. 5 1
      packages/semi-foundation/image/constants.ts
  20. 3 2
      packages/semi-foundation/image/previewInnerFoundation.ts
  21. 33 12
      packages/semi-foundation/input/textareaFoundation.ts
  22. 4 0
      packages/semi-foundation/modal/modalFoundation.ts
  23. 2 2
      packages/semi-foundation/package.json
  24. 2 0
      packages/semi-foundation/treeSelect/foundation.ts
  25. 1 1
      packages/semi-icons-lab/package.json
  26. 1 1
      packages/semi-icons/package.json
  27. 1 1
      packages/semi-illustrations/package.json
  28. 2 2
      packages/semi-next/package.json
  29. 1 1
      packages/semi-rspack/package.json
  30. 1 1
      packages/semi-scss-compile/package.json
  31. 1 1
      packages/semi-theme-default/package.json
  32. 3 1
      packages/semi-ui/_utils/index.tsx
  33. 13 0
      packages/semi-ui/cascader/__test__/cascader.test.js
  34. 59 0
      packages/semi-ui/cascader/_story/cascader.stories.jsx
  35. 1 1
      packages/semi-ui/cascader/index.tsx
  36. 3 1
      packages/semi-ui/datePicker/_story/datePicker.stories.jsx
  37. 22 0
      packages/semi-ui/datePicker/_story/v2/FixNeedConfirmControlled.tsx
  38. 23 0
      packages/semi-ui/datePicker/_story/v2/PresetsFunctionType.tsx
  39. 2 0
      packages/semi-ui/datePicker/_story/v2/index.js
  40. 1 1
      packages/semi-ui/dropdown/__test__/dropdown.test.js
  41. 5 4
      packages/semi-ui/dropdown/dropdownItem.tsx
  42. 2 1
      packages/semi-ui/image/interface.tsx
  43. 2 2
      packages/semi-ui/image/previewFooter.tsx
  44. 5 4
      packages/semi-ui/image/previewInner.tsx
  45. 16 0
      packages/semi-ui/input/_story/input.stories.jsx
  46. 56 78
      packages/semi-ui/input/textarea.tsx
  47. 2 0
      packages/semi-ui/modal/Modal.tsx
  48. 7 7
      packages/semi-ui/package.json
  49. 47 42
      packages/semi-ui/table/Body/BaseRow.tsx
  50. 27 21
      packages/semi-ui/table/Body/SectionRow.tsx
  51. 6 6
      packages/semi-ui/table/Body/index.tsx
  52. 187 109
      packages/semi-ui/table/ColumnFilter.tsx
  53. 59 16
      packages/semi-ui/table/Table.tsx
  54. 4 14
      packages/semi-ui/table/__test__/table.test.js
  55. 140 0
      packages/semi-ui/table/_story/RowSelectionRenderCell/index.jsx
  56. 10 1
      packages/semi-ui/table/_story/table.stories.jsx
  57. 135 0
      packages/semi-ui/table/_story/v2/FeatRenderFilterDropdown/index.tsx
  58. 117 0
      packages/semi-ui/table/_story/v2/InputFilter/index.tsx
  59. 2 0
      packages/semi-ui/table/_story/v2/index.js
  60. 31 11
      packages/semi-ui/table/interface.ts
  61. 21 0
      packages/semi-ui/treeSelect/__test__/treeSelect.test.js
  62. 7 1
      packages/semi-ui/treeSelect/index.tsx
  63. 1 1
      packages/semi-webpack/package.json
  64. 190 190
      sitemap.xml
  65. 1 1
      src/sitePages/newHome/components/comments/comments.jsx
  66. 1 1
      src/sitePages/newHome/components/operateButton/operateButton.jsx
  67. 86 0
      yarn.lock

+ 4 - 1
.eslintrc.js

@@ -43,7 +43,9 @@ module.exports = {
                 'jsx-a11y/mouse-events-have-key-events': ['warn'],
                 'object-curly-spacing': ['error', 'always'],
                 'space-before-blocks': ['error', 'always'],
-                'max-len': 'off'
+                'max-len': 'off',
+                'react/forbid-foreign-prop-types': ['error', { "allowInPropTypes": true }]
+
             },
             globals: {
                 "sinon": "readonly",
@@ -130,6 +132,7 @@ module.exports = {
                         }
                     }
                 ],
+                'react/forbid-foreign-prop-types': ['error', { "allowInPropTypes": true }]
             }
         },
     ],

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

@@ -915,7 +915,7 @@ function Demo() {
 | placeholder        | Input box prompts text                                                                                                                                                                                                                        | string\|string[]                                                                                                                                                                                                    | 'Select date'                                                                         |                           |
 | position           | Floating layer position, optional value with [Popover #API Reference · position](/en-US/show/popover#API%20Reference)                                                                                                                         | string                                                                                                                                                                                                    | 'bottomLeft'                                                                          |                           |
 | prefix             | Prefix content                                                                                                                                                                                                                                | string\|ReactNode                                                                                                                                                                                         |                                                                                       |                           |
-| presets            | Date Time Shortcut                                                                                                                                                                                                                            |  <ApiType detail='Array< { start: BaseValueType, end :BaseValueType, text: string } \| () => { start:B aseValueType, end: BaseValueType, text: string }>'>Array</ApiType>                                  | []                                                                                    |                           |
+| presets            | Date Time Shortcut, start and end support function type after v2.52                                                                                                                                                                                                                            |  <ApiType detail='type PresetType = { start?: BaseValueType \| (() => BaseValueType); end?: BaseValueType \| (() => BaseValueType); text?: string }; type PresetsType = Array<PresetType \| (() => PresetType)>;'>Array</ApiType>                                  | []                                                                                    |                           |
 | 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 |  |  |
 | presetPosition     | Date time shortcut panel position, optional 'left', 'right', 'top', 'bottom'                                                                                                                                                                  | 'bottom' | **2.18.0** |
 | rangeSeparator     | Custom range type picker separator of input trigger                                                                                                                                                                                           | string | '~' | **1.31.0** 

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

@@ -880,7 +880,7 @@ function Demo() {
 | placeholder | 输入框提示文字                                                                                              | string\|string[] | 'Select date' |  |
 | position | 浮层位置,可选值同[Popover#API 参考·position 参数](/zh-CN/show/popover#API参考)                                     | string | 'bottomLeft' |  |
 | prefix | 前缀内容                                                                                                 | string\|ReactNode |  |  |
-| presets | 日期时间快捷方式                                                                                             |  <ApiType detail='Array< { start: BaseValueType, end :BaseValueType, text: string } \| () => { start:B aseValueType, end: BaseValueType, text: string }>'>Array</ApiType> | [] |  |
+| 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 | '~' | **1.31.0** |

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

@@ -1468,6 +1468,7 @@ function Demo() {
 | 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 |
+| 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|
 | onSearch                 | Callback function when search value changes. `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**      | function(input: string, filteredExpandedKeys: string[])                                        | -           |     |

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

@@ -1448,6 +1448,7 @@ function Demo() {
 | onBlur | 失去焦点时的回调                                                                                                                                   | function(event) | - | - |
 | onChange | 选中树节点时调用此函数,默认返回值为当前所有选中项的value值及节点属性;如果是通过tag关闭,event参数为null                                                                              | Function | - | - |
 | onChangeWithObject | 是否将选中项 option 的其他属性作为回调。设为 true 时,onChange 的入参类型Function(node\|node[], e) 此时如果是受控,也需要把 value 设置成 object,且必须含有 value 的键值;defaultValue同理。    | boolean | false | 1.0.0 |
+| onClear     | 点击清除按钮时触发的回调                                                                                                                             | (e: Event) => void |  -  |   2.52.0  |
 | onExpand | 展示节点时调用                                                                                                                                    | <ApiType detail='(expandedKeys:array, {expanded: bool, node}) => void'>(expandedKeys, object) => void</ApiType> | - | - |
 | onFocus | 聚焦时的回调                                                                                                                                     | function(event) | - | - |
 | onLoad | 节点加载完毕时触发的回调                                                                                                                               | <ApiType detail='(loadedKeys: Set<string\>, treeNode: TreeNodeData) => void'>(loadedKeys, treeNode) => void</ApiType> |- |  1.32.0|

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

@@ -1318,6 +1318,296 @@ function App() {
 render(App);
 ```
 
+### Custom Header Filtering
+
+If you need to display the filter input box in the table header, you can pass ReactNode in the `title` and use it with `filteredValue`.
+
+```jsx live=true noInline=true dir="column"
+import React, { useState, useEffect, useRef } from 'react';
+import { Table, Avatar, Input, Space } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+
+function App() {
+    const [dataSource, setData] = useState([]);
+    const [filteredValue, setFilteredValue] = useState([]);
+    const compositionRef = useRef({ isComposition: false });
+
+    const DAY = 24 * 60 * 60 * 1000;
+    const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+
+    const handleChange = (value) => {
+        if (compositionRef.current.isComposition) {
+            return;
+        }
+        const newFilteredValue = value ? [value] : [];
+        setFilteredValue(newFilteredValue);
+    };
+    const handleCompositionStart = () => {
+        compositionRef.current.isComposition = true;
+    };
+
+    const handleCompositionEnd = (event) => {
+        compositionRef.current.isComposition = false;
+        const value = event.target.value;
+        const newFilteredValue = value ? [value] : [];
+        setFilteredValue(newFilteredValue);
+    };
+
+
+    const columns = [
+        {
+            title: (
+                <Space>
+                    <span>Title</span>
+                    <Input
+                        placeholder="Input filter value"
+                        style={{ width: 200 }}
+                        onCompositionStart={handleCompositionStart}
+                        onCompositionEnd={handleCompositionEnd}
+                        onChange={handleChange}
+                        showClear 
+                    />
+                </Space>
+            ),
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            onFilter: (value, record) => record.name.includes(value),
+            filteredValue,
+        },
+        {
+            title: 'Size',
+            dataIndex: 'size',
+            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
+            render: text => `${text} KB`,
+        },
+        {
+            title: 'Owner',
+            dataIndex: 'owner',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>
+                            {typeof text === 'string' && text.slice(0, 1)}
+                        </Avatar>
+                        {text}
+                    </div>
+                );
+            },
+        },
+        {
+            title: 'Update',
+            dataIndex: 'updateTime',
+            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
+            render: value => {
+                return dateFns.format(new Date(value), 'yyyy-MM-dd');
+            },
+        },
+    ];
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design design draft${i}.fig` : `Semi D2C design draft${i}.fig`,
+                owner: isSemiDesign ? 'Jiang Pengzhi' : 'Hao Xuan',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red',
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData();
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} />;
+}
+
+render(App);
+```
+
+
+
+### Custom Filter Rendering
+
+Use `renderFilterDropdown` to customize the render filter panel. v2.52 supported.
+
+You can call `setTempFilteredValue` to store the filter value when the user enters the filter value, and call `confirm` to trigger the actual filtering after the filter value is entered. You can also filter directly through `confirm({ filteredValue })`.
+
+The reason for setting `tempFilteredValue` is that in scenarios where temporary filtered values need to be stored, there is no need to declare a state to save this temporary filtered value.
+
+```typescript
+type RenderFilterDropdown = (props?: RenderFilterDropdownProps) => React.ReactNode;
+interface RenderFilterDropdownProps {
+     /** Temporary filter value, the initial value is `filteredValue` or `defaultFilteredValue` */
+     tempFilteredValue: any[];
+     /** Set temporary filter value */
+     setTempFilteredValue: (tempFilteredValue: any[]) => void;
+     /** `confirm` will assign `tempFilteredValue` to `filteredValue` by default and trigger the `onChange` event. You can also set the filter value directly by passing in `filteredValue` */
+     confirm: (props?: { closeDropdown?: boolean; filteredValue?: any[] }) => void;
+     /** Clear filter values and temporary filter values */
+     clear: (props?: { closeDropdown?: boolean }) => void;
+     /** Close dropdown */
+     close: () => void;
+     /** Filter configuration items, do not pass if not required */
+     filters?: RenderDropdownProps['filters']
+}
+```
+
+
+```jsx live=true noInline=true dir="column"
+import React, { useState, useEffect, useRef } from 'react';
+import { Table, Avatar, Input, Button, Space } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+
+function App() {
+    const [dataSource, setData] = useState([]);
+    const inputRef = useRef();
+
+    const DAY = 24 * 60 * 60 * 1000;
+    const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+    const columns = [
+        {
+            title: 'Title',
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            onFilter: (value, record) => record.name.includes(value),
+            renderFilterDropdown: (props) => {
+                console.log('renderFilterDropdown', props);
+                const { tempFilteredValue, setTempFilteredValue, confirm, clear, close } = props;
+
+                const handleChange = value => {
+                    const filteredValue = value ? [value] : [];
+                    setTempFilteredValue(filteredValue);
+                    // You can also filter directly when the input value changes
+                    // confirm({ filteredValue });
+                };
+
+                return (
+                    <Space vertical align='start' style={{ padding: 8 }}>
+                        <Input ref={inputRef} value={tempFilteredValue[0]} onChange={handleChange}/>
+                        <Space>
+                            <Button onClick={() => confirm({ closeDropdown: true })}>Filter+Close</Button>
+                            <Button onClick={() => clear({ closeDropdown: true })}>Clear+Close</Button>
+                            <Button onClick={() => close()}>Close</Button>
+                        </Space>
+                    </Space>
+                );
+            },
+            onFilterDropdownVisibleChange: (visible) => {
+                console.log('inputRef', visible, inputRef);
+                if (inputRef.current && inputRef.current.focus) {
+                    inputRef.current.focus();
+                }
+            }
+        },
+        {
+            title: 'Size',
+            dataIndex: 'size',
+            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
+            render: text => `${text} KB`,
+        },
+        {
+            title: 'Owner',
+            dataIndex: 'owner',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>
+                            {typeof text === 'string' && text.slice(0, 1)}
+                        </Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            onFilter: (value, record) => record.owner.includes(value),
+            defaultFilteredValue: ['Jiang Pengzhi'],
+            renderFilterDropdown: (props) => {
+                console.log('renderFilterDropdown', props);
+                const { tempFilteredValue, setTempFilteredValue, confirm, clear, close } = props;
+
+                const handleChange = (value) => {
+                    if (value) {
+                        setTempFilteredValue([value]);
+                    } else {
+                        setTempFilteredValue([]);
+                    }
+                };
+
+                return (
+                    <Space vertical align='start' style={{ padding: 8 }}>
+                        <Input value={tempFilteredValue[0]} onChange={handleChange}/>
+                        <Space>
+                            <Button onClick={() => confirm({ closeDropdown: false })}>Filter+Close</Button>
+                            <Button onClick={() => clear({ closeDropdown: false })}>Clear+Close</Button>
+                            <Button onClick={() => close()}>Close</Button>
+                        </Space>
+                    </Space>
+                );
+            },
+        },
+        {
+            title: 'Update',
+            dataIndex: 'updateTime',
+            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
+            render: value => {
+                return dateFns.format(new Date(value), 'yyyy-MM-dd');
+            },
+        },
+    ];
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design design draft${i}.fig` : `Semi D2C design draft${i}.fig`,
+                owner: isSemiDesign ? 'Jiang Pengzhi' : 'Hao Xuan',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red',
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData();
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} />;
+}
+
+render(App);
+```
+
 ### Custom Filter Item Rendering
 
 Since the **1.1.0** version, it is supported to pass in `renderFilterDropdownItem` to customize the rendering method of each filter item.
@@ -5142,6 +5432,7 @@ import { Table } from '@douyinfe/semi-ui';
 | fixed | Whether the column is fixed, optional true (equivalent to left) 'left' 'right' | boolean\|string | false |
 | key | The key required by React, if a unique dataIndex has been set, can ignore this property | string |  |
 | render | A rendering function that generates complex data, the parameters are the value of the current row, the current row data, the row index, and the table row / column merge can be set in return object | (text: any, record: RecordType, index: number, { expandIcon?: ReactNode, selection?: ReactNode, indentText?: ReactNode }) => React\|object |  |
+| 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** |
 | sortChildrenRecord | Whether to sort child data locally | boolean |  | **0.29.0** |
@@ -5178,6 +5469,7 @@ type Filter = {
 | getCheckboxProps | Default property configuration for the selection box | (record: RecordType) => object |  |  |
 | hidden | Hide selection column or not | boolean | false | **0.34.0** |
 | selectedRowKeys | Specifies the key array of the selected item, which needs to work with onChange | string [] |  |  |
+| renderCell         | Custom rendering checkbox                                                                                 | ({ selected: boolean, record: RecordType, originNode: JSX.Element, inHeader: boolean, disabled: boolean, indeterminate: boolean, index?: number, selectRow?: (selected: boolean, e: Event) => void, selectAll?: (selected: boolean, e: Event) => void }) => ReactNode |        |      **2.52.0**      |
 | width | Custom list selection box width | string | number |  |
 | onChange | A callback in the event of a change in the selected item. The first parameter will save the row keys selected last time, even if you do paging control or update the dataSource [FAQ](#faq) | (selectedRowKeys: number[]\|string[], selectedRows: RecordType[]) => void |  |  |
 | onSelect | Callback when the user manually clicks the selection box of a row | (record: RecordType, selected: boolean, selectedRows: RecordType[], nativeEvent: MouseEvent) => void |  |  |

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

@@ -1324,6 +1324,294 @@ function App() {
 render(App);
 ```
 
+### 自定义表头筛选
+
+如果你需要将筛选器输入框展示在表头,可在 `title` 传入 ReactNode,配合 `filteredValue` 使用。
+
+```jsx live=true noInline=true dir="column"
+import React, { useState, useEffect, useRef } from 'react';
+import { Table, Avatar, Input, Space } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+
+function App() {
+    const [dataSource, setData] = useState([]);
+    const [filteredValue, setFilteredValue] = useState([]);
+    const compositionRef = useRef({ isComposition: false });
+
+    const DAY = 24 * 60 * 60 * 1000;
+    const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+
+    const handleChange = (value) => {
+        if (compositionRef.current.isComposition) {
+            return;
+        }
+        const newFilteredValue = value ? [value] : [];
+        setFilteredValue(newFilteredValue);
+    };
+    const handleCompositionStart = () => {
+        compositionRef.current.isComposition = true;
+    };
+
+    const handleCompositionEnd = (event) => {
+        compositionRef.current.isComposition = false;
+        const value = event.target.value;
+        const newFilteredValue = value ? [value] : [];
+        setFilteredValue(newFilteredValue);
+    };
+
+
+    const columns = [
+        {
+            title: (
+                <Space>
+                    <span>标题</span>
+                    <Input
+                        placeholder="请输入筛选值"
+                        style={{ width: 200 }}
+                        onCompositionStart={handleCompositionStart}
+                        onCompositionEnd={handleCompositionEnd}
+                        onChange={handleChange}
+                        showClear 
+                    />
+                </Space>
+            ),
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            onFilter: (value, record) => record.name.includes(value),
+            filteredValue,
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
+            render: text => `${text} KB`,
+        },
+        {
+            title: '所有者',
+            dataIndex: 'owner',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>
+                            {typeof text === 'string' && text.slice(0, 1)}
+                        </Avatar>
+                        {text}
+                    </div>
+                );
+            },
+        },
+        {
+            title: '更新日期',
+            dataIndex: 'updateTime',
+            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
+            render: value => {
+                return dateFns.format(new Date(value), 'yyyy-MM-dd');
+            },
+        },
+    ];
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 首页${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date('2024-01-25').valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red',
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData();
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} />;
+}
+
+render(App);
+```
+
+### 自定义筛选器
+
+使用 `renderFilterDropdown` 自定义渲染筛选器面板。v2.52 支持。
+
+你可以在用户输入筛选值的时候调用 `setTempFilteredValue` 存储筛选值,在筛选值输入完毕后调用 `confirm` 触发真正的筛选。也可以通过 `confirm({ filteredValue })` 直接筛选。
+
+设置 `tempFilteredValue` 的原因是在需要存储临时筛选值的场景,不需要自己声明一个 state 保存这个临时筛选值。
+
+```typescript
+type RenderFilterDropdown = (props?: RenderFilterDropdownProps) => React.ReactNode;
+interface RenderFilterDropdownProps {
+    /** 临时筛选值,初始值为 `filteredValue` 或 `defaultFilteredValue`  */
+    tempFilteredValue: any[];
+    /** 设置临时筛选值  */
+    setTempFilteredValue: (tempFilteredValue: any[]) => void;
+    /** `confirm` 默认会将 `tempFilteredValue` 赋值给 `filteredValue` 并触发 `onChange` 事件。你也可以通过传入 `filteredValue` 直接设置筛选值  */
+    confirm: (props?: { closeDropdown?: boolean; filteredValue?: any[] }) => void;
+    /** 清除筛选值、临时筛选值  */
+    clear: (props?: { closeDropdown?: boolean }) => void;
+    /** 关闭 dropdown  */
+    close: () => void;
+    /** 筛选器配置项,如不需要可以不传  */
+    filters?: RenderDropdownProps['filters']
+}
+```
+
+
+```jsx live=true noInline=true dir="column"
+import React, { useState, useEffect, useRef } from 'react';
+import { Table, Avatar, Input, Button, Space } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+
+function App() {
+    const [dataSource, setData] = useState([]);
+    const inputRef = useRef();
+
+    const DAY = 24 * 60 * 60 * 1000;
+    const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+    const columns = [
+        {
+            title: '标题',
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            onFilter: (value, record) => record.name.includes(value),
+            renderFilterDropdown: (props) => {
+                console.log('renderFilterDropdown', props);
+                const { tempFilteredValue, setTempFilteredValue, confirm, clear, close } = props;
+
+                const handleChange = value => {
+                    const filteredValue = value ? [value] : [];
+                    setTempFilteredValue(filteredValue);
+                    // 你也可以在 input value 变化时直接筛选
+                    // confirm({ filteredValue });
+                };
+
+                return (
+                    <Space vertical align='start' style={{ padding: 8 }}>
+                        <Input ref={inputRef} value={tempFilteredValue[0]} onChange={handleChange}/>
+                        <Space>
+                            <Button onClick={() => confirm({ closeDropdown: true })}>筛选+关闭</Button>
+                            <Button onClick={() => clear({ closeDropdown: true })}>清除+关闭</Button>
+                            <Button onClick={() => close()}>直接关闭</Button>
+                        </Space>
+                    </Space>
+                );
+            },
+            onFilterDropdownVisibleChange: (visible) => {
+                console.log('inputRef', visible, inputRef);
+                if (inputRef.current && inputRef.current.focus) {
+                    inputRef.current.focus();
+                }
+            }
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
+            render: text => `${text} KB`,
+        },
+        {
+            title: '所有者',
+            dataIndex: 'owner',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>
+                            {typeof text === 'string' && text.slice(0, 1)}
+                        </Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            onFilter: (value, record) => record.owner.includes(value),
+            defaultFilteredValue: ['姜鹏志'],
+            renderFilterDropdown: (props) => {
+                console.log('renderFilterDropdown', props);
+                const { tempFilteredValue, setTempFilteredValue, confirm, clear, close } = props;
+
+                const handleChange = (value) => {
+                    if (value) {
+                        setTempFilteredValue([value]);
+                    } else {
+                        setTempFilteredValue([]);
+                    }
+                };
+
+                return (
+                    <Space vertical align='start' style={{ padding: 8 }}>
+                        <Input value={tempFilteredValue[0]} onChange={handleChange}/>
+                        <Space>
+                            <Button onClick={() => confirm({ closeDropdown: false })}>筛选后不关闭</Button>
+                            <Button onClick={() => clear({ closeDropdown: false })}>清除后不关闭</Button>
+                            <Button onClick={() => close()}>直接关闭</Button>
+                        </Space>
+                    </Space>
+                );
+            },
+        },
+        {
+            title: '更新日期',
+            dataIndex: 'updateTime',
+            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
+            render: value => {
+                return dateFns.format(new Date(value), 'yyyy-MM-dd');
+            },
+        },
+    ];
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red',
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData();
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} />;
+}
+
+render(App);
+```
+
 
 
 ### 自定义筛选项渲染
@@ -5158,6 +5446,7 @@ import { Table } from '@douyinfe/semi-ui';
 | fixed | 列是否固定,可选 true(等效于 left) 'left' 'right',在 RTL 时会自动切换 | boolean\|string | false |
 | key | React 需要的 key,如果已经设置了唯一的 dataIndex,可以忽略这个属性 | string |  |
 | render | 生成复杂数据的渲染函数,参数分别为当前行的值,当前行数据,行索引,@return 里面可以设置表格行/列合并 | (text: any, record: RecordType, index: number, { expandIcon?: ReactNode, selection?: ReactNode, indentText?: ReactNode }) => object\|ReactNode |  |
+| 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** |
 | sortChildrenRecord | 是否对子级数据进行本地排序 | boolean |  | **0.29.0** |
@@ -5195,6 +5484,7 @@ type Filter = {
 | fixed            | 把选择框列固定在左边                                                                                         | boolean                                                                                              | false  |            |
 | getCheckboxProps | 选择框的默认属性配置                                                                                         | (record: RecordType) => object                                                                       |        |            |
 | hidden           | 是否隐藏选择列                                                                                               | boolean                                                                                              | false  | **0.34.0** |
+| renderCell         | 自定义渲染勾选框                                                                                 | ({ selected: boolean, record: RecordType, originNode: JSX.Element, inHeader: boolean, disabled: boolean, indeterminate: boolean, index?: number, selectRow?: (selected: boolean, e: Event) => void, selectAll?: (selected: boolean, e: Event) => void }) => ReactNode |        |      **2.52.0**     |
 | selectedRowKeys  | 指定选中项的 key 数组,需要和 onChange 进行配合                                                               | string[]                                                                                             |        |            |
 | width            | 自定义列表选择框宽度                                                                                         | string\|number                                                                                       |        |            |
 | onChange         | 选中项发生变化时的回调。第一个参数会保存上次选中的 row keys,即使你做了分页受控或更新了 dataSource [FAQ](#faq) | (selectedRowKeys: number[]\|string[], selectedRows: RecordType[]) => void                            |        |            |

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

@@ -16,6 +16,26 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
 
 ---
 
+#### 🎉 2.51.3 (2024-01-19)
+- 【Fix】
+    - Fixed Table propTypes is removed in prod mode
+
+#### 🎉 2.51.2 (2024-01-19)
+- 【Fix】
+    - Fixed TextArea autoSize is not work when textarea resize [#2026](https://github.com/DouyinFE/semi-design/issues/2026)
+    - Fixed controlled DatePicker input value is wrong when type is dateTimeRange and needConfirm is opened [#2024](https://github.com/DouyinFE/semi-design/issues/2024)
+    - Fixed the problem of triggering onClose/onPreview twice when clicking the edge of the close button in the preview state in the ImagePreview component [#2027](https://github.com/DouyinFE/semi-design/pull/2027)
+
+#### 🎉 2.51.1 (2024-01-18)
+- 【Fix】
+    - Unloading the Modal directly when the Modal is not collapsed may cause the page to scroll abnormally. [#2023](https://github.com/DouyinFE/semi-design/pull/2023)
+
+#### 🎉 2.51.0 (2024-01-12)
+- 【Fix】
+    - Fixed the problem that when a TextArea with maxLength is input in Chinese, clicking outside triggers blur, and the echoed content does not comply with the maxLength setting  [#2005](https://github.com/DouyinFE/semi-design/issues/2005)
+    - Fix typeError in Cascader when autoMergeValue is false and value is [] [#2017](https://github.com/DouyinFE/semi-design/pull/2017)
+- 【Style】
+    - The default zIndex value of ImagePreview's preview layer is adjusted from 1000 to 1070
 
 #### 🎉 2.51.0-beta.0 (2024-01-09)
 - 【Feat】

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

@@ -13,6 +13,27 @@ Semi 版本号遵循 **Semver** 规范(主版本号-次版本号-修订版本
 -   修订版本号(patch):仅会进行 bugfix,发布时间不限
 -   不同版本间的详细关系,可查阅 [FAQ](/zh-CN/start/faq)
 
+#### 🎉 2.51.3 (2024-01-19)
+- 【Fix】
+    - 修复 Table propTypes 被打包工具移除掉导致报错问题
+
+#### 🎉 2.51.2 (2024-01-19)
+- 【Fix】
+    - 修复 TextArea autosize 未监听文本域宽度变化 [#2026](https://github.com/DouyinFE/semi-design/issues/2026)
+    - 修复受控 DatePicker dateTimeRange + needConfirm 时选择一个日期时输入框回显错误 [#2024](https://github.com/DouyinFE/semi-design/issues/2024)
+    - 修复 ImagePreview 组件在预览时点击关闭按钮边缘时触发 onClose/onPreview 两次问题 [#2027](https://github.com/DouyinFE/semi-design/pull/2027)
+
+#### 🎉 2.51.1 (2024-01-18)
+- 【Fix】
+    - 修复当 Modal 未收起时直接时直接卸载 Modal 导致页面可能滚动异常的问题 [#2023](https://github.com/DouyinFE/semi-design/pull/2023)
+
+#### 🎉 2.51.0 (2024-01-12)
+- 【Fix】
+    - 修复有 maxLength的 TextArea 在中文输入时,点击外部触发 blur,回显内容不符合 maxLength 设置问题  [#2005](https://github.com/DouyinFE/semi-design/issues/2005)
+    - 修复 Cascader 中 autoMergeValue 为 false, value 为 [] 时的 typeError [#2017](https://github.com/DouyinFE/semi-design/pull/2017)
+- 【Style】
+    - ImagePreview 预览层的默认 zIndex 从 1000 调整为 1070
+
 #### 🎉 2.51.0-beta.0 (2024-01-09)
 - 【Feat】
     - Dropdown.Item 支持透传 data-* 属性到 dom

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

@@ -802,4 +802,26 @@ describe('DatePicker', () => {
             cy.get('.semi-datepicker-day-selected-end').contains("13");
         })
     });
+
+    it('test split first inset input + dateTimeRange', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=datepicker--fix-need-confirm-controlled&viewMode=story');
+        cy.get('.semi-input').eq(0).click();
+        cy.get('.semi-datepicker-day').contains('15').trigger('click');
+        cy.get('.semi-input').should('have.value', '2024-02-15 00:00:00');
+        cy.get('button').contains('确定').trigger('click');
+        cy.get('.semi-input').should('have.value', '');
+        cy.get('.semi-input').eq(1).click();
+        cy.get('.semi-datepicker-day').contains('15').trigger('click');
+        cy.get('.semi-input').eq(1).should('have.value', '2024-02-15 00:00:00');
+        cy.get('button').contains('确定').trigger('click');
+        cy.get('.semi-input').eq(1).should('have.value', '');
+    });
+
+    it('presets start and end support function type', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=datepicker--presets-function-type&viewMode=story');
+        cy.get('.semi-input').eq(0).click();
+        cy.get('button').contains('动态时间').click();
+        cy.get('.semi-input').eq(0).should('have.value', '2024-01-24');
+        cy.get('.semi-input').eq(1).should('have.value', '2024-02-26');
+    });
 });

+ 33 - 0
cypress/e2e/table.spec.js

@@ -246,4 +246,37 @@ describe('table', () => {
         cy.get('button').contains('点击全选').click();
         cy.get('.semi-table-thead .semi-checkbox-checked').should('exist')
     });
+
+    it('test renderFilterDropdown', () => {
+        cy.visit('http://localhost:6006/iframe.html?args=&id=table--feat-render-filter-dropdown&viewMode=story');
+
+        // 测试第一个筛选器
+        cy.get('.semi-table-column-filter').eq(0).click();
+        cy.get('.semi-input').should('be.focused');
+        cy.get('.semi-input').type('12');
+        cy.get('.semi-button').contains('筛选+关闭').click();
+        cy.get('.semi-table-tbody .semi-table-row').should('have.length', 1);
+        cy.get('.semi-table-column-filter').eq(0).click();
+        cy.get('.semi-input').should('be.focused');
+        cy.get('.semi-button').contains('清除+关闭').click();
+        cy.get('.semi-table-tbody .semi-table-row').should('have.length', 10);
+        cy.get('.semi-table-column-filter').eq(0).click();
+        cy.get('.semi-input').should('be.focused');
+        cy.get('.semi-button').contains('直接关闭').click();
+        cy.get('.semi-dropdown').should('not.exist');
+
+        // 测试第二个筛选器
+        cy.get('.semi-table-column-filter').eq(1).click();
+        cy.get('.semi-input').should('have.value', '姜鹏志');
+        cy.get('.semi-button').contains('清除后不关闭').click();
+        cy.get('.semi-table-pagination-info').should('contain', '显示第 1 条-第 10 条,共 46 条');
+        cy.get('.semi-dropdown').should('exist');
+        cy.get('.semi-table-column-filter').eq(1).click();
+        cy.get('.semi-input').type('郝宣');
+        cy.get('.semi-button').contains('筛选后不关闭').click();
+        cy.get('.semi-table-pagination-info').should('contain', '显示第 1 条-第 10 条,共 23 条');
+        cy.get('.semi-dropdown').should('exist');
+        cy.get('.semi-button').contains('直接关闭').click();
+        cy.get('.semi-dropdown').should('not.exist');
+    });
 });

+ 11 - 0
cypress/e2e/textarea.spec.js

@@ -99,4 +99,15 @@ describe('textarea', () => {
             });
         });
     });
+
+    it('autosize + textarea resize', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=input--text-auto-size-resize&viewMode=story');
+        cy.get('button').contains('width=100').trigger('click');
+        cy.wait(100);
+        cy.document().then(document => {
+            const textAreaDOM = document.querySelector(".semi-input-textarea");
+            const { scrollHeight, clientHeight } = textAreaDOM;
+            expect(scrollHeight).eq(clientHeight);
+        });
+    });
 });

+ 1 - 1
lerna.json

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

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

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

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

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

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

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

+ 14 - 22
packages/semi-foundation/datePicker/foundation.ts

@@ -45,9 +45,10 @@ export type DisabledDateOptions = {
      */
     rangeInputFocus?: 'rangeStart' | 'rangeEnd' | false
 };
+
 export type PresetType = {
-    start?: string | Date | number;
-    end?: string | Date | number;
+    start?: BaseValueType | (() => BaseValueType);
+    end?: BaseValueType | (() => BaseValueType);
     text?: string
 };
 
@@ -994,26 +995,15 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
              * 受控时如果输入不完整,由于没有触发 notifyChange
              * 需要组件内更新一下输入框的值,否则会出现选了一个日期但是输入框没有回显日期的问题 #1357
              */
-            if (!this._adapter.needConfirm() || fromPreset) {
-                if (isRangeTypeAndInputIncomplete) {
-                    // do not change value when selected value is incomplete
-                    this._adapter.updateInputValue(inputValue);
-                    this._adapter.updateInsetInputValue(insetInputValue);
-                    return;
-                } else {
-                    if (!controlled || fromPreset) {
-                        this._updateValueAndInput(dates, true, inputValue);
-                        this._adapter.updateInsetInputValue(insetInputValue);
-                    }
-                }
-            }
-            if (!controlled && this._adapter.needConfirm()) {
-                // select date only change inputValue when needConfirm is true
+            if (isRangeTypeAndInputIncomplete) {
+                // do not change value when selected value is incomplete
                 this._adapter.updateInputValue(inputValue);
                 this._adapter.updateInsetInputValue(insetInputValue);
-                // if inputValue is not complete, don't notifyChange
-                if (isRangeTypeAndInputIncomplete) {
-                    return;
+                return;
+            } else {
+                if (!controlled || fromPreset) {
+                    this._updateValueAndInput(dates, true, inputValue);
+                    this._adapter.updateInsetInputValue(insetInputValue);
                 }
             }
             if (!isEqual(value, stateValue)) {
@@ -1076,18 +1066,20 @@ export default class DatePickerFoundation extends BaseFoundation<DatePickerAdapt
     handlePresetClick(item: PresetType, e: any) {
         const { type, timeZone } = this.getProps();
         const prevTimeZone = this.getState('prevTimezone');
+        const start = typeof item.start === 'function' ? item.start() : item.start;
+        const end = typeof item.end === 'function' ? item.end() : item.end;
 
         let value;
         switch (type) {
             case 'month':
             case 'dateTime':
             case 'date':
-                value = this.parseWithTimezone([item.start], timeZone, prevTimeZone);
+                value = this.parseWithTimezone([start], timeZone, prevTimeZone);
                 this.handleSelectedChange(value);
                 break;
             case 'dateTimeRange':
             case 'dateRange':
-                value = this.parseWithTimezone([item.start, item.end], timeZone, prevTimeZone);
+                value = this.parseWithTimezone([start, end], timeZone, prevTimeZone);
                 this.handleSelectedChange(value, { needCheckFocusRecord: false });
                 break;
             default:

+ 5 - 1
packages/semi-foundation/image/constants.ts

@@ -4,4 +4,8 @@ const cssClasses = {
     PREFIX: `${BASE_CLASS_PREFIX}-image`,
 } as const;
 
-export { cssClasses };
+const numbers = {
+    DEFAULT_Z_INDEX: 1070,
+};
+
+export { cssClasses, numbers };

+ 3 - 2
packages/semi-foundation/image/previewInnerFoundation.ts

@@ -127,7 +127,7 @@ export default class PreviewInnerFoundation<P = Record<string, any>, S = Record<
             couldClose = false;
         }
         if (couldClose && maskClosable) {
-            this.handlePreviewClose();
+            this._adapter.notifyVisibleChange(false);
         }
     }
 
@@ -177,9 +177,10 @@ export default class PreviewInnerFoundation<P = Record<string, any>, S = Record<
         this._adapter.notifyDownload(downloadSrc, currentIndex);
     }
 
-    handlePreviewClose = () => {
+    handlePreviewClose = (e: any) => {
         this._adapter.notifyVisibleChange(false);
         this._adapter.notifyClose();
+        handlePrevent(e);
     }
 
     handleAdjustRatio = (type: RatioType) => {

+ 33 - 12
packages/semi-foundation/input/textareaFoundation.ts

@@ -113,15 +113,21 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdapter>
      */
     handleVisibleMaxLength(value: string) {
         const { maxLength, getValueLength } = this._adapter.getProps();
-        if (isNumber(maxLength) && maxLength >= 0 && isFunction(getValueLength) && isString(value)) {
-            const valueLength = getValueLength(value);
-            if (valueLength > maxLength) {
-                console.warn('[Semi TextArea] The input character is truncated because the input length exceeds the maximum length limit');
-                const truncatedValue = this.handleTruncateValue(value, maxLength);
-                return truncatedValue;
+        if (isNumber(maxLength) && maxLength >= 0 && isString(value)) {
+            if (isFunction(getValueLength)) {
+                const valueLength = getValueLength(value);
+                if (valueLength > maxLength) {
+                    console.warn('[Semi TextArea] The input character is truncated because the input length exceeds the maximum length limit');
+                    const truncatedValue = this.handleTruncateValue(value, maxLength);
+                    return truncatedValue;
+                }
             } else {
-                return value;
+                if (value.length > maxLength) {
+                    console.warn('[Semi TextArea] The input character is truncated because the input length exceeds the maximum length limit');
+                    return value.slice(0, maxLength);
+                }
             }
+            return value;
         }
         return undefined;
     }
@@ -158,8 +164,26 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdapter>
 
     handleBlur(e: any) {
         const { value } = this.getStates();
+        const { maxLength } = this.getProps();
+        let realValue = value;
+        if (maxLength) {
+            // 如果设置了 maxLength,在中文输输入过程中,如果点击外部触发 blur,则拼音字符的所有内容会回显,
+            // 该表现不符合 maxLength 规定,因此需要在 blur 的时候二次确认
+            // 详情见 https://github.com/DouyinFE/semi-design/issues/2005
+            // If maxLength is set, during the Chinese input process, if you click outside to trigger blur, 
+            // all the contents of the Pinyin characters will be echoed.
+            // This behavior does not meet the maxLength requirement, so we need to confirm twice when blurring。
+            // For details, see https://github.com/DouyinFE/semi-design/issues/2005
+            realValue = this.handleVisibleMaxLength(value);
+            if (realValue !== value) {
+                if (!this._isControlledComponent()) {
+                    this._adapter.setValue(realValue);
+                }
+                this._adapter.notifyChange(realValue, e);
+            }
+        }
         this._adapter.toggleFocusing(false);
-        this._adapter.notifyBlur(value, e);
+        this._adapter.notifyBlur(realValue, e);
     }
 
     handleKeyDown(e: any) {
@@ -169,14 +193,13 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdapter>
         }
     }
 
-    resizeTextarea = (cb?: any) => {
+    resizeTextarea = () => {
         const { height } = this.getStates();
         const { rows, autosize } = this.getProps();
         const node = this._adapter.getRef();
         const nodeSizingData = getSizingData(node);
 
         if (!nodeSizingData) {
-            cb && cb();
             return;
         }
 
@@ -197,8 +220,6 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdapter>
             node.style.height = `${newHeight}px`;
             return;
         }
-
-        cb && cb();
     };
 
     // e: MouseEvent

+ 4 - 0
packages/semi-foundation/modal/modalFoundation.ts

@@ -112,6 +112,10 @@ export default class ModalFoundation extends BaseFoundation<ModalAdapter> {
         this._adapter.notifyClose();
     }
 
+    enabledBodyScroll() {
+        this._adapter.enabledBodyScroll();
+    }
+
     // afterClose() {
     //     this._adapter.notifyClose();
     // }

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

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

+ 2 - 0
packages/semi-foundation/treeSelect/foundation.ts

@@ -189,6 +189,7 @@ export interface TreeSelectAdapter<P = Record<string, any>, S = Record<string, a
     closeMenu: (cb?: () => void) => void;
     getTriggerWidth: () => boolean | number;
     setOptionWrapperWidth: (width: null | number) => void;
+    notifyClear: (e: any) => void;
     notifyChange: BasicOnChangeWithBasic;
     notifyChangeWithObject: BasicOnChangeWithObject;
     notifyExpand: (expandedKeys: Set<string>, expandedOtherProps: BasicExpandedOtherProps) => void;
@@ -548,6 +549,7 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
                 }
             }
         }
+        this._adapter.notifyClear(e);
     }
 
     /**

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -24,7 +24,9 @@ export function stopPropagation(e: React.MouseEvent | React.FocusEvent<HTMLEleme
  * 
  * skip clone function and react element
  */
-export function cloneDeep(value: any, customizer?: (value: any) => void) {
+export function cloneDeep<T>(value: T): T;
+export function cloneDeep<T>(value: T, customizer: (value: any) => any): any;
+export function cloneDeep(value: any, customizer?: (value: any) => any) {
     return cloneDeepWith(value, v => {
         if (typeof customizer === 'function') {
             return customizer(v);

+ 13 - 0
packages/semi-ui/cascader/__test__/cascader.test.js

@@ -1369,4 +1369,17 @@ describe('Cascader', () => {
         r.close();
         expect(select.state().isOpen).toEqual(false);
     });
+
+    it('autoMerge false & value []', () => {
+        const cascader = render({
+            multiple: true,
+            autoMergeValue: false,
+            value: [],
+            placeholder: "autoMergeValue 为 false"
+        });
+
+        const placeholder = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-selection-placeholder`)
+        expect(placeholder.getDOMNode().textContent).toEqual('autoMergeValue 为 false');
+        cascader.unmount();
+    })
 });

+ 59 - 0
packages/semi-ui/cascader/_story/cascader.stories.jsx

@@ -2264,3 +2264,62 @@ export const ControlledPF = () => {
     />
   )
 }
+
+export const AutoMergeFalse = () => {
+  const [value, setValue] = useState([]);
+  const onChange = value => {
+      console.log(value);
+      setValue(value);
+  };
+  const treeData = [
+      {
+          label: '浙江省',
+          value: 'zhejiang',
+          children: [
+              {
+                  label: '杭州市',
+                  value: 'hangzhou',
+                  children: [
+                      {
+                          label: '西湖区',
+                          value: 'xihu',
+                      },
+                      {
+                          label: '萧山区',
+                          value: 'xiaoshan',
+                      },
+                      {
+                          label: '临安区',
+                          value: 'linan',
+                      },
+                  ],
+              },
+              {
+                  label: '宁波市',
+                  value: 'ningbo',
+                  children: [
+                      {
+                          label: '海曙区',
+                          value: 'haishu',
+                      },
+                      {
+                          label: '江北区',
+                          value: 'jiangbei',
+                      }
+                  ]
+              },
+          ],
+      }
+  ];
+  return (
+      <Cascader
+          style={{ width: 300 }}
+          treeData={treeData}
+          placeholder="autoMergeValue 为 false"
+          value={value}
+          multiple
+          autoMergeValue={false}
+          onChange={e => onChange(e)}
+      />
+  );
+}

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

@@ -441,7 +441,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                 const formatItem: (string | number)[] = onChangeWithObject && isObject(valueItem[0]) ?
                     (valueItem as CascaderData[]).map(i => i?.value) :
                     valueItem as (string | number)[];
-                formatValuePath.push(formatItem);
+                formatItem.length > 0 && (formatValuePath.push(formatItem));
             });
             // formatKeys is used to save key of value
             const formatKeys = formatValuePath.map(v => getKeyByValuePath(v));

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

@@ -69,7 +69,9 @@ export {
     FeatEtcGMT,
     FixDisabledDate,
     FeatInsetInputShowClear,
-    AutoSplitInput
+    AutoSplitInput,
+    FixNeedConfirmControlled,
+    PresetsFunctionType
 } from './v2';
 
 

+ 22 - 0
packages/semi-ui/datePicker/_story/v2/FixNeedConfirmControlled.tsx

@@ -0,0 +1,22 @@
+import React, { useState } from 'react';
+import { Button, DatePicker, Space } from '@douyinfe/semi-ui';
+
+/**
+ * test with cypress, please modify this story
+ * @returns
+ */
+export default function App() {
+    const [value, setValue] = useState([]);
+    return (
+        <Space>
+            <DatePicker
+                needConfirm
+                defaultPickerValue="2024-02-15"
+                value={value}
+                type="dateTimeRange"
+                onChange={v => setValue(v)}
+            />
+            <Button>body click</Button>
+        </Space>
+    );
+}

+ 23 - 0
packages/semi-ui/datePicker/_story/v2/PresetsFunctionType.tsx

@@ -0,0 +1,23 @@
+import React from 'react';
+import DatePicker, { PresetsType } from '../../index';
+
+/**
+ * test with cypress, please don't modify this story
+ */
+export default function App() {
+    const presets: PresetsType = [
+        {
+            text: '动态时间',
+            start: () => new Date('2024-01-24'),
+            end: () => new Date('2024-02-26'),
+        },
+    ];
+    return (
+        <div>
+            <h4>presets start 和 end 使用函数类型</h4>
+            <div>
+                <DatePicker type="dateRange" presets={presets} />
+            </div>
+        </div>
+    );
+}

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

@@ -27,3 +27,5 @@ export { default as FixDisabledDate } from './FixDisabledDate';
 export { default as FeatYearScrollRange } from './FeatYearScrollRange';
 export { default as FeatInsetInputShowClear } from './FeatInsetInputShowClear';
 export { default as AutoSplitInput } from './AutoSplitInput';
+export { default as FixNeedConfirmControlled } from './FixNeedConfirmControlled';
+export { default as PresetsFunctionType } from './PresetsFunctionType';

+ 1 - 1
packages/semi-ui/dropdown/__test__/dropdown.test.js

@@ -228,7 +228,7 @@ describe('Dropdown', () => {
                 value: 'B1',
             },
         };
-        targetItem.simulate('mousedown', event);
+        targetItem.simulate('click', event);
         expect(spyItemCLick.calledOnce).toEqual(true);
         expect(spyItemCLick.calledWithMatch(event)).toEqual(true);
     });

+ 5 - 4
packages/semi-ui/dropdown/dropdownItem.tsx

@@ -47,7 +47,7 @@ class DropdownItem extends BaseComponent<DropdownItemProps> {
         forwardRef: PropTypes.func,
         type: PropTypes.oneOf(strings.ITEM_TYPE),
         active: PropTypes.bool,
-        icon: PropTypes.node
+        icon: PropTypes.node,
     };
 
     static contextType = DropdownContext;
@@ -93,9 +93,10 @@ class DropdownItem extends BaseComponent<DropdownItemProps> {
         const events = {};
         if (!disabled) {
             ['onClick', 'onMouseEnter', 'onMouseLeave', 'onContextMenu'].forEach(eventName => {
-                if (eventName === "onClick") {
-                    events["onMouseDown"] = (e: React.MouseEvent<HTMLLIElement, MouseEvent>)=>{
-                        if (e.button===0) { 
+                const isInAnotherDropdown = this.context.level !== 1;
+                if (isInAnotherDropdown && eventName === "onClick") {
+                    events["onMouseDown"] = (e: React.MouseEvent<HTMLLIElement, MouseEvent>) => {
+                        if (e.button === 0) {
                             this.props[eventName]?.(e);
                         }
                     };

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

@@ -126,7 +126,7 @@ export interface HeaderProps {
     title?: string;
     titleStyle?: React.CSSProperties;
     className?: string;
-    onClose?: () => void
+    onClose?: (e: React.MouseEvent<HTMLElement>) => void
 }
 
 export interface FooterProps extends SliderProps {
@@ -147,6 +147,7 @@ export interface FooterProps extends SliderProps {
     adaptiveTip?: string;
     originTip?: string;
     showTooltip?: boolean;
+    zIndex?: number;
     onZoomIn?: (zoom: number) => void;
     onZoomOut?: (zoom: number) => void;
     onPrev?: () => void;

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

@@ -116,9 +116,9 @@ export default class Footer extends BaseComponent<FooterProps> {
     // According to showTooltip in props, decide whether to use Tooltip to pack a layer
     // 根据 props 中的 showTooltip 决定是否使用 Tooltip 包一层
     getFinalIconElement = (element: ReactNode, content: ReactNode, key: string) => {
-        const { showTooltip } = this.props;
+        const { showTooltip, zIndex } = this.props;
         return showTooltip ? (
-            <Tooltip content={content} key={`tooltip-${key}`}>
+            <Tooltip content={content} key={`tooltip-${key}`} zIndex={zIndex + 1}>
                 {element}
             </Tooltip>
         ): element;

+ 5 - 4
packages/semi-ui/image/previewInner.tsx

@@ -2,7 +2,7 @@ import React, { CSSProperties } from "react";
 import BaseComponent from "../_base/baseComponent";
 import { PreviewInnerProps, PreviewInnerStates } from "./interface";
 import PropTypes from "prop-types";
-import { cssClasses } from "@douyinfe/semi-foundation/image/constants";
+import { cssClasses, numbers } from "@douyinfe/semi-foundation/image/constants";
 import cls from "classnames";
 import { isEqual, isFunction } from "lodash";
 import Portal from "../_portal";
@@ -72,7 +72,7 @@ export default class PreviewInner extends BaseComponent<PreviewInnerProps, Previ
         lazyLoad: false,
         preLoad: true,
         preLoadGap: 2,
-        zIndex: 1000,
+        zIndex: numbers.DEFAULT_Z_INDEX,
         maskClosable: true,
         viewerVisibleDelay: 10000,
         maxZoom: 5,
@@ -273,8 +273,8 @@ export default class PreviewInner extends BaseComponent<PreviewInnerProps, Previ
         this.foundation.handleDownload();
     }
 
-    handlePreviewClose = () => {
-        this.foundation.handlePreviewClose();
+    handlePreviewClose = (e: React.MouseEvent<HTMLElement>) => {
+        this.foundation.handlePreviewClose(e);
     }
 
     handleAdjustRatio = (type: RatioType) => {
@@ -449,6 +449,7 @@ export default class PreviewInner extends BaseComponent<PreviewInnerProps, Previ
                         ratio={ratio}
                         prevTip={prevTip}
                         nextTip={nextTip}
+                        zIndex={zIndex}
                         zoomInTip={zoomInTip}
                         zoomOutTip={zoomOutTip}
                         rotateTip={rotateTip}

+ 16 - 0
packages/semi-ui/input/_story/input.stories.jsx

@@ -1015,3 +1015,19 @@ export const forwardRefFocus = () => {
     </div>
   </>
 )};
+
+export const TextAutoSizeResize = () => {
+  const [width, setWidth] = useState(800);
+
+  return (
+    <div>
+      <Space style={{ marginBottom: 20 }}>
+        <Button onClick={() => setWidth(100)}>width=100</Button>
+        <Button onClick={() => setWidth(1000)}>width=1000</Button>
+      </Space>
+      <div style={{ width, maxWidth: '100%' }}>
+        <TextArea autosize defaultValue='semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design semi design ' />
+      </div>
+    </div>
+  )
+};

+ 56 - 78
packages/semi-ui/input/textarea.tsx

@@ -5,8 +5,10 @@ import TextAreaFoundation from '@douyinfe/semi-foundation/input/textareaFoundati
 import { cssClasses } from '@douyinfe/semi-foundation/input/constants';
 import BaseComponent, { ValidateStatus } from '../_base/baseComponent';
 import '@douyinfe/semi-foundation/input/textarea.scss';
-import { noop, omit, isFunction, isUndefined, isObject } from 'lodash';
+import { noop, omit, isFunction, isUndefined, isObject, throttle } from 'lodash';
+import type { DebouncedFunc } from 'lodash';
 import { IconClear } from '@douyinfe/semi-icons';
+import ResizeObserver from '../resizeObserver';
 
 const prefixCls = cssClasses.PREFIX;
 
@@ -20,15 +22,14 @@ type OmitTextareaAttr =
     | 'onKeyDown'
     | 'onKeyPress'
     | 'onKeyUp'
-    | 'onResize'
+    | 'onResize';
 
 export type AutosizeRow = {
     minRows?: number;
     maxRows?: number
-}
+};
 
-export interface TextAreaProps extends
-    Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, OmitTextareaAttr> {
+export interface TextAreaProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, OmitTextareaAttr> {
     autosize?: boolean | AutosizeRow;
     borderless?: boolean;
     placeholder?: string;
@@ -53,7 +54,7 @@ export interface TextAreaProps extends
     onKeyPress?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
     onEnterPress?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
     onPressEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
-    onResize?: (data: {height: number}) => void;
+    onResize?: (data: { height: number }) => void;
     getValueLength?: (value: string) => number;
     forwardRef?: ((instance: HTMLTextAreaElement) => void) | React.MutableRefObject<HTMLTextAreaElement> | null
 }
@@ -107,9 +108,8 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
 
     focusing: boolean;
     libRef: React.RefObject<HTMLInputElement>;
-    _resizeLock: boolean;
-    _resizeListener: any;
     foundation: TextAreaFoundation;
+    throttledResizeTextarea: DebouncedFunc<typeof this.foundation.resizeTextarea>;
 
     constructor(props: TextAreaProps) {
         super(props);
@@ -124,17 +124,18 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
         this.foundation = new TextAreaFoundation(this.adapter);
 
         this.libRef = React.createRef<HTMLInputElement>();
-        this._resizeLock = false;
+        this.throttledResizeTextarea = throttle(this.foundation.resizeTextarea, 10);
     }
 
     get adapter() {
         return {
             ...super.adapter,
-            setValue: (value: string) => this.setState({ value }, () => {
-                if (this.props.autosize) {
-                    this.foundation.resizeTextarea();
-                }
-            }),
+            setValue: (value: string) =>
+                this.setState({ value }, () => {
+                    if (this.props.autosize) {
+                        this.foundation.resizeTextarea();
+                    }
+                }),
             getRef: () => this.libRef.current,
             toggleFocusing: (focusing: boolean) => this.setState({ isFocus: focusing }),
             toggleHovering: (hovering: boolean) => this.setState({ isHover: hovering }),
@@ -169,35 +170,18 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
         return willUpdateStates;
     }
 
-    componentDidMount() {
-        this.foundation.init();
-        this._resizeListener = null;
-        if (this.props.autosize) {
-            // Working around Firefox bug which runs resize listeners even when other JS is running at the same moment
-            // causing competing rerenders (due to setState in the listener) in React.
-            // More can be found here - facebook/react#6324
-            // // Reference to https://github.com/andreypopp/react-textarea-autosize/
-            this._resizeListener = () => {
-                if (this._resizeLock) {
-                    return;
-                }
-                this._resizeLock = true;
-                this.foundation.resizeTextarea(() => {
-                    this._resizeLock = false;
-                });
-            };
-            window.addEventListener('resize', this._resizeListener);
+    componentWillUnmount(): void {
+        if (this.throttledResizeTextarea) {
+            this.throttledResizeTextarea?.cancel?.();
+            this.throttledResizeTextarea = null;
         }
     }
 
-    componentWillUnmount() {
-        this.foundation.destroy();
-        this._resizeListener && window.removeEventListener('resize', this._resizeListener);
-    }
-
     componentDidUpdate(prevProps: TextAreaProps, prevState: TextAreaState) {
-
-        if ((this.props.value !== prevProps.value || this.props.placeholder !== prevProps.placeholder) && this.props.autosize) {
+        if (
+            (this.props.value !== prevProps.value || this.props.placeholder !== prevProps.placeholder) &&
+            this.props.autosize
+        ) {
             this.foundation.resizeTextarea();
         }
     }
@@ -215,10 +199,7 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
         if (showClear) {
             return (
                 // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
-                <div
-                    className={clearCls}
-                    onClick={this.handleClear}
-                >
+                <div className={clearCls} onClick={this.handleClear}>
                     <IconClear />
                 </div>
             );
@@ -227,25 +208,21 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
     }
 
     renderCounter() {
-        let counter: React.ReactNode,
-            current: number,
-            total: number,
-            countCls: string;
+        let counter: React.ReactNode, current: number, total: number, countCls: string;
         const { showCounter, maxCount, getValueLength } = this.props;
         if (showCounter || maxCount) {
             const { value } = this.state;
             // eslint-disable-next-line no-nested-ternary
-            current = value ? isFunction(getValueLength) ? getValueLength(value) : value.length : 0;
+            current = value ? (isFunction(getValueLength) ? getValueLength(value) : value.length) : 0;
             total = maxCount || null;
-            countCls = cls(
-                `${prefixCls}-textarea-counter`,
-                {
-                    [`${prefixCls}-textarea-counter-exceed`]: current > total
-                }
-            );
+            countCls = cls(`${prefixCls}-textarea-counter`, {
+                [`${prefixCls}-textarea-counter-exceed`]: current > total,
+            });
             counter = (
                 <div className={countCls}>
-                    {current}{total ? '/' : null}{total}
+                    {current}
+                    {total ? '/' : null}
+                    {total}
                 </div>
             );
         } else {
@@ -289,28 +266,21 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
             ...rest
         } = this.props;
         const { isFocus, value, minLength: stateMinLength } = this.state;
-        const wrapperCls = cls(
-            className,
-            `${prefixCls}-textarea-wrapper`,
-            {
-                [`${prefixCls}-textarea-borderless`]: borderless,
-                [`${prefixCls}-textarea-wrapper-disabled`]: disabled,
-                [`${prefixCls}-textarea-wrapper-readonly`]: readonly,
-                [`${prefixCls}-textarea-wrapper-${validateStatus}`]: Boolean(validateStatus),
-                [`${prefixCls}-textarea-wrapper-focus`]: isFocus,
-                // [`${prefixCls}-textarea-wrapper-resize`]: !autosize && resize,
-            }
-        );
+        const wrapperCls = cls(className, `${prefixCls}-textarea-wrapper`, {
+            [`${prefixCls}-textarea-borderless`]: borderless,
+            [`${prefixCls}-textarea-wrapper-disabled`]: disabled,
+            [`${prefixCls}-textarea-wrapper-readonly`]: readonly,
+            [`${prefixCls}-textarea-wrapper-${validateStatus}`]: Boolean(validateStatus),
+            [`${prefixCls}-textarea-wrapper-focus`]: isFocus,
+            // [`${prefixCls}-textarea-wrapper-resize`]: !autosize && resize,
+        });
         // const ref = this.props.forwardRef || this.textAreaRef;
-        const itemCls = cls(
-            `${prefixCls}-textarea`,
-            {
-                [`${prefixCls}-textarea-disabled`]: disabled,
-                [`${prefixCls}-textarea-readonly`]: readonly,
-                [`${prefixCls}-textarea-autosize`]: isObject(autosize) ? isUndefined(autosize?.maxRows) : autosize,
-                [`${prefixCls}-textarea-showClear`]: showClear,
-            }
-        );
+        const itemCls = cls(`${prefixCls}-textarea`, {
+            [`${prefixCls}-textarea-disabled`]: disabled,
+            [`${prefixCls}-textarea-readonly`]: readonly,
+            [`${prefixCls}-textarea-autosize`]: isObject(autosize) ? isUndefined(autosize?.maxRows) : autosize,
+            [`${prefixCls}-textarea-showClear`]: showClear,
+        });
         const itemProps = {
             ...omit(rest, 'insetLabel', 'insetLabelId', 'getValueLength', 'onClear', 'showClear'),
             autoFocus: autoFocus || this.props['autofocus'],
@@ -338,7 +308,13 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
                 onMouseEnter={e => this.foundation.handleMouseEnter(e)}
                 onMouseLeave={e => this.foundation.handleMouseLeave(e)}
             >
-                <textarea {...itemProps} ref={this.setRef} />
+                {autosize ? (
+                    <ResizeObserver onResize={this.throttledResizeTextarea}>
+                        <textarea {...itemProps} ref={this.setRef} />
+                    </ResizeObserver>
+                ) : (
+                    <textarea {...itemProps} ref={this.setRef} />
+                )}
                 {this.renderClearBtn()}
                 {this.renderCounter()}
             </div>
@@ -346,6 +322,8 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
     }
 }
 
-const ForwardTextarea = React.forwardRef<HTMLTextAreaElement, Omit<TextAreaProps, 'forwardRef'>>((props, ref) => <TextArea {...props} forwardRef={ref} />);
+const ForwardTextarea = React.forwardRef<HTMLTextAreaElement, Omit<TextAreaProps, 'forwardRef'>>((props, ref) => (
+    <TextArea {...props} forwardRef={ref} />
+));
 
 export default ForwardTextarea;

+ 2 - 0
packages/semi-ui/modal/Modal.tsx

@@ -244,6 +244,8 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
     componentWillUnmount() {
         if (this.props.visible) {
             this.foundation.destroy();
+        } else {
+            this.foundation.enabledBodyScroll();
         }
     }
 

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.51.0-beta.0",
+    "version": "2.51.3",
     "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,12 +20,12 @@
         "@dnd-kit/core": "^6.0.8",
         "@dnd-kit/sortable": "^7.0.2",
         "@dnd-kit/utilities": "^3.2.1",
-        "@douyinfe/semi-animation": "2.51.0-beta.0",
-        "@douyinfe/semi-animation-react": "2.51.0-beta.0",
-        "@douyinfe/semi-foundation": "2.51.0-beta.0",
-        "@douyinfe/semi-icons": "2.51.0-beta.0",
-        "@douyinfe/semi-illustrations": "2.51.0-beta.0",
-        "@douyinfe/semi-theme-default": "2.51.0-beta.0",
+        "@douyinfe/semi-animation": "2.51.3",
+        "@douyinfe/semi-animation-react": "2.51.3",
+        "@douyinfe/semi-foundation": "2.51.3",
+        "@douyinfe/semi-icons": "2.51.3",
+        "@douyinfe/semi-illustrations": "2.51.3",
+        "@douyinfe/semi-theme-default": "2.51.3",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
         "copy-text-to-clipboard": "^2.1.1",

+ 47 - 42
packages/semi-ui/table/Body/BaseRow.tsx

@@ -62,51 +62,56 @@ export interface BaseRowProps {
     virtualized?: Virtualized;
     visible: boolean; // required
     /** whether display none */
-    displayNone?: boolean;
+    displayNone?: boolean
 }
 
+/**
+ * avoid affected by https://www.npmjs.com/package/babel-plugin-transform-react-remove-prop-types
+ */
+export const baseRowPropTypes = {
+    anyColumnFixed: PropTypes.bool,
+    cellWidths: PropTypes.array.isRequired,
+    className: PropTypes.string,
+    columns: PropTypes.array.isRequired,
+    components: PropTypes.object.isRequired,
+    disabled: PropTypes.bool,
+    expandIcon: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.node]),
+    expandableRow: PropTypes.bool,
+    expanded: PropTypes.bool,
+    displayNone: PropTypes.bool,
+    expandedRow: PropTypes.bool,
+    fixed: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
+    height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+    hideExpandedColumn: PropTypes.bool,
+    hovered: PropTypes.bool.isRequired,
+    indent: PropTypes.number,
+    indentSize: PropTypes.number,
+    index: PropTypes.number,
+    isSection: PropTypes.bool,
+    level: PropTypes.number,
+    onDidUpdate: PropTypes.func,
+    onHover: PropTypes.func,
+    onRow: PropTypes.func,
+    onRowClick: PropTypes.func,
+    onRowContextMenu: PropTypes.func,
+    onRowDoubleClick: PropTypes.func,
+    onRowMouseEnter: PropTypes.func,
+    onRowMouseLeave: PropTypes.func,
+    prefixCls: PropTypes.string,
+    record: PropTypes.object,
+    renderExpandIcon: PropTypes.func,
+    replaceClassName: PropTypes.string,
+    rowExpandable: PropTypes.func,
+    rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, // real key of the row
+    selected: PropTypes.bool,
+    store: PropTypes.object,
+    style: PropTypes.object,
+    virtualized: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
+    visible: PropTypes.bool.isRequired,
+};
+
 export default class TableRow extends BaseComponent<BaseRowProps, Record<string, any>> {
-    static propTypes = {
-        anyColumnFixed: PropTypes.bool,
-        cellWidths: PropTypes.array.isRequired,
-        className: PropTypes.string,
-        columns: PropTypes.array.isRequired,
-        components: PropTypes.object.isRequired,
-        disabled: PropTypes.bool,
-        expandIcon: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.node]),
-        expandableRow: PropTypes.bool,
-        expanded: PropTypes.bool,
-        displayNone: PropTypes.bool,
-        expandedRow: PropTypes.bool,
-        fixed: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
-        height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
-        hideExpandedColumn: PropTypes.bool,
-        hovered: PropTypes.bool.isRequired,
-        indent: PropTypes.number,
-        indentSize: PropTypes.number,
-        index: PropTypes.number,
-        isSection: PropTypes.bool,
-        level: PropTypes.number,
-        onDidUpdate: PropTypes.func,
-        onHover: PropTypes.func,
-        onRow: PropTypes.func,
-        onRowClick: PropTypes.func,
-        onRowContextMenu: PropTypes.func,
-        onRowDoubleClick: PropTypes.func,
-        onRowMouseEnter: PropTypes.func,
-        onRowMouseLeave: PropTypes.func,
-        prefixCls: PropTypes.string,
-        record: PropTypes.object,
-        renderExpandIcon: PropTypes.func,
-        replaceClassName: PropTypes.string,
-        rowExpandable: PropTypes.func,
-        rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, // real key of the row
-        selected: PropTypes.bool,
-        store: PropTypes.object,
-        style: PropTypes.object,
-        virtualized: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
-        visible: PropTypes.bool.isRequired,
-    };
+    static propTypes = baseRowPropTypes;
 
     static defaultProps = {
         columns: [] as [],

+ 27 - 21
packages/semi-ui/table/Body/SectionRow.tsx

@@ -40,32 +40,38 @@ export interface SectionRowProps {
     rowKey?: RowKey<any>
 }
 
+/**
+ * avoid affected by https://www.npmjs.com/package/babel-plugin-transform-react-remove-prop-types
+ */
+export const sectionRowPropTypes = {
+    record: PropTypes.object,
+    index: PropTypes.number,
+    columns: PropTypes.array,
+    group: PropTypes.object.isRequired,
+    groupKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+    data: PropTypes.array,
+    renderGroupSection: PropTypes.func, // render group title
+    onGroupedRow: PropTypes.func,
+    clickGroupedRowToExpand: PropTypes.bool,
+    components: PropTypes.object,
+    expanded: PropTypes.bool,
+    prefixCls: PropTypes.string,
+    onExpand: PropTypes.func,
+    virtualized: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
+    style: PropTypes.object,
+    renderExpandIcon: PropTypes.func, // passing to baseRow
+    className: PropTypes.string,
+    store: PropTypes.object,
+    rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]),
+};
+
 /**
  * Grouping component title row
  */
 class SectionRow extends PureComponent<SectionRowProps> {
     static contextType = TableContext;
-    static propTypes = {
-        record: PropTypes.object,
-        index: PropTypes.number,
-        columns: PropTypes.array,
-        group: PropTypes.object.isRequired,
-        groupKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
-        data: PropTypes.array,
-        renderGroupSection: PropTypes.func, // render group title
-        onGroupedRow: PropTypes.func,
-        clickGroupedRowToExpand: PropTypes.bool,
-        components: PropTypes.object,
-        expanded: PropTypes.bool,
-        prefixCls: PropTypes.string,
-        onExpand: PropTypes.func,
-        virtualized: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
-        style: PropTypes.object,
-        renderExpandIcon: PropTypes.func, // passing to baseRow
-        className: PropTypes.string,
-        store: PropTypes.object,
-        rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]),
-    };
+    static propTypes = sectionRowPropTypes;
+
     static defaultProps = {
         prefixCls: cssClasses.PREFIX,
         components: {

+ 6 - 6
packages/semi-ui/table/Body/index.tsx

@@ -22,9 +22,9 @@ import Store from '@douyinfe/semi-foundation/utils/Store';
 import BaseComponent, { BaseProps } from '../../_base/baseComponent';
 import { logger } from '../utils';
 import ColGroup from '../ColGroup';
-import BaseRow from './BaseRow';
+import BaseRow, { baseRowPropTypes } from './BaseRow';
 import ExpandedRow from './ExpandedRow';
-import SectionRow from './SectionRow';
+import SectionRow, { sectionRowPropTypes } from './SectionRow';
 import TableHeader from '../TableHeader';
 import ConfigContext from '../../configProvider/context';
 import TableContext, { TableContextProps } from '../table-context';
@@ -74,7 +74,7 @@ export interface BodyProps extends BaseProps {
     renderExpandIcon: (record: Record<string, any>, isNested: boolean) => ReactNode | null;
     headerRef?: React.MutableRefObject<HTMLDivElement> | ((instance: any) => void);
     onScroll?: VirtualizedOnScroll;
-    keepDOM?: boolean;
+    keepDOM?: boolean
 }
 
 export interface BodyState {
@@ -457,7 +457,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
      */
     renderSectionRow = (props: RenderSectionRowProps = { groupKey: undefined }) => {
         const { dataSource, rowKey, group, groupKey, index } = props;
-        const sectionRowPickKeys = Object.keys(SectionRow.propTypes);
+        const sectionRowPickKeys = Object.keys(sectionRowPropTypes);
         const sectionRowProps: any = pick(props, sectionRowPickKeys);
 
         const { handleRowExpanded } = this.context;
@@ -543,7 +543,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
             expandRowByClick,
         } = props;
 
-        const baseRowPickKeys = Object.keys(BaseRow.propTypes);
+        const baseRowPickKeys = Object.keys(baseRowPropTypes);
         const baseRowProps: Record<string, any> = pick(props, baseRowPickKeys);
 
         let key = getRecordKey(record, rowKey);
@@ -847,7 +847,7 @@ export interface RenderExpandedRowProps {
     virtualized?: Virtualized;
     level?: number;
     keepDOM?: boolean;
-    displayNone?: boolean;
+    displayNone?: boolean
 }
 
 export interface RenderSectionRowProps {

+ 187 - 109
packages/semi-ui/table/ColumnFilter.tsx

@@ -1,12 +1,11 @@
-import React, { isValidElement } from 'react';
+import React, { isValidElement, useEffect, useState } from 'react';
 import cls from 'classnames';
-import { noop } from 'lodash';
+import { isEqual, noop, pick } from 'lodash';
 import { IconFilter } from '@douyinfe/semi-icons';
 
 import { cssClasses } from '@douyinfe/semi-foundation/table/constants';
 
 import Dropdown, { DropdownProps } from '../dropdown';
-import { Trigger, Position } from '../tooltip';
 import { Radio } from '../radio';
 import { Checkbox } from '../checkbox';
 import {
@@ -16,7 +15,7 @@ import {
     RenderFilterDropdownItem
 } from './interface';
 
-function renderDropdown(props: RenderDropdownProps = {}, nestedElem: React.ReactNode = null, level = 0) {
+function renderDropdown(props: RenderDropdownProps, nestedElem: React.ReactNode = null, level = 0) {
     const {
         filterMultiple = true,
         filters = [],
@@ -26,95 +25,99 @@ function renderDropdown(props: RenderDropdownProps = {}, nestedElem: React.React
         onFilterDropdownVisibleChange = noop,
         trigger = 'click',
         position = 'bottom',
+        renderFilterDropdown,
         renderFilterDropdownItem,
-    } = props;
+    } = props ?? {};
+
+    const renderFilterDropdownProps: RenderFilterDropdownProps = pick(props, ['tempFilteredValue', 'setTempFilteredValue', 'confirm', 'clear', 'close', 'filters']);
+    const render = typeof renderFilterDropdown === 'function' ? renderFilterDropdown(renderFilterDropdownProps) : (
+        <Dropdown.Menu>
+            {Array.isArray(filters) &&
+                filters.map((filter, index) => {
+                    const changeFn = (e: React.MouseEvent<HTMLLIElement>) => {
+                        const domEvent = e && e.nativeEvent;
+                        if (domEvent) {
+                            // Block this event to prevent the pop-up layer from closing
+                            domEvent.stopImmediatePropagation();
+
+                            // Prevent bubbling and default events to prevent label click events from triggering twice
+                            domEvent.stopPropagation();
+                            domEvent.preventDefault();
+                        }
+                        let values = [...filteredValue];
+
+                        const included = values.includes(filter.value);
+                        const idx = values.indexOf(filter.value);
+
+                        if (idx > -1) {
+                            values.splice(idx, 1);
+                        } else if (filterMultiple) {
+                            values.push(filter.value);
+                        } else {
+                            values = [filter.value];
+                        }
+                        return onSelect({
+                            value: filter.value,
+                            filteredValue: values,
+                            included: !included,
+                            domEvent,
+                        });
+                    };
+
+                    const checked = filteredValue.includes(filter.value);
+                    const { text } = filter;
+                    const { value } = filter;
+                    const key = `${level}_${index}`;
+
+                    const dropdownItem =
+                        typeof renderFilterDropdownItem === 'function' ?
+                            renderFilterDropdownItem({
+                                onChange: changeFn,
+                                filterMultiple,
+                                value,
+                                text,
+                                checked,
+                                filteredValue,
+                                level,
+                            }) :
+                            null;
+
+                    let item =
+                        dropdownItem && React.isValidElement(dropdownItem) ? (
+                            React.cloneElement(dropdownItem, { key })
+                        ) : (
+                            <Dropdown.Item key={key} onClick={changeFn}>
+                                {filterMultiple ? (
+                                    <Checkbox checked={checked}>{text}</Checkbox>
+                                ) : (
+                                    <Radio checked={checked}>{text}</Radio>
+                                )}
+                            </Dropdown.Item>
+                        );
+
+                    if (Array.isArray(filter.children) && filter.children.length) {
+                        const childrenDropdownProps = {
+                            ...props,
+                            filters: filter.children,
+                            trigger: 'hover' as const,
+                            position: 'right' as const,
+                        };
+
+                        delete childrenDropdownProps.filterDropdownVisible;
+
+                        item = renderDropdown(childrenDropdownProps, item, level + 1);
+                    }
+                    return item;
+                })}
+        </Dropdown.Menu>
+    );
 
     const dropdownProps: DropdownProps = {
         ...props,
         onVisibleChange: (visible: boolean) => onFilterDropdownVisibleChange(visible),
         trigger,
         position,
-        render: (
-            <Dropdown.Menu>
-                {Array.isArray(filters) &&
-                    filters.map((filter, index) => {
-                        const changeFn = (e: React.MouseEvent<HTMLLIElement>) => {
-                            const domEvent = e && e.nativeEvent;
-                            if (domEvent) {
-                                // Block this event to prevent the pop-up layer from closing
-                                domEvent.stopImmediatePropagation();
-
-                                // Prevent bubbling and default events to prevent label click events from triggering twice
-                                domEvent.stopPropagation();
-                                domEvent.preventDefault();
-                            }
-                            let values = [...filteredValue];
-
-                            const included = values.includes(filter.value);
-                            const idx = values.indexOf(filter.value);
-
-                            if (idx > -1) {
-                                values.splice(idx, 1);
-                            } else if (filterMultiple) {
-                                values.push(filter.value);
-                            } else {
-                                values = [filter.value];
-                            }
-                            return onSelect({
-                                value: filter.value,
-                                filteredValue: values,
-                                included: !included,
-                                domEvent,
-                            });
-                        };
-
-                        const checked = filteredValue.includes(filter.value);
-                        const { text } = filter;
-                        const { value } = filter;
-                        const key = `${level}_${index}`;
-
-                        const dropdownItem =
-                            typeof renderFilterDropdownItem === 'function' ?
-                                renderFilterDropdownItem({
-                                    onChange: changeFn,
-                                    filterMultiple,
-                                    value,
-                                    text,
-                                    checked,
-                                    filteredValue,
-                                    level,
-                                }) :
-                                null;
-
-                        let item =
-                            dropdownItem && React.isValidElement(dropdownItem) ? (
-                                React.cloneElement(dropdownItem, { key })
-                            ) : (
-                                <Dropdown.Item key={key} onClick={changeFn}>
-                                    {filterMultiple ? (
-                                        <Checkbox checked={checked}>{text}</Checkbox>
-                                    ) : (
-                                        <Radio checked={checked}>{text}</Radio>
-                                    )}
-                                </Dropdown.Item>
-                            );
-
-                        if (Array.isArray(filter.children) && filter.children.length) {
-                            const childrenDropdownProps = {
-                                ...props,
-                                filters: filter.children,
-                                trigger: 'hover' as const,
-                                position: 'right' as const,
-                            };
-
-                            delete childrenDropdownProps.filterDropdownVisible;
-
-                            item = renderDropdown(childrenDropdownProps, item, level + 1);
-                        }
-                        return item;
-                    })}
-            </Dropdown.Menu>
-        ),
+        render,
     };
 
     if (filterDropdownVisible != null) {
@@ -128,27 +131,75 @@ function renderDropdown(props: RenderDropdownProps = {}, nestedElem: React.React
     );
 }
 
-export interface ColumnFilterProps {
-    prefixCls?: string;
-    filteredValue?: any[];
-    filterIcon?: FilterIcon;
-    filterDropdown?: React.ReactElement;
-    renderFilterDropdown?: (props: RenderDropdownProps, options: { iconElem: React.ReactNode }) => React.ReactElement;
-    filterDropdownProps?: DropdownProps;
-    onFilterDropdownVisibleChange?: OnFilterDropdownVisibleChange;
-    onSelect?: (data: OnSelectData) => void
-}
-
 export default function ColumnFilter(props: ColumnFilterProps = {}): React.ReactElement {
     const {
         prefixCls = cssClasses.PREFIX,
         filteredValue,
         filterIcon = 'filter',
-        renderFilterDropdown,
         filterDropdownProps,
+        onSelect,
+        filterDropdownVisible,
+        renderFilterDropdown,
+        onFilterDropdownVisibleChange
     } = props;
     let { filterDropdown = null } = props;
 
+    
+    // custom filter related status
+    const isFilterDropdownVisibleControlled = typeof filterDropdownVisible !== 'undefined';
+    const isCustomFilterDropdown = typeof renderFilterDropdown === 'function';
+    const isCustomDropdownVisible = !isFilterDropdownVisibleControlled && isCustomFilterDropdown;
+    const [tempFilteredValue, setTempFilteredValue] = useState<any[]>(filteredValue);
+    const dropdownVisibleInitValue = isCustomDropdownVisible ? false : filterDropdownVisible;
+    const [dropdownVisible, setDropdownVisible] = useState<boolean | undefined>(dropdownVisibleInitValue);
+
+    useEffect(() => {
+        if (typeof filterDropdownVisible !== 'undefined') {
+            setDropdownVisible(filterDropdownVisible);
+        }
+    }, [filterDropdownVisible]);
+
+    useEffect(() => {
+        setTempFilteredValue(filteredValue);
+    }, [filteredValue]);
+
+    const confirm: RenderFilterDropdownProps['confirm'] = (props = {}) => {
+        const newFilteredValue = props?.filteredValue || tempFilteredValue;
+        if (!isEqual(newFilteredValue, filteredValue)) {
+            onSelect({ filteredValue: newFilteredValue });
+        }
+        if (props.closeDropdown) {
+            setDropdownVisible(false);
+        }
+    };
+
+    const clear: RenderFilterDropdownProps['clear'] = (props: { closeDropdown?: boolean } = {}) => {
+        setTempFilteredValue([]);
+        onSelect({ filteredValue: [] });
+        if (props.closeDropdown) {
+            setDropdownVisible(false);
+        }
+    };
+
+    const close: RenderFilterDropdownProps['close'] = () => {
+        setDropdownVisible(false);
+    };
+
+    const handleFilterDropdownVisibleChange = (visible: boolean) => {
+        if (isCustomDropdownVisible) {
+            setDropdownVisible(visible);
+        }
+        onFilterDropdownVisibleChange(visible);
+    };
+
+    const renderFilterDropdownProps: RenderFilterDropdownProps = {
+        tempFilteredValue,
+        setTempFilteredValue,
+        confirm,
+        clear,
+        close
+    };
+
     const finalCls = cls(`${prefixCls}-column-filter`, {
         on: Array.isArray(filteredValue) && filteredValue.length,
     });
@@ -177,32 +228,59 @@ export default function ColumnFilter(props: ColumnFilterProps = {}): React.React
     const renderProps = {
         ...props,
         ...filterDropdownProps,
+        ...renderFilterDropdownProps,
+        filterDropdownVisible: isFilterDropdownVisibleControlled ? filterDropdownVisible : dropdownVisible,
+        onFilterDropdownVisibleChange: handleFilterDropdownVisibleChange,
     };
 
     filterDropdown = React.isValidElement<ColumnFilterProps>(filterDropdown) ?
         filterDropdown :
-        typeof renderFilterDropdown === 'function' ?
-            renderFilterDropdown(renderProps, { iconElem }) :
-            renderDropdown(renderProps, iconElem);
+        renderDropdown(renderProps, iconElem);
 
     return filterDropdown;
 }
 
-export interface OnSelectData {
-    value: any;
-    filteredValue: any;
-    included: boolean;
-    domEvent: React.MouseEvent<HTMLElement>
+export interface ColumnFilterProps extends Omit<RenderDropdownProps, keyof RenderFilterDropdownProps> {
+    prefixCls?: string;
+    filteredValue?: any[];
+    filterIcon?: FilterIcon;
+    filterDropdown?: React.ReactElement;
+    filterDropdownProps?: FilterDropdownProps;
+    filters?: Filter[]
 }
 
-export interface RenderDropdownProps {
+export interface RenderDropdownProps extends FilterDropdownProps, RenderFilterDropdownProps {
     filterMultiple?: boolean;
     filters?: Filter[];
     filteredValue?: any[];
     filterDropdownVisible?: boolean;
     onSelect?: (data: OnSelectData) => void;
     onFilterDropdownVisibleChange?: OnFilterDropdownVisibleChange;
-    trigger?: Trigger;
-    position?: Position;
+    renderFilterDropdown?: (props?: RenderFilterDropdownProps) => React.ReactNode;
     renderFilterDropdownItem?: RenderFilterDropdownItem
+}
+
+export interface FilterDropdownProps extends Omit<DropdownProps, 'render' | 'onVisibleChange'> {}
+
+export interface OnSelectData {
+    value?: any;
+    /** only this value is used now  */
+    filteredValue: any;
+    included?: boolean;
+    domEvent?: React.MouseEvent<HTMLElement>
+}
+
+export interface RenderFilterDropdownProps {
+    /** temporary filteredValue  */
+    tempFilteredValue: any[];
+    /** set temporary filteredValue  */
+    setTempFilteredValue: (tempFilteredValue: any[]) => void;
+    /** set tempFilteredValue to filteredValue. You can also pass filteredValue to directly set the filteredValue  */
+    confirm: (props?: { closeDropdown?: boolean; filteredValue?: any[] }) => void;
+    /** clear tempFilteredValue and filteredValue  */
+    clear: (props?: { closeDropdown?: boolean }) => void;
+    /** close dropdown  */
+    close: () => void;
+    /** column filters  */
+    filters?: RenderDropdownProps['filters']
 }

+ 59 - 16
packages/semi-ui/table/Table.tsx

@@ -693,7 +693,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
 
     getCurrentPageData = () => {
         const pageData = this.foundation.getCurrentPageData();
-        const retObj = ['dataSource', 'groups'].reduce((result, key) => {
+        const retObj: Pick<BasePageData<RecordType>, 'dataSource' | 'groups'> = ['dataSource', 'groups'].reduce((result, key) => {
             if (pageData[key]) {
                 result[key] = pageData[key];
             }
@@ -839,36 +839,59 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
         }
     };
 
-    renderSelection = (record = {} as any, inHeader = false): React.ReactNode => {
+    renderSelection = (record = {} as any, inHeader = false, index?: number): React.ReactNode => {
         const { rowSelection, allDisabledRowKeysSet } = this.state;
 
         if (rowSelection && typeof rowSelection === 'object') {
-            const { selectedRowKeys = [], selectedRowKeysSet = new Set(), getCheckboxProps, disabled } = rowSelection;
+            const {
+                selectedRowKeys = [],
+                selectedRowKeysSet = new Set(),
+                getCheckboxProps,
+                disabled,
+                renderCell,
+            } = rowSelection;
+
+            const allRowKeys = this.cachedFilteredSortedRowKeys;
+            const allRowKeysSet = this.cachedFilteredSortedRowKeysSet;
+            const allIsSelected = this.foundation.allIsSelected(selectedRowKeysSet, allDisabledRowKeysSet, allRowKeys);
+            const hasRowSelected = this.foundation.hasRowSelected(selectedRowKeys, allRowKeysSet);
+            const indeterminate = hasRowSelected && !allIsSelected;
 
             if (inHeader) {
                 const columnKey = get(rowSelection, 'key', strings.DEFAULT_KEY_COLUMN_SELECTION);
-                const allRowKeys = this.cachedFilteredSortedRowKeys;
-                const allRowKeysSet = this.cachedFilteredSortedRowKeysSet;
-                const allIsSelected = this.foundation.allIsSelected(selectedRowKeysSet, allDisabledRowKeysSet, allRowKeys);
-                const hasRowSelected = this.foundation.hasRowSelected(selectedRowKeys, allRowKeysSet);
-                return (
+
+                const originNode = (
                     <ColumnSelection
                         aria-label={`${allIsSelected ? 'Deselect' : 'Select'} all rows`}
                         disabled={disabled}
                         key={columnKey}
                         selected={allIsSelected}
-                        indeterminate={hasRowSelected && !allIsSelected}
+                        indeterminate={indeterminate}
                         onChange={(selected, e) => {
                             this.toggleSelectAllRow(selected, e);
                         }}
                     />
                 );
+
+                const selectAll = (selected: boolean, e: Event) =>
+                    this.toggleSelectAllRow(selected, e as TableSelectionCellEvent);
+
+                return isFunction(renderCell)
+                    ? renderCell({
+                        selected: allIsSelected,
+                        record,
+                        originNode,
+                        inHeader,
+                        disabled,
+                        indeterminate,
+                        selectAll,
+                    })
+                    : originNode;
             } else {
                 const key = this.foundation.getRecordKey(record);
                 const selected = selectedRowKeysSet.has(key);
                 const checkboxPropsFn = () => (typeof getCheckboxProps === 'function' ? getCheckboxProps(record) : {});
-
-                return (
+                const originNode = (
                     <ColumnSelection
                         aria-label={`${selected ? 'Deselect' : 'Select'} this row`}
                         getCheckboxProps={checkboxPropsFn}
@@ -876,13 +899,28 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
                         onChange={(status, e) => this.toggleSelectRow(status, key, e)}
                     />
                 );
+                const selectRow = (selected: boolean, e: Event) =>
+                    this.toggleSelectRow(selected, key, e as TableSelectionCellEvent);
+
+                return isFunction(renderCell)
+                    ? renderCell({
+                        selected,
+                        record,
+                        index,
+                        originNode,
+                        inHeader: false,
+                        disabled,
+                        indeterminate,
+                        selectRow,
+                    })
+                    : originNode;
             }
         }
         return null;
     };
 
-    renderRowSelectionCallback = (text: string, record: RecordType = {} as RecordType) => this.renderSelection(record);
-    renderTitleSelectionCallback = () => this.renderSelection(null, true);
+    renderRowSelectionCallback = (text: string, record: RecordType = {} as RecordType, index: number) => this.renderSelection(record, false, index);
+    renderTitleSelectionCallback = () => this.renderSelection(undefined, true);
 
     normalizeSelectionColumn = (props: { rowSelection?: TableStateRowSelection<RecordType>; prefixCls?: string } = {}) => {
         const { rowSelection, prefixCls } = props;
@@ -969,7 +1007,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
       */
     addFnsInColumn = (column: ColumnProps = {}) => {
         const { prefixCls } = this.props;
-        if (column && (column.sorter || column.filters || column.useFullRender)) {
+        if (column && (column.sorter || column.filters || column.onFilter || column.useFullRender)) {
             let hasSorterOrFilter = false;
             const { dataIndex, title: rawTitle, useFullRender } = column;
             const curQuery = this.foundation.getQuery(dataIndex);
@@ -1016,11 +1054,16 @@ 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)) {
+            if (
+                (Array.isArray(column.filters) && column.filters.length) ||
+                isValidElement(column.filterDropdown) ||
+                typeof column.renderFilterDropdown === 'function'
+            ) {
+
                 const filter = (
                     <ColumnFilter
                         key={strings.DEFAULT_KEY_COLUMN_FILTER}
-                        {...curQuery}
+                        {...omit(curQuery, 'children')}
                         filteredValue={filteredValue}
                         onFilterDropdownVisibleChange={(visible: boolean) =>
                             this.foundation.toggleShowFilter(dataIndex, visible)

+ 4 - 14
packages/semi-ui/table/__test__/table.test.js

@@ -560,8 +560,7 @@ describe(`Table`, () => {
         demo.find(`.${BASE_CLASS_PREFIX}-dropdown .${BASE_CLASS_PREFIX}-dropdown-item`)
             .at(0)
             .find(`.${BASE_CLASS_PREFIX}-checkbox`)
-            .simulate('mousedown', {
-                button:0,
+            .simulate('click', {
                 nativeEvent: null,
             });
         expect(onChange.callCount).toBe(++onChangeCalledCount); // click first filter again
@@ -569,8 +568,7 @@ describe(`Table`, () => {
         demo.find(`.${BASE_CLASS_PREFIX}-dropdown .${BASE_CLASS_PREFIX}-dropdown-item`)
             .at(0)
             .find(`.${BASE_CLASS_PREFIX}-checkbox`)
-            .simulate('mousedown', {
-                button:0,
+            .simulate('click', {
                 nativeEvent: null,
             });
         expect(onChange.callCount).toBe(++onChangeCalledCount); // to page 2
@@ -630,8 +628,7 @@ describe(`Table`, () => {
         demo.find(`.${BASE_CLASS_PREFIX}-dropdown .${BASE_CLASS_PREFIX}-dropdown-item`)
             .at(0)
             .find(`.${BASE_CLASS_PREFIX}-checkbox`)
-            .simulate('mousedown', {
-                button:0,
+            .simulate('click', {
                 nativeEvent: null,
             });
         const nameColList = demo.find('.semi-table-tbody .name-col');
@@ -2167,14 +2164,7 @@ describe(`Table`, () => {
         const tableNode = mount(<Table columns={columns} dataSource={data} onChange={onChange}/>);
         tableNode.find('.semi-table-column-filter').simulate('click');
         const filterNode = Array.from(document.querySelectorAll('.semi-checkbox-addon')).filter(node => node.textContent === 'Semi Design 设计稿');
-
-        const mousedownEvent = new MouseEvent('mousedown', {
-            bubbles: true,
-            cancelable: true,
-            button: 0,
-        });
-        filterNode[0].dispatchEvent(mousedownEvent);
-
+        filterNode[0].click();
         expect(onChange.calledOnce).toBe(true);
         const arg = onChange.getCall(0).args[0];
         expect(arg.sorter.defaultSortOrder).toBe(defaultSortOrder);

+ 140 - 0
packages/semi-ui/table/_story/RowSelectionRenderCell/index.jsx

@@ -0,0 +1,140 @@
+import { Table, Tooltip } from '@douyinfe/semi-ui';
+import React, { useMemo, useState } from 'react';
+
+function App() {
+    const defaultSelectedRowKeys = useMemo(() => ['3'], []);
+    const [selectedRowKeys, setSelectedRowKeys] = useState(defaultSelectedRowKeys);
+    const [headerOrigin, setHeaderOrigin] = useState(false);
+    const [rowOrigin, setRowOrigin] = useState(false);
+
+    const columns = useMemo(
+        () => [
+            {
+                title: 'Name',
+                dataIndex: 'name',
+                render: text => <span>{text}</span>,
+            },
+            {
+                title: 'Age',
+                dataIndex: 'age',
+            },
+            {
+                title: 'Address',
+                dataIndex: 'address',
+            },
+            {
+                render: () => (
+                    <input
+                        type="checkbox"
+                        onClick={(...args) => {
+                            console.log('onClick: ', ...args);
+                        }}
+                        onChange={(...args) => {
+                            console.log('onChange: ', ...args);
+                        }}
+                    />
+                ),
+            },
+        ],
+        []
+    );
+
+    const data = useMemo(() => {
+        const _data = [];
+        for (let i = 0; i < 5; i++) {
+            let age = i * 1000;
+            let name = `Edward King ${i}`;
+            _data.push({
+                key: '' + i,
+                name,
+                age,
+                address: `London, Park Lane no. ${i} Lake Park`,
+                description: `My name is ${name}, I am ${age} years old, living in New York No. ${i + 1} Lake Park.`,
+            });
+        }
+        return _data;
+    }, []);
+
+    const rowSelection = {
+        renderCell: ({ selected, record, index, originNode, inHeader, disabled, indeterminate, selectRow, selectAll }) => {
+            console.log('selected', selected);
+            console.log('index', index);
+            console.log('inHeader', inHeader);
+            console.log('disabled', disabled);
+            console.log('indeterminate', indeterminate);
+
+            if (inHeader && headerOrigin) {
+                return (
+                    <Tooltip content="自定义表头 renderCell,我是表头">
+                        <div>{originNode}</div>
+                    </Tooltip>
+                );
+            }
+
+            if (inHeader && !headerOrigin) {
+                return (
+                    <Tooltip content="自定义表头 renderCell,我是表头,不使用 originNode 控制选中">
+                        <div onClick={e => selectAll && selectAll(!selected, e)}>222</div>
+                    </Tooltip>
+                );
+            }
+
+            if (record.age === 2000 && !rowOrigin) {
+                return (
+                    <Tooltip content="自定义 renderCell, 不使用 originNode 控制选中">
+                        <div
+                            style={{ color: selected ? 'red' : 'black' }}
+                            onClick={e => selectRow && selectRow(!selected, e)}
+                        >
+                            111
+                        </div>
+                    </Tooltip>
+                );
+            }
+
+            if (record.age > 2000) {
+                return (
+                    <Tooltip content="自定义 renderCell">
+                        <div>{originNode}</div>
+                    </Tooltip>
+                );
+            }
+            return originNode;
+        },
+        onChange: (selectedRowKeys, selectedRows) => {
+            console.log(
+                `rowSelection.onChanged: selectedRowKeys: ${JSON.stringify(selectedRowKeys)}`,
+                'selectedRows: ',
+                selectedRows
+            );
+            setSelectedRowKeys(selectedRowKeys);
+        },
+        onSelectAll: (selected, selectedRows, changedRows) => {
+            console.log(
+                `rowSelection.onSelectAll: selected :${selected}, selectedRows: ${selectedRows}, changedRows: ${changedRows}`
+            );
+        },
+        getCheckboxProps: record => ({
+            // disabled: record.age < 2000,
+            name: record.name,
+            onClick: (...args) => {
+                console.log('Clicked checkbox: ', ...args);
+            },
+        }),
+        onSelect: (record, selected) => {
+            console.log('onSelect: ', record, selected);
+        },
+        selectedRowKeys,
+        defaultSelectedRowKeys,
+    };
+
+    return (
+        <div>
+            <button onClick={() => setHeaderOrigin(!headerOrigin)}>表头{headerOrigin ? '不使用' : '使用'} originNode</button>
+            <button onClick={() => setRowOrigin(!rowOrigin)}>选择行{ rowOrigin ? '不使用' : '使用' } originNode</button>
+            <Table rowKey={record => record.key} columns={columns} dataSource={data} rowSelection={rowSelection} />
+        </div>
+    );
+}
+
+export default App;

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

@@ -47,6 +47,7 @@ import ExpandAllGroupRows from './ExpandAllGroupRows';
 import ExpandRowByClick from './ExpandRowByClick';
 import FixAllColumnsWithoutWidth from './FixAllColumnsWithoutWidth';
 import HugeData from "./HugeData"
+import RowSelectionRenderCell from './RowSelectionRenderCell';
 
 export default {
   title: 'Table'
@@ -107,7 +108,9 @@ export {
     ShowHeader,
     KeepDOM,
     SortIcon,
-    FixedAllDisabledAndSelected
+    FixedAllDisabledAndSelected,
+    FeatRenderFilterDropdown,
+    InputFilter
 } from './v2';
 export { default as FixSelectAll325 } from './Demos/rowSelection';
 
@@ -632,3 +635,9 @@ export const HugeDataDemo = ()=><HugeData/>
 HugeDataDemo.parameters = {
   chromatic: { disableSnapshot: true },
 };
+
+export const _RowSelectionRenderCell = () => <RowSelectionRenderCell />;
+
+_RowSelectionRenderCell.story = {
+  name: 'RowSelection RenderCell',
+};

+ 135 - 0
packages/semi-ui/table/_story/v2/FeatRenderFilterDropdown/index.tsx

@@ -0,0 +1,135 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Table, Avatar, Input, Button, Space } from '@douyinfe/semi-ui';
+import type { ColumnProps } from '../../../interface';
+import * as dateFns from 'date-fns';
+
+/**
+ * test with cypress, please don't modify this story
+ */
+export default function App() {
+    const [dataSource, setData] = useState([]);
+    const inputRef = useRef<HTMLInputElement>();
+
+    const DAY = 24 * 60 * 60 * 1000;
+    const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+    const columns: ColumnProps[] = [
+        {
+            title: '标题',
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            onFilter: (value, record) => record.name.includes(value),
+            renderFilterDropdown: (props) => {
+                console.log('renderFilterDropdown', props);
+                const { tempFilteredValue, setTempFilteredValue, confirm, clear, close } = props;
+
+                const handleChange = (value: any) => {
+                    const filteredValue = value ? [value] : [];
+                    setTempFilteredValue(filteredValue);
+                    // 你也可以在 input value 变化时直接筛选
+                    // confirm({ filteredValue });
+                };
+
+                return (
+                    <Space vertical align='start' style={{ padding: 8 }}>
+                        <Input ref={inputRef} value={tempFilteredValue[0]} onChange={handleChange}/>
+                        <Space>
+                            <Button onClick={() => confirm({ closeDropdown: true })}>筛选+关闭</Button>
+                            <Button onClick={() => clear({ closeDropdown: true })}>清除+关闭</Button>
+                            <Button onClick={() => close()}>直接关闭</Button>
+                        </Space>
+                    </Space>
+                );
+            },
+            onFilterDropdownVisibleChange: (visible) => {
+                console.log('inputRef', visible, inputRef);
+                inputRef.current?.focus?.();
+            }
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
+            render: text => `${text} KB`,
+        },
+        {
+            title: '所有者',
+            dataIndex: 'owner',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>
+                            {typeof text === 'string' && text.slice(0, 1)}
+                        </Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            onFilter: (value, record) => record.owner.includes(value),
+            defaultFilteredValue: ['姜鹏志'],
+            renderFilterDropdown: (props) => {
+                console.log('renderFilterDropdown', props);
+                const { tempFilteredValue, setTempFilteredValue, confirm, clear, close } = props;
+
+                const handleChange = (value: any) => {
+                    if (value) {
+                        setTempFilteredValue([value]);
+                    } else {
+                        setTempFilteredValue([]);
+                    }
+                };
+
+                return (
+                    <Space vertical align='start' style={{ padding: 8 }}>
+                        <Input value={tempFilteredValue[0]} onChange={handleChange}/>
+                        <Space>
+                            <Button onClick={() => confirm({ closeDropdown: false })}>筛选后不关闭</Button>
+                            <Button onClick={() => clear({ closeDropdown: false })}>清除后不关闭</Button>
+                            <Button onClick={() => close()}>直接关闭</Button>
+                        </Space>
+                    </Space>
+                );
+            },
+        },
+        {
+            title: '更新日期',
+            dataIndex: 'updateTime',
+            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
+            render: value => {
+                return dateFns.format(new Date(value), 'yyyy-MM-dd');
+            },
+        },
+    ];
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 首页${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date('2024-01-25').valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red',
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData();
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} />;
+}

+ 117 - 0
packages/semi-ui/table/_story/v2/InputFilter/index.tsx

@@ -0,0 +1,117 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Table, Avatar, Input, Space } from '@douyinfe/semi-ui';
+import type { ColumnProps } from '../../../interface';
+import * as dateFns from 'date-fns';
+
+/**
+ * test with cypress, please don't modify this story
+ */
+export default function App() {
+    const [dataSource, setData] = useState([]);
+    const [filteredValue, setFilteredValue] = useState(['设计稿']);
+    const compositionRef = useRef({ isComposition: false });
+
+    const DAY = 24 * 60 * 60 * 1000;
+    const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+    const handleChange = (value: string) => {
+        if (compositionRef.current?.isComposition) {
+            return;
+        }
+        const newFilteredValue = value ? [value] : [];
+        setFilteredValue(newFilteredValue);
+    };
+    const handleCompositionStart = () => {
+        compositionRef.current.isComposition = true;
+    };
+
+    const handleCompositionEnd = (event: React.CompositionEvent) => {
+        compositionRef.current.isComposition = false;
+        const value = event.target?.value;
+        const newFilteredValue = value ? [value] : [];
+        setFilteredValue(newFilteredValue);
+    };
+
+
+    const columns: ColumnProps[] = [
+        {
+            title: (
+                <Space>
+                    <span>标题</span>
+                    <Input
+                        style={{ width: 200 }}
+                        defaultValue={filteredValue[0]}
+                        onCompositionStart={handleCompositionStart}
+                        onCompositionEnd={handleCompositionEnd}
+                        onChange={handleChange}
+                        showClear 
+                    />
+                </Space>
+            ),
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            onFilter: (value, record) => record.name.includes(value),
+            filteredValue,
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
+            render: text => `${text} KB`,
+        },
+        {
+            title: '所有者',
+            dataIndex: 'owner',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>
+                            {typeof text === 'string' && text.slice(0, 1)}
+                        </Avatar>
+                        {text}
+                    </div>
+                );
+            },
+        },
+        {
+            title: '更新日期',
+            dataIndex: 'updateTime',
+            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
+            render: value => {
+                return dateFns.format(new Date(value), 'yyyy-MM-dd');
+            },
+        },
+    ];
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 首页${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date('2024-01-25').valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red',
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData();
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} />;
+}

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

@@ -28,3 +28,5 @@ export { default as ShowHeader } from './ShowHeader';
 export { default as KeepDOM } from './KeepDOM';
 export { default as SortIcon } from './SortIcon';
 export { default as FixedAllDisabledAndSelected } from './FixedAllDisabledAndSelected';
+export { default as FeatRenderFilterDropdown } from './FeatRenderFilterDropdown';
+export { default as InputFilter } from './InputFilter';

+ 31 - 11
packages/semi-ui/table/interface.ts

@@ -1,13 +1,12 @@
 import React, { ReactNode, MutableRefObject } from 'react';
 
-import { BaseProps } from '../_base/baseComponent';
-import { PaginationProps } from '../pagination';
-import { CheckboxProps } from '../checkbox';
-import { DropdownProps } from '../dropdown';
-import { Locale } from '../locale/interface';
-import { ArrayElement } from '../_base/base';
+import type { BaseProps } from '../_base/baseComponent';
+import type { PaginationProps } from '../pagination';
+import type { CheckboxProps } from '../checkbox';
+import type { Locale } from '../locale/interface';
+import type { ArrayElement } from '../_base/base';
 import { strings } from '@douyinfe/semi-foundation/table/constants';
-import {
+import type {
     BaseRowKeyType,
     BaseSortOrder,
     BaseGroupBy,
@@ -21,7 +20,8 @@ import {
     BaseIncludeGroupRecord,
     BaseEllipsis
 } from '@douyinfe/semi-foundation/table/foundation';
-import { ScrollDirection, CSSDirection } from 'react-window';
+import type { ScrollDirection, CSSDirection } from 'react-window';
+import type { ColumnFilterProps } from './ColumnFilter';
 
 export interface TableProps<RecordType extends Record<string, any> = any> extends BaseProps {
     bordered?: boolean;
@@ -81,29 +81,37 @@ export interface ColumnProps<RecordType extends Record<string, any> = any> {
     children?: Array<ColumnProps<RecordType>>;
     className?: string;
     colSpan?: number;
+    /** use `dataIndex` to get current column data item from record. If you use `sorter` or `onFilter`, a unique `dataIndex` is required  */
     dataIndex?: string;
     defaultFilteredValue?: any[];
     defaultSortOrder?: SortOrder;
     filterChildrenRecord?: boolean;
-    filterDropdown?: React.ReactNode;
-    filterDropdownProps?: DropdownProps;
+    filterDropdown?: ColumnFilterProps['filterDropdown'];
+    /** render filter Dropdown panel content  */
+    renderFilterDropdown?: ColumnFilterProps['renderFilterDropdown'];
+    /** filter Dropdown props  */
+    filterDropdownProps?: ColumnFilterProps['filterDropdownProps'];
     filterDropdownVisible?: boolean;
     filterIcon?: FilterIcon;
     filterMultiple?: boolean;
     filteredValue?: any[];
+    /** `filters` is not required if you use `renderFilterDropdown`  */
     filters?: Filter[];
     fixed?: Fixed;
+    /** the key required by React. If you have already set the `dataIndex`, the key does not need to be set again.  */
     key?: string | number;
     render?: ColumnRender<RecordType>;
     renderFilterDropdownItem?: RenderFilterDropdownItem;
     sortChildrenRecord?: boolean;
     sortOrder?: SortOrder;
+    /** enable sorting, `dataIndex` is required at the same time  */
     sorter?: Sorter<RecordType>;
     sortIcon?: SortIcon;
     title?: ColumnTitle;
     useFullRender?: boolean;
     width?: string | number;
     onCell?: OnCell<RecordType>;
+    /** enable filtering, `dataIndex` is required at the same time  */
     onFilter?: OnFilter<RecordType>;
     onFilterDropdownVisibleChange?: OnFilterDropdownVisibleChange;
     onHeaderCell?: OnHeaderCell<RecordType>;
@@ -237,9 +245,21 @@ export interface RowSelectionProps<RecordType> {
     width?: string | number;
     onChange?: RowSelectionOnChange<RecordType>;
     onSelect?: RowSelectionOnSelect<RecordType>;
-    onSelectAll?: RowSelectionOnSelectAll<RecordType>
+    onSelectAll?: RowSelectionOnSelectAll<RecordType>;
+    renderCell?: RowSelectionRenderCell<RecordType>
 }
 
+export type RowSelectionRenderCell<RecordType> = (renderCellArgs: {
+    selected: boolean;
+    record: RecordType;
+    originNode: JSX.Element;
+    inHeader: boolean;
+    disabled: boolean;
+    indeterminate: boolean;
+    index?: number;
+    selectRow?: (selected: boolean, e: Event) => void;
+    selectAll?: (selected: boolean, e: Event) => void
+}) => ReactNode;
 export type GetCheckboxProps<RecordType> = (record: RecordType) => CheckboxProps;
 export type RowSelectionOnChange<RecordType> = (selectedRowKeys?: (string | number)[], selectedRows?: RecordType[]) => void;
 export type RowSelectionOnSelect<RecordType> = (

+ 21 - 0
packages/semi-ui/treeSelect/__test__/treeSelect.test.js

@@ -595,6 +595,27 @@ describe('TreeSelect', () => {
         expect(treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-select-selection`).getDOMNode().textContent).toEqual('亚洲');
     });
 
+    it('onClear', () => {
+        let spyOnClear = sinon.spy(() => { });
+        let treeSelect = getTreeSelect({
+            defaultExpandAll: true,
+            onClear: spyOnClear,
+            showClear: true,
+        });
+
+        let nodeBeijing = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`).at(0);
+        // select beijing
+        nodeBeijing.simulate('click');
+
+        let nodeSelectTree = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-select.${BASE_CLASS_PREFIX}-tree-select-single`).at(0);
+        nodeSelectTree.simulate('mouseenter');
+
+        let nodeClearIcon = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-select-clearbtn`).at(0);
+        nodeClearIcon.simulate('click');
+
+        expect(spyOnClear.calledOnce).toBe(true);
+    })
+
     it('onChange + value not changed', () => {
         let spyOnSelect = sinon.spy(() => { });
         let spyOnChange = sinon.spy(() => { });

+ 7 - 1
packages/semi-ui/treeSelect/index.tsx

@@ -149,7 +149,8 @@ export interface TreeSelectProps extends Omit<BasicTreeSelectProps, OverrideComm
     onBlur?: (e: React.MouseEvent) => void;
     onChange?: OnChange;
     onFocus?: (e: React.MouseEvent) => void;
-    onVisibleChange?: (isVisible: boolean) => void
+    onVisibleChange?: (isVisible: boolean) => void;
+    onClear?: (e: React.MouseEvent | React.KeyboardEvent<HTMLDivElement>) => void
 }
 
 export type OverrideCommonState =
@@ -204,6 +205,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         virtualize: PropTypes.object,
         treeNodeFilterProp: PropTypes.string,
         onChange: PropTypes.func,
+        onClear: PropTypes.func,
         onSearch: PropTypes.func,
         onSelect: PropTypes.func,
         onExpand: PropTypes.func,
@@ -667,6 +669,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         | 'notifySearch'
         | 'cacheFlattenNodes'
         | 'notifyLoad'
+        | 'notifyClear'
         > = {
             updateState: states => {
                 this.setState({ ...states } as TreeSelectState);
@@ -683,6 +686,9 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
             notifyLoad: (newLoadedKeys, data) => {
                 const { onLoad } = this.props;
                 isFunction(onLoad) && onLoad(newLoadedKeys, data);
+            },
+            notifyClear: (e: React.MouseEvent | React.KeyboardEvent<HTMLDivElement>) => {
+                this.props.onClear && this.props.onClear(e);
             }
         };
         return {

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

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

+ 190 - 190
sitemap.xml

@@ -2,22 +2,22 @@
 <urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
     <url>
         <loc>https://juejin.cn/post/7267418854124699702</loc>
-        <lastmod>2024-01-09T12:22:37.080Z</lastmod>
+        <lastmod>2024-01-19T10:05:20.882Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://medium.com/front-end-weekly/how-we-test-semi-design-component-libraries-64b854f63b65</loc>
-        <lastmod>2024-01-09T12:22:35.825Z</lastmod>
+        <lastmod>2024-01-19T10:05:20.636Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://mp.weixin.qq.com/s/noHoWRuA25PgqFNcurhIUA</loc>
-        <lastmod>2024-01-09T12:22:38.740Z</lastmod>
+        <lastmod>2024-01-19T10:05:23.681Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://mp.weixin.qq.com/s/O3js-SZDNPEOjGxh-aAkbw</loc>
-        <lastmod>2024-01-09T12:22:38.756Z</lastmod>
+        <lastmod>2024-01-19T10:05:23.508Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
@@ -670,852 +670,852 @@
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/introduction/case</loc>
-        <lastmod>2024-01-02T07:34:04.048Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.319Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/introduction/changelog</loc>
-        <lastmod>2024-01-02T07:34:04.326Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.045Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/introduction/faq</loc>
-        <lastmod>2024-01-02T07:34:04.206Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.543Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/introduction/roadmap</loc>
-        <lastmod>2024-01-02T07:34:05.031Z</lastmod>
+        <lastmod>2024-01-18T06:20:36.067Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/introduction/start</loc>
-        <lastmod>2024-01-02T07:34:04.441Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.326Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/plugin/changeThemeModel</loc>
-        <lastmod>2024-01-02T07:34:04.552Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.351Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/plugin/genDocs</loc>
-        <lastmod>2024-01-02T07:34:04.632Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.488Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/plugin/intallPlugin</loc>
-        <lastmod>2024-01-02T07:34:04.776Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.491Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/plugin/syncInFigma</loc>
-        <lastmod>2024-01-02T07:34:04.847Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.667Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/plugin/writeTheme</loc>
-        <lastmod>2024-01-02T07:34:04.810Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.649Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/themeStore/fork</loc>
-        <lastmod>2024-01-02T07:34:04.802Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.799Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/themeStore/preview</loc>
-        <lastmod>2024-01-02T07:34:04.845Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.813Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/themeStore/publish</loc>
-        <lastmod>2024-01-02T07:34:04.784Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.948Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/web/addPartner</loc>
-        <lastmod>2024-01-02T07:34:05.121Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.802Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/web/componentToken</loc>
-        <lastmod>2024-01-02T07:34:05.091Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.970Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/web/globalToken</loc>
-        <lastmod>2024-01-02T07:34:05.264Z</lastmod>
+        <lastmod>2024-01-18T06:20:35.990Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/web/use</loc>
-        <lastmod>2024-01-02T07:34:05.487Z</lastmod>
+        <lastmod>2024-01-18T06:20:36.121Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/web/web_publish</loc>
-        <lastmod>2024-01-02T07:34:05.227Z</lastmod>
+        <lastmod>2024-01-18T06:20:36.105Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm_manual/zh-CN/web/web_start</loc>
-        <lastmod>2024-01-02T07:34:05.238Z</lastmod>
+        <lastmod>2024-01-18T06:20:36.435Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.70</scm>
+        <scm>1.0.0.72</scm>
     </url>
     <url>
         <loc>https://semi.design/dsm/landing</loc>
-        <lastmod>2024-01-09T12:22:38.369Z</lastmod>
+        <lastmod>2024-01-19T10:05:31.040Z</lastmod>
         <changefreq>weekly</changefreq>
-        <scm>1.0.0.159</scm>
+        <scm>1.0.0.165</scm>
     </url>
     <url>
         <loc>https://semi.design/en-US/basic/divider</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/basic/grid</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/basic/icon</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/basic/layout</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/basic/space</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/basic/tokens</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/basic/typography</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/feedback/banner</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/feedback/notification</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/feedback/popconfirm</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/feedback/progress</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/feedback/skeleton</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/feedback/spin</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/feedback/toast</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/autocomplete</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/button</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/cascader</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/checkbox</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/datepicker</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/form</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/input</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/inputnumber</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/radio</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/rating</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/select</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/slider</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/switch</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/taginput</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/timepicker</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/transfer</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/treeselect</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/input/upload</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/navigation/anchor</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/navigation/backtop</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/navigation/breadcrumb</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/navigation/navigation</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/navigation/pagination</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/navigation/steps</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/navigation/tabs</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/navigation/tree</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/other/configprovider</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/other/locale</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/avatar</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/badge</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/calendar</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/card</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/carousel</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/collapse</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/collapsible</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/descriptions</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/dropdown</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/empty</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/highlight</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/image</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/list</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/modal</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/overflowlist</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/popover</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/scrolllist</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/sidesheet</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/table</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/tag</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/timeline</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/show/tooltip</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/start/accessibility</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/start/changelog</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/start/customize-theme</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/start/dark-mode</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/start/faq</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/start/getting-started</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/start/introduction</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/start/overview</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/en-US/start/update-to-v2</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/basic/divider</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/basic/grid</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/basic/icon</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/basic/layout</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/basic/space</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/basic/tokens</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/basic/typography</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/feedback/banner</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/feedback/notification</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/feedback/popconfirm</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/feedback/progress</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/feedback/skeleton</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/feedback/spin</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/feedback/toast</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/autocomplete</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/button</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/cascader</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/checkbox</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/datepicker</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/form</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/input</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/inputnumber</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/radio</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/rating</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/select</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/slider</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/switch</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/taginput</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/timepicker</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/transfer</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/treeselect</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/input/upload</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/navigation/anchor</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/navigation/backtop</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/navigation/breadcrumb</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/navigation/navigation</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/navigation/pagination</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/navigation/steps</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/navigation/tabs</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/navigation/tree</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/other/configprovider</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/other/locale</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/avatar</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/badge</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/calendar</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/card</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/carousel</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/collapse</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/collapsible</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/descriptions</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/dropdown</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/empty</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/highlight</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/image</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/list</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/modal</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/overflowlist</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/popover</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/scrolllist</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/sidesheet</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/table</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/tag</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/timeline</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/show/tooltip</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/start/accessibility</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/start/changelog</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/start/customize-theme</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/start/dark-mode</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/start/faq</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/start/getting-started</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/start/introduction</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/start/overview</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
     <url>
         <loc>https://semi.design/zh-CN/start/update-to-v2</loc>
-        <lastmod>2024-01-09T12:18:46.000Z</lastmod>
+        <lastmod>2024-01-19T09:54:35.000Z</lastmod>
         <changefreq>weekly</changefreq>
     </url>
 </urlset>

+ 1 - 1
src/sitePages/newHome/components/comments/comments.jsx

@@ -40,7 +40,7 @@ function numberAnimation(number, s, dom) {
     requestAnimationFrame(fn);
 }
 
-const realNumber = [7100, 560, 5000000, 100];
+const realNumber = [7600, 560, 5000000, 100];
 
 function Comments(props) {
     useEffect(()=> {

+ 1 - 1
src/sitePages/newHome/components/operateButton/operateButton.jsx

@@ -32,7 +32,7 @@ function OperateButton() {
         >
             <span style={{ display: 'flex' }}>
                 GitHub
-                <span className={styles.badge}>7.3k</span>
+                <span className={styles.badge}>7.6k</span>
             </span>
         </Button>
     </div>);

+ 86 - 0
yarn.lock

@@ -1519,11 +1519,25 @@
     "@douyinfe/semi-animation-styled" "2.23.2"
     classnames "^2.2.6"
 
+"@douyinfe/[email protected]":
+  version "2.49.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.49.2.tgz#3f69c3faee29666d51775529abac7c8a541cbcc8"
+  integrity sha512-aACyOnW2raTpeeXfDMohHDf0r0k4LgXSpKNUIexKslR2xBoByt1H1yzSyyYBpVPMW3l4ywFsuzcUX47W4BKHlA==
+  dependencies:
+    "@douyinfe/semi-animation" "2.49.2"
+    "@douyinfe/semi-animation-styled" "2.49.2"
+    classnames "^2.2.6"
+
 "@douyinfe/[email protected]":
   version "2.23.2"
   resolved "https://registry.npmjs.org/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.23.2.tgz#f18bc074515441c297cc636ed98521e249d093c9"
   integrity sha512-cKaA1yGHPF76Rx7EZDZicj+1oX1su2wnqb/UGFOTquAwqWmkTfgQ+EKxCd/N704WH+RtmGf4xbrJKpBvvcEdSQ==
 
+"@douyinfe/[email protected]":
+  version "2.49.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.49.2.tgz#c7d4b7f5bb39ae5b2961ba91635c53bb8289dd93"
+  integrity sha512-+4YSNo2juDSkS5NBIl6Cy6oKzpCUCXg2Q4PYL/Azn3VYiGhJBtHuEyTnPj23w61yQddxkarfzEvULDh0QdZLTQ==
+
 "@douyinfe/[email protected]":
   version "2.12.0"
   resolved "https://registry.npmjs.org/@douyinfe/semi-animation/-/semi-animation-2.12.0.tgz#51fe52d3911c2591a80a6e9fe96e6809c1511f13"
@@ -1539,6 +1553,13 @@
   dependencies:
     bezier-easing "^2.1.0"
 
+"@douyinfe/[email protected]":
+  version "2.49.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.49.2.tgz#869f4f137c1f59027350a7718572d30bf300903c"
+  integrity sha512-XSqXhTbC+Wx9i2BL+3iDj6qGrW3gKSoN8AYLp67p+xl8rTHbj0zAWqGuvY4JyS4ME/8DgUoeg7EM5aBR5dRnOg==
+  dependencies:
+    bezier-easing "^2.1.0"
+
 "@douyinfe/[email protected]":
   version "2.33.1"
   resolved "https://registry.npmjs.org/@douyinfe/semi-foundation/-/semi-foundation-2.33.1.tgz#1dfe6233e35a4ed768cb580b0c9a677d1c34ffba"
@@ -1553,6 +1574,20 @@
     memoize-one "^5.2.1"
     scroll-into-view-if-needed "^2.2.24"
 
+"@douyinfe/[email protected]":
+  version "2.49.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.49.2.tgz#37be245456aebb57d98c8dd0a0ce7cc123762840"
+  integrity sha512-YPPiL9MsF38KXiaWgGFBd5/c8HqDcrUejFIQaKfAXLPas2BYBJxNZ9NH/IDqJ4N65PaAJ960sap0EjkiW7s3Jw==
+  dependencies:
+    "@douyinfe/semi-animation" "2.49.2"
+    async-validator "^3.5.0"
+    classnames "^2.2.6"
+    date-fns "^2.29.3"
+    date-fns-tz "^1.3.8"
+    lodash "^4.17.21"
+    memoize-one "^5.2.1"
+    scroll-into-view-if-needed "^2.2.24"
+
 "@douyinfe/[email protected]", "@douyinfe/semi-icons@latest":
   version "2.33.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.33.1.tgz#8e2871d9bc0ab7e12df74e3c71802d53d69b7425"
@@ -1560,11 +1595,23 @@
   dependencies:
     classnames "^2.2.6"
 
+"@douyinfe/[email protected]", "@douyinfe/semi-icons@^2.0.0":
+  version "2.49.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.49.2.tgz#3067ee19f7e6a3438ab0ab3b1002239ddf7698b9"
+  integrity sha512-sV/yPfpjZekepalpXu3Dwiu8uwPblm7jTArqtAvv62nsMB21HP/+AQC6Kvk3+kAcNXEQs3YF0U1IcrlLJpov/g==
+  dependencies:
+    classnames "^2.2.6"
+
 "@douyinfe/[email protected]":
   version "2.33.1"
   resolved "https://registry.npmjs.org/@douyinfe/semi-illustrations/-/semi-illustrations-2.33.1.tgz#530ab851f4dc32a52221c4067c778c800b9b55d7"
   integrity sha512-tTTUN8QwnQiF++sk4VBNzfkG87aYZ4iUeqk2ys8/ymVUmCZQ7y46ys020GO1MfPHRR47OMFPI82FVcH1WQtE3g==
 
+"@douyinfe/[email protected]":
+  version "2.49.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.49.2.tgz#4c3a5e89053b252c9bb97f8c126b2699e42115f8"
+  integrity sha512-oBKTOY/KQkeYinIJZYdfW2B3T/cPhMw9d86ZsoMiMN2evo+4sSSMUXIDCl7EKsNrPZl3QDlCCP0QuVTNOIf1Gw==
+
 "@douyinfe/[email protected]":
   version "2.23.2"
   resolved "https://registry.npmjs.org/@douyinfe/semi-scss-compile/-/semi-scss-compile-2.23.2.tgz#30884bb194ee9ae1e81877985e5663c3297c1ced"
@@ -1638,6 +1685,40 @@
   dependencies:
     glob "^7.1.6"
 
+"@douyinfe/[email protected]":
+  version "2.49.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.49.2.tgz#6311aa411d7492b295bc39ee2154c24f2fd87727"
+  integrity sha512-c/YqtKsT0iqNFOg2BiF87Im0MXxVugU/qbe8eVZNfXILhjnbOUYKxKfVRs+px3NXx2rpu4BiRzjtX0EGAG4ERw==
+  dependencies:
+    glob "^7.1.6"
+
+"@douyinfe/semi-ui@^2.0.0":
+  version "2.49.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.49.2.tgz#c7aeea65a68cc7776d38aec43b7cb40b789445c1"
+  integrity sha512-yRB7QXVW/Ymyl5gIL3p5FwiVUeWzK+fVCwmI+8NTN0dfZZ39Ckc/2b+QeO5AZS6JlZ6RcrtJqQfclPP4UiEtCQ==
+  dependencies:
+    "@dnd-kit/core" "^6.0.8"
+    "@dnd-kit/sortable" "^7.0.2"
+    "@dnd-kit/utilities" "^3.2.1"
+    "@douyinfe/semi-animation" "2.49.2"
+    "@douyinfe/semi-animation-react" "2.49.2"
+    "@douyinfe/semi-foundation" "2.49.2"
+    "@douyinfe/semi-icons" "2.49.2"
+    "@douyinfe/semi-illustrations" "2.49.2"
+    "@douyinfe/semi-theme-default" "2.49.2"
+    async-validator "^3.5.0"
+    classnames "^2.2.6"
+    copy-text-to-clipboard "^2.1.1"
+    date-fns "^2.29.3"
+    date-fns-tz "^1.3.8"
+    lodash "^4.17.21"
+    prop-types "^15.7.2"
+    react-resizable "^3.0.5"
+    react-window "^1.8.2"
+    resize-observer-polyfill "^1.5.1"
+    scroll-into-view-if-needed "^2.2.24"
+    utility-types "^3.10.0"
+
 "@douyinfe/semi-ui@latest":
   version "2.33.1"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.33.1.tgz#3234ca96eb3560b8299bc9750fbe59446522d9bb"
@@ -11584,6 +11665,11 @@ eslint-plugin-react@^7.20.6, eslint-plugin-react@^7.24.0:
     semver "^6.3.0"
     string.prototype.matchall "^4.0.8"
 
+eslint-plugin-semi-design@^2.33.0:
+  version "2.49.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-semi-design/-/eslint-plugin-semi-design-2.49.2.tgz#1aad5231f012ccd28016dd45a426323deccc91ca"
+  integrity sha512-ogcoN5o2iJDpgb5WfIyOxBEzkRllBc43aSLZUK0POEGp9eSwDaAc7xKtvxQQ8SuvQgQmJJSIT++8zM/Q2WZ+ug==
+
 eslint-rule-composer@^0.3.0:
   version "0.3.0"
   resolved "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9"