1
0
Эх сурвалжийг харах

Feat/semi UI 19 (#2996)

* feat: add condiction compile code, compact with react 19 (80%), #2743

* fix: 支持semi-ui支持react19, 并验证兼容性

Change-Id: I05940820f391fe0a6ddc2fc09003ec66ad398e20

* fix: 检查&修复ReactDOM.findDOMNode编译标记

* feat: add yarn lock

Change-Id: Ief96bc72ea2dcfa88a82c04bec19de048a3f2f26

* fix: remove input unless

Change-Id: If466319952aec516400931e958fbe9d802558ca3

* fix: 解决版本升级导致的依赖问题 & semi-ui-19包添加必须依赖

Change-Id: I6b684a9f6822325e8fb9145b2f55328957250229

* feat: 删除弃用findDomNode用法 && 兼容tooltip组件

* fix: 调整github workflow为主动触发

Change-Id: I4a80e30a505fb147befa0deeac6e244901af5384

* fix: 删除build-and-publish触发条件,由手动控制触发

Change-Id: Ib35e6ee04c884b83941073c5681d2b7b87be9b5b

* fix: 补充resizeObserver组件,react19 children.ref解构写法兼容

Change-Id: I7d78a78dfe47994cbc97cea6b5512bc6880e8506

* chore: react-version worklow change

* chore: react-version worklow change

* chore: react-version worklow change

* chore: react-version worklow change

* chore: react-version worklow change

* chore: react-version worklow change

* chore: publish 2.87.0-alpha.4

* chore: react-version worklow change

* chore: publish 2.87.0-alpha.5

* v2.87.0-alpha.5

* fix: ensure that the script output path is correct

* v2.87.0-alpha.6

* fix: 修复React.createElement运行时机问题,确保ref赋值之后再调用方法

* v2.87.0-alpha.7

* fix: add default props defined to function component

* v2.87.0-alpha.8

* chore: udpate react-version yml before merge to main

* chore: fix rebase error in package.json

* chore: update yarn.lock

---------

Co-authored-by: point.halo <[email protected]>
Co-authored-by: zhaoanwei <[email protected]>
Co-authored-by: semi-team <[email protected]>
Co-authored-by: 林艳 <[email protected]>
YyumeiZhang 3 өдөр өмнө
parent
commit
3bfbf4f8bb
46 өөрчлөгдсөн 649 нэмэгдсэн , 358 устгасан
  1. 153 0
      .github/workflows/react-versions.yml
  2. 2 1
      package.json
  3. 4 2
      packages/semi-ui/autoComplete/index.tsx
  4. 1 1
      packages/semi-ui/avatar/avatarGroup.tsx
  5. 6 3
      packages/semi-ui/calendar/monthCalendar.tsx
  6. 4 3
      packages/semi-ui/cascader/index.tsx
  7. 1 1
      packages/semi-ui/checkbox/checkboxGroup.tsx
  8. 0 14
      packages/semi-ui/collapsible/index.tsx
  9. 10 0
      packages/semi-ui/dragMove/index.ts
  10. 0 4
      packages/semi-ui/dropdown/dropdownDivider.tsx
  11. 1 1
      packages/semi-ui/form/hooks/useStateWithGetter.ts
  12. 2 4
      packages/semi-ui/list/_story/list.stories.jsx
  13. 34 0
      packages/semi-ui/modal/confirm.tsx
  14. 1 1
      packages/semi-ui/navigation/Item.tsx
  15. 1 1
      packages/semi-ui/navigation/index.tsx
  16. 39 0
      packages/semi-ui/notification/index.tsx
  17. 1 1
      packages/semi-ui/pincode/index.tsx
  18. 0 6
      packages/semi-ui/popover/Arrow.tsx
  19. 1 1
      packages/semi-ui/rating/index.tsx
  20. 8 1
      packages/semi-ui/rating/item.tsx
  21. 16 5
      packages/semi-ui/resizeObserver/index.tsx
  22. 10 1
      packages/semi-ui/scripts/compileScss.js
  23. 5 5
      packages/semi-ui/select/index.tsx
  24. 3 3
      packages/semi-ui/select/utils.tsx
  25. 1 1
      packages/semi-ui/skeleton/item.tsx
  26. 1 2
      packages/semi-ui/slider/index.tsx
  27. 5 24
      packages/semi-ui/steps/basicStep.tsx
  28. 9 29
      packages/semi-ui/steps/basicSteps.tsx
  29. 1 16
      packages/semi-ui/steps/fillStep.tsx
  30. 3 20
      packages/semi-ui/steps/fillSteps.tsx
  31. 1 14
      packages/semi-ui/steps/navStep.tsx
  32. 3 20
      packages/semi-ui/steps/navSteps.tsx
  33. 4 22
      packages/semi-ui/table/CustomExpandIcon.tsx
  34. 3 3
      packages/semi-ui/table/_story/LinkedScroll/index.jsx
  35. 1 1
      packages/semi-ui/tabs/TabBar.tsx
  36. 3 3
      packages/semi-ui/tabs/index.tsx
  37. 44 0
      packages/semi-ui/toast/index.tsx
  38. 2 2
      packages/semi-ui/toast/toast.tsx
  39. 35 8
      packages/semi-ui/tooltip/index.tsx
  40. 7 8
      packages/semi-ui/tree/__test__/autosizer.test.js
  41. 1 1
      packages/semi-ui/tree/indent.tsx
  42. 4 3
      packages/semi-ui/treeSelect/index.tsx
  43. 8 8
      packages/semi-ui/typography/base.tsx
  44. 196 0
      scripts/react19-build.js
  45. 13 10
      scripts/version.js
  46. 1 104
      yarn.lock

+ 153 - 0
.github/workflows/react-versions.yml

@@ -0,0 +1,153 @@
+name: React Multi-Version Build and Publish
+
+on:
+  workflow_dispatch:
+    inputs:
+      release_type:
+        description: 'release type: minor | patch | beta'
+        required: true
+        default: 'patch'
+      publish_react19:
+        description: 'publish React 19 version'
+        type: boolean
+        required: false
+        default: true
+
+jobs:
+  # 暂时不加测试
+  # test-react-18:
+  #   name: Test with React 18
+  #   runs-on: ubuntu-latest
+  #   steps:
+  #     - uses: actions/checkout@v4
+      
+  #     - name: Setup Node.js
+  #       uses: actions/setup-node@v4
+  #       with:
+  #         node-version: '20'
+  #         cache: 'yarn'
+          
+  #     - name: Install dependencies
+  #       run: yarn install --frozen-lockfile
+        
+  #     - name: Run tests with React 18
+  #       run: |
+  #         yarn test
+  #         yarn build
+          
+  # test-react-19:
+  #   name: Test with React 19
+  #   runs-on: ubuntu-latest
+    
+  #   steps:
+  #     - uses: actions/checkout@v4
+      
+  #     - name: Setup Node.js
+  #       uses: actions/setup-node@v4
+  #       with:
+  #         node-version: '20'
+  #         cache: 'yarn'
+          
+  #     - name: Install dependencies with React 19
+  #       run: |
+  #         yarn install --frozen-lockfile
+  #         # 升级到 React 19 预发布版本
+  #         yarn add react@beta react-dom@beta --dev
+          
+  #     - name: Build React 19 compatible version
+  #       run: |
+  #         node scripts/react19-build.js 19
+          
+  #     - name: Test React 19 version
+  #       run: |
+  #         # 在这里运行适合 React 19 的测试
+  #         echo "Running React 19 specific tests..."
+          
+  build-and-publish:
+    name: Build and Publish
+    runs-on: ubuntu-latest
+    
+    steps:
+      - name: gen token
+        run: |
+            git config --global user.name 'semi-team'
+            git config --global user.email '[email protected]'
+            mkdir ~/.ssh
+            echo $SEMI_TEAM_PRIVATE_KEY > ~/.ssh/ssh-ed25519
+            echo $SEMI_TEAM_PRIVATE_KEY_PUB > ~/.ssh/ssh-ed25519.pub
+            echo "Host\n  github.com\n  AddKeysToAgent yes\n  UseKeychain yes\n  IdentityFile ~/.ssh/id_ed25519" > ~/.ssh/config
+
+      - uses: actions/checkout@v4
+        with:
+          ref: ${{ github.head_ref || github.ref_name }}
+
+      - name: Print branch
+        run: echo "branch=${{ github.head_ref || github.ref_name }}"
+       
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '20'
+          cache: 'yarn'
+          registry-url: 'https://registry.npmjs.org'
+
+      # 安装
+      - name: npm install
+        run: npm i -g lerna@^4.0.0 && npm run bootstrap
+          
+      # 生成 semi-ui-19 的包
+      - name: Build React 19 version
+        run: |
+          if: ${{ github.event.inputs.publish_react19 }}
+            node scripts/react19-build.js 19
+            echo "Build semi-ui-19";
+
+      # 再运行一次,保证 semi-ui-19 中的依赖包安装正确
+      - name: npm install again
+        run: npm run bootstrap
+      
+      - name: get version list
+        run: |
+            PKG_NAME=@douyinfe/semi-ui
+            echo "VERSION_LIST="$(npm view $PKG_NAME versions --json)"" >> $GITHUB_ENV
+      
+      - name: get version
+        run: echo "RELEASE_VERSION="$(node scripts/version.js)"" >> $GITHUB_ENV
+        env:
+            RELEASE_TYPE: ${{ github.event.inputs.release_type }}
+
+     # publish 的时候不提交 semi-ui-19 相关的修改
+      - name: publish
+        run: |
+            git config --global user.name 'semi-team'
+            git config --global user.email '[email protected]'
+            node scripts/sitemap_update.js
+            if [ -n "$(git status --porcelain)" ]; then
+              echo "there are changes";
+              git add . ':(exclude)packages/semi-ui-19'
+              git commit --no-verify -m "chore: publish ${{ env.RELEASE_VERSION }}"
+            else
+              echo "no changes";
+            fi
+            git add packages/semi-ui-19
+            git commit --no-verify -m "chore: semi-ui-19-related"
+            npm config set registry=https://registry.npmjs.org/
+            npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}
+            npm whoami
+            DIST_TAG=latest
+            if [[ ${{ github.event.inputs.release_type }} == 'beta' ]]; then
+              DIST_TAG=beta
+            elif [[ ${{ github.event.inputs.release_type }} == 'alpha' ]]; then
+              DIST_TAG=alpha
+            fi
+            echo "$RELEASE_VERSION"
+            echo "$DIST_TAG"
+            lerna version $RELEASE_VERSION --exact --force-publish --yes --no-push
+            lerna publish from-package --dist-tag $DIST_TAG --yes
+            git reset HEAD~2
+            rm -rf packages/semi-ui-19
+            git add .
+            git commit --no-verify -m "v${{env.RELEASE_VERSION}}"
+            git tag -d "v${{env.RELEASE_VERSION}}"
+            git tag "v${{env.RELEASE_VERSION}}"
+            git push -o ci.skip --follow-tags --no-verify --atomic

