فهرست منبع

feat: new tooltip smart pos adjust (#1176)

* feat: new tooltip smart pos adjust

* feat: add margin to tooltips api

* test: add autoAdjust demo for convenvice visual test on chromatic

* feat: add off screen render to calcPosition

* feat: new tooltip smart pos adjust

* feat: remove leftopover and leftbottomover overflow judge

* feat: improve cypress test

* test: improve cypress

* feat: Correct spelling in tooltip foundation

Co-authored-by: pointhalo <[email protected]>
YannLynn 2 سال پیش
والد
کامیت
fa962bc7eb

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

@@ -408,6 +408,7 @@ import { Popconfirm, Tooltip, Button } from '@douyinfe/semi-ui';
 | clickToHide | Whether to automatically close the elastic layer when clicking on the floating layer and any element inside | boolean | false | **0.24.0** |
 | disableFocusListener | When trigger is `hover`, does not respond to the keyboard focus popup event, see details at [issue#977](https://github.com/DouyinFE/semi-design/issues/977) | boolean | false | **2.17.0** |
 | getPopupContainer | Specifies the parent DOM, and the bullet layer will be rendered to the DOM | () => HTMLElement | () => document.body |
+| margin | Calculate the added redundancy value when overflowing, see [issue#549](https://github.com/DouyinFE/semi-design/issues/549) | number | { marginLeft: number; marginTop: number; marginRight: number; marginBottom: number } | 0 |  **2.23.0**|
 | mouseEnterDelay | After the mouse is moved in, the display delay time, in milliseconds (only effective when the trigger is hover/focus) | number | 50 |  |
 | mouseLeaveDelay | The time for the delay to disappear after the mouse is moved out, in milliseconds (only effective when the trigger is hover/focus), and is not less than mouseEnterDelay | number | 50 |  |
 | motion | Whether to show the pop-up motion | boolean | true |  |

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

@@ -441,6 +441,7 @@ function Demo() {
 | clickToHide | 点击弹出层及内部任一元素时是否自动关闭弹层 | boolean | false | **0.24.0** |
 | disableFocusListener | trigger为`hover`时,不响应键盘聚焦弹出浮层事件,详见[issue#977](https://github.com/DouyinFE/semi-design/issues/977) | boolean | false | **2.17.0** |
 | getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative` | function():HTMLElement | () => document.body |  |
+| margin | 计算溢出时的增加的冗余值,详见[issue#549](https://github.com/DouyinFE/semi-design/issues/549) | number | { marginLeft: number; marginTop: number; marginRight: number; marginBottom: number } | 0 |  **2.23.0**|
 | mouseEnterDelay | 鼠标移入后,延迟显示的时间,单位毫秒(仅当 trigger 为 hover/focus 时生效) | number | 50 |  |
 | mouseLeaveDelay | 鼠标移出后,延迟消失的时间,单位毫秒(仅当 trigger 为 hove/focus 时生效),不小于 mouseEnterDelay | number | 50 |  |
 | motion | 是否展示弹出层动画 | boolean | true |  |

+ 1 - 1
cypress/integration/anchor.spec.js

@@ -19,7 +19,7 @@ describe('anchor', () => {
         cy.get('.semi-anchor-link-title-active').contains('doc1');
         cy.wait(500);
         cy.get('#box').scrollTo('top');
-        cy.get('h1').contains('whatever').click();
+        cy.get('h1').contains('whatever').click({ force: true });
     });
 
     it('click', () => {

+ 3 - 3
cypress/integration/tabs.spec.js

@@ -25,10 +25,10 @@ describe('tabs', () => {
 
     it('collapse', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=tabs--collapse-tabs&args=&viewMode=story');
-        cy.viewport(800, 800);
+        cy.viewport(800, 1600);
         cy.get('.semi-tabs-content').eq(0).contains('Content of card tab 0');
-        cy.get('.semi-button').eq(1).trigger('mouseover');
-        cy.get('.semi-dropdown').contains('Tab-6').click();
+        cy.get('.semi-button').eq(1).trigger('mouseover', { force: true });
+        cy.get('.semi-dropdown').contains('Tab-6').click({ force: true });
         cy.get('.semi-tabs-content').eq(0).contains('Content of card tab 6');
 
         // Tab-10 visible

+ 26 - 1
cypress/integration/tooltip.spec.js

@@ -31,7 +31,7 @@ describe('tooltip', () => {
     it('position with over autoAdjustOverflow', () => {
         const viewportWidth = 600;
         const viewportHeight = 400;
-        const overList = ['leftTopOver', 'rightTopOver', 'rightBottomOver', 'leftBottomOver',];
+        const overList = ['leftTopOver', 'rightTopOver', 'rightBottomOver', 'leftBottomOver'];
 
         cy.visit('http://127.0.0.1:6006/iframe.html?id=tooltip--left-top-over-demo&args=&viewMode=story');
         cy.viewport(viewportWidth, viewportHeight);
@@ -120,4 +120,29 @@ describe('tooltip', () => {
             cy.get('[x-placement="'+ topAndLeft[i] +'"]').should('have.length', 1);
         }
     });
+
+    it('test position in tblr, fine tune to other directions', () => {
+        const viewportWidth = 1000;
+        const viewportHeight = 800;
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=tooltip--adjust-pos-if-need-tblr&args=&viewMode=story');
+        cy.viewport(viewportWidth, viewportHeight);
+        cy.get('div .semi-tag').eq(0).click({ force: true });
+        const posRow = ['top', 'bottom', 'left', 'right'];
+        const posList1 = ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'];
+        const posList2 = ['leftTop', 'leftBottom', 'rightTop', 'rightBottom'];
+
+        for (let i = 0; i < posList1.length; i++) {
+            cy.get('div .semi-tag').contains(posRow[0] + ' to ' + posList1[i]).click({ force: true });
+            cy.get('[x-placement="' + posList1[i] + '"]').should('have.length', 1);
+            cy.get('div .semi-tag').contains(posRow[1] + ' to ' + posList1[i]).click({ force: true });
+            cy.get('[x-placement="' + posList1[i] + '"]').should('have.length', 1);
+        }
+
+        for (let i = 0; i < posList2.length; i++) {
+            cy.get('div .semi-tag').contains(posRow[2] + ' to ' + posList2[i]).click({ force: true });
+            cy.get('[x-placement="' + posList2[i] + '"]').should('have.length', 1);
+            cy.get('div .semi-tag').contains(posRow[3] + ' to ' + posList2[i]).click({ force: true });
+            cy.get('[x-placement="' + posList2[i] + '"]').should('have.length', 1);
+        }
+    });
 });

+ 1 - 0
packages/semi-foundation/tooltip/constants.ts

@@ -39,6 +39,7 @@ const numbers = {
     MOUSE_ENTER_DELAY: 50,
     MOUSE_LEAVE_DELAY: 50,
     SPACING: 8, // Values are consistent with spacing-tight in scss
+    MARGIN: 0,
 } as const;
 
 export { cssClasses, strings, numbers };

+ 318 - 78
packages/semi-foundation/tooltip/foundation.ts

@@ -133,11 +133,26 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
     unBindResizeEvent() {
         this._adapter.unregisterResizeHandler(this.onResize);
     }
-
+  
     removePortal = () => {
         this._adapter.removePortal();
     }
 
+    _adjustPos(position = '', isVertical = false, adjustType = 'reverse', concatPos?: any) {
+        switch (adjustType) {
+            case 'reverse':
+                return this._reversePos(position, isVertical);
+            case 'expand':
+                // only happens when position is top/bottom/left/right
+                return this._expandPos(position, concatPos);
+            case 'reduce':
+                // only happens when position other than top/bottom/left/right
+                return this._reducePos(position);
+            default:
+                return this._reversePos(position, isVertical);
+        }
+    }
+
     _reversePos(position = '', isVertical = false) {
         if (isVertical) {
             if (REGS.TOP.test(position)) {
@@ -153,6 +168,16 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
         return position;
     }
 
+    _expandPos(position = '', concatPos: string) {
+        return position.concat(concatPos);
+    }
+
+    _reducePos(position = '') {
+        // if cur position consists of two directions, remove the last position
+        const found = ['Top', 'Bottom', 'Left', 'Right'].find(pos => position.endsWith(pos));
+        return found ? position.replace(found, ''): position;
+    }
+
     clearDelayTimer() {
         if (this._timer) {
             clearTimeout(this._timer);
@@ -285,9 +310,7 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
             this._togglePortalVisible(true);
         });
 
-        const position = this.calcPosition(null, null, null, false);
-
-        this._adapter.insertPortal(content, position);
+        this._adapter.insertPortal(content, { left: -9990, top: -9999 }); // offscreen rendering
 
         if (trigger === 'custom') {
             // eslint-disable-next-line
@@ -358,14 +381,15 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
         return null;
     }
 
-    calcPosStyle(triggerRect: DOMRect, wrapperRect: DOMRect, containerRect: PopupContainerDOMRect, position?: Position, spacing?: number) {
-        triggerRect = (isEmpty(triggerRect) ? triggerRect : this._adapter.getTriggerBounding()) || { ...defaultRect as any };
-        containerRect = (isEmpty(containerRect) ? containerRect : this._adapter.getPopupContainerRect()) || {
+    calcPosStyle(props: {triggerRect: DOMRect; wrapperRect: DOMRect; containerRect: PopupContainerDOMRect; position?: Position; spacing?: number; isOverFlow?: [boolean, boolean]}) {
+        const { spacing, isOverFlow } = props;
+        const triggerRect = (isEmpty(props.triggerRect) ? props.triggerRect : this._adapter.getTriggerBounding()) || { ...defaultRect as any };
+        const containerRect = (isEmpty(props.containerRect) ? props.containerRect : this._adapter.getPopupContainerRect()) || {
             ...defaultRect,
         };
-        wrapperRect = (isEmpty(wrapperRect) ? wrapperRect : this._adapter.getWrapperBounding()) || { ...defaultRect as any };
+        const wrapperRect = (isEmpty(props.wrapperRect) ? props.wrapperRect : this._adapter.getWrapperBounding()) || { ...defaultRect as any };
         // eslint-disable-next-line
-        position = position != null ? position : this.getProp('position');
+        const position = props.position != null ? props.position : this.getProp('position');
         // eslint-disable-next-line
         const SPACING = spacing != null ? spacing : this.getProp('spacing');
         const { arrowPointAtCenter, showArrow, arrowBounding } = this.getProps();
@@ -389,67 +413,104 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
         const offsetXWithArrow = positionOffsetX + horizontalArrowWidth / 2;
         const offsetYWithArrow = positionOffsetY + verticalArrowHeight / 2;
 
+        const heightDifference = wrapperRect.height - containerRect.height;
+        const widthDifference = wrapperRect.width - containerRect.width;
+
+        const offsetHeight = heightDifference > 0 ? heightDifference : 0;
+        const offsetWidth = widthDifference > 0 ? widthDifference : 0;
+        const isHeightOverFlow = isOverFlow && isOverFlow[0];
+        const isWidthOverFlow = isOverFlow && isOverFlow[1];
+
+        const isTriggerNearLeft = middleX - containerRect.left < containerRect.right - middleX;
+        const isTriggerNearTop = middleY - containerRect.top < containerRect.bottom - middleY;
+
+
         switch (position) {
             case 'top':
-                left = middleX;
-                top = triggerRect.top - SPACING;
+                // left = middleX;
+                // top = triggerRect.top - SPACING;
+                left = isWidthOverFlow ? (isTriggerNearLeft ? containerRect.left + wrapperRect.width / 2 : containerRect.right - wrapperRect.width / 2 + offsetWidth): middleX;
+                top = isHeightOverFlow ? containerRect.bottom + offsetHeight : triggerRect.top - SPACING;
                 translateX = -0.5;
                 translateY = -1;
                 break;
             case 'topLeft':
-                left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
-                top = triggerRect.top - SPACING;
+                // left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
+                // top = triggerRect.top - SPACING;
+                left = isWidthOverFlow ? containerRect.left : (pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left);
+                top = isHeightOverFlow ? containerRect.bottom + offsetHeight : triggerRect.top - SPACING;
                 translateY = -1;
                 break;
             case 'topRight':
-                left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
-                top = triggerRect.top - SPACING;
+                // left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
+                // top = triggerRect.top - SPACING;
+                left = isWidthOverFlow ? containerRect.right + offsetWidth : (pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right);
+                top = isHeightOverFlow ? containerRect.bottom + offsetHeight : triggerRect.top - SPACING;
                 translateY = -1;
                 translateX = -1;
                 break;
             case 'left':
-                left = triggerRect.left - SPACING;
-                top = middleY;
+                // left = triggerRect.left - SPACING;
+                // top = middleY;
+                // left = isWidthOverFlow? containerRect.right - SPACING : triggerRect.left - SPACING;
+                left = isWidthOverFlow ? containerRect.right + offsetWidth - SPACING + offsetXWithArrow : triggerRect.left - SPACING;
+                top = isHeightOverFlow ? (isTriggerNearTop ? containerRect.top + wrapperRect.height / 2 : containerRect.bottom - wrapperRect.height / 2 + offsetHeight): middleY;
                 translateX = -1;
                 translateY = -0.5;
                 break;
             case 'leftTop':
-                left = triggerRect.left - SPACING;
-                top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
+                // left = triggerRect.left - SPACING;
+                // top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
+                left = isWidthOverFlow ? containerRect.right + offsetWidth - SPACING + offsetXWithArrow : triggerRect.left - SPACING;
+                top = isHeightOverFlow ? containerRect.top : (pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top);
                 translateX = -1;
                 break;
             case 'leftBottom':
-                left = triggerRect.left - SPACING;
-                top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
+                // left = triggerRect.left - SPACING;
+                // top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
+                left = isWidthOverFlow ? containerRect.right + offsetWidth - SPACING + offsetXWithArrow: triggerRect.left - SPACING;
+                top = isHeightOverFlow ? containerRect.bottom + offsetHeight: (pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom);
                 translateX = -1;
                 translateY = -1;
                 break;
             case 'bottom':
-                left = middleX;
-                top = triggerRect.top + triggerRect.height + SPACING;
+                // left = middleX;
+                // top = triggerRect.top + triggerRect.height + SPACING;
+                left = isWidthOverFlow ? (isTriggerNearLeft ? containerRect.left + wrapperRect.width / 2 : containerRect.right - wrapperRect.width / 2 + offsetWidth): middleX;
+                top = isHeightOverFlow ? containerRect.top + offsetYWithArrow - SPACING: triggerRect.top + triggerRect.height + SPACING;
                 translateX = -0.5;
                 break;
             case 'bottomLeft':
-                left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
-                top = triggerRect.bottom + SPACING;
+                // left = pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left;
+                // top = triggerRect.bottom + SPACING;
+                left = isWidthOverFlow ? containerRect.left : (pointAtCenter ? middleX - offsetXWithArrow : triggerRect.left);
+                top = isHeightOverFlow ? containerRect.top + offsetYWithArrow - SPACING : triggerRect.top + triggerRect.height + SPACING;
                 break;
             case 'bottomRight':
-                left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
-                top = triggerRect.bottom + SPACING;
+                // left = pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right;
+                // top = triggerRect.bottom + SPACING;
+                left = isWidthOverFlow ? containerRect.right + offsetWidth : (pointAtCenter ? middleX + offsetXWithArrow : triggerRect.right);
+                top = isHeightOverFlow ? containerRect.top + offsetYWithArrow - SPACING : triggerRect.top + triggerRect.height + SPACING;
                 translateX = -1;
                 break;
             case 'right':
-                left = triggerRect.right + SPACING;
-                top = middleY;
+                // left = triggerRect.right + SPACING;
+                // top = middleY;
+                left = isWidthOverFlow ? containerRect.left - SPACING + offsetXWithArrow : triggerRect.right + SPACING;
+                top = isHeightOverFlow ? (isTriggerNearTop ? containerRect.top + wrapperRect.height / 2 : containerRect.bottom - wrapperRect.height / 2 + offsetHeight) : middleY;
                 translateY = -0.5;
                 break;
             case 'rightTop':
-                left = triggerRect.right + SPACING;
-                top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
+                // left = triggerRect.right + SPACING;
+                // top = pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top;
+                left = isWidthOverFlow ? containerRect.left - SPACING + offsetXWithArrow : triggerRect.right + SPACING;
+                top = isHeightOverFlow ? containerRect.top : (pointAtCenter ? middleY - offsetYWithArrow : triggerRect.top);
                 break;
             case 'rightBottom':
-                left = triggerRect.right + SPACING;
-                top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
+                // left = triggerRect.right + SPACING;
+                // top = pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom;
+                left = isWidthOverFlow ? containerRect.left - SPACING + offsetXWithArrow : triggerRect.right + SPACING;
+                top = isHeightOverFlow ? containerRect.bottom + offsetHeight : (pointAtCenter ? middleY + offsetYWithArrow : triggerRect.bottom);
                 translateY = -1;
                 break;
             case 'leftTopOver':
@@ -574,18 +635,18 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
 
         // console.log('containerRect: ', containerRect, 'triggerRect: ', triggerRect, 'wrapperRect: ', wrapperRect);
 
-        let style = this.calcPosStyle(triggerRect, wrapperRect, containerRect);
+        let style = this.calcPosStyle({ triggerRect, wrapperRect, containerRect });
 
         let position = this.getProp('position');
 
         if (this.getProp('autoAdjustOverflow')) {
             // console.log('style: ', style, '\ntriggerRect: ', triggerRect, '\nwrapperRect: ', wrapperRect);
-            const adjustedPos = this.adjustPosIfNeed(position, style, triggerRect, wrapperRect, containerRect);
+            const { position: adjustedPos, isHeightOverFlow, isWidthOverFlow } = this.adjustPosIfNeed(position, style, triggerRect, wrapperRect, containerRect);
 
-            if (position !== adjustedPos) {
+            if (position !== adjustedPos || isHeightOverFlow || isWidthOverFlow) {
                 position = adjustedPos;
 
-                style = this.calcPosStyle(triggerRect, wrapperRect, containerRect, position);
+                style = this.calcPosStyle({ triggerRect, wrapperRect, containerRect, position, spacing: null, isOverFlow: [ isHeightOverFlow, isWidthOverFlow ] });
             }
         }
 
@@ -598,17 +659,66 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
     };
 
     isLR(position = '') {
-        return position.indexOf('left') === 0 || position.indexOf('right') === 0;
+        return position.includes('left') || position.includes('right');
     }
 
     isTB(position = '') {
-        return position.indexOf('top') === 0 || position.indexOf('bottom') === 0;
+        return position.includes('top') || position.includes('bottom');
+    }
+
+    isReverse(rowSpace: number, reverseSpace: number, size: number) {
+        // 原空间不足,反向空间足够
+        // Insufficient original space, enough reverse space
+        return rowSpace < size && reverseSpace > size;
+    }
+
+    isOverFlow(rowSpace: number, reverseSpace: number, size: number){
+        // 原空间且反向空间都不足
+        // The original space and the reverse space are not enough
+        return rowSpace < size && reverseSpace < size;
+    }
+
+    isHalfOverFlow(posSpace: number, negSpace: number, size: number){
+        // 正半空间或者负半空间不足,即表示有遮挡,需要偏移
+        // Insufficient positive half space or negative half space means that there is occlusion and needs to be offset
+        return posSpace < size || negSpace < size;
+    }
+
+    isHalfAllEnough(posSpace: number, negSpace: number, size: number){
+        // 正半空间和负半空间都足够,即表示可以从 topLeft/topRight 变成 top
+        // Both positive and negative half-spaces are sufficient, which means you can change from topLeft/topRight to top
+        return posSpace >= size || negSpace >= size;
+    }
+
+    getReverse(viewOverFlow: boolean, containerOverFlow: boolean, shouldReverseView: boolean, shouldReverseContainer: boolean) {
+        /**
+         * 基于视口和容器一起判断,以下几种情况允许从原方向转到反方向,以判断是否应该由top->bottom为例子
+         *
+         * 1. 视口上下空间不足 且 容器上空间❌下空间✅
+         * 2. 视口上空间❌下空间✅ 且 容器上下空间不足
+         * 3. 视口上空间❌下空间✅ 且 容器上空间❌下空间✅
+         * 
+         * Based on the judgment of the viewport and the container, the following situations are allowed to turn from the original direction to the opposite direction
+         * to judge whether it should be top->bottom as an example
+         * 1. There is insufficient space above and below the viewport and the space above the container ❌ the space below ✅
+         * 2. The space above the viewport ❌ the space below ✅ and the space above and below the container is insufficient
+         * 3. Viewport upper space ❌ lower space✅ and container upper space ❌ lower space✅
+         */
+        return (viewOverFlow && shouldReverseContainer) || (shouldReverseView && containerOverFlow) || (shouldReverseView && shouldReverseContainer);
     }
 
     // place the dom correctly
     adjustPosIfNeed(position: Position | string, style: Record<string, any>, triggerRect: DOMRect, wrapperRect: DOMRect, containerRect: PopupContainerDOMRect) {
         const { innerWidth, innerHeight } = window;
-        const { spacing } = this.getProps();
+        const { spacing, margin } = this.getProps();
+
+        const marginLeft = typeof margin === 'number' ? margin : margin.marginLeft;
+        const marginTop = typeof margin === 'number' ? margin : margin.marginTop;
+        const marginRight = typeof margin === 'number' ? margin : margin.marginRight;
+        const marginBottom = typeof margin === 'number' ? margin : margin.marginBottom;
+
+        let isHeightOverFlow = false;
+        let isWidthOverFlow = false;
 
         if (wrapperRect.width > 0 && wrapperRect.height > 0) {
             // let clientLeft = left + translateX * wrapperRect.width - containerRect.scrollLeft;
@@ -638,144 +748,274 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
 
             // The wrapperR ect.top|bottom equivalent cannot be directly used here for comparison, which is easy to cause jitter
 
-            const shouldReverseTop = clientTop < wrapperRect.height + spacing && restClientBottom > wrapperRect.height + spacing;
-            const shouldReverseLeft = clientLeft < wrapperRect.width + spacing && restClientRight > wrapperRect.width + spacing;
-            const shouldReverseBottom = restClientBottom < wrapperRect.height + spacing && clientTop > wrapperRect.height + spacing;
-            const shouldReverseRight = restClientRight < wrapperRect.width + spacing && clientLeft > wrapperRect.width + spacing;
+            // 基于视口的微调判断
+            // Fine-tuning judgment based on viewport
+            const shouldViewReverseTop = clientTop - marginTop < wrapperRect.height + spacing && restClientBottom - marginBottom > wrapperRect.height + spacing;
+            const shouldViewReverseLeft = clientLeft - marginLeft < wrapperRect.width + spacing && restClientRight - marginRight > wrapperRect.width + spacing;
+            const shouldViewReverseBottom = restClientBottom - marginBottom < wrapperRect.height + spacing && clientTop - marginTop > wrapperRect.height + spacing;
+            const shouldViewReverseRight = restClientRight - marginRight < wrapperRect.width + spacing && clientLeft - marginLeft > wrapperRect.width + spacing;
+            const shouldViewReverseTopOver = restClientTop - marginBottom< wrapperRect.height + spacing && clientBottom - marginTop> wrapperRect.height + spacing;
+            const shouldViewReverseBottomOver = clientBottom - marginTop < wrapperRect.height + spacing && restClientTop - marginBottom > wrapperRect.height + spacing;
+
+            const shouldViewReverseTopSide = restClientTop < wrapperRect.height && clientBottom > wrapperRect.height;
+            const shouldViewReverseBottomSide = clientBottom < wrapperRect.height && restClientTop > wrapperRect.height;
+            const shouldViewReverseLeftSide = restClientLeft < wrapperRect.width && clientRight > wrapperRect.width;
+            const shouldViewReverseRightSide = clientRight < wrapperRect.width && restClientLeft > wrapperRect.width;
+
             const shouldReverseTopOver = restClientTop < wrapperRect.height + spacing && clientBottom > wrapperRect.height + spacing;
             const shouldReverseBottomOver = clientBottom < wrapperRect.height + spacing && restClientTop > wrapperRect.height + spacing;
 
-            const shouldReverseTopSide = restClientTop < wrapperRect.height && clientBottom > wrapperRect.height;
-            const shouldReverseBottomSide = clientBottom < wrapperRect.height && restClientTop > wrapperRect.height;
-            const shouldReverseLeftSide = restClientLeft < wrapperRect.width && clientRight > wrapperRect.width;
-            const shouldReverseRightSide = clientRight < wrapperRect.width && restClientLeft > wrapperRect.width;
-
             const shouldReverseLeftOver = restClientLeft < wrapperRect.width && clientRight > wrapperRect.width;
             const shouldReverseRightOver = clientRight < wrapperRect.width && restClientLeft > wrapperRect.width;
 
+            // 基于容器的微调判断
+            // Fine-tuning judgment based on container
+            const clientTopInContainer = clientTop - containerRect.top;
+            const clientLeftInContainer = clientLeft - containerRect.left;
+            const clientBottomInContainer = clientTopInContainer + triggerRect.height;
+            const clientRightInContainer = clientLeftInContainer + triggerRect.width;
+
+            const restClientBottomInContainer = containerRect.bottom - clientBottom;
+            const restClientRightInContainer = containerRect.right - clientRight;
+            const restClientTopInContainer = restClientBottomInContainer + triggerRect.height;
+            const restClientLeftInContainer = restClientRightInContainer + triggerRect.width;
+
+            // 当原空间不足,反向空间足够时,可以反向。
+            // When the original space is insufficient and the reverse space is sufficient, the reverse can be performed.
+            const shouldContainerReverseTop = this.isReverse(clientTopInContainer - marginTop, restClientBottomInContainer - marginBottom, wrapperRect.height + spacing);
+            const shouldContainerReverseLeft = this.isReverse(clientLeftInContainer - marginLeft, restClientRightInContainer - marginRight, wrapperRect.width + spacing);
+            const shouldContainerReverseBottom = this.isReverse(restClientBottomInContainer - marginBottom, clientTopInContainer - marginTop, wrapperRect.height + spacing);
+            const shouldContainerReverseRight = this.isReverse(restClientRightInContainer - marginRight, clientLeftInContainer - marginLeft, wrapperRect.width + spacing);
+            const shouldContainerReverseTopOver = this.isReverse(restClientTopInContainer - marginBottom, clientBottomInContainer - marginTop, wrapperRect.height + spacing);
+            const shouldContainerReverseBottomOver = this.isReverse(clientBottomInContainer - marginTop, restClientTopInContainer - marginBottom, wrapperRect.height + spacing);
+
+            const shouldContainerReverseTopSide = this.isReverse(restClientTopInContainer, clientBottomInContainer, wrapperRect.height);
+            const shouldContainerReverseBottomSide = this.isReverse(clientBottomInContainer, restClientTopInContainer, wrapperRect.height);
+            const shouldContainerReverseLeftSide = this.isReverse(restClientLeftInContainer, clientRightInContainer, wrapperRect.width);
+            const shouldContainerReverseRightSide = this.isReverse(clientRightInContainer, restClientLeftInContainer, wrapperRect.width);
+
+            const halfHeight = triggerRect.height / 2;
+            const halfWidth = triggerRect.width / 2;
+
+            // 视口, 原空间与反向空间是否都不足判断
+            // Viewport, whether the original space and the reverse space are insufficient to judge
+            const isViewYOverFlow = this.isOverFlow(clientTop - marginTop, restClientBottom - marginBottom, wrapperRect.height + spacing);
+            const isViewXOverFlow = this.isOverFlow(clientLeft - marginLeft, restClientRight - marginRight, wrapperRect.width + spacing);
+            const isViewYOverFlowSide = this.isOverFlow(clientBottom - marginTop, restClientTop - marginBottom, wrapperRect.height + spacing);
+            const isViewXOverFlowSide = this.isOverFlow(clientRight - marginLeft, restClientLeft - marginRight, wrapperRect.width + spacing);
+            const isViewYOverFlowSideHalf = this.isHalfOverFlow(clientBottom - halfHeight, restClientTop - halfHeight, wrapperRect.height / 2);
+            const isViewXOverFlowSideHalf = this.isHalfOverFlow(clientRight - halfWidth, restClientLeft - halfWidth, wrapperRect.width / 2);
+            const isViewYEnoughSideHalf = this.isHalfAllEnough(clientBottom - halfHeight, restClientTop - halfHeight, wrapperRect.height / 2);
+            const isViewXEnoughSideHalf = this.isHalfAllEnough(clientRight - halfWidth, restClientLeft - halfWidth, wrapperRect.width / 2);
+
+            // 容器, 原空间与反向空间是否都不足判断
+            // container, whether the original space and the reverse space are insufficient to judge
+            const isContainerYOverFlow = this.isOverFlow(clientTopInContainer - marginTop, restClientBottomInContainer - marginBottom, wrapperRect.height + spacing);
+            const isContainerXOverFlow = this.isOverFlow(clientLeftInContainer - marginLeft, restClientRightInContainer - marginRight, wrapperRect.width + spacing);
+            const isContainerYOverFlowSide = this.isOverFlow(clientBottomInContainer - marginTop, restClientTopInContainer - marginBottom, wrapperRect.height + spacing);
+            const isContainerXOverFlowSide = this.isOverFlow(clientRightInContainer - marginLeft, restClientLeftInContainer - marginRight, wrapperRect.width + spacing);
+            const isContainerYOverFlowSideHalf = this.isHalfOverFlow(clientBottomInContainer - halfHeight, restClientTopInContainer - halfHeight, wrapperRect.height / 2);
+            const isContainerXOverFlowSideHalf = this.isHalfOverFlow(clientRightInContainer - halfWidth, restClientLeftInContainer - halfWidth, wrapperRect.width / 2);
+            const isContainerYEnoughSideHalf = this.isHalfAllEnough(clientBottomInContainer - halfHeight, restClientTopInContainer - halfHeight, wrapperRect.height / 2);
+            const isContainerXEnoughSideHalf = this.isHalfAllEnough(clientRightInContainer - halfWidth, restClientLeftInContainer - halfWidth, wrapperRect.width / 2);
+
+            // 综合 viewport + container 判断微调,即视口 + 容器都放置不行时才能考虑位置调整
+            // Comprehensive viewport + container judgment fine-tuning, that is, the position adjustment can only be considered when the viewport + container cannot be placed.
+            const shouldReverseTop = this.getReverse(isViewYOverFlow, isContainerYOverFlow, shouldViewReverseTop, shouldContainerReverseTop);
+            const shouldReverseLeft = this.getReverse(isViewXOverFlow, isContainerXOverFlow, shouldViewReverseLeft, shouldContainerReverseLeft);
+            const shouldReverseBottom = this.getReverse(isViewYOverFlow, isContainerYOverFlow, shouldViewReverseBottom, shouldContainerReverseBottom);
+            const shouldReverseRight = this.getReverse(isViewXOverFlow, isContainerXOverFlow, shouldViewReverseRight, shouldContainerReverseRight);
+
+            // const shouldReverseTopOver = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseTopOver, shouldContainerReverseTopOver);
+            // const shouldReverseBottomOver = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseBottomOver, shouldContainerReverseBottomOver);
+
+            const shouldReverseTopSide = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseTopSide, shouldContainerReverseTopSide);
+            const shouldReverseBottomSide = this.getReverse(isViewYOverFlowSide, isContainerYOverFlowSide, shouldViewReverseBottomSide, shouldContainerReverseBottomSide);
+            const shouldReverseLeftSide = this.getReverse(isViewXOverFlowSide, isContainerXOverFlowSide, shouldViewReverseLeftSide, shouldContainerReverseLeftSide);
+            const shouldReverseRightSide = this.getReverse(isViewXOverFlowSide, isContainerXOverFlowSide, shouldViewReverseRightSide, shouldContainerReverseRightSide);
+
+            const isYOverFlowSideHalf = isViewYOverFlowSideHalf && isContainerYOverFlowSideHalf;
+            const isXOverFlowSideHalf = isViewXOverFlowSideHalf && isContainerXOverFlowSideHalf;
+
             switch (position) {
                 case 'top':
                     if (shouldReverseTop) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isXOverFlowSideHalf && (shouldReverseLeftSide || shouldReverseRightSide)) {
+                        position = this._adjustPos(position, true, 'expand', shouldReverseLeftSide ? 'Right' : 'Left');
                     }
                     break;
                 case 'topLeft':
                     if (shouldReverseTop) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseLeftSide && widthIsBigger) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
+                        position = this._adjustPos(position, true, 'reduce');
                     }
                     break;
                 case 'topRight':
                     if (shouldReverseTop) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseRightSide && widthIsBigger) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
+                    }
+                    if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
+                        position = this._adjustPos(position, true, 'reduce');
                     }
                     break;
                 case 'left':
                     if (shouldReverseLeft) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
+                    }
+                    if (isYOverFlowSideHalf && (shouldReverseTopSide || shouldReverseBottomSide)) {
+                        position = this._adjustPos(position, false, 'expand', shouldReverseTopSide ? 'Bottom' : 'Top');
                     }
                     break;
                 case 'leftTop':
                     if (shouldReverseLeft) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     if (shouldReverseTopSide && heightIsBigger) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true); 
+                    }
+                    if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
+                        position = this._adjustPos(position, false, 'reduce');
                     }
                     break;
                 case 'leftBottom':
                     if (shouldReverseLeft) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     if (shouldReverseBottomSide && heightIsBigger) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
+                        position = this._adjustPos(position, false, 'reduce');
                     }
                     break;
                 case 'bottom':
                     if (shouldReverseBottom) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isXOverFlowSideHalf && (shouldReverseLeftSide || shouldReverseRightSide)) {
+                        position = this._adjustPos(position, true, 'expand', shouldReverseLeftSide ? 'Right' : 'Left');
                     }
                     break;
                 case 'bottomLeft':
                     if (shouldReverseBottom) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseLeftSide && widthIsBigger) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
+                    }
+                    if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
+                        position = this._adjustPos(position, true, 'reduce');
                     }
                     break;
                 case 'bottomRight':
                     if (shouldReverseBottom) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseRightSide && widthIsBigger) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
+                    }
+                    if (isWidthOverFlow && (isViewXEnoughSideHalf || isContainerXEnoughSideHalf)) {
+                        position = this._adjustPos(position, true, 'reduce');
                     }
                     break;
                 case 'right':
                     if (shouldReverseRight) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
+                    }
+                    if (isYOverFlowSideHalf && (shouldReverseTopSide || shouldReverseBottomSide)) {
+                        position = this._adjustPos(position, false, 'expand', shouldReverseTopSide ? 'Bottom' : 'Top');
                     }
                     break;
                 case 'rightTop':
                     if (shouldReverseRight) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     if (shouldReverseTopSide && heightIsBigger) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
+                        position = this._adjustPos(position, false, 'reduce');
                     }
                     break;
                 case 'rightBottom':
                     if (shouldReverseRight) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     if (shouldReverseBottomSide && heightIsBigger) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
+                    }
+                    if (isHeightOverFlow && (isViewYEnoughSideHalf || isContainerYEnoughSideHalf)) {
+                        position = this._adjustPos(position, false, 'reduce');
                     }
                     break;
                 case 'leftTopOver':
                     if (shouldReverseTopOver) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseLeftOver) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     break;
                 case 'leftBottomOver':
                     if (shouldReverseBottomOver) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseLeftOver) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     break;
                 case 'rightTopOver':
                     if (shouldReverseTopOver) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseRightOver) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     break;
                 case 'rightBottomOver':
                     if (shouldReverseBottomOver) {
-                        position = this._reversePos(position, true);
+                        position = this._adjustPos(position, true);
                     }
                     if (shouldReverseRightOver) {
-                        position = this._reversePos(position);
+                        position = this._adjustPos(position);
                     }
                     break;
                 default:
                     break;
             }
+
+            // 判断溢出 Judgment overflow
+            // 上下方向 top and bottom
+            if (this.isTB(position)){
+                isHeightOverFlow = isViewYOverFlow && isContainerYOverFlow;
+                if (position === 'top' || position === 'bottom') {
+                    isWidthOverFlow = isViewXOverFlowSideHalf && isContainerXOverFlowSideHalf;
+                } else {
+                    isWidthOverFlow = isViewXOverFlowSide && isContainerXOverFlowSide;
+                }
+            }
+            // 左右方向 left and right
+            if (this.isLR(position)){
+                isWidthOverFlow = isViewXOverFlow && isContainerXOverFlow;
+                if (position === 'left' || position === 'right') {
+                    isHeightOverFlow = isViewYOverFlowSideHalf && isContainerYOverFlowSideHalf;
+                } else {
+                    isHeightOverFlow = isViewYOverFlowSide && isContainerYOverFlowSide;
+                }
+            }
         }
 
-        return position;
+        return { position, isHeightOverFlow, isWidthOverFlow };
     }
 
     delayHide = () => {

+ 1 - 1
packages/semi-ui/table/_story/v2/stickyHeader/index.scss

@@ -1,3 +1,3 @@
 body {
-    height: 150vh;
+    // height: 150vh;
 }

+ 1 - 0
packages/semi-ui/tooltip/__test__/tooltip.test.js

@@ -57,6 +57,7 @@ describe(`Tooltip`, () => {
         expect(document.querySelectorAll(`.${BASE_CLASS_PREFIX}-tooltip-wrapper`).length).toBe(1);
 
         // scroll
+        await sleep(100);
         const deltaY = 50;
         const scrollContainer = demo.find(`#${scrollContainerId}`);
         const oldTop = window.getComputedStyle(tooltipOuter).top;

+ 159 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/bottom2Other.jsx

@@ -0,0 +1,159 @@
+import React, { PureComponent, useState } from 'react';
+import { Tooltip, Button, Popover } from '@douyinfe/semi-ui';
+import { PopupContent, Trigger } from './common';
+
+
+const Bottom2Top = () => {
+    const [pos, setPos] = useState('top');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    bottom: 0,
+                    left: 100
+                }}
+            >
+                pos: {pos}
+            </Trigger>
+        </Tooltip>
+    </div>);
+};
+
+const Bottom2TopLeft = () => {
+    const [pos, setPos] = useState('top');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Bottom2TopRight = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 40,
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Bottom2BottomLeft = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    left: 40
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Bottom2BottomRight = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 40
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+export { Bottom2Top, Bottom2TopLeft, Bottom2TopRight, Bottom2BottomLeft, Bottom2BottomRight };

+ 52 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/common.jsx

@@ -0,0 +1,52 @@
+import React, { PureComponent, useState } from 'react';
+import { Tooltip, Button, Popover } from '@douyinfe/semi-ui';
+
+
+const commonTriggerStyle = {
+    border: '1px solid var(--semi-color-primary)',
+    justifyContent: 'center',
+    alignItems: 'center',
+    display: 'inline-flex',
+    color: 'var(--semi-color-primary)',
+};
+
+const commonContentStyle = {
+    justifyContent: 'center',
+    alignItems: 'center',
+    backgroundColor: 'rgba(var(--semi-light-blue-5), 1)',
+};
+
+
+class Trigger extends PureComponent {
+    render() {
+        const { w = 100, h = 100, style, children, ...rest } = this.props;
+        return (
+            <div
+                {...rest}
+                style={{
+                    width: w,
+                    height: h,
+                    ...commonTriggerStyle,
+                    ...style,
+                }}>
+                Trigger w:{w}
+                <br/>
+                {children}
+            </div>
+        );
+    }
+}
+
+const PopupContent = ({ w = 100, h = 40, style, ...rest }) => (
+    <div style={{
+        width: w,
+        height: h,
+        ...commonContentStyle,
+        ...style,
+    }}
+    >
+        Popup Content width {w}
+    </div>
+);
+
+export { PopupContent, Trigger };

+ 11 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/index.jsx

@@ -0,0 +1,11 @@
+import { Right2Left, Right2LeftTop, Right2LeftBottom, Right2RightTop, Right2RightBottom } from './right2Other';
+import { Left2Right, Left2RightTop, Left2RightBottom, Left2LeftTop, Left2LeftBottom } from './left2Other';
+import { Top2Bottom, Top2BottomLeft, Top2BottomRight, Top2TopLeft, Top2TopRight } from './top2Other';
+import { Bottom2Top, Bottom2TopLeft, Bottom2TopRight, Bottom2BottomLeft, Bottom2BottomRight } from './bottom2Other';
+
+export {
+    Left2Right, Left2RightTop, Left2RightBottom, Left2LeftTop, Left2LeftBottom,
+    Right2Left, Right2LeftTop, Right2LeftBottom, Right2RightTop, Right2RightBottom,
+    Top2Bottom, Top2BottomLeft, Top2BottomRight, Top2TopLeft, Top2TopRight,
+    Bottom2Top, Bottom2TopLeft, Bottom2TopRight, Bottom2BottomLeft, Bottom2BottomRight
+};

+ 160 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/left2Other.jsx

@@ -0,0 +1,160 @@
+import React, { PureComponent, useState } from 'react';
+import { Tooltip, Button, Popover } from '@douyinfe/semi-ui';
+import { PopupContent, Trigger } from './common';
+
+const Left2Right = () => {
+    const [pos, setPos] = useState('left');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    top: 70
+                }}
+            >
+                pos: {pos}
+            </Trigger>
+        </Tooltip>
+    </div>);
+};
+
+const Left2RightTop = () => {
+    const [pos, setPos] = useState('left');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                // style={{
+                //     position: 'absolute',
+                //     right: 0,
+                //     top: 0
+                // }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Left2RightBottom = () => {
+    const [pos, setPos] = useState('left');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    // right: 0,
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Left2LeftTop = () => {
+    const [pos, setPos] = useState('left');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 0,
+                    top: 50
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Left2LeftBottom = () => {
+    const [pos, setPos] = useState('left');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 0,
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+export { Left2Right, Left2RightTop, Left2RightBottom, Left2LeftTop, Left2LeftBottom };

+ 163 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/right2Other.jsx

@@ -0,0 +1,163 @@
+import React, { PureComponent, useState } from 'react';
+import { Tooltip, Button, Popover } from '@douyinfe/semi-ui';
+
+import { PopupContent, Trigger } from './common';
+
+// ❌
+const Right2Left = () => {
+    const [pos, setPos] = useState('right');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 0,
+                    bottom: 300
+                }}
+            >
+                pos: {pos}
+            </Trigger>
+        </Tooltip>
+    </div>);
+};
+
+// ✅
+const Right2LeftTop = () => {
+    const [pos, setPos] = useState('right');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 0,
+                    top: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+// ❌
+const Right2LeftBottom = () => {
+    const [pos, setPos] = useState('right');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 0,
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Right2RightTop = () => {
+    const [pos, setPos] = useState('right');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    top: 40,
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Right2RightBottom = () => {
+    const [pos, setPos] = useState('right');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    bottom: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+export { Right2Left, Right2LeftTop, Right2LeftBottom, Right2RightTop, Right2RightBottom };

+ 158 - 0
packages/semi-ui/tooltip/_story/AutoAdjustOverflow/top2Other.jsx

@@ -0,0 +1,158 @@
+import React, { PureComponent, useState } from 'react';
+import { Tooltip, Button, Popover } from '@douyinfe/semi-ui';
+import { PopupContent, Trigger } from './common';
+
+
+const Top2Bottom = () => {
+    const [pos, setPos] = useState('top');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    left: 300,
+                }}
+            >
+                pos: {pos}
+            </Trigger>
+        </Tooltip>
+    </div>);
+};
+
+const Top2BottomLeft = () => {
+    const [pos, setPos] = useState('top');
+    return (<div>
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    left: 40,
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Top2BottomRight = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    right: 40,
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Top2TopLeft = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    bottom: 0,
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+const Top2TopRight = () => {
+    const [pos, setPos] = useState('top');
+    return (<div
+        style={{
+        }}
+    >
+        <Tooltip
+            content={
+                <PopupContent
+                    w={200}
+                    h={200}
+                />
+            }
+            arrowPointAtCenter={false}
+            visible
+            trigger='custom'
+            position={pos}
+            key={pos}
+        >
+            <Trigger
+                w={100}
+                h={100}
+                style={{
+                    position: 'absolute',
+                    top: 224,
+                    right: 0
+                }}
+            />
+        </Tooltip>
+    </div>);
+};
+
+export { Top2Bottom, Top2BottomLeft, Top2BottomRight, Top2TopLeft, Top2TopRight };

+ 2 - 2
packages/semi-ui/tooltip/_story/InTable/index.jsx

@@ -15,13 +15,13 @@ export default function InTableDemo(props = {}) {
                 return (
                     <div id="dropdown-wrap" style={{ position: 'relative' }}>
                         <Dropdown
-                            getPopupContainer={getPopupContainer}
+                            // getPopupContainer={getPopupContainer}
                             position="rightTop"
                             trigger="click"
                             render={
                                 <Dropdown.Menu>
                                     <Dropdown
-                                        getPopupContainer={getPopupContainer}
+                                        // getPopupContainer={getPopupContainer}
                                         position="rightTop"
                                         render={
                                             <Dropdown.Menu>

+ 1 - 0
packages/semi-ui/tooltip/_story/story.scss

@@ -1,5 +1,6 @@
 body.sb-show-main {
     position: relative;
+    // width: 200px;
 }
 
 .demo {

+ 319 - 2
packages/semi-ui/tooltip/_story/tooltip.stories.jsx

@@ -1,4 +1,4 @@
-import React, { useState, useMemo } from 'react';
+import React, { useState, useMemo, PureComponent } from 'react';
 import Tooltip from '../index';
 import './story.scss';
 import {
@@ -14,6 +14,8 @@ import {
   Space,
   Popover,
   Input,
+  RadioGroup,
+  SideSheet
 } from '@douyinfe/semi-ui';
 
 import InTableDemo from './InTable';
@@ -24,6 +26,13 @@ import ArrowPointAtCenter from './ArrowPointAtCenter';
 import CustomContainer from './CustomContainer';
 import ContainerPosition from './ContainerPosition';
 import { IconList, IconSidebar, IconEdit } from '@douyinfe/semi-icons';
+import {  
+  Right2Left, Right2LeftTop, Right2LeftBottom, Right2RightTop, Right2RightBottom,
+  Left2Right, Left2RightTop, Left2RightBottom, Left2LeftTop, Left2LeftBottom,
+  Top2Bottom, Top2BottomLeft, Top2BottomRight, Top2TopLeft, Top2TopRight,
+  Bottom2Top, Bottom2TopLeft, Bottom2TopRight, Bottom2BottomLeft, Bottom2BottomRight,
+ } from './AutoAdjustOverflow';
+
 
 export default {
   title: 'Tooltip',
@@ -1094,4 +1103,312 @@ export const TransitionDemo = () => {
     <Button onClick={() => setKey(Math.random())}>reset Demo</Button>
   </>
   )
-}
+}
+
+export const AdjustPosIfNeedTBLR = () => {
+  
+  const content = <article>
+    Hi ByteDancer, this is a tooltip.
+    <br /> We have 2 lines.
+  </article>
+
+  const contentHigh = <article>
+    Hi ByteDancer, this is a tooltip.
+    <br /> We have 2 lines.
+    <br /> We have 2 lines.
+  </article>
+
+  return (
+    <div style={{ paddingLeft: 0, width: 800, height: '100%' }}>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'top'}
+      >
+        <Tag style={{ position: 'absolute', left: 20, top: 40 }}>top to bottomLeft</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'top'}
+      >
+        <Tag style={{ position: 'absolute', right: 20, top: 40 }}>top to bottomRight</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'top'}
+      >
+        <Tag style={{ position: 'absolute', left: 20, top: 70 }}>top to topLeft</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'top'}
+      >
+        <Tag style={{ position: 'absolute', right: 20, top: 70 }}>top to topRight</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'bottom'}
+      >
+        <Tag data-cy={'bottom'} style={{ position: 'absolute', left: 20, bottom: 70 }}>bottom to bottomLeft</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'bottom'}
+      >
+        <Tag data-cy={'bottom'} style={{ position: 'absolute', right: 20, bottom: 70 }}>bottom to bottomRight</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'bottom'}
+      >
+        <Tag data-cy={'bottom'} style={{ position: 'absolute', left: 20, bottom: 40 }}>bottom to topLeft</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={content}
+        position={'bottom'}
+      >
+        <Tag data-cy={'bottom'} style={{ position: 'absolute', right: 20, bottom: 40 }}>bottom to topRight</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'left'}
+      >
+        <Tag style={{ position: 'absolute', left: 300, top: 20 }}>left to leftTop</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'left'}
+      >
+        <Tag style={{ position: 'absolute', left: 300, bottom: 20 }}>left to leftBottom</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'left'}
+      >
+        <Tag style={{ position: 'absolute', left: 180, top: 20 }}>left to rightTop</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'left'}
+      >
+        <Tag style={{ position: 'absolute', left: 180, bottom: 20 }}>left to rightBottom</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'right'}
+      >
+        <Tag style={{ position: 'absolute', right: 300, top: 20 }}>right to rightTop</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'right'}
+      >
+        <Tag style={{ position: 'absolute', right: 300, bottom: 20 }}>right to rightBottom</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'right'}
+      >
+        <Tag style={{ position: 'absolute', right: 180, top: 20 }}>right to leftTop</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'right'}
+      >
+        <Tag style={{ position: 'absolute', right: 180, bottom: 20 }}>right to leftBottom</Tag>
+      </Tooltip>
+      <Tooltip
+        showArrow
+        arrowPointAtCenter
+        content={contentHigh}
+        position={'rightTop'}
+      >
+        <Tag style={{ position: 'absolute', right: 180, bottom: 50 }}>right to leftBottom</Tag>
+      </Tooltip>
+    </div>
+  );
+}
+
+export const marginDemo = () => {
+  const [visible, setVisible] = useState(false);
+  const change = () => {
+    setVisible(!visible);
+  };
+  return (
+    <>
+      <Button onClick={change}>Open SideSheet</Button>
+      <SideSheet title="滑动侧边栏" visible={visible} onCancel={change}>
+        <div style={{ height: '800px', overflow: 'scroll' }}>
+          <div
+            id='test'
+            style={{
+              height: '880px',
+              display: 'flex',
+              flexDirection: 'column-reverse',
+              position: 'relative'
+            }}
+          >
+            <Tooltip
+              getPopupContainer={() => document.querySelector('#test')}
+              content='cecece'
+              position='bottom'
+              margin={{ marginTop: 0, marginLeft: 0, marginBottom: 36, marginRight: 0 }}
+            >
+              <div style={{ marginBottom: 20 }}>
+                test
+              </div>
+            </Tooltip>
+          </div>
+        </div>
+        <footer style={{
+          position: 'sticky',
+          bottom: 0,
+          height: 36,
+          border: '1px solid pink'
+        }}>
+          i am footer
+        </footer>
+      </SideSheet>
+    </>
+  );
+};
+
+export const SmartPosAdjustDemo = () => {
+  const [pos, setPosition] = useState('top');
+  const onChange = (e) => {
+    setPosition(e.target.value);
+  };
+  return (
+    <div style={{ width: 800, height: 800 }}>
+      <Popover 
+        position={pos}
+        showArrow={true}
+        content={
+          <div style={{ minWidth: 900, height: 900, backgroundColor: 'lightblue' }}>
+            <article>
+              <p>hi semi! hi semi! hi semi!hi semi! hi semi</p>
+              <p>hi semi! hi semi! hi semi!hi semi! hi semi</p>
+              <p>hi semi! hi semi! hi semi!</p>
+            </article>
+          </div>
+        }
+      >
+        <Tag style={{ marginLeft: 450, marginTop: 550 }}>悬停此处</Tag>
+      </Popover>
+      <div style={{ marginLeft: 250, width: 300 }}>
+        <RadioGroup onChange={onChange} value={pos} aria-label="position" name="position">
+          <Radio value={'topLeft'}>TL </Radio>
+          <Radio value={'top'}>top </Radio>
+          <Radio value={'topRight'}>TR </Radio>
+          <Radio value={'bottomLeft'}>BL</Radio>
+          <Radio value={'bottom'}>Bottom</Radio>
+          <Radio value={'bottomRight'}>BR</Radio>
+          <Radio value={'leftTop'}>LT</Radio>
+          <Radio value={'left'}>Left</Radio>
+          <Radio value={'leftBottom'}>LB</Radio>
+          <Radio value={'rightTop'}>RT</Radio>
+          <Radio value={'right'}>Right</Radio>
+          <Radio value={'rightBottom'}>RB</Radio>
+        </RadioGroup>
+      </div>
+    </div>
+  )
+}
+
+
+// right -> other
+export const AutoRight2LeftDemo = () => <Right2Left />;
+AutoRight2LeftDemo.storyName = `✅ auto : right -> left`;
+
+export const AutoRight2LeftBottomDemo = () => <Right2LeftBottom />;
+AutoRight2LeftBottomDemo.storyName = `✅ auto : right -> leftBottom`;
+
+export const AutoRight2LeftTopDemo = () => <Right2LeftTop />;
+AutoRight2LeftTopDemo.storyName = `✅ auto : riht -> leftTop`;
+
+export const AutoRight2RightBottomDemo = () => <Right2RightBottom />;
+AutoRight2RightBottomDemo.storyName = `✅ auto : right -> rightBottom`;
+
+export const AutoRight2RightTopDemo = () => <Right2RightTop />;
+AutoRight2RightTopDemo.storyName = `✅ auto : riht -> rightTop`;
+
+
+// left -> other
+export const Left2RightDemo = () => <Left2Right />;
+Left2RightDemo.storyName = `✅ auto : left -> right`;
+
+export const Left2LeftBottomDemo = () => <Left2LeftBottom />;
+Left2LeftBottomDemo.storyName = `✅ auto : left -> leftBottom`;
+
+export const Left2LeftTopDemo = () => <Left2LeftTop />;
+Left2LeftTopDemo.storyName = `✅ auto : left -> leftTop`;
+
+export const Left2RightBottomDemo = () => <Left2RightBottom />;
+Left2RightBottomDemo.storyName = `✅ auto : left -> rightBottom`;
+
+export const Left2RightTopDemo = () => <Left2RightTop />;
+Left2RightTopDemo.storyName = `✅ auto : left -> rightTop`;
+
+// top -> other
+export const Top2BottomDemo = () => <Top2Bottom />;
+Top2BottomDemo.storyName = `✅ auto : top -> bottom`;
+
+export const Top2BottomLeftDemo = () => <Top2BottomLeft />;
+Top2BottomLeftDemo.storyName = `✅ auto : top -> bottomLeft`;
+
+export const Top2BottomRightDemo = () => <Top2BottomRight />;
+Top2BottomRightDemo.storyName = `✅ auto : top -> bottomRight`;
+
+export const Top2TopLeftDemo = () => <Top2TopLeft />;
+Top2TopLeftDemo.storyName = `✅ auto : top -> topLeft`;
+
+export const Top2TopRightDemo = () => <Top2TopRight />;
+Top2TopRightDemo.storyName = `✅ auto : top -> topRight`;
+
+
+// bottom -> other
+export const Bottom2TopDemo = () => <Bottom2Top />;
+Bottom2TopDemo.storyName = `✅ auto : bottom -> top`;
+
+export const Bottom2TopLeftDemo = () => <Bottom2TopLeft />;
+Bottom2TopLeftDemo.storyName = `✅ auto : bottom -> topLeft`;
+
+export const Bottom2TopRightDemo = () => <Bottom2TopRight />;
+Bottom2TopRightDemo.storyName = `✅ auto : bottom -> topRight`;
+
+export const Bottom2BottomLeftDemo = () => <Bottom2BottomLeft />;
+Bottom2BottomLeftDemo.storyName = `✅ auto : bottom -> bottomLeft`;
+
+export const Bottom2BottomRightDemo = () => <Bottom2BottomRight />;
+Bottom2BottomRightDemo.storyName = `✅ auto : bottom -> bottomRight`;

