From b6343cb49eba94d4c816bef129ab97d894fae164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Thu, 18 Sep 2025 14:17:43 +0800 Subject: [PATCH 1/6] refactor: remove useless structure --- src/Popup.tsx | 26 ++++++------------ src/Tooltip.tsx | 63 ++++++++++++++++++++++++++++---------------- tests/index.test.tsx | 10 +++---- 3 files changed, 54 insertions(+), 45 deletions(-) diff --git a/src/Popup.tsx b/src/Popup.tsx index 8f38a4b..03cf51c 100644 --- a/src/Popup.tsx +++ b/src/Popup.tsx @@ -12,26 +12,16 @@ export interface ContentProps { } const Popup: React.FC = (props) => { - const { - children, - prefixCls, - id, - overlayInnerStyle: innerStyle, - bodyClassName, - className, - style, - } = props; + const { children, prefixCls, id, className, style, bodyClassName, overlayInnerStyle } = props; return ( -
- + ); }; diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx index bc14731..1c907c6 100644 --- a/src/Tooltip.tsx +++ b/src/Tooltip.tsx @@ -8,6 +8,8 @@ import { useImperativeHandle, useRef } from 'react'; import { placements } from './placements'; import Popup from './Popup'; +export type SemanticName = 'root' | 'arrow' | 'body'; + export interface TooltipProps extends Pick< TriggerProps, @@ -21,30 +23,39 @@ export interface TooltipProps | 'forceRender' | 'popupVisible' > { + // Style + classNames?: Partial>; + styles?: Partial>; + + /** Config popup motion */ + motion?: TriggerProps['popupMotion']; + + /** @deprecated Please use `styles={{ root: {} }}` */ + overlayStyle?: React.CSSProperties; + /** @deprecated Please use `classNames={{ root: '' }}` */ + overlayClassName?: string; + /** @deprecated Please use `styles={{ body: {} }}` */ + overlayInnerStyle?: React.CSSProperties; + + // Rest trigger?: ActionType | ActionType[]; defaultVisible?: boolean; visible?: boolean; placement?: string; - /** Config popup motion */ - motion?: TriggerProps['popupMotion']; + onVisibleChange?: (visible: boolean) => void; afterVisibleChange?: (visible: boolean) => void; overlay: (() => React.ReactNode) | React.ReactNode; - /** @deprecated Please use `styles={{ root: {} }}` */ - overlayStyle?: React.CSSProperties; - /** @deprecated Please use `classNames={{ root: '' }}` */ - overlayClassName?: string; + getTooltipContainer?: (node: HTMLElement) => HTMLElement; destroyOnHidden?: boolean; align?: AlignType; showArrow?: boolean | ArrowType; arrowContent?: React.ReactNode; id?: string; - /** @deprecated Please use `styles={{ body: {} }}` */ - overlayInnerStyle?: React.CSSProperties; + zIndex?: number; - styles?: TooltipStyles; - classNames?: TooltipClassNames; + /** * Configures Tooltip to reuse the background for transition usage. * This is an experimental API and may not be stable. @@ -52,16 +63,6 @@ export interface TooltipProps unique?: TriggerProps['unique']; } -export interface TooltipStyles { - root?: React.CSSProperties; - body?: React.CSSProperties; -} - -export interface TooltipClassNames { - root?: string; - body?: string; -} - export interface TooltipRef extends TriggerRef {} const Tooltip = React.forwardRef((props, ref) => { @@ -108,7 +109,8 @@ const Tooltip = React.forwardRef((props, ref) => { prefixCls={prefixCls} id={mergedId} bodyClassName={tooltipClassNames?.body} - overlayInnerStyle={{ ...overlayInnerStyle, ...tooltipStyles?.body }} + overlayInnerStyle={overlayInnerStyle} + style={tooltipStyles?.body} > {overlay} @@ -124,6 +126,23 @@ const Tooltip = React.forwardRef((props, ref) => { return React.cloneElement(children, childProps) as any; }; + // Process arrow configuration + const getArrowConfig = () => { + if (!showArrow) { + return false; + } + + // Convert true to object for unified processing + const arrowConfig = showArrow === true ? {} : showArrow; + + // Apply semantic styles with unified logic + return { + ...arrowConfig, + className: classNames(arrowConfig.className, tooltipClassNames?.arrow), + content: arrowConfig.content || arrowContent, + }; + }; + return ( ((props, ref) => { mouseLeaveDelay={mouseLeaveDelay} popupStyle={{ ...overlayStyle, ...tooltipStyles?.root }} mouseEnterDelay={mouseEnterDelay} - arrow={showArrow} + arrow={getArrowConfig()} {...extraProps} > {getChildren()} diff --git a/tests/index.test.tsx b/tests/index.test.tsx index 9586008..bc4da49 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -1,6 +1,6 @@ import { act, fireEvent, render } from '@testing-library/react'; import React from 'react'; -import Tooltip, { TooltipRef } from '../src'; +import Tooltip, { type TooltipRef } from '../src'; const verifyContent = (wrapper: HTMLElement, content: string) => { expect(wrapper.querySelector('.x-content').textContent).toBe(content); @@ -73,7 +73,7 @@ describe('rc-tooltip', () => { ); fireEvent.click(container.querySelector('.target')); expect( - (container.querySelector('.rc-tooltip-inner') as HTMLElement).style.background, + (container.querySelector('.rc-tooltip-body') as HTMLElement).style.background, ).toEqual('red'); }); @@ -269,11 +269,11 @@ describe('rc-tooltip', () => { ); const tooltipElement = container.querySelector('.rc-tooltip') as HTMLElement; - const tooltipBodyElement = container.querySelector('.rc-tooltip-inner') as HTMLElement; + const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; // 验证 classNames - expect(tooltipElement.classList).toContain('custom-root'); - expect(tooltipBodyElement.classList).toContain('custom-body'); + expect(tooltipElement).toHaveClass('custom-root'); + expect(tooltipBodyElement).toHaveClass('custom-body'); // 验证 styles expect(tooltipElement.style.backgroundColor).toBe('blue'); From 5cc870866c256a858bec9ffd0b40d03f46199af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Thu, 18 Sep 2025 14:55:44 +0800 Subject: [PATCH 2/6] chore: align the case --- src/Popup.tsx | 15 +++++----- src/Tooltip.tsx | 74 +++++++++++++++++++++---------------------------- 2 files changed, 39 insertions(+), 50 deletions(-) diff --git a/src/Popup.tsx b/src/Popup.tsx index 03cf51c..7537cab 100644 --- a/src/Popup.tsx +++ b/src/Popup.tsx @@ -1,24 +1,23 @@ -import classNames from 'classnames'; +import cls from 'classnames'; import * as React from 'react'; +import type { TooltipProps } from './Tooltip'; export interface ContentProps { prefixCls?: string; children: (() => React.ReactNode) | React.ReactNode; id?: string; - overlayInnerStyle?: React.CSSProperties; - className?: string; - style?: React.CSSProperties; - bodyClassName?: string; + classNames?: TooltipProps['classNames']; + styles?: TooltipProps['styles']; } const Popup: React.FC = (props) => { - const { children, prefixCls, id, className, style, bodyClassName, overlayInnerStyle } = props; + const { children, prefixCls, id, classNames, styles } = props; return ( } + visible + showArrow + > + + , + ); + + const tooltipElement = container.querySelector('.rc-tooltip') as HTMLElement; + const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; + const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow') as HTMLElement; + + // 验证 styles + expect(tooltipElement.style.backgroundColor).toBe('blue'); + expect(tooltipElement.style.zIndex).toBe('1000'); + expect(tooltipBodyElement.style.color).toBe('red'); + expect(tooltipBodyElement.style.fontSize).toBe('14px'); + expect(tooltipArrowElement.style.borderColor).toBe('green'); + }); - const tooltipElement = container.querySelector('.rc-tooltip') as HTMLElement; - const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; + it('should apply both classNames and styles simultaneously', () => { + const customClassNames = { + root: 'custom-root', + body: 'custom-body', + arrow: 'custom-arrow', + }; - // 验证 classNames - expect(tooltipElement).toHaveClass('custom-root'); - expect(tooltipBodyElement).toHaveClass('custom-body'); + const customStyles = { + root: { backgroundColor: 'blue' }, + body: { color: 'red' }, + arrow: { borderColor: 'green' }, + }; - // 验证 styles - expect(tooltipElement.style.backgroundColor).toBe('blue'); - expect(tooltipBodyElement.style.color).toBe('red'); + const { container } = render( + Tooltip content
} + visible + showArrow + > + + , + ); + + const tooltipElement = container.querySelector('.rc-tooltip') as HTMLElement; + const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; + const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow') as HTMLElement; + + // 验证 classNames 和 styles 同时生效 + expect(tooltipElement).toHaveClass('custom-root'); + expect(tooltipElement.style.backgroundColor).toBe('blue'); + expect(tooltipBodyElement).toHaveClass('custom-body'); + expect(tooltipBodyElement.style.color).toBe('red'); + expect(tooltipArrowElement).toHaveClass('custom-arrow'); + expect(tooltipArrowElement.style.borderColor).toBe('green'); + }); + + it('should work with partial classNames and styles', () => { + const partialClassNames = { + body: 'custom-body', + }; + + const partialStyles = { + root: { backgroundColor: 'blue' }, + }; + + const { container } = render( + Tooltip content} + visible + showArrow + > + + , + ); + + const tooltipElement = container.querySelector('.rc-tooltip') as HTMLElement; + const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; + const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow') as HTMLElement; + + // 验证部分配置生效 + expect(tooltipElement.style.backgroundColor).toBe('blue'); + expect(tooltipBodyElement).toHaveClass('custom-body'); + + // 验证未配置的不会有自定义类名或样式 + expect(tooltipElement).not.toHaveClass('custom-root'); + expect(tooltipArrowElement).not.toHaveClass('custom-arrow'); + }); + + it('should not break when showArrow is false', () => { + const customClassNames = { + root: 'custom-root', + body: 'custom-body', + arrow: 'custom-arrow', // 即使配置了arrow,但不显示箭头时不应该报错 + }; + + const customStyles = { + root: { backgroundColor: 'blue' }, + body: { color: 'red' }, + arrow: { borderColor: 'green' }, + }; + + const { container } = render( + Tooltip content} + visible + showArrow={false} + > + + , + ); + + const tooltipElement = container.querySelector('.rc-tooltip') as HTMLElement; + const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; + const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow'); + + // 验证没有箭头时 + expect(tooltipArrowElement).toBeFalsy(); + + // 其他样式仍然生效 + expect(tooltipElement).toHaveClass('custom-root'); + expect(tooltipElement.style.backgroundColor).toBe('blue'); + expect(tooltipBodyElement).toHaveClass('custom-body'); + expect(tooltipBodyElement.style.color).toBe('red'); + }); }); describe('children handling', () => { From 209358271865241ad731d16df3cd8e038dfc12cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Thu, 18 Sep 2025 15:46:12 +0800 Subject: [PATCH 4/6] chore: eng trans --- tests/index.test.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/index.test.tsx b/tests/index.test.tsx index 9f210b0..d09f635 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -339,7 +339,7 @@ describe('rc-tooltip', () => { const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow') as HTMLElement; - // 验证 classNames + // Verify classNames expect(tooltipElement).toHaveClass('custom-root'); expect(tooltipBodyElement).toHaveClass('custom-body'); expect(tooltipArrowElement).toHaveClass('custom-arrow'); @@ -367,7 +367,7 @@ describe('rc-tooltip', () => { const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow') as HTMLElement; - // 验证 styles + // Verify styles expect(tooltipElement.style.backgroundColor).toBe('blue'); expect(tooltipElement.style.zIndex).toBe('1000'); expect(tooltipBodyElement.style.color).toBe('red'); @@ -404,7 +404,7 @@ describe('rc-tooltip', () => { const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow') as HTMLElement; - // 验证 classNames 和 styles 同时生效 + // Verify that classNames and styles work simultaneously expect(tooltipElement).toHaveClass('custom-root'); expect(tooltipElement.style.backgroundColor).toBe('blue'); expect(tooltipBodyElement).toHaveClass('custom-body'); @@ -438,11 +438,11 @@ describe('rc-tooltip', () => { const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow') as HTMLElement; - // 验证部分配置生效 + // Verify partial configuration takes effect expect(tooltipElement.style.backgroundColor).toBe('blue'); expect(tooltipBodyElement).toHaveClass('custom-body'); - // 验证未配置的不会有自定义类名或样式 + // Verify that unconfigured elements don't have custom class names or styles expect(tooltipElement).not.toHaveClass('custom-root'); expect(tooltipArrowElement).not.toHaveClass('custom-arrow'); }); @@ -476,10 +476,10 @@ describe('rc-tooltip', () => { const tooltipBodyElement = container.querySelector('.rc-tooltip-body') as HTMLElement; const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow'); - // 验证没有箭头时 + // Verify when arrow is not shown expect(tooltipArrowElement).toBeFalsy(); - // 其他样式仍然生效 + // Other styles still take effect expect(tooltipElement).toHaveClass('custom-root'); expect(tooltipElement.style.backgroundColor).toBe('blue'); expect(tooltipBodyElement).toHaveClass('custom-body'); @@ -522,7 +522,7 @@ describe('rc-tooltip', () => { const btn = container.querySelector('button'); expect(btn).toHaveClass('custom-btn'); - // 触发原始事件处理器 + // Trigger original event handler fireEvent.mouseEnter(btn); expect(onMouseEnter).toHaveBeenCalled(); }); From 83802c9fcf31d721360a200453966ad53b0e967a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Thu, 18 Sep 2025 15:49:25 +0800 Subject: [PATCH 5/6] test: update test --- tests/index.test.tsx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/index.test.tsx b/tests/index.test.tsx index d09f635..fd6511a 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -368,11 +368,9 @@ describe('rc-tooltip', () => { const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow') as HTMLElement; // Verify styles - expect(tooltipElement.style.backgroundColor).toBe('blue'); - expect(tooltipElement.style.zIndex).toBe('1000'); - expect(tooltipBodyElement.style.color).toBe('red'); - expect(tooltipBodyElement.style.fontSize).toBe('14px'); - expect(tooltipArrowElement.style.borderColor).toBe('green'); + expect(tooltipElement).toHaveStyle({ backgroundColor: 'blue', zIndex: '1000' }); + expect(tooltipBodyElement).toHaveStyle({ color: 'red', fontSize: '14px' }); + expect(tooltipArrowElement).toHaveStyle({ borderColor: 'green' }); }); it('should apply both classNames and styles simultaneously', () => { @@ -406,11 +404,11 @@ describe('rc-tooltip', () => { // Verify that classNames and styles work simultaneously expect(tooltipElement).toHaveClass('custom-root'); - expect(tooltipElement.style.backgroundColor).toBe('blue'); + expect(tooltipElement).toHaveStyle({ backgroundColor: 'blue' }); expect(tooltipBodyElement).toHaveClass('custom-body'); - expect(tooltipBodyElement.style.color).toBe('red'); + expect(tooltipBodyElement).toHaveStyle({ color: 'red' }); expect(tooltipArrowElement).toHaveClass('custom-arrow'); - expect(tooltipArrowElement.style.borderColor).toBe('green'); + expect(tooltipArrowElement).toHaveStyle({ borderColor: 'green' }); }); it('should work with partial classNames and styles', () => { @@ -439,7 +437,7 @@ describe('rc-tooltip', () => { const tooltipArrowElement = container.querySelector('.rc-tooltip-arrow') as HTMLElement; // Verify partial configuration takes effect - expect(tooltipElement.style.backgroundColor).toBe('blue'); + expect(tooltipElement).toHaveStyle({ backgroundColor: 'blue' }); expect(tooltipBodyElement).toHaveClass('custom-body'); // Verify that unconfigured elements don't have custom class names or styles @@ -481,9 +479,9 @@ describe('rc-tooltip', () => { // Other styles still take effect expect(tooltipElement).toHaveClass('custom-root'); - expect(tooltipElement.style.backgroundColor).toBe('blue'); + expect(tooltipElement).toHaveStyle({ backgroundColor: 'blue' }); expect(tooltipBodyElement).toHaveClass('custom-body'); - expect(tooltipBodyElement.style.color).toBe('red'); + expect(tooltipBodyElement).toHaveStyle({ color: 'red' }); }); }); From bf086d29f3e074d9b42e21bcd339988288c6ca0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Thu, 18 Sep 2025 15:55:46 +0800 Subject: [PATCH 6/6] fix: ts --- docs/examples/formError.tsx | 2 +- docs/examples/placement.tsx | 4 ++-- docs/examples/point.tsx | 2 +- docs/examples/simple.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/examples/formError.tsx b/docs/examples/formError.tsx index 68f12a0..4c31f96 100644 --- a/docs/examples/formError.tsx +++ b/docs/examples/formError.tsx @@ -34,7 +34,7 @@ class Test extends Component { visible={this.state.visible} motion={{ motionName: 'rc-tooltip-zoom' }} trigger={[]} - overlayStyle={{ zIndex: 1000 }} + styles={{ root: { zIndex: 1000 } }} overlay={required!} > diff --git a/docs/examples/placement.tsx b/docs/examples/placement.tsx index 087a065..e23454e 100644 --- a/docs/examples/placement.tsx +++ b/docs/examples/placement.tsx @@ -94,8 +94,8 @@ const Test: React.FC = () => (
Debug Usage
Test diff --git a/docs/examples/point.tsx b/docs/examples/point.tsx index 8850763..c476249 100644 --- a/docs/examples/point.tsx +++ b/docs/examples/point.tsx @@ -30,7 +30,7 @@ const Test: React.FC = () => { } > diff --git a/docs/examples/simple.tsx b/docs/examples/simple.tsx index 60afc2f..b3700c1 100644 --- a/docs/examples/simple.tsx +++ b/docs/examples/simple.tsx @@ -217,7 +217,7 @@ class Test extends Component { offset: [this.state.offsetX, this.state.offsetY], }} motion={{ motionName: this.state.transitionName }} - overlayInnerStyle={state.overlayInnerStyle} + styles={{ body: state.overlayInnerStyle }} >
trigger