Răsfoiți Sursa

feat: [Cascader] add virtualizeInSearch to virtualize search result panel (#1815)

* feat: [Cascader] add virtualization for search result panel

* feat: [Cascader] add virtualization for search result panel

* chore: upate yarn.lock file

* docs: change version in description
YyumeiZhang 2 ani în urmă
părinte
comite
e67a8a8108

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

@@ -474,6 +474,77 @@ import { Cascader, Typography, Checkbox } from '@douyinfe/semi-ui';
 
 ```
 
+If there are a lot of options in the search results, you can optimize performance by setting virtualizeInSearch to enable the virtualization of the search result panel. virtualizeInSearch has been available since v2.44.0. virtualizeInSearch is an object containing the following values:
+
+- height: Option list height value
+- width: Option list width value
+- itemSize: The height of each row of Option
+
+```jsx live=true
+import React from 'react';
+import { Cascader, Checkbox, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const treeData = useMemo(() => (
+        ['Generic', 'Scene'].map((label, m) => ({
+            label: label,
+            value: m,
+            children: new Array(100).fill(0).map((item, n)=> ({
+                value: `${m}-${n}`,
+                label: `${m}-${n} level one`,
+                children: new Array(20).fill(0).map((item, o)=> ({
+                    value: `${m}-${n}-${o}`,
+                    label: `${m}-${n}-${o} level two detail info`,
+                })),
+            }))
+        }))
+    ), []);
+    
+    let virtualize = {
+        // The height is the panel's default height of 180px minus the upper and lower padding 2 * 8px
+        height: 172,
+        width: 320,
+        itemSize: 36, 
+    };
+
+    const filterRender = useCallback((props) => {
+        const { data, onCheck, checkStatus, className } = props;
+        return (
+            <div 
+                key={data.value}
+                className={className}
+                style={{ justifyContent: 'start', padding: '8px 16px 8px 12px', boxSizing: 'border-box' }}
+            >
+                <Checkbox
+                    onChange={onCheck}
+                    indeterminate={checkStatus.halfChecked}
+                    checked={checkStatus.checked}
+                    style={{ marginRight: 8 }}
+                />
+                <Typography.Text
+                    ellipsis={{ showTooltip: { opts: { style: { wordBreak: 'break-all' } } } }}
+                    style={{ maxWidth: 260 }}
+                >
+                    {data.map(item => item.label).join(' | ')}
+                </Typography.Text>
+            </div>
+        );
+    }, []);
+     
+    return (
+        <Cascader
+            multiple
+            filterTreeNode
+            style={{ width: 320 }}
+            treeData={treeData}
+            placeholder="Enter generic or scene to search"
+            virtualizeInSearch={virtualize}
+            filterRender={filterRender}
+        />
+    );
+};
+```
+
 ### Limit Tags Displayed
 
 version: >= 1.28.0
@@ -1799,6 +1870,7 @@ function Demo() {
 | triggerRender | Method to create a custom trigger                                                                                                                                                                                                             | (props: TriggerRenderProps) => ReactNode | - | 0.34.0 |
 | value | Selected value (controlled mode)                                                                                                                                                                                                              | string\|number\|CascaderData\|(string\|number\|CascaderData)[][]  | - | -  |
 | validateStatus | The validation status of the trigger only affects the display style. Optional: default、error、warning                                                                                                                                          | string | `default` | - |
+| virtualizeInSearch  | Search result list virtualization, used when there are a large number of tree nodes, composed of height, width,itemSize  | Object | -  | -  | - |
 | zIndex | zIndex for dropdown menu                                                                                                                                                                                                                      | number | 1030 | - |
 | enableLeafClick | Multiple mode, click the leaf option enable trigger check                                                                                                                                                                                     | boolean | false | 2.2.0 |
 | onBlur | Out of focus Cascader's callback                                                                                                                                                                                                              | (e: MouseEvent) => void | - | - |

+ 74 - 2
content/input/cascader/index.md

@@ -458,7 +458,7 @@ import { Cascader, Typography, Checkbox } from '@douyinfe/semi-ui';
             <p>鼠标 hover 到选项可查看被省略文本完整内容</p>
             <br />
             <Cascader
-                style={{ width: 300 }}
+                style={{ width: 320 }}
                 treeData={treeData}
                 placeholder="单选,输入 s 自定义搜索选项渲染结果"
                 filterTreeNode
@@ -467,7 +467,7 @@ import { Cascader, Typography, Checkbox } from '@douyinfe/semi-ui';
             <br />
             <Cascader
                 multiple
-                style={{ width: 300, marginTop: 20 }}
+                style={{ width: 320, marginTop: 20 }}
                 treeData={treeData}
                 placeholder="多选,输入 s 自定义搜索选项渲染结果"
                 filterTreeNode
@@ -479,6 +479,77 @@ import { Cascader, Typography, Checkbox } from '@douyinfe/semi-ui';
 
 ```
 
+如果搜索结果中存在大量 Option,可以通过设置 virtualizeInSearch 开启搜索结果面板的虚拟化来优化性能,virtualizeInSearch 自 v2.44.0 提供。virtualizeInSearch 是一个包含下列值的对象:
+
+- height: Option 列表高度值
+- width: Option 列表宽度值
+- itemSize: 每行 Option 的高度
+
+```jsx live=true
+import React from 'react';
+import { Cascader, Checkbox, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const treeData = useMemo(() => (
+        ['通用', '场景'].map((label, m) => ({
+            label: label,
+            value: m,
+            children: new Array(100).fill(0).map((item, n)=> ({
+                value: `${m}-${n}`,
+                label: `${m}-${n} 第二级`,
+                children: new Array(20).fill(0).map((item, o)=> ({
+                    value: `${m}-${n}-${o}`,
+                    label: `${m}-${n}-${o} 第三级详细内容`,
+                })),
+            }))
+        }))
+    ), []);
+    
+    let virtualize = {
+        // 高度为面板默认高度为 180px 减去上下padding 2 * 8px
+        height: 172,
+        width: 320,
+        itemSize: 36, 
+    };
+
+    const filterRender = useCallback((props) => {
+        const { data, onCheck, checkStatus, className } = props;
+        return (
+            <div 
+                key={data.value}
+                className={className}
+                style={{ justifyContent: 'start', padding: '8px 16px 8px 12px', boxSizing: 'border-box' }}
+            >
+                <Checkbox
+                    onChange={onCheck}
+                    indeterminate={checkStatus.halfChecked}
+                    checked={checkStatus.checked}
+                    style={{ marginRight: 8 }}
+                />
+                <Typography.Text
+                    ellipsis={{ showTooltip: { opts: { style: { wordBreak: 'break-all' } } } }}
+                    style={{ maxWidth: 260 }}
+                >
+                    {data.map(item => item.label).join(' | ')}
+                </Typography.Text>
+            </div>
+        );
+    }, []);
+     
+    return (
+        <Cascader
+            multiple
+            filterTreeNode
+            style={{ width: 320 }}
+            treeData={treeData}
+            placeholder="输入 通用 or 场景 进行搜索"
+            virtualizeInSearch={virtualize}
+            filterRender={filterRender}
+        />
+    );
+};
+```
+
 ### 限制标签展示数量
 
 version: >= 1.28.0
@@ -1782,6 +1853,7 @@ function Demo() {
 | triggerRender        | 自定义触发器渲染方法                                                                                                                                          | (props: TriggerRenderProps) => ReactNode                                                  | -                              | 0.34.0 |
 | validateStatus       | trigger 的校验状态,仅影响展示样式。可选: default、error、warning                                                                                                     | string                                                                                    | `default`                      | -      |
 | value                | (受控)选中的条目                                                                                                                                           | string\|number\|CascaderData\|(string\|number\|CascaderData)[]                            | -                              | -      |
+| virtualizeInSearch   | 搜索列表虚拟化,用于大量树节点的情况,由 height, width, itemSize 组成 | Object | - | - | - |
 | zIndex               | 下拉菜单的 zIndex                                                                                                                                        | number                                                                                    | 1030                           | -      |
 | enableLeafClick      | 多选时,是否启动点击叶子节点选项触发勾选                                                                                                                                | boolean                                                                                   | false                          | 2.2.0  |
 | onBlur               | 失焦 Cascader 的回调                                                                                                                                     | (e: MouseEvent) => void                                                                   | -                              | -      |

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

@@ -395,6 +395,7 @@ $module: #{$prefix}-cascader;
         padding-right: $spacing-cascader_option-paddingRight;
     }
 
+
     .#{$module}-option-list {
         box-sizing: border-box;
         display: inline-block;

+ 7 - 0
packages/semi-foundation/cascader/foundation.ts

@@ -28,6 +28,12 @@ export interface BasicData {
     pathData?: BasicCascaderData[]
 }
 
+export interface Virtualize {
+    itemSize: number;
+    height?: number | string;
+    width?: number | string
+}
+
 export interface BasicEntities {
     [idx: string]: BasicEntity
 }
@@ -157,6 +163,7 @@ export interface BasicCascaderProps {
     leafOnly?: boolean;
     enableLeafClick?: boolean;
     preventScroll?: boolean;
+    virtualizeInSearch?: Virtualize;
     onClear?: () => void;
     triggerRender?: (props: BasicTriggerRenderProps) => any;
     onListScroll?: (e: any, panel: BasicScrollPanelProps) => void;

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

@@ -2124,3 +2124,63 @@ export const DisabledAndPlusN = () => {
     </> 
   )
 }
+
+export const VirtualizeInSearch = () => {
+  const treeData = useMemo(() => (
+      ['通用', '场景'].map((label, m) => ({
+          label: label,
+          value: m,
+          children: new Array(100).fill(0).map((item, n)=> ({
+              value: `${m}-${n}`,
+              label: `${m}-${n} 第二级`,
+              children: new Array(20).fill(0).map((item, o)=> ({
+                  value: `${m}-${n}-${o}`,
+                  label: `${m}-${n}-${o} 第三级详细内容`,
+              })),
+          }))
+      }))
+  ), []);
+  
+  let virtualize = {
+      // 高度为面板默认高度为 180px 减去上下padding 2 * 8px
+      height: 172,
+      width: 320,
+      itemSize: 36, 
+  };
+
+  const filterRender = useCallback((props) => {
+      const { data, onCheck, checkStatus, className } = props;
+      return (
+          <div 
+              key={data.value}
+              className={className}
+              style={{ justifyContent: 'start', padding: '8px 16px 8px 12px', boxSizing: 'border-box' }}
+          >
+              <Checkbox
+                  onChange={onCheck}
+                  indeterminate={checkStatus.halfChecked}
+                  checked={checkStatus.checked}
+                  style={{ marginRight: 8 }}
+              />
+              <Typography.Text
+                  ellipsis={{ showTooltip: { opts: { style: { wordBreak: 'break-all' } } } }}
+                  style={{ maxWidth: 260 }}
+              >
+                  {data.map(item => item.label).join(' | ')}
+              </Typography.Text>
+          </div>
+      );
+  }, []);
+   
+  return (
+      <Cascader
+          multiple
+          filterTreeNode
+          style={{ width: 320 }}
+          treeData={treeData}
+          placeholder="输入 通用 or 场景 进行搜索"
+          virtualizeInSearch={virtualize}
+          filterRender={filterRender}
+      />
+  );
+};

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

@@ -664,7 +664,8 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             bottomSlot,
             showNext,
             multiple,
-            filterRender
+            filterRender,
+            virtualizeInSearch
         } = this.props;
         const searchable = Boolean(filterTreeNode) && isSearching;
         const popoverCls = cls(dropdownClassName, `${prefixcls}-popover`);