+ 3 - 0
packages/semi-ui/tooltip/index.tsx

@@ -58,6 +58,7 @@ export interface TooltipProps extends BaseProps {
     onVisibleChange?: (visible: boolean) => void;
     onClickOutSide?: (e: React.MouseEvent) => void;
     spacing?: number;
+    margin?: number | { marginLeft: number; marginTop: number; marginRight: number; marginBottom: number };
     showArrow?: boolean | React.ReactNode;
     zIndex?: number;
     rePosKey?: string | number;
@@ -126,6 +127,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         onVisibleChange: PropTypes.func,
         onClickOutSide: PropTypes.func,
         spacing: PropTypes.number,
+        margin: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
         showArrow: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
         zIndex: PropTypes.number,
         rePosKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
@@ -156,6 +158,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         onVisibleChange: noop,
         onClickOutSide: noop,
         spacing: numbers.SPACING,
+        margin: numbers.MARGIN,
         showArrow: true,
         wrapWhenSpecial: true,
         zIndex: numbers.DEFAULT_Z_INDEX,

+ 4 - 4
yarn.lock

@@ -1567,10 +1567,10 @@
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-site-doc-style/-/semi-site-doc-style-0.0.1.tgz#c3c803014218ec00441dac32db9a875f6222ed0b"
   integrity sha512-y7Jc1i9q/O2idfaqckSJvghpt4AboQJgZ4iTEK8UMqjQkyWmb5I/NRzVWjOP9S0LEbJNs76OKfZil7DwsOmY/A==
 
-"@douyinfe/semi-site-header@^0.0.15":
-  version "0.0.15"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-site-header/-/semi-site-header-0.0.15.tgz#874c0c41aa67e04def6c6eb9009ce4eb5dd3cf44"
-  integrity sha512-xbQggMSgkF9/GiokM5dOfs4URrPKWAzO2nkpelQ/38iANFRrjoKP/9QgBpRvZeTVhIO5LAyV8KWWj+p4J42TIw==
+"@douyinfe/semi-site-header@^0.0.16":
+  version "0.0.16"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-site-header/-/semi-site-header-0.0.16.tgz#0d49be21499c38a489c839d0bc6d4bb8b67ae0c8"
+  integrity sha512-OH4UhZXw+5fWIbtt8VxglqoL3cJ003s+subfSCAFwADlZiWAPEuawmaPUCLGmcriLnRiCmTb4egJ4rK0pWsyRQ==
   dependencies:
     "@douyinfe/semi-icons" "^2.0.0"
     "@douyinfe/semi-ui" "^2.0.0"