-
- {typeof children === 'function' ? children() : children}
-
+
+ {typeof children === 'function' ? children() : children}
);
};
diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx
index bc14731..319915c 100644
--- a/src/Tooltip.tsx
+++ b/src/Tooltip.tsx
@@ -2,12 +2,14 @@ import type { ArrowType, TriggerProps, TriggerRef } from '@rc-component/trigger'
import Trigger from '@rc-component/trigger';
import type { ActionType, AlignType } from '@rc-component/trigger/lib/interface';
import useId from '@rc-component/util/lib/hooks/useId';
-import classNames from 'classnames';
+import cls from 'classnames';
import * as React from 'react';
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,32 @@ export interface TooltipProps
| 'forceRender'
| 'popupVisible'
> {
+ // Style
+ classNames?: Partial
>;
+ styles?: Partial>;
+
+ /** Config popup motion */
+ motion?: TriggerProps['popupMotion'];
+
+ // 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,25 +56,13 @@ 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) => {
const {
- overlayClassName,
trigger = ['hover'],
mouseEnterDelay = 0,
mouseLeaveDelay = 0.1,
- overlayStyle,
prefixCls = 'rc-tooltip',
children,
onVisibleChange,
@@ -81,13 +73,12 @@ const Tooltip = React.forwardRef((props, ref) => {
destroyOnHidden = false,
defaultVisible,
getTooltipContainer,
- overlayInnerStyle,
arrowContent,
overlay,
id,
showArrow = true,
- classNames: tooltipClassNames,
- styles: tooltipStyles,
+ classNames,
+ styles,
...restProps
} = props;
@@ -102,18 +93,26 @@ const Tooltip = React.forwardRef((props, ref) => {
extraProps.popupVisible = props.visible;
}
- const getPopupElement = () => (
-
- {overlay}
-
- );
+ // ========================= Arrow ==========================
+ // Process arrow configuration
+ const mergedArrow = React.useMemo(() => {
+ 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: cls(arrowConfig.className, classNames?.arrow),
+ style: { ...arrowConfig.style, ...styles?.arrow },
+ content: arrowConfig.content ?? arrowContent,
+ };
+ }, [showArrow, classNames?.arrow, styles?.arrow, arrowContent]);
+ // ======================== Children ========================
const getChildren = () => {
const child = React.Children.only(children);
const originalProps = child?.props || {};
@@ -124,11 +123,22 @@ const Tooltip = React.forwardRef((props, ref) => {
return React.cloneElement(children, childProps) as any;
};
+ // ========================= Render =========================
return (
+ {overlay}
+
+ }
action={trigger}
builtinPlacements={placements}
popupPlacement={placement}
@@ -141,9 +151,9 @@ const Tooltip = React.forwardRef((props, ref) => {
defaultPopupVisible={defaultVisible}
autoDestroy={destroyOnHidden}
mouseLeaveDelay={mouseLeaveDelay}
- popupStyle={{ ...overlayStyle, ...tooltipStyles?.root }}
+ popupStyle={styles?.root}
mouseEnterDelay={mouseEnterDelay}
- arrow={showArrow}
+ arrow={mergedArrow}
{...extraProps}
>
{getChildren()}
diff --git a/tests/index.test.tsx b/tests/index.test.tsx
index 9586008..fd6511a 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);
@@ -59,23 +59,7 @@ describe('rc-tooltip', () => {
verifyContent(container, 'Tooltip content');
});
- // https://github.com/ant-design/ant-design/pull/23155
- it('using style inner style', () => {
- const { container } = render(
- Tooltip content}
- overlayInnerStyle={{ background: 'red' }}
- >
- Click this
- ,
- );
- fireEvent.click(container.querySelector('.target'));
- expect(
- (container.querySelector('.rc-tooltip-inner') as HTMLElement).style.background,
- ).toEqual('red');
- });
+
it('access of ref', () => {
const domRef = React.createRef();
@@ -216,6 +200,87 @@ describe('rc-tooltip', () => {
);
expect(container.querySelector('.rc-tooltip-arrow')).toBeFalsy();
});
+
+ it('should merge arrow className from showArrow and classNames.arrow', () => {
+ const { container } = render(
+ Tooltip content}
+ showArrow={{
+ className: 'from-showArrow',
+ }}
+ classNames={{
+ arrow: 'from-classNames',
+ }}
+ visible
+ >
+ Click this
+ ,
+ );
+
+ const arrowElement = container.querySelector('.rc-tooltip-arrow');
+ expect(arrowElement).toHaveClass('from-showArrow');
+ expect(arrowElement).toHaveClass('from-classNames');
+ });
+
+ it('should use arrowContent from showArrow object', () => {
+ const { container } = render(
+ Tooltip content}
+ showArrow={{
+ content: ↑,
+ }}
+ visible
+ >
+ Click this
+ ,
+ );
+
+ expect(container.querySelector('.custom-arrow-content')).toBeTruthy();
+ expect(container.querySelector('.custom-arrow-content').textContent).toBe('↑');
+ });
+
+ it('should use arrowContent prop when showArrow has no content', () => {
+ const { container } = render(
+ Tooltip content}
+ showArrow
+ arrowContent={→}
+ visible
+ >
+ Click this
+ ,
+ );
+
+ expect(container.querySelector('.prop-arrow-content')).toBeTruthy();
+ expect(container.querySelector('.prop-arrow-content').textContent).toBe('→');
+ });
+
+ it('should prioritize showArrow.content over arrowContent prop', () => {
+ const { container } = render(
+ Tooltip content}
+ showArrow={{
+ content: ↑,
+ }}
+ arrowContent={→}
+ visible
+ >
+ Click this
+ ,
+ );
+
+ expect(container.querySelector('.showArrow-content')).toBeTruthy();
+ expect(container.querySelector('.prop-content')).toBeFalsy();
+ expect(container.querySelector('.showArrow-content').textContent).toBe('↑');
+ });
});
it('visible', () => {
@@ -251,33 +316,173 @@ describe('rc-tooltip', () => {
expect(nodeRef.current.nativeElement).toBe(container.querySelector('button'));
});
- it('should apply custom styles to Tooltip', () => {
- const customClassNames = {
- body: 'custom-body',
- root: 'custom-root',
- };
+ describe('classNames and styles', () => {
+ it('should apply custom classNames to all semantic elements', () => {
+ const customClassNames = {
+ root: 'custom-root',
+ body: 'custom-body',
+ arrow: 'custom-arrow',
+ };
- const customStyles = {
- body: { color: 'red' },
- root: { backgroundColor: 'blue' },
- };
+ const { container } = render(
+ Tooltip content }
+ visible
+ showArrow
+ >
+