+ 2 - 1
package.json

@@ -236,7 +236,8 @@
         "@types/react": "^18.0.5",
         "@types/react-dom": "^18.0.1",
         "babel-plugin-lodash/@babel/types": "~7.20.0",
-        "cheerio": "1.0.0-rc.12"
+        "cheerio": "1.0.0-rc.12",
+        "@types/minimatch": "5.1.2"
     },
     "lint-staged": {
         "src/**/*.{js,jsx,ts,tsx}": [

+ 4 - 2
packages/semi-ui/autoComplete/index.tsx

@@ -199,6 +199,7 @@ class AutoComplete<T extends AutoCompleteItems> extends BaseComponent<AutoComple
 
     triggerRef: React.RefObject<HTMLDivElement> | null;
     optionsRef: React.RefObject<HTMLDivElement> | null;
+    optionContainerEl: React.RefObject<HTMLDivElement> | null;
     optionListId: string;
 
     private clickOutsideHandler: (e: Event) => void | null;
@@ -222,6 +223,7 @@ class AutoComplete<T extends AutoCompleteItems> extends BaseComponent<AutoComple
         };
         this.triggerRef = React.createRef();
         this.optionsRef = React.createRef();
+        this.optionContainerEl = React.createRef();
         this.clickOutsideHandler = null;
         this.optionListId = '';
 
@@ -327,9 +329,8 @@ class AutoComplete<T extends AutoCompleteItems> extends BaseComponent<AutoComple
             },
             registerClickOutsideHandler: cb => {
                 const clickOutsideHandler = (e: Event) => {
-                    const optionInstance = this.optionsRef && this.optionsRef.current;
                     const triggerDom = this.triggerRef && this.triggerRef.current;
-                    const optionsDom = ReactDOM.findDOMNode(optionInstance);
+                    const optionsDom = this.optionContainerEl && this.optionContainerEl.current;
                     const target = e.target as Element;
                     const path = e.composedPath && e.composedPath() || [target];
                     if (
@@ -525,6 +526,7 @@ class AutoComplete<T extends AutoCompleteItems> extends BaseComponent<AutoComple
         };
         return (
             <div
+                ref={this.optionContainerEl}
                 className={listCls}
                 role="listbox"
                 style={style}

+ 1 - 1
packages/semi-ui/avatar/avatarGroup.tsx

@@ -54,7 +54,7 @@ export default class AvatarGroup extends PureComponent<AvatarGroupProps> {
         const { renderMore } = this.props;
         const moreCls = cls(`${prefixCls}-item-more`);
         const restAvatarAlt = restAvatars?.reduce((pre, cur) => {
-            const { children, alt } = (cur as ReactElement).props;
+            const { children, alt } = (cur as ReactElement<any>).props;
             const avatarInfo = alt ?? ((typeof children === 'string') ? children : '');
             if (avatarInfo.length === 0) {
                 return pre;

+ 6 - 3
packages/semi-ui/calendar/monthCalendar.tsx

@@ -61,6 +61,7 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
     cellDom: React.RefObject<HTMLDivElement>;
     foundation: CalendarFoundation;
     cardRef: Map<string, ReactInstance>;
+    optionContainerEl: Map<string, HTMLDivElement>;
     contentCellHeight: number;
     monthlyData: MonthData;
 
@@ -78,6 +79,7 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
         this.foundation = new CalendarFoundation(this.adapter);
         this.handleClick = this.handleClick.bind(this);
         this.cardRef = new Map();
+        this.optionContainerEl = new Map();
     }
 
     get adapter(): CalendarAdapter<MonthCalendarProps, MonthCalendarState> {
@@ -85,8 +87,9 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
             ...super.adapter,
             registerClickOutsideHandler: (key: string, cb: () => void) => {
                 const clickOutsideHandler = (e: MouseEvent) => {
-                    const cardInstance = this.cardRef && this.cardRef.get(key);
-                    const cardDom = ReactDOM.findDOMNode(cardInstance);
+                    
+                    const cardDom = this.optionContainerEl && this.optionContainerEl.get(key);
+                    
                     const target = e.target as Element;
                     const path = e.composedPath && e.composedPath() || [target];
                     if (cardDom && !cardDom.contains(target) && !path.includes(cardDom)) {
@@ -253,7 +256,7 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
             </div>
         );
         const content = (
-            <div className={cardCls}>
+            <div className={cardCls} ref={ref => this.optionContainerEl.set(key, ref)}>
                 <div className={`${cardCls}-content`}>
                     <div className={`${cardCls}-header`}>
                         {header}

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

@@ -227,6 +227,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
     inputRef: React.RefObject<typeof Input>;
     triggerRef: React.RefObject<HTMLDivElement>;
     optionsRef: React.RefObject<any>;
+    optionContainerEl: React.RefObject<HTMLDivElement>;
     clickOutsideHandler: any;
     mergeType: string;
     context: ContextValue;
@@ -279,6 +280,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         this.inputRef = React.createRef();
         this.triggerRef = React.createRef();
         this.optionsRef = React.createRef();
+        this.optionContainerEl = React.createRef();
         this.clickOutsideHandler = null;
         this.foundation = new CascaderFoundation(this.adapter);
         this.loadingKeysRef = React.createRef();
@@ -312,9 +314,8 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         > = {
             registerClickOutsideHandler: cb => {
                 const clickOutsideHandler = (e: Event) => {
-                    const optionInstance = this.optionsRef && this.optionsRef.current;
                     const triggerDom = this.triggerRef && this.triggerRef.current;
-                    const optionsDom = ReactDOM.findDOMNode(optionInstance);
+                    const optionsDom = this.optionContainerEl && this.optionContainerEl.current;
                     const target = e.target as Element;
                     const path = e.composedPath && e.composedPath() || [target];
                     if (
@@ -732,7 +733,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         const isEmpty = !renderData || !renderData.length;
         const realDropDownStyle = isEmpty ? {...dropdownStyle, minWidth: this.state.emptyContentMinWidth } : dropdownStyle;
         const content = (
-            <div className={popoverCls} role="listbox" style={realDropDownStyle} onKeyDown={this.foundation.handleKeyDown}>
+            <div className={popoverCls} role="listbox" style={realDropDownStyle} onKeyDown={this.foundation.handleKeyDown} ref={this.optionContainerEl}>
                 {topSlot}
                 <Item
                     activeKeys={activeKeys}

+ 1 - 1
packages/semi-ui/checkbox/checkboxGroup.tsx

@@ -158,7 +158,7 @@ class CheckboxGroup extends BaseComponent<CheckboxGroupProps, CheckboxGroupState
                 }
             });
         } else if (children) {
-            inner = (React.Children.toArray(children) as React.ReactElement[]).map((itm, index) => React.cloneElement(itm, { key: index, role: 'listitem' }));
+            inner = (React.Children.toArray(children) as React.ReactElement<any>[]).map((itm, index) => React.cloneElement(itm, { key: index, role: 'listitem' }));
         }
 
         return (

+ 0 - 14
packages/semi-ui/collapsible/index.tsx

@@ -221,19 +221,5 @@ class Collapsible extends BaseComponent<CollapsibleProps, CollapsibleState> {
     }
 }
 
-Collapsible.propTypes = {
-    motion: PropTypes.bool,
-    children: PropTypes.node,
-    isOpen: PropTypes.bool,
-    duration: PropTypes.number,
-    keepDOM: PropTypes.bool,
-    collapseHeight: PropTypes.number,
-    style: PropTypes.object,
-    className: PropTypes.string,
-    reCalcKey: PropTypes.oneOfType([
-        PropTypes.string,
-        PropTypes.number
-    ]),
-};
 
 export default Collapsible;

+ 10 - 0
packages/semi-ui/dragMove/index.ts

@@ -63,7 +63,12 @@ export default class DragMove extends BaseComponent<DragMoveProps, null> {
             getDragElement: () => {
                 let elementDom = this.elementRef.current;
                 if (!isHTMLElement(elementDom)) {
+                    /* REACT_18_START */
                     elementDom = ReactDOM.findDOMNode(elementDom as React.ReactInstance);
+                    /* REACT_18_END */
+                    /* REACT_19_START */
+                    // console.warn(`[Semi DragMove] elementDom should be a valid DOM element. The element's ref is not returning a DOM node. This may cause dragMove positioning issues. Please ensure the element has a proper ref that returns a DOM node.`);
+                    /* REACT_19_END */
                 }
                 return elementDom as HTMLElement;
             },
@@ -123,7 +128,12 @@ export default class DragMove extends BaseComponent<DragMoveProps, null> {
             ref: (node: React.ReactNode) => {
                 (this.elementRef as any).current = node;
                 // Call the original ref, if any
+                /* REACT_18_START */
                 const { ref } = children as any;
+                /* REACT_18_END */
+                /* REACT_19_START */
+                // const { ref } = (children as any).props;
+                /* REACT_19_END */
                 if (typeof ref === 'function') {
                     ref(node);
                 } else if (ref && typeof ref === 'object') {

+ 0 - 4
packages/semi-ui/dropdown/dropdownDivider.tsx

@@ -15,9 +15,5 @@ const DropdownDivider: React.FC<DropdownDividerProps> = (props = {}) => {
     return <div className={classnames(`${prefixCls}-divider`, className)} style={style} />;
 };
 
-DropdownDivider.propTypes = {
-    style: PropTypes.object,
-    className: PropTypes.string,
-};
 
 export default DropdownDivider;

+ 1 - 1
packages/semi-ui/form/hooks/useStateWithGetter.ts

@@ -1,7 +1,7 @@
 import { useRef, useState } from 'react';
 // https://github.com/facebook/react/issues/14543
 export default function useStateWithGetter(initial?: any) {
-    const ref = useRef();
+    const ref = useRef(undefined);
     const [state, setState] = useState(initial);
     ref.current = state;
     const set = (value: any) => {

+ 2 - 4
packages/semi-ui/list/_story/list.stories.jsx

@@ -1,6 +1,4 @@
 import React from 'react';
-import ReactDOM from 'react-dom';
-
 import InfiniteScroll from 'react-infinite-scroller';
 import { DndProvider, DragSource, DropTarget, useDrag, useDrop } from 'react-dnd';
 import HTML5Backend from 'react-dnd-html5-backend';
@@ -664,7 +662,7 @@ const cardTarget = {
     if (dragIndex === hoverIndex) {
       return;
     }
-    const hoverBoundingRect = ReactDOM.findDOMNode(component).getBoundingClientRect();
+    const hoverBoundingRect = component.node.getBoundingClientRect();
     const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
     const clientOffset = monitor.getClientOffset();
     const hoverClientY = clientOffset.y - hoverBoundingRect.top;
@@ -708,7 +706,7 @@ class DraggableItem extends React.Component {
 
     return connectDragSource(
       connectDropTarget(
-        <div ref={node => (this.node = node)} style={{ ...style, opacity }}>
+        <div ref={node => {this.node = node}} style={{ ...style, opacity }}>
           {component}
         </div>
       )

+ 34 - 0
packages/semi-ui/modal/confirm.tsx

@@ -1,5 +1,10 @@
 import React from 'react';
+/* REACT_18_START */
 import ReactDOM from 'react-dom';
+/* REACT_18_END */
+/* REACT_19_START */
+// import { createRoot } from 'react-dom/client';
+/* REACT_19_END */
 import { destroyFns, ModalReactProps } from './Modal';
 import ConfirmModal from './ConfirmModal';
 
@@ -17,13 +22,28 @@ export default function confirm<T>(props: ConfirmProps) {
     const div = document.createElement('div');
     document.body.appendChild(div);
 
+    /* REACT_19_START */
+    // let root: any = null;
+    /* REACT_19_END */
+
     let currentConfig = { ...props };
 
     const destroy = () => {
+        /* REACT_18_START */
         const unmountResult = ReactDOM.unmountComponentAtNode(div);
         if (unmountResult && div.parentNode) {
             div.parentNode.removeChild(div);
         }
+        /* REACT_18_END */
+        
+        /* REACT_19_START */
+        // if (root) {
+        //     root.unmount();
+        //     if (div.parentNode) {
+        //         div.parentNode.removeChild(div);
+        //     }
+        // }
+        /* REACT_19_END */
 
         for (let i = 0; i < destroyFns.length; i++) {
             const fn = destroyFns[i];
@@ -38,12 +58,26 @@ export default function confirm<T>(props: ConfirmProps) {
 
     function render(renderProps: ConfirmProps) {
         const { afterClose } = renderProps;
+        
+        /* REACT_18_START */
         //@ts-ignore
         ReactDOM.render(<ConfirmModal {...renderProps} afterClose={(...args: any) => {
             //@ts-ignore
             afterClose?.(...args);
             destroy();
         }} motion={props.motion}/>, div);
+        /* REACT_18_END */
+        
+        /* REACT_19_START */
+        // if (!root) {
+        //     root = createRoot(div);
+        // }
+        // root.render(<ConfirmModal {...renderProps} afterClose={(...args: any) => {
+        //     //@ts-ignore
+        //     afterClose?.(...args);
+        //     destroy();
+        // }} motion={props.motion}/>);
+        /* REACT_19_END */
     }
 
     function close() {

+ 1 - 1
packages/semi-ui/navigation/Item.tsx

@@ -143,7 +143,7 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
 
         return (
             <i className={className} key={key}>
-                {isSemiIcon(icon) ? React.cloneElement((icon as React.ReactElement), { size: (icon as React.ReactElement).props.size || iconSize }) : icon}
+                {isSemiIcon(icon) ? React.cloneElement((icon as React.ReactElement<any>), { size: (icon as React.ReactElement<any>).props.size || iconSize }) : icon}
             </i>
         );
     }

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

@@ -72,7 +72,7 @@ export interface NavProps extends BaseProps {
     onDeselect?: (data?: any) => void;
     onOpenChange?: (data: { itemKey?: ItemKey; openKeys?: ItemKey[]; domEvent?: MouseEvent; isOpen?: boolean }) => void;
     onSelect?: (data: OnSelectedData) => void;
-    renderWrapper?: ({ itemElement, isSubNav, isInSubNav, props }: { itemElement: ReactElement; isInSubNav: boolean; isSubNav: boolean; props: NavItemProps | SubNavProps }) => ReactNode
+    renderWrapper?: ({ itemElement, isSubNav, isInSubNav, props }: { itemElement: ReactElement<any>; isInSubNav: boolean; isSubNav: boolean; props: NavItemProps | SubNavProps }) => ReactNode
 }
 
 export interface NavState {

+ 39 - 0
packages/semi-ui/notification/index.tsx

@@ -1,5 +1,10 @@
 import React, { CSSProperties } from 'react';
+/* REACT_18_START */
 import ReactDOM from 'react-dom';
+/* REACT_18_END */
+/* REACT_19_START */
+// import { createRoot } from 'react-dom/client';
+/* REACT_19_END */
 import cls from 'classnames';
 import PropTypes from 'prop-types';
 import ConfigContext, { ContextValue } from '../configProvider/context';
@@ -65,6 +70,9 @@ class NotificationList extends BaseComponent<NotificationListProps, Notification
     static defaultProps = {};
     static useNotification: typeof useNotification;
     private static wrapperId: string;
+    /* REACT_19_START */
+    // private static root: any = null;
+    /* REACT_19_END */
     private noticeStorage: NoticeInstance[];
     private removeItemStorage: NoticeInstance[];
 
@@ -115,9 +123,28 @@ class NotificationList extends BaseComponent<NotificationListProps, Notification
             } else {
                 document.body.appendChild(div);
             }
+            /* REACT_18_START */
             ReactDOM.render(React.createElement(NotificationList, { ref: instance => (ref = instance) }), div, () => {
                 ref.add({ ...notice, id });
             });
+            /* REACT_18_END */
+            
+            /* REACT_19_START */
+            // if (!this.root) {
+            //     this.root = createRoot(div);
+            // }
+            // this.root.render(React.createElement(NotificationList, { ref: instance => (ref = instance) }));
+            // // 在 React 19 中,render 是同步的,确保 ref 已赋值后再执行add方法
+            // if (typeof queueMicrotask === 'function') {
+            //     queueMicrotask(() => {
+            //         ref.add({ ...notice, id });
+            //     });
+            // } else {
+            //     Promise.resolve().then(() => {
+            //         ref.add({ ...notice, id });
+            //     });
+            // }
+            /* REACT_19_END */
         } else {
             if (ref.has(`${id}`)) {
                 ref.update(id, notice);
@@ -165,8 +192,20 @@ class NotificationList extends BaseComponent<NotificationListProps, Notification
         if (ref) {
             ref.destroyAll();
             const wrapper = document.querySelector(`#${this.wrapperId}`);
+            
+            /* REACT_18_START */
             ReactDOM.unmountComponentAtNode(wrapper);
             wrapper && wrapper.parentNode.removeChild(wrapper);
+            /* REACT_18_END */
+            
+            /* REACT_19_START */
+            // if (this.root) {
+            //     this.root.unmount();
+            //     this.root = null;
+            // }
+            // wrapper && wrapper.parentNode.removeChild(wrapper);
+            /* REACT_19_END */
+            
             ref = null;
             this.wrapperId = null;
         }

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

@@ -123,7 +123,7 @@ class PinCode extends BaseComponent<PinCodeProps, PinCodeState> {
 
 
     render() {
-        const inputElements: ReactElement[] = [];
+        const inputElements: ReactElement<any>[] = [];
         for (let i = 0; i < this.props.count; i++) {
             inputElements.push(this.renderSingleInput(i));
         }

+ 0 - 6
packages/semi-ui/popover/Arrow.tsx

@@ -61,10 +61,4 @@ const Arrow: React.FC<ArrowProps> = (props = {}) => {
     );
 };
 
-Arrow.propTypes = {
-    position: PropTypes.string,
-    className: PropTypes.string,
-    arrowStyle: PropTypes.object,
-    popStyle: PropTypes.object,
-};
 export default Arrow;

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

@@ -140,7 +140,7 @@ export default class Rating extends BaseComponent<RatingProps, RatingState> {
             },
             getStarDOM: (index: number) => {
                 const instance = this.stars && this.stars[index];
-                return ReactDOM.findDOMNode(instance) as Element;
+                return instance.getDomNode() as Element;
             },
             notifyHoverChange: (hoverValue: number, clearedValue: number) => {
                 const { onHoverChange } = this.props;

+ 8 - 1
packages/semi-ui/rating/item.tsx

@@ -62,6 +62,7 @@ export default class Item extends BaseComponent<RatingItemProps, RatingItemState
 
     constructor(props: RatingItemProps) {
         super(props);
+        this.liRef = React.createRef();
         this.state = {
             firstStarFocus: false,
             secondStarFocus: false,
@@ -69,6 +70,12 @@ export default class Item extends BaseComponent<RatingItemProps, RatingItemState
         this.foundation = new RatingItemFoundation(this.adapter);
     }
 
+    public getDomNode = () => {
+        return this.liRef && this.liRef.current;
+    }
+    
+    liRef: React.RefObject<HTMLLIElement>;
+
     get adapter(): RatingItemAdapter<RatingItemProps, RatingItemState> {
         return {
             ...super.adapter,
@@ -222,7 +229,7 @@ export default class Item extends BaseComponent<RatingItemProps, RatingItemState
         };
        
         return (
-            <li className={starCls} style={{ ...sizeStyle }} key={index} >
+            <li className={starCls} style={{ ...sizeStyle }} key={index} ref={this.liRef}>
                 <div {...(starWrapProps as any)}>
                     {allowHalf && !isEmpty && <div {...firstStarProps} style={{ width: `${firstWidth * 100}%` }}>{content}</div>}
                     <div {...secondStarProps} x-semi-prop="character">{content}</div>

+ 16 - 5
packages/semi-ui/resizeObserver/index.tsx

@@ -72,7 +72,13 @@ export default class ReactResizeObserver extends BaseComponent<ReactResizeObserv
             // using findDOMNode for two reasons:
             // 1. cloning to insert a ref is unwieldy and not performant.
             // 2. ensure that we resolve to an actual DOM node (instead of any JSX ref instance).
+            
+            /* REACT_18_START */
             return findDOMNode(this.childNode || this);
+            /* REACT_18_END */
+            /* REACT_19_START */
+            // return this.childNode || null;
+            /* REACT_19_END */
         } catch (error) {
             // swallow error if findDOMNode is run on unmounted component.
             return null;
@@ -80,14 +86,14 @@ export default class ReactResizeObserver extends BaseComponent<ReactResizeObserv
     };
 
 
-    handleResizeEventTriggered = (entries: ResizeEntry[])=>{
+    handleResizeEventTriggered = (entries: ResizeEntry[]) => {
         if (this.props.observerProperty === ObserverProperty.All) {
             this.props.onResize?.(entries);
         } else {
             const finalEntries: ResizeEntry[] = [];
             for (const entry of entries) {
                 if (this.formerPropertyValue.has(entry.target)) {
-                    if (entry.contentRect[this.props.observerProperty]!==this.formerPropertyValue.get(entry.target)) {
+                    if (entry.contentRect[this.props.observerProperty] !== this.formerPropertyValue.get(entry.target)) {
                         this.formerPropertyValue.set(entry.target, entry.contentRect[this.props.observerProperty]);
                         finalEntries.push(entry);
                     }
@@ -96,13 +102,13 @@ export default class ReactResizeObserver extends BaseComponent<ReactResizeObserv
                     finalEntries.push(entry);
                 }
             }
-            if (finalEntries.length>0) {
+            if (finalEntries.length > 0) {
                 this.props.onResize?.(finalEntries);
             }
         }
     }
 
-    observeElement = (force = false)=>{
+    observeElement = (force = false) => {
         const element = this.getElement();
         if (!this.observer) {
             this.observer = new ResizeObserver(this.handleResizeEventTriggered);
@@ -149,8 +155,13 @@ export default class ReactResizeObserver extends BaseComponent<ReactResizeObserv
 
     render() {
         const child = React.Children.only(this.props.children);
+        /* REACT_18_START */
         const { ref } = child as any;
-        return React.cloneElement(child as React.ReactElement, {
+        /* REACT_18_END */
+        /* REACT_19_START */
+        // const { ref } = (child as any).props;
+        /* REACT_19_END */
+        return React.cloneElement(child as React.ReactElement<any>, {
             ref: (node: HTMLDivElement) => this.mergeRef(ref, node),
         });
     }

+ 10 - 1
packages/semi-ui/scripts/compileScss.js

@@ -5,12 +5,16 @@ function resolve(dir) {
     return path.join(__dirname, '../..', dir);
 }
 
-
 compile({
     foundationPath: resolve('semi-foundation/'),
     themePath: resolve('semi-theme-default/'),
     iconPath: resolve('semi-icons/'),
+    /* REACT_18_START */
     outputPath: resolve('semi-ui/dist/css/semi.min.css'),
+    /* REACT_18_END */
+    /* REACT_19_START */
+    // outputPath: resolve('semi-ui-19/dist/css/semi.min.css'),
+    /* REACT_19_END */
     isMin: true
 });
 
@@ -18,7 +22,12 @@ compile({
     foundationPath: resolve('semi-foundation/'),
     themePath: resolve('semi-theme-default/'),
     iconPath: resolve('semi-icons/'),
+    /* REACT_18_START */
     outputPath: resolve('semi-ui/dist/css/semi.css'),
+    /* REACT_18_END */
+    /* REACT_19_START */
+    // outputPath: resolve('semi-ui-19/dist/css/semi.css'),
+    /* REACT_19_END */
     isMin: false
 });
 

+ 5 - 5
packages/semi-ui/select/index.tsx

@@ -462,9 +462,9 @@ class Select extends BaseComponent<SelectProps, SelectState> {
             getMaxLimit: () => this.props.max,
             registerClickOutsideHandler: (cb: (e: MouseEvent) => void) => {
                 const clickOutsideHandler: (e: MouseEvent) => void = e => {
-                    const optionInstance = this.optionsRef && this.optionsRef.current;
                     const triggerDom = (this.triggerRef && this.triggerRef.current) as Element;
-                    const optionsDom = ReactDOM.findDOMNode(optionInstance as ReactInstance);
+                    const optionsDom = (this.optionContainerEl && this.optionContainerEl.current) as Element;
+
                     const target = e.target as Element;
                     const path = (e as any).composedPath && (e as any).composedPath() || [target];
 
@@ -1174,7 +1174,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
     }
 
 
-    renderCollapsedTags(selections: [React.ReactNode, any][], length: number | undefined): React.ReactElement {
+    renderCollapsedTags(selections: [React.ReactNode, any][], length: number | undefined): React.ReactElement<any> {
         const { overflowItemCount } = this.state;
         const normalTags = typeof length === 'number' ? selections.slice(0, length) : selections;
         return (
@@ -1191,7 +1191,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         );
     }
 
-    renderOneLineTags(selectedItems: [React.ReactNode, any][], n: number | undefined): React.ReactElement {
+    renderOneLineTags(selectedItems: [React.ReactNode, any][], n: number | undefined): React.ReactElement<any> {
         let { renderSelectedItem } = this.props;
         const { showRestTagsPopover, restTagsPopoverProps, maxTagCount } = this.props;
         const { isFullTags } = this.state;
@@ -1475,7 +1475,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
                 aria-describedby={this.props['aria-describedby']}
                 aria-required={this.props['aria-required']}
                 className={selectionCls}
-                ref={ref => ((this.triggerRef as any).current = ref)}
+                ref={ref => {((this.triggerRef as any).current = ref);}}
                 onClick={e => this.foundation.handleClick(e)}
                 style={style}
                 id={this.selectID}

+ 3 - 3
packages/semi-ui/select/utils.tsx

@@ -3,7 +3,7 @@ import warning from '@douyinfe/semi-foundation/utils/warning';
 import { OptionProps } from './option';
 import { OptionGroupProps } from './optionGroup';
 
-const generateOption = (child: React.ReactElement, parent: any, index: number, newKey?: string | number): OptionProps => {
+const generateOption = (child: React.ReactElement<any>, parent: any, index: number, newKey?: string | number): OptionProps => {
     const childProps = child.props;
     if (!child || !childProps) {
         return null;
@@ -38,7 +38,7 @@ const getOptionsFromGroup = (selectChildren: React.ReactNode) => {
     } = { label: '', children: [], _show: false };
 
     // avoid null
-    let childNodes = React.Children.toArray(selectChildren) as React.ReactElement[];
+    let childNodes = React.Children.toArray(selectChildren) as React.ReactElement<any>[];
     childNodes = childNodes.filter((childNode) => childNode && childNode.props);    
 
     let type = '';
@@ -64,7 +64,7 @@ const getOptionsFromGroup = (selectChildren: React.ReactNode) => {
                 originKeys.push(children.key);
             }
             children = React.Children.toArray(children);
-            const childrenOption = children.map((option: React.ReactElement, index: number) => {
+            const childrenOption = children.map((option: React.ReactElement<any>, index: number) => {
                 let newKey = option.key;
                 if (originKeys[index] === null) {
                     newKey = child.key + '' + option.key; // if option in group and didn't set key, concat parent key to avoid conflict (default generate key just like .0, .1)

+ 1 - 1
packages/semi-ui/skeleton/item.tsx

@@ -29,7 +29,7 @@ const shapeSet = strings.SHAPE;
 
 const generator = <T extends BasicProps>(type: string) => (BasicComponent: ComponentType<T>): FC<T> => (
     props
-): ReactElement => <BasicComponent type={type} {...props} />;
+): ReactElement<any> => <BasicComponent type={type} {...props} />;
 
 class Generic extends PureComponent<GenericProps> {
     static propTypes = {

+ 1 - 2
packages/semi-ui/slider/index.tsx

@@ -175,8 +175,7 @@ export default class Slider extends BaseComponent<SliderProps, SliderState> {
                     if (!handle) {
                         return;
                     }
-                    const handleInstance = handle && handle.current;
-                    const handleDom = ReactDOM.findDOMNode(handleInstance);
+                    const handleDom = handle && handle.current;
                     if (handleDom && handleDom.contains(e.target as Node)) {
                         flag = true;
                     }

+ 5 - 24
packages/semi-ui/steps/basicStep.tsx

@@ -34,15 +34,15 @@ export enum stepSizeMapIconSize {
 
 const BasicStep = (props: BasicStepProps) => {
     const {
-        prefixCls,
-        className,
+        prefixCls = css.ITEM,
+        className = '',
         size,
         title,
         description,
-        status,
+        status = 'wait',
         style,
-        active,
-        done,
+        active = false,
+        done = false,
         icon,
         stepNumber,
         onClick,
@@ -120,24 +120,5 @@ const BasicStep = (props: BasicStepProps) => {
     );
 };
 
-BasicStep.propTypes = {
-    prefixCls: PropTypes.string,
-    description: PropTypes.node,
-    icon: PropTypes.node,
-    status: PropTypes.oneOf(['wait', 'process', 'finish', 'error', 'warning']),
-    title: PropTypes.node,
-    className: PropTypes.string,
-    style: PropTypes.object,
-    onClick: PropTypes.func,
-    active: PropTypes.bool,
-    done: PropTypes.bool,
-};
-BasicStep.defaultProps = {
-    prefixCls: css.ITEM,
-    active: false,
-    done: false,
-    status: 'wait',
-    className: '',
-};
 
 export default BasicStep;

+ 9 - 29
packages/semi-ui/steps/basicSteps.tsx

@@ -24,22 +24,22 @@ export interface BasicStepsProps {
 
 const Steps = (props: BasicStepsProps) => {
     const {
-        size,
-        current,
-        status,
+        size = '',
+        current = 0,
+        status = 'process',
         children,
-        prefixCls,
-        initial,
-        direction,
+        prefixCls = css.PREFIX,
+        initial = 0,
+        direction = 'horizontal',
         className,
         style,
-        hasLine,
+        hasLine = true,
         onChange,
         ...rest
     } = props;
     const inner = useMemo(() => {
-        const filteredChildren = Children.toArray(children).filter(c => isValidElement(c)) as Array<ReactElement>;
-        const content = Children.map(filteredChildren, (child: React.ReactElement, index) => {
+        const filteredChildren = Children.toArray(children).filter(c => isValidElement(c)) as Array<ReactElement<any>>;
+        const content = Children.map(filteredChildren, (child: React.ReactElement<any>, index) => {
             if (!child) {
                 return null;
             }
@@ -89,25 +89,5 @@ const Steps = (props: BasicStepsProps) => {
     );
 };
 
-Steps.propTypes = {
-    prefixCls: PropTypes.string,
-    className: PropTypes.string,
-    style: PropTypes.object,
-    current: PropTypes.number,
-    initial: PropTypes.number,
-    direction: PropTypes.oneOf(['horizontal', 'vertical']),
-    status: PropTypes.oneOf(['wait', 'process', 'finish', 'error', 'warning']),
-    hasLine: PropTypes.bool,
-};
-
-Steps.defaultProps = {
-    prefixCls: css.PREFIX,
-    current: 0,
-    direction: 'horizontal',
-    size: '',
-    initial: 0,
-    hasLine: true,
-    status: 'process',
-};
 
 export default Steps;

+ 1 - 16
packages/semi-ui/steps/fillStep.tsx

@@ -24,7 +24,7 @@ export interface FillStepProps {
 }
 
 const FillStep = (props: FillStepProps) => {
-    const { prefixCls, className, title, description, status, style, onClick, icon, onChange, stepNumber, onKeyDown } = props;
+    const { prefixCls = css.ITEM, className = '', title, description, status = 'wait', style, onClick, icon, onChange, stepNumber, onKeyDown } = props;
     const renderIcon = () => {
         let inner, progress;
 
@@ -108,20 +108,5 @@ const FillStep = (props: FillStepProps) => {
     );
 };
 
-FillStep.propTypes = {
-    prefixCls: PropTypes.string,
-    description: PropTypes.node,
-    icon: PropTypes.node,
-    status: PropTypes.oneOf(['wait', 'process', 'finish', 'error', 'warning']),
-    title: PropTypes.node,
-    className: PropTypes.string,
-    style: PropTypes.object,
-    onClick: PropTypes.func,
-};
-FillStep.defaultProps = {
-    prefixCls: css.ITEM,
-    status: 'wait',
-    className: '',
-};
 
 export default FillStep;

+ 3 - 20
packages/semi-ui/steps/fillSteps.tsx

@@ -22,11 +22,11 @@ export interface FillStepsProps {
 }
 
 const Steps = (props: FillStepsProps) => {
-    const { current, status, children, prefixCls, initial, direction, className, style, onChange, ...rest } = props;
+    const { current = 0, status = 'process', children, prefixCls = css.PREFIX, initial = 0, direction = 'horizontal', className, style, onChange, ...rest } = props;
     const inner = useMemo(() => {
-        const filteredChildren = Children.toArray(children).filter(c => isValidElement(c)) as Array<ReactElement>;
+        const filteredChildren = Children.toArray(children).filter(c => isValidElement(c)) as Array<ReactElement<any>>;
         const colStyle = direction === 'vertical' ? null : { width: `${100 / filteredChildren.length}%` };
-        const content = Children.map(filteredChildren, (child: ReactElement, index) => {
+        const content = Children.map(filteredChildren, (child: ReactElement<any>, index) => {
             if (!child) {
                 return null;
             }
@@ -80,22 +80,5 @@ const Steps = (props: FillStepsProps) => {
     );
 };
 
-Steps.propTypes = {
-    prefixCls: PropTypes.string,
-    className: PropTypes.string,
-    style: PropTypes.object,
-    current: PropTypes.number,
-    initial: PropTypes.number,
-    direction: PropTypes.oneOf(['horizontal', 'vertical']),
-    status: PropTypes.oneOf(['wait', 'process', 'finish', 'error', 'warning'])
-};
-
-Steps.defaultProps = {
-    prefixCls: css.PREFIX,
-    current: 0,
-    direction: 'horizontal',
-    initial: 0,
-    status: 'process',
-};
 
 export default Steps;

+ 1 - 14
packages/semi-ui/steps/navStep.tsx

@@ -21,7 +21,7 @@ export interface NavStepProps {
 }
 
 const NavStep = (props: NavStepProps) => {
-    const { prefixCls, className, title, style, active, index, total, onClick, onKeyDown, onChange } = props;
+    const { prefixCls = css.ITEM, className = '', title, style, active = false, index, total, onClick, onKeyDown, onChange } = props;
     const classString = classnames(prefixCls, {
         [`${prefixCls}-active`]: active
     }, className);
@@ -53,18 +53,5 @@ const NavStep = (props: NavStepProps) => {
     );
 };
 
-NavStep.propTypes = {
-    prefixCls: PropTypes.string,
-    title: PropTypes.node,
-    className: PropTypes.string,
-    style: PropTypes.object,
-    onClick: PropTypes.func,
-    active: PropTypes.bool,
-};
-NavStep.defaultProps = {
-    prefixCls: css.ITEM,
-    active: false,
-    className: '',
-};
 
 export default NavStep;

+ 3 - 20
packages/semi-ui/steps/navSteps.tsx

@@ -18,11 +18,11 @@ export interface NavStepsProps {
 }
 
 const Steps = (props: NavStepsProps) => {
-    const { size, current, initial, children, prefixCls, className, style, onChange, ...rest } = props;
+    const { size = 'default', current = 0, initial = 0, children, prefixCls = css.PREFIX, className, style, onChange, ...rest } = props;
     const inner = useMemo(() => {
-        const filteredChildren = Children.toArray(children).filter(c => isValidElement(c)) as Array<ReactElement>;
+        const filteredChildren = Children.toArray(children).filter(c => isValidElement(c)) as Array<ReactElement<any>>;
         const total = filteredChildren.length;
-        const content = Children.map(filteredChildren, (child: React.ReactElement, index) => {
+        const content = Children.map(filteredChildren, (child: React.ReactElement<any>, index) => {
             if (!child) {
                 return null;
             }
@@ -54,22 +54,5 @@ const Steps = (props: NavStepsProps) => {
     );
 };
 
-Steps.propTypes = {
-    prefixCls: PropTypes.string,
-    className: PropTypes.string,
-    style: PropTypes.object,
-    current: PropTypes.number,
-    initial: PropTypes.number,
-    size: PropTypes.oneOf(['small', 'default']),
-};
-
-Steps.defaultProps = {
-    prefixCls: css.PREFIX,
-    current: 0,
-    direction: 'horizontal',
-    size: 'default',
-    initial: 0,
-    status: 'process',
-};
 
 export default Steps;

+ 4 - 22
packages/semi-ui/table/CustomExpandIcon.tsx

@@ -26,7 +26,7 @@ export interface CustomExpandIconProps {
 export default function CustomExpandIcon(props: CustomExpandIconProps) {
     const {
         expanded,
-        componentType,
+        componentType = 'expand',
         onClick = noop,
         onMouseEnter = noop,
         onMouseLeave = noop,
@@ -58,9 +58,9 @@ export default function CustomExpandIcon(props: CustomExpandIconProps) {
 
     if (motion) {
         const originIcon = icon;
-        icon = <CSSAnimation animationState={expanded?"enter":"leave"} startClassName={`${cssClasses.PREFIX}-expandedIcon-${expanded?'show':"hide"}`}>
-            {({ animationClassName })=>{
-                return React.cloneElement(originIcon, { className: (originIcon.props.className||"")+" "+animationClassName });
+        icon = <CSSAnimation animationState={expanded ? "enter" : "leave"} startClassName={`${cssClasses.PREFIX}-expandedIcon-${expanded ? 'show' : "hide"}`}>
+            {({ animationClassName }) => {
+                return React.cloneElement(originIcon, { className: (originIcon.props.className || "") + " " + animationClassName });
             }}
         </CSSAnimation>;
     }
@@ -81,21 +81,3 @@ export default function CustomExpandIcon(props: CustomExpandIconProps) {
     );
 }
 
-CustomExpandIcon.propTypes = {
-    expanded: PropTypes.bool,
-    componentType: PropTypes.oneOf(['tree', 'expand']),
-    onClick: PropTypes.func,
-    onMouseEnter: PropTypes.func,
-    onMouseLeave: PropTypes.func,
-    expandIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
-    prefixCls: PropTypes.string,
-    motion: PropTypes.bool,
-};
-
-CustomExpandIcon.defaultProps = {
-    componentType: 'expand',
-    onClick: noop,
-    onMouseEnter: noop,
-    onMouseLeave: noop,
-    prefixCls: cssClasses.PREFIX,
-};

+ 3 - 3
packages/semi-ui/table/_story/LinkedScroll/index.jsx

@@ -109,7 +109,7 @@ export default class LinkedScroll extends React.Component {
             <div style={{ position: 'relative', display: 'flex', padding: 20 }}>
                 <div
                     style={{ width: 200, height: 200, overflow: 'scroll' }}
-                    ref={node => (this.leftRef = node)}
+                    ref={node => {this.leftRef = node;}}
                     onScroll={this.handleBodyScrollTop}
                 >
                     <p>{content}</p>
@@ -117,7 +117,7 @@ export default class LinkedScroll extends React.Component {
                 </div>
                 <div
                     style={{ width: 200, height: 200, overflow: 'scroll' }}
-                    ref={node => (this.ref = node)}
+                    ref={node => {this.ref = node;}}
                     onScroll={this.handleBodyScrollTop}
                 >
                     <p>{content}</p>
@@ -125,7 +125,7 @@ export default class LinkedScroll extends React.Component {
                 </div>
                 <div
                     style={{ width: 200, height: 200, overflow: 'scroll' }}
-                    ref={node => (this.rightRef = node)}
+                    ref={node => {this.rightRef = node;}}
                     onScroll={this.handleBodyScrollTop}
                 >
                     <p>{content}</p>

+ 1 - 1
packages/semi-ui/tabs/TabBar.tsx

@@ -84,7 +84,7 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
         const { tabBarExtraContent, type, size } = this.props;
         const tabBarExtraContentDefaultStyle = { float: 'right' };
         const tabBarExtraContentStyle =
-            tabBarExtraContent && (tabBarExtraContent as ReactElement).props ? (tabBarExtraContent as ReactElement).props.style : {};
+            tabBarExtraContent && (tabBarExtraContent as ReactElement<any>).props ? (tabBarExtraContent as ReactElement<any>).props.style : {};
         const extraCls = cls(cssClasses.TABS_BAR_EXTRA, {
             [`${cssClasses.TABS_BAR}-${type}-extra`]: type,
             [`${cssClasses.TABS_BAR}-${type}-extra-${size}`]: size,

+ 3 - 3
packages/semi-ui/tabs/index.tsx

@@ -211,10 +211,10 @@ class Tabs extends BaseComponent<TabsProps, TabsState> {
     };
 
     /* istanbul ignore next */
-    rePosChildren = (children: ReactElement[], activeKey: string): ReactElement[] => {
-        const newChildren: ReactElement[] = [];
+    rePosChildren = (children: ReactElement<any>[], activeKey: string): ReactElement<any>[] => {
+        const newChildren: ReactElement<any>[] = [];
 
-        const falttenChildren = React.Children.toArray(children) as ReactElement[];
+        const falttenChildren = React.Children.toArray(children) as ReactElement<any>[];
 
         if (children.length) {
             newChildren.push(...falttenChildren.filter(child => child.props && child.props.itemKey === activeKey));

+ 44 - 0
packages/semi-ui/toast/index.tsx

@@ -1,5 +1,10 @@
 import React, { CSSProperties } from 'react';
+/* REACT_18_START */
 import ReactDOM from 'react-dom';
+/* REACT_18_END */
+/* REACT_19_START */
+// import { createRoot } from 'react-dom/client';
+/* REACT_19_END */
 import PropTypes from 'prop-types';
 import ToastListFoundation, {
     ToastListAdapter,
@@ -50,6 +55,9 @@ const createBaseToast = () => class ToastList extends BaseComponent<ToastListPro
 
     static defaultProps = {};
     static wrapperId: null | string;
+    /* REACT_19_START */
+    // static root: any = null;
+    /* REACT_19_END */
     stack: boolean = false;
 
     innerWrapperRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -120,6 +128,7 @@ const createBaseToast = () => class ToastList extends BaseComponent<ToastListPro
             } else {
                 document.body.appendChild(div);
             }
+            /* REACT_18_START */
             ReactDOM.render(React.createElement( 
                 ToastList,
                 { ref: instance => (ToastList.ref = instance) }
@@ -129,6 +138,29 @@ const createBaseToast = () => class ToastList extends BaseComponent<ToastListPro
                 ToastList.ref.add({ ...opts, id });
                 ToastList.ref.stack = Boolean(opts.stack);
             });
+            /* REACT_18_END */
+            
+            /* REACT_19_START */
+            // if (!this.root) {
+            //     this.root = createRoot(div);
+            // }
+            // this.root.render(React.createElement( 
+            //     ToastList,
+            //     { ref: instance => (ToastList.ref = instance) }
+            // ));
+            // // 在 React 19 中,render 是同步的,确保 ref 已赋值后再执行add方法
+            // if (typeof queueMicrotask === 'function') {
+            //     queueMicrotask(() => {
+            //         ToastList.ref.add({ ...opts, id });
+            //         ToastList.ref.stack = Boolean(opts.stack);
+            //     });
+            // } else {
+            //     Promise.resolve().then(() => {
+            //         ToastList.ref.add({ ...opts, id });
+            //         ToastList.ref.stack = Boolean(opts.stack);
+            //     });
+            // }
+            /* REACT_19_END */
         } else {
             const node = document.querySelector(`#${this.wrapperId}`) as HTMLElement;
             ['top', 'left', 'bottom', 'right'].map(pos => {
@@ -158,8 +190,20 @@ const createBaseToast = () => class ToastList extends BaseComponent<ToastListPro
         if (ToastList.ref) {
             ToastList.ref.destroyAll();
             const wrapper = document.querySelector(`#${this.wrapperId}`);
+            
+            /* REACT_18_START */
             ReactDOM.unmountComponentAtNode(wrapper);
             wrapper && wrapper.parentNode.removeChild(wrapper);
+            /* REACT_18_END */
+            
+            /* REACT_19_START */
+            // if (this.root) {
+            //     this.root.unmount();
+            //     this.root = null;
+            // }
+            // wrapper && wrapper.parentNode.removeChild(wrapper);
+            /* REACT_19_END */
+            
             ToastList.ref = null;
             this.wrapperId = null;
         }

+ 2 - 2
packages/semi-ui/toast/toast.tsx

@@ -115,7 +115,7 @@ class Toast extends BaseComponent<ToastReactProps, ToastState> {
         const iconSize = 'large';
         const iconCls = cls(`${prefixCls}-icon`, `${prefixCls}-icon-${type}`);
         if (icon) {
-            return isSemiIcon(icon) ? React.cloneElement((icon as React.ReactElement), { size: iconSize, className: `${prefixCls}-icon` }) : icon;
+            return isSemiIcon(icon) ? React.cloneElement((icon as React.ReactElement<any>), { size: iconSize, className: `${prefixCls}-icon` }) : icon;
         }
         if (type && iconType) {
             return React.cloneElement(iconType, { size: iconSize, className: iconCls });
@@ -144,7 +144,7 @@ class Toast extends BaseComponent<ToastReactProps, ToastState> {
             className={toastCls}
             style={{
                 ...style,
-                transform: `translate3d(0,0,${reservedIndex*-10}px)`,
+                transform: `translate3d(0,0,${reservedIndex * -10}px)`,
             }}
             onMouseEnter={this.clearCloseTimer}
             onMouseLeave={this.startCloseTimer}

+ 35 - 8
packages/semi-ui/tooltip/index.tsx

@@ -360,9 +360,14 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
                         return false;
                     }
                     let el = this.triggerEl && this.triggerEl.current;
-                    let popupEl = this.containerEl && this.containerEl.current;
+                    let popupEl = (this.containerEl && this.containerEl.current) as HTMLDivElement;
+
+                    /* REACT_18_START */
                     el = ReactDOM.findDOMNode(el as React.ReactInstance);
-                    popupEl = ReactDOM.findDOMNode(popupEl as React.ReactInstance) as HTMLDivElement;
+                    /* REACT_18_END */
+                    /* REACT_19_START */
+                    // el = el as HTMLElement;
+                    /* REACT_19_END */
                     const target = e.target as Element;
                     const path = (e as any).composedPath && (e as any).composedPath() || [target];
                     const isClickTriggerToHide = this.props.clickTriggerToHide ? el && (el as any).contains(target) || path.includes(el) : false;
@@ -451,7 +456,13 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
             getTriggerNode: () => {
                 let triggerDOM = this.triggerEl.current;
                 if (!isHTMLElement(this.triggerEl.current)) {
+                    /* REACT_18_START */
                     triggerDOM = ReactDOM.findDOMNode(this.triggerEl.current as React.ReactInstance);
+                    /* REACT_18_END */
+                    /* REACT_19_START */
+                    // console.warn(`[Semi Tooltip] triggerDOM should be a valid DOM element. The trigger element's ref is not returning a DOM node. This may cause tooltip positioning issues. Please ensure the trigger element has a proper ref that returns a DOM node.`);
+                    // triggerDOM = this.triggerEl.current;
+                    /* REACT_19_END */
                 }
                 return triggerDOM as Element;
             },
@@ -476,7 +487,12 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
             },
             getTriggerDOM: () => {
                 if (this.triggerEl.current) {
+                    /* REACT_18_START */
                     return ReactDOM.findDOMNode(this.triggerEl.current as ReactInstance) as HTMLElement;
+                    /* REACT_18_END */
+                    /* REACT_19_START */
+                    // return this.triggerEl.current as HTMLElement;
+                    /* REACT_19_END */
                 } else {
                     return null;
                 }
@@ -493,7 +509,13 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
             let triggerEle = this.triggerEl.current;
             if (triggerEle) {
                 if (!(triggerEle instanceof HTMLElement)) {
+                    /* REACT_18_START */
                     triggerEle = findDOMNode(triggerEle as ReactInstance);
+                    /* REACT_18_END */
+                    /* REACT_19_START */
+                    // console.warn(`[Semi Tooltip] triggerEle should be a valid DOM element. The trigger element's ref is not returning a DOM node. This may cause tooltip positioning issues. Please ensure the trigger element has a proper ref that returns a DOM node.`);
+                    // triggerEle = triggerEle as HTMLElement;
+                    /* REACT_19_END */
                 }
             }
             this.foundation.updateStateIfCursorOnTrigger(triggerEle as HTMLElement);
@@ -722,7 +744,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         );
     };
 
-    wrapSpan = (elem: React.ReactNode | React.ReactElement) => {
+    wrapSpan = (elem: React.ReactNode | React.ReactElement<any>) => {
         const { wrapperClassName } = this.props;
         const display = get(elem, 'props.style.display');
         const block = get(elem, 'props.block');
@@ -779,7 +801,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
                     extraStyle.cursor = 'not-allowed';
                 }
 
-                children = cloneElement(children as React.ReactElement, { style: childrenStyle });
+                children = cloneElement(children as React.ReactElement<any>, { style: childrenStyle });
                 if (trigger !== 'custom') {
                     // no need to wrap span when trigger is custom, cause it don't need bind event
                     children = this.wrapSpan(children);
@@ -803,10 +825,10 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         }
 
         // The incoming children is a single valid element, otherwise wrap a layer with span
-        const newChild = React.cloneElement(children as React.ReactElement, {
+        const newChild = React.cloneElement(children as React.ReactElement<any>, {
             ...ariaAttribute,
-            ...(children as React.ReactElement).props,
-            ...this.mergeEvents((children as React.ReactElement).props, triggerEventSet),
+            ...(children as React.ReactElement<any>).props,
+            ...this.mergeEvents((children as React.ReactElement<any>).props, triggerEventSet),
             style: {
                 ...get(children, 'props.style') as React.CSSProperties,
                 ...extraStyle,
@@ -819,7 +841,12 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
                 // Keep your own reference
                 (this.triggerEl as any).current = node;
                 // Call the original ref, if any
+                /* REACT_18_START */
                 const { ref } = children as any;
+                /* REACT_18_END */
+                /* REACT_19_START */
+                // const { ref } = (children as any).props;
+                /* REACT_19_END */
                 // this.log('tooltip render() - get ref', ref);
                 if (typeof ref === 'function') {
                     ref(node);
@@ -827,7 +854,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
                     ref.current = node;
                 }
             },
-            tabIndex: (children as React.ReactElement).props.tabIndex || 0, // a11y keyboard, in some condition select's tabindex need to -1 or 0
+            tabIndex: (children as React.ReactElement<any>).props.tabIndex || 0, // a11y keyboard, in some condition select's tabindex need to -1 or 0
             'data-popupid': id
         });
 

+ 7 - 8
packages/semi-ui/tree/__test__/autosizer.test.js

@@ -1,5 +1,5 @@
 import * as React from 'react';
-import ReactDOM, { findDOMNode } from 'react-dom';
+import ReactDOM from 'react-dom';
 import AutoSizer from '../autoSizer';
 
 function render(markup) {
@@ -72,7 +72,6 @@ describe('AutoSizer', () => {
   }
 
   it('should set the correct initial width and height of ChildComponent or React child', () => {
-    const rendered = findDOMNode(render(getAutoSizer()));
     // TODO
     // expect(rendered.textContent).toContain('height:100');
     // expect(rendered.textContent).toContain('width:100%');
@@ -86,13 +85,13 @@ describe('AutoSizer', () => {
   }
 
   it('should update :height after a resize event', async done => {
-    const rendered = findDOMNode(
-      render(
-        getAutoSizer({
-          height: 100,
-        }),
-      ),
+    render(
+      getAutoSizer({
+        height: 100,
+      }),
     );
+    // 直接使用 mountNode 替代
+    const rendered = render._mountNode;
     // expect(rendered.textContent).toContain('height:100');
     await simulateResize({ element: rendered, height: 400, });
     // TODO

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

@@ -10,7 +10,7 @@ interface IndentProps {
 
 const Indent = ({ prefixcls, level, isEnd, showLine }: IndentProps) => {
     const baseClassName = `${prefixcls}-indent-unit`;
-    const list: React.ReactElement[] = [];
+    const list: React.ReactElement<any>[] = [];
     for (let i = 0; i < level; i += 1) {
         list.push(
             <span

+ 4 - 3
packages/semi-ui/treeSelect/index.tsx

@@ -317,6 +317,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
     tagInputRef: React.RefObject<TagInput>;
     triggerRef: React.RefObject<HTMLDivElement>;
     optionsRef: React.RefObject<any>;
+    optionContainerEl: React.RefObject<HTMLDivElement>;
     clickOutsideHandler: any;
     // _flattenNodes: TreeState['flattenNodes'];
     onNodeClick: any;
@@ -360,6 +361,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         this.tagInputRef = React.createRef();
         this.triggerRef = React.createRef();
         this.optionsRef = React.createRef();
+        this.optionContainerEl = React.createRef();
         this.clickOutsideHandler = null;
         this.foundation = new TreeSelectFoundation(this.adapter);
         this.treeSelectID = Math.random().toString(36).slice(2);
@@ -628,9 +630,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
             registerClickOutsideHandler: cb => {
                 this.adapter.unregisterClickOutsideHandler();
                 const clickOutsideHandler = (e: Event) => {
-                    const optionInstance = this.optionsRef && this.optionsRef.current as React.ReactInstance;
                     const triggerDom = this.triggerRef && this.triggerRef.current;
-                    const optionsDom = ReactDOM.findDOMNode(optionInstance);
+                    const optionsDom = (this.optionContainerEl && this.optionContainerEl.current) as HTMLDivElement;
                     const target = e.target as Element;
                     const path = e.composedPath && e.composedPath() || [target];
 
@@ -812,7 +813,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         const style = { minWidth: dropdownMinWidth, ...dropdownStyle };
         const popoverCls = cls(dropdownClassName, `${prefixcls}-popover`);
         return (
-            <div className={popoverCls} style={style} onKeyDown={this.foundation.handleKeyDown}>
+            <div className={popoverCls} style={style} onKeyDown={this.foundation.handleKeyDown} ref={this.optionContainerEl}>
                 {this.renderTree()}
             </div>
         );

+ 8 - 8
packages/semi-ui/typography/base.tsx

@@ -199,7 +199,7 @@ export default class Base extends Component<BaseTypographyProps, BaseTypographyS
     componentDidMount() {
         if (this.props.ellipsis) {
             // runAfterTicks: make sure start observer on the next tick
-            this.onResize().then(()=>runAfterTicks(()=>this.observerTakingEffect = true, 1));
+            this.onResize().then(() => runAfterTicks(() => this.observerTakingEffect = true, 1));
         }
     }
 
@@ -239,7 +239,7 @@ export default class Base extends Component<BaseTypographyProps, BaseTypographyS
             window.cancelAnimationFrame(this.rafId);
         }
         return new Promise<void>(resolve => {
-            this.rafId = window.requestAnimationFrame(async ()=>{
+            this.rafId = window.requestAnimationFrame(async () => {
                 await this.getEllipsisState();
                 resolve();
             });
@@ -334,7 +334,7 @@ export default class Base extends Component<BaseTypographyProps, BaseTypographyS
         return defaultOpts;
     };
 
-    onHover = ()=>{
+    onHover = () => {
         const canUseCSSEllipsis = this.canUseCSSEllipsis();
         if (canUseCSSEllipsis) {
             const { rows, suffix, pos } = this.getEllipsisOpt();
@@ -349,7 +349,7 @@ export default class Base extends Component<BaseTypographyProps, BaseTypographyS
         }
     }
 
-    getEllipsisState = async ()=> {
+    getEllipsisState = async () => {
         const { rows, suffix, pos } = this.getEllipsisOpt();
         const { children, strong } = this.props;
         // wait until element mounted
@@ -372,7 +372,7 @@ export default class Base extends Component<BaseTypographyProps, BaseTypographyS
 
         // If children is null, css/js truncated flag isTruncate is false
         if (isNull(children)) {
-            return new Promise<void>(resolve=>{
+            return new Promise<void>(resolve => {
                 this.setState({
                     isTruncated: false,
                     isOverflowed: false
@@ -409,7 +409,7 @@ export default class Base extends Component<BaseTypographyProps, BaseTypographyS
             pos,
             strong
         );
-        return new Promise<void>(resolve=>{
+        return new Promise<void>(resolve => {
             this.setState({
                 isOverflowed: false,
                 ellipsisContent: content,
@@ -627,7 +627,7 @@ export default class Base extends Component<BaseTypographyProps, BaseTypographyS
         const iconSize: Size = realSize === 'small' ? 'small' : 'default';
         return (
             <span className={`${prefixCls}-icon`} x-semi-prop="icon">
-                {isSemiIcon(icon) ? React.cloneElement((icon as React.ReactElement), { size: iconSize }) : icon}
+                {isSemiIcon(icon) ? React.cloneElement((icon as React.ReactElement<any>), { size: iconSize }) : icon}
             </span>
         );
     }
@@ -750,7 +750,7 @@ export default class Base extends Component<BaseTypographyProps, BaseTypographyS
         );
         if (this.props.ellipsis) {
             return (
-                <ResizeObserver onResize={(...args)=>{
+                <ResizeObserver onResize={(...args) => {
                     if (this.observerTakingEffect) {
                         this.onResize(...args);
                     }

+ 196 - 0
scripts/react19-build.js

@@ -0,0 +1,196 @@
+#!/usr/bin/env node
+
+const fs = require('fs');
+const path = require('path');
+const glob = require('glob');
+
+/**
+ * React 19 构建脚本
+ * 只进行版本激活,不进行其他操作
+ */
+
+const REACT_18_START = /\/\* REACT_18_START \*\/([\s\S]*?)\/\* REACT_18_END \*\//g;
+const REACT_19_START = /\/\* REACT_19_START \*\/([\s\S]*?)\/\* REACT_19_END \*\//g;
+
+const outputDir = 'packages/semi-ui-19';
+
+function processReact19Code(content) {
+    // 移除 React 18 的代码块
+    content = content.replace(REACT_18_START, '');
+    
+    // 启用 React 19 的代码块(移除注释标记和注释符号)
+    content = content.replace(REACT_19_START, (match, codeBlock) => {
+        // 移除每行开头的 // (注意处理缩进)
+        return codeBlock.replace(/^(\s*)\/\/ /gm, '$1').trim();
+    });
+    
+    return content;
+}
+
+function processReact18Code(content) {
+    // 移除 React 19 的代码块  
+    content = content.replace(REACT_19_START, '');
+    
+    // 启用 React 18 的代码块(仅移除标记)
+    content = content.replace(REACT_18_START, (match, codeBlock) => {
+        return codeBlock.trim();
+    });
+    
+    return content;
+}
+
+function generateReact19PackageJson(sourcePackageJsonPath, outputPackageJsonPath) {
+    const packageJson = JSON.parse(fs.readFileSync(sourcePackageJsonPath, 'utf8'));
+    
+    // 修改包名
+    packageJson.name = '@douyinfe/semi-ui-19';
+    
+    // 修改描述
+    packageJson.description = packageJson.description + ' (React 19 Compatible)';
+    
+    // 添加 React 19 相关的 peerDependencies
+    if (!packageJson.peerDependencies) {
+        packageJson.peerDependencies = {};
+    }
+    packageJson.peerDependencies.react = '^19.0.0';
+    packageJson.peerDependencies['react-dom'] = '^19.0.0';
+    
+    // 添加 React 19 相关的 devDependencies
+    if (!packageJson.devDependencies) {
+        packageJson.devDependencies = {};
+    }
+    packageJson.devDependencies['@types/react'] = '^19.0.0';
+    packageJson.devDependencies['@types/react-dom'] = '^19.0.0';
+    packageJson.devDependencies['react'] = '^19.0.0';
+    packageJson.devDependencies['react-dom'] = '^19.0.0';
+    
+    // 更新 null-loader 版本以兼容 webpack 5
+    packageJson.devDependencies['null-loader'] = '4.0.1';
+    
+    // 添加 React 19 相关的 engines
+    if (!packageJson.engines) {
+        packageJson.engines = {};
+    }
+    packageJson.engines.node = '>=18.0.0';
+    
+    // 添加 React 19 相关的 keywords
+    if (!packageJson.keywords) {
+        packageJson.keywords = [];
+    }
+    if (!packageJson.keywords.includes('react-19')) {
+        packageJson.keywords.push('react-19', 'react19');
+    }
+    
+    // 写入文件
+    fs.writeFileSync(outputPackageJsonPath, JSON.stringify(packageJson, null, 2));
+    console.log('  ✅ 生成 package.json (React 19 版本)');
+}
+
+/**
+ * 生成 React 19 版本的 package.json
+ */
+function generatePackageJson() {
+    const sourcePackageJsonPath = 'packages/semi-ui/package.json';
+    const outputPackageJsonPath = path.join(outputDir, 'package.json');
+    
+    if (fs.existsSync(sourcePackageJsonPath)) {
+        generateReact19PackageJson(sourcePackageJsonPath, outputPackageJsonPath);
+    } else {
+        console.error('❌ 源 package.json 文件不存在');
+        process.exit(1);
+    }
+}
+function buildForReact19() {
+    const sourceDir = 'packages/semi-ui';
+    const targetDir = outputDir;
+    
+    // 需要过滤的目录
+    const excludeDirs = ['node_modules', 'lib', '.git', 'dist', 'build'];
+    
+    // 确保输出目录存在
+    if (!fs.existsSync(targetDir)) {
+        fs.mkdirSync(targetDir, { recursive: true });
+    }
+    
+    // 递归复制所有文件和目录
+    function copyDirectory(src, dest) {
+        if (!fs.existsSync(dest)) {
+            fs.mkdirSync(dest, { recursive: true });
+        }
+        
+        const items = fs.readdirSync(src);
+        
+        items.forEach(item => {
+            // 跳过需要过滤的目录
+            if (excludeDirs.includes(item)) {
+                console.log(`  ⏭️  跳过目录 ${item}`);
+                return;
+            }
+            
+            const srcPath = path.join(src, item);
+            const destPath = path.join(dest, item);
+            const stat = fs.statSync(srcPath);
+            
+            if (stat.isDirectory()) {
+                // 递归复制子目录
+                copyDirectory(srcPath, destPath);
+            } else {
+                // 复制文件
+                const relativePath = path.relative(sourceDir, srcPath);
+                const isTsFile = /\.(ts|tsx)$/.test(item);
+                const isScssScript = item.includes('compileScss.js');
+
+                if (isTsFile || isScssScript) {
+                    // 对 .ts/.tsx 文件进行 React 19 转换
+                    const content = fs.readFileSync(srcPath, 'utf8');
+                    const processedContent = processReact19Code(content);
+                    fs.writeFileSync(destPath, processedContent);
+                } else {
+                    // 直接复制其他文件
+                    fs.copyFileSync(srcPath, destPath);
+                }
+            }
+        });
+    }
+    
+    // 开始复制整个目录
+    console.log('开始复制所有文件...');
+    copyDirectory(sourceDir, targetDir);
+    
+    // 生成 React 19 版本的 package.json(覆盖已复制的)
+    generatePackageJson();
+    
+    console.log('✅ React 19 版本构建完成');
+}
+
+function buildForReact18() {
+    const sourcePattern = 'packages/semi-ui/**/*.{ts,tsx}';
+    
+    const files = glob.sync(sourcePattern);
+    
+    files.forEach(filePath => {
+        const content = fs.readFileSync(filePath, 'utf8');
+        
+        // 只处理版本激活
+        const processedContent = processReact18Code(content);
+        
+        // 直接覆盖原文件
+        fs.writeFileSync(filePath, processedContent);
+    });
+    
+    console.log('✅ React 18 版本构建完成');
+}
+
+// 命令行参数处理
+const args = process.argv.slice(2);
+const version = args[0];
+
+if (version === '19') {
+    buildForReact19();
+} else if (version === '18') {
+    buildForReact18();
+} else {
+    console.log('用法: node scripts/react19-build.js [18|19]');
+    console.log('  18: 构建 React 18 兼容版本');
+    console.log('  19: 构建 React 19 兼容版本到 packages/semi-ui-19');
+} 

+ 13 - 10
scripts/version.js

@@ -6,17 +6,20 @@ const findLatestVersion = (versionSet) => {
 };
 
 const getAlphaVersion = (versionSet) => {
-    let latestVersion = findLatestVersion(versionSet);
-    const nextBetaVersion = semver.inc(latestVersion, 'preminor', 'beta');
-    // 如果已经有beta版本了,那么就得发下个版本的alpla版
-    if (versionSet.has(nextBetaVersion)) {
-        latestVersion = semver.inc(latestVersion, 'minor');
+    // 1. 找到最新的正式版(不带 pre-release 的)
+    const arr = Array.isArray(versionSet) ? versionSet : [...versionSet];
+    const validVersions = arr.filter(v => semver.valid(v));
+    validVersions.sort(semver.rcompare);
+    const latestStable = validVersions.find(v => !semver.prerelease(v));
+    // 2. 计算下一个 minor 的 alpha.0
+    const nextMinor = semver.inc(latestStable, 'minor');
+    let nextAlpha = `${nextMinor}-alpha.0`;
+    // 3. 如果已存在,则递增 alpha 号
+    while (versionSet.has(nextAlpha)) {
+        // 取当前 alpha 号,递增
+        nextAlpha = semver.inc(nextAlpha, 'prerelease', 'alpha');
     }
-    let nextVersion = semver.inc(latestVersion, 'preminor', 'alpha');
-    while (versionSet.has(nextVersion)) {
-        nextVersion = semver.inc(nextVersion, 'prerelease', 'alpha');
-    }
-    return nextVersion;
+    return nextAlpha;
 };
 
 const getBetaVersion = (versionSet) => {

+ 1 - 104
yarn.lock

@@ -1585,25 +1585,11 @@
     "@douyinfe/semi-animation-styled" "2.65.0"
     classnames "^2.2.6"
 
-"@douyinfe/[email protected]":
-  version "2.87.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.87.1.tgz#09116a713a786bfaaea7feff307e0fb57d5fd651"
-  integrity sha512-txcIFMIgQyaoIxgAfJe7V95B2mqKxN5eeCY4+TiMWr8cDaD13TxDe41VZ7crHhKMwV3Ija1iIeXrKFtIEVC6+Q==
-  dependencies:
-    "@douyinfe/semi-animation" "2.87.1"
-    "@douyinfe/semi-animation-styled" "2.87.1"
-    classnames "^2.2.6"
-
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.65.0.tgz#8c56047a5704a45b05cc9809a2a126cc24526ea1"
   integrity sha512-YFF8Ptcz/jwS0phm28XZV7ROqMQ233sjVR0Uy33FImCITr6EAPe5wcCeEmzVZoYS7x3tUFR30SF+0hSO01rQUg==
 
-"@douyinfe/[email protected]":
-  version "2.87.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.87.1.tgz#ae0d5452f8ef448744f1cbe7a7e7f360ee8c8a2d"
-  integrity sha512-Ug8estsobxUk9DHX4JS/Y/vJabDoq9vwn+yhMHn+GURbyPJnjMY18TCgp1MlAyhFMAxrzgK2qXzBSgA33hdfUA==
-
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.65.0.tgz#f544a6b420c3e948c09836019e6b63f1382cd12c"
@@ -1611,13 +1597,6 @@
   dependencies:
     bezier-easing "^2.1.0"
 
-"@douyinfe/[email protected]":
-  version "2.87.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.87.1.tgz#6b6d759e5888e9bd7e4e83e5899cd3d599841d75"
-  integrity sha512-eTDGHJBQBmTtGOmrH1WZ93w4G6U4SQZ/7PgS8R6E9Y+Mz1Qq/qt05q27l7dkToZQKsFajkWRFgSoj57M+UOicw==
-  dependencies:
-    bezier-easing "^2.1.0"
-
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.65.0.tgz#20466a9b4baacdde2249930fb709ba035c5a7bea"
@@ -1637,26 +1616,6 @@
     remark-gfm "^4.0.0"
     scroll-into-view-if-needed "^2.2.24"
 
-"@douyinfe/[email protected]":
-  version "2.87.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.87.1.tgz#43813aea8ecb3a91631aa4fe0afde4715334ff59"
-  integrity sha512-40jDpa8xLmg9ZaPMT9cKZNP40/2dCamUBXF/poLGT1r5O4bdgJmDfhB7CbBRN5sFBxVzo+6x3VNPeMn9KYb7mQ==
-  dependencies:
-    "@douyinfe/semi-animation" "2.87.1"
-    "@douyinfe/semi-json-viewer-core" "2.87.1"
-    "@mdx-js/mdx" "^3.0.1"
-    async-validator "^3.5.0"
-    classnames "^2.2.6"
-    date-fns "^2.29.3"
-    date-fns-tz "^1.3.8"
-    fast-copy "^3.0.1 "
-    lodash "^4.17.21"
-    lottie-web "^5.12.2"
-    memoize-one "^5.2.1"
-    prismjs "^1.29.0"
-    remark-gfm "^4.0.0"
-    scroll-into-view-if-needed "^2.2.24"
-
 "@douyinfe/[email protected]", "@douyinfe/semi-icons@latest":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.65.0.tgz#af39cbd5431ebccedcf7d9ce689646e54bebc432"
@@ -1664,30 +1623,11 @@
   dependencies:
     classnames "^2.2.6"
 
-"@douyinfe/[email protected]", "@douyinfe/semi-icons@^2.0.0":
-  version "2.87.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.87.1.tgz#a71405c90d975d4dfe6725e00b78faeac69a13f6"
-  integrity sha512-AhmWv//CZml4/5wKGjaqBSvCVVuv7gauOE2+Rz9UZoLRBzF0xROsXDnP42/SX42ltbYuBl7xbFWVNRpSN9onHA==
-  dependencies:
-    classnames "^2.2.6"
-
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.65.0.tgz#9916c540c91222a1d9f48cd34a941d28b8a05d2f"
   integrity sha512-1IhOztyBYiSu8WrcvN+oWWtcJTC9+x6zbnYtufx4ToISs5UO1te1PQofABpkDzIJYFtW9yYLxg4uoL4wGjqYMA==
 
-"@douyinfe/[email protected]":
-  version "2.87.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.87.1.tgz#aa8e0172c05fe9784e3cce9c580f1ab3c0b5c01d"
-  integrity sha512-UzPhA1HTHiY2hLewqObFW8VnQ9d0dIdruWz3GVSyWhwBpTmWBkOAKi53mtIab+wjlDr7E1HwiMMKnXVHl2EzEg==
-
-"@douyinfe/[email protected]":
-  version "2.87.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-json-viewer-core/-/semi-json-viewer-core-2.87.1.tgz#7941292759f89f581c96f148184060811abc43a1"
-  integrity sha512-0Q30Hfz8a3d9cQC2Hxvx7L1EOnOOxTVbe5xKcD38zW5IWdJHPENjauCnGiDJvAyB+cUj0BNSSN82kusIC3JPbQ==
-  dependencies:
-    jsonc-parser "^3.3.1"
-
 "@douyinfe/[email protected]":
   version "2.23.2"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-scss-compile/-/semi-scss-compile-2.23.2.tgz#30884bb194ee9ae1e81877985e5663c3297c1ced"
@@ -1759,39 +1699,6 @@
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.61.0.tgz#a7e9bf9534721c12af1d0eeb5d5a2de615896a23"
   integrity sha512-obn/DOw4vZyKFAlWvZxHTpBLAK9FO9kygTSm2GROgvi+UDB2PPU6l20cuUCsdGUNWJRSqYlTTVZ1tNYIyFZ5Sg==
 
-"@douyinfe/[email protected]":
-  version "2.87.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.87.1.tgz#9316f31a990ff578cfb83dba0d3d35e9b0a8e173"
-  integrity sha512-70nWvI6mzg/Z6ijLygmlZbsMXiRDiho8qTiB+1EPRFJlksLxohwtF1wuQGegiUHc0c2+n7jLWXQxX4u5J+X9ig==
-
-"@douyinfe/semi-ui@^2.0.0":
-  version "2.87.1"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.87.1.tgz#b7a70b3b1d4fac31c753c2912484638455f6435a"
-  integrity sha512-Znu5v2Qn+6ncBQs482EzDIliuKK6omkA7P7EkXbW6olPAdlkygm93YUd32KAO81JXzMVOC260N288YeSjgk8kw==
-  dependencies:
-    "@dnd-kit/core" "^6.0.8"
-    "@dnd-kit/sortable" "^7.0.2"
-    "@dnd-kit/utilities" "^3.2.1"
-    "@douyinfe/semi-animation" "2.87.1"
-    "@douyinfe/semi-animation-react" "2.87.1"
-    "@douyinfe/semi-foundation" "2.87.1"
-    "@douyinfe/semi-icons" "2.87.1"
-    "@douyinfe/semi-illustrations" "2.87.1"
-    "@douyinfe/semi-theme-default" "2.87.1"
-    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"
-    fast-copy "^3.0.1 "
-    jsonc-parser "^3.3.1"
-    lodash "^4.17.21"
-    prop-types "^15.7.2"
-    react-resizable "^3.0.5"
-    react-window "^1.8.2"
-    scroll-into-view-if-needed "^2.2.24"
-    utility-types "^3.10.0"
-
 "@douyinfe/semi-ui@latest":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.65.0.tgz#295eb0dd8e9e961adb4ddd7c7bbce3468d1b7430"
@@ -5931,16 +5838,11 @@
   resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
   integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==
 
-"@types/minimatch@*", "@types/minimatch@^5.1.2":
+"@types/minimatch@*", "@types/minimatch@5.1.2", "@types/minimatch@^3.0.3", "@types/minimatch@^5.1.2":
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
   integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
 
-"@types/minimatch@^3.0.3":
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
-  integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
-
 "@types/minimist@^1.2.0":
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e"
@@ -12249,11 +12151,6 @@ eslint-plugin-react@^7.20.6, eslint-plugin-react@^7.24.0:
     string.prototype.matchall "^4.0.11"
     string.prototype.repeat "^1.0.0"
 
-eslint-plugin-semi-design@^2.33.0:
-  version "2.87.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-semi-design/-/eslint-plugin-semi-design-2.87.1.tgz#459b91ad9b73b08c442246821ba4de2c4fe0e58d"
-  integrity sha512-xDv9d9oNTM2rV6+iKd5FsjM9Rb9/sPAdXGVStWWeNd3FB++cRZK65KgKWWw/avO+tt34y3/n4SkTeeowTAGGYw==
-
 eslint-rule-composer@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9"