@@ -692,6 +693,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                     checkedKeys={checkedKeys}
                     halfCheckedKeys={halfCheckedKeys}
                     filterRender={filterRender}
+                    virtualize={virtualizeInSearch}
                 />
                 {bottomSlot}
             </div>

+ 91 - 57
packages/semi-ui/cascader/item.tsx

@@ -14,8 +14,11 @@ import {
     BasicCascaderData,
     BasicEntity,
     ShowNextType,
-    BasicData
+    BasicData,
+    Virtualize
 } from '@douyinfe/semi-foundation/cascader/foundation';
+import { FixedSizeList as List } from 'react-window';
+import VirtualRow from './virtualRow';
 
 export interface CascaderData extends BasicCascaderData {
     label: React.ReactNode
@@ -73,7 +76,8 @@ export interface CascaderItemProps {
     multiple: boolean;
     checkedKeys: Set<string>;
     halfCheckedKeys: Set<string>;
-    filterRender?: (props: FilterRenderProps) => ReactNode
+    filterRender?: (props: FilterRenderProps) => ReactNode;
+    virtualize?: Virtualize
 }
 
 const prefixcls = cssClasses.PREFIX_OPTION;
@@ -93,7 +97,8 @@ export default class Item extends PureComponent<CascaderItemProps> {
         halfCheckedKeys: PropTypes.object,
         onItemCheckboxClick: PropTypes.func,
         separator: PropTypes.string,
-        keyword: PropTypes.string
+        keyword: PropTypes.string,
+        virtualize: PropTypes.object
     };
 
     static defaultProps = {
@@ -197,68 +202,97 @@ export default class Item extends PureComponent<CascaderItemProps> {
         return content;
     };
 
+    renderFlattenOptionItem = (data: Data, index?: number, style?: any) => {
+        const { multiple, selectedKeys, checkedKeys, halfCheckedKeys, keyword, filterRender, virtualize } = this.props;
+        const { searchText, key, disabled, pathData } = data;
+        const selected = selectedKeys.has(key);
+        const className = cls(prefixcls, {
+            [`${prefixcls}-flatten`]: true && !filterRender,
+            [`${prefixcls}-disabled`]: disabled,
+            [`${prefixcls}-select`]: selected && !multiple,
+        });
+        const onClick = e => {
+            this.onClick(e, data);
+        };
+        const onKeyPress = e => this.handleItemEnterPress(e, data);
+        const onCheck = (e: CheckboxEvent) => this.onCheckboxChange(e, data);
+        if (filterRender) {
+            const props = {
+                className,
+                inputValue: keyword,
+                disabled,
+                data: pathData,
+                checkStatus: {
+                    checked: checkedKeys.has(data.key),
+                    halfChecked: halfCheckedKeys.has(data.key),
+                },
+                selected,
+                onClick,
+                onCheck
+            };
+            const item = filterRender(props) as any;
+            const otherProps = virtualize ? { 
+                key, 
+                style: {
+                    ...(item.props.style ?? {}),
+                    ...style
+                },
+            } : { key };
+            return React.cloneElement(item, otherProps );
+        }
+        return (
+            <li
+                role='menuitem'
+                className={className}
+                style={style}
+                key={key}
+                onClick={onClick}
+                onKeyPress={onKeyPress}
+            >
+                <span className={`${prefixcls}-label`}>
+                    {!multiple && this.renderIcon('empty')}
+                    {multiple && (
+                        <Checkbox
+                            onChange={onCheck}
+                            disabled={disabled}
+                            indeterminate={halfCheckedKeys.has(data.key)}
+                            checked={checkedKeys.has(data.key)}
+                            className={`${prefixcls}-label-checkbox`}
+                        />
+                    )}
+                    {this.highlight(searchText)}
+                </span>
+            </li>
+        );
+    }
+
     renderFlattenOption = (data: Data[]) => {
-        const { multiple, selectedKeys, checkedKeys, halfCheckedKeys, keyword, filterRender } = this.props;
+        const { virtualize } = this.props;
         const content = (
             <ul className={`${prefixcls}-list`} key={'flatten-list'}>
-                {data.map(item => {
-                    const { searchText, key, disabled, pathData } = item;
-                    const selected = selectedKeys.has(key);
-                    const className = cls(prefixcls, {
-                        [`${prefixcls}-flatten`]: true && !filterRender,
-                        [`${prefixcls}-disabled`]: disabled,
-                        [`${prefixcls}-select`]: selected && !multiple,
-                    });
-                    const onClick = e => {
-                        this.onClick(e, item);
-                    };
-                    const onKeyPress = e => this.handleItemEnterPress(e, item);
-                    const onCheck = (e: CheckboxEvent) => this.onCheckboxChange(e, item);
-                    if (filterRender) {
-                        const props = {
-                            className,
-                            inputValue: keyword,
-                            disabled,
-                            data: pathData,
-                            checkStatus: {
-                                checked: checkedKeys.has(item.key),
-                                halfChecked: halfCheckedKeys.has(item.key),
-                            },
-                            selected,
-                            onClick,
-                            onCheck
-                        };
-                        return React.cloneElement(filterRender(props) as any, { key });
-                    }
-                    return (
-                        <li
-                            role='menuitem'
-                            className={className}
-                            key={key}
-                            onClick={onClick}
-                            onKeyPress={onKeyPress}
-                        >
-                            <span className={`${prefixcls}-label`}>
-                                {!multiple && this.renderIcon('empty')}
-                                {multiple && (
-                                    <Checkbox
-                                        onChange={onCheck}
-                                        disabled={disabled}
-                                        indeterminate={halfCheckedKeys.has(item.key)}
-                                        checked={checkedKeys.has(item.key)}
-                                        className={`${prefixcls}-label-checkbox`}
-                                    />
-                                )}
-                                {this.highlight(searchText)}
-                            </span>
-                        </li>
-                    );
-                })}
+                {virtualize ? this.renderVirtualizeList(data) : data.map(item => this.renderFlattenOptionItem(item))}
             </ul>
         );
         return content;
     };
 
+    renderVirtualizeList = (visibleOptions: any) => {
+        const { direction } = this.context;
+        const { virtualize } = this.props;
+        return (
+            <List
+                height={virtualize.height}
+                itemCount={visibleOptions.length}
+                itemSize={virtualize.itemSize}
+                itemData={{ visibleOptions, renderOption: this.renderFlattenOptionItem }}
+                width={virtualize.width ?? '100%'}
+                style={{ direction }}
+            >
+                {VirtualRow}
+            </List>
+        );
+    }
+
     renderItem(renderData: Array<Entity>, content: Array<React.ReactNode> = []) {
         const { multiple, checkedKeys, halfCheckedKeys } = this.props;
         let showChildItem: Entity;

+ 15 - 0
packages/semi-ui/cascader/virtualRow.tsx

@@ -0,0 +1,15 @@
+import React from 'react';
+
+export interface VirtualRowProps{
+    index: number;
+    data: Record<string, any>;
+    style?: React.CSSProperties
+}
+
+const VirtualRow = ({ index, data, style }: VirtualRowProps) => {
+    const { visibleOptions, renderOption } = data;
+    const option = visibleOptions[index];
+    return renderOption(option, index, style);
+};
+
+export default VirtualRow;

+ 86 - 0
yarn.lock

@@ -1519,11 +1519,25 @@
     "@douyinfe/semi-animation-styled" "2.23.2"
     classnames "^2.2.6"
 
+"@douyinfe/[email protected]":
+  version "2.42.4"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.42.4.tgz#6a1219c473af5c96e77561525afe59b739d56976"
+  integrity sha512-oODJNx2x8ZGIT8al+yfPu+ZCLiz2C3m3fB9VQRDtm749fPBbKh6URoO3qZQaiyLGLL7HTV98v0KVnrF4uwX2sg==
+  dependencies:
+    "@douyinfe/semi-animation" "2.42.4"
+    "@douyinfe/semi-animation-styled" "2.42.4"
+    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.42.4"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.42.4.tgz#0be0e69ef607a58f1ea47c96027ef544144a0f94"
+  integrity sha512-XNfTVh1nmAN/LW/jK3FaMGFijuPBX3fynoHV19VWl/qX22VZcrBQgqdXud8sDWiMcUjpHL7sgqdj+sOu1v91TA==
+
 "@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.42.4"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.42.4.tgz#91f33bd9a85b8ff1a4ffe48fc144b967707a2803"
+  integrity sha512-TRtrZqQydslIsjSR7t3GgwXsKZHznoeNSo82/nbV9CasWtuS0EkxioVIcHXo6urBOaO0SCUwZ6MFxw8Yw2DlPA==
+  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.42.4"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.42.4.tgz#ad4678beeea8ad36868a9f17855d8c9098a83623"
+  integrity sha512-egEFfewnJhnLFjwpW0FVjUx0VM08jkPKPVq3K2SaVMrH559Od0M3ik3vFi2LC3zFQfafc3mWwbRf3fPHyEQGKQ==
+  dependencies:
+    "@douyinfe/semi-animation" "2.42.4"
+    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.42.4"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.42.4.tgz#76bf40b2dc3c493a295cbc5b682d33f2f56058d6"
+  integrity sha512-8Z9Hsg+CJlHUk7WHr+FFcZ6jEc5dI7NOWhfz6evu/n7FrLKETmA3JtLjZFP46bh8RB9It7mjGuk27ZwQ9NP2jQ==
+  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.42.4"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.42.4.tgz#70a5fadb735aec39b367511a2688a5c7cb79eb76"
+  integrity sha512-cIj2wTxIX+mjvWhFpAHSbI68HG5o0vKnubJCsLPvmGMg/3W+SjoxYYQMoGEPvN9iqQqdA42TBeNoHs/AbNWYHw==
+
 "@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.42.4"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.42.4.tgz#6547f30eff6ad3180e4840f34ccf8e8afaecb887"
+  integrity sha512-AAR+gGTpFQhuTKJmgv60pHUplxrIqEh8Z4fCyleWYrk7rKDI5Bg0pBMM5RdagutOzkjJby3UY8RlXO6/b60SwA==
+  dependencies:
+    glob "^7.1.6"
+
+"@douyinfe/semi-ui@^2.0.0":
+  version "2.42.4"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.42.4.tgz#60a378a7d08fcf7d897797e8dc9a169d19e47ab4"
+  integrity sha512-fcVzGq0k9oKWHtCc/MdqWONEOXAm27KCB77CEownFjJRlIk5Cc32oQ9sutxDtOeVs2gthLnacnJ5rBW3XTWM2w==
+  dependencies:
+    "@dnd-kit/core" "^6.0.8"
+    "@dnd-kit/sortable" "^7.0.2"
+    "@dnd-kit/utilities" "^3.2.1"
+    "@douyinfe/semi-animation" "2.42.4"
+    "@douyinfe/semi-animation-react" "2.42.4"
+    "@douyinfe/semi-foundation" "2.42.4"
+    "@douyinfe/semi-icons" "2.42.4"
+    "@douyinfe/semi-illustrations" "2.42.4"
+    "@douyinfe/semi-theme-default" "2.42.4"
+    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"
@@ -11205,6 +11286,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.42.4"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-semi-design/-/eslint-plugin-semi-design-2.42.4.tgz#bbf9007dc02b2a45d162ec00593c8ff0b45a2975"
+  integrity sha512-0aaOGn4I9XtqWEsuTjTky+InSzZ5LVdeYCtz8io5Jj2Naeo2gcrAuTMa7PpJEwmDCblSzg19dNZZ1bQOySRL+g==
+
 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"