Skip to content

Commit 28eea9f

Browse files
authored
🐛 fix: resolve Tooltip Popover hover conflict (#488)
- Update TooltipInGroup and TooltipStandalone for popover integration - Add popover-wrapping-tooltip and popover-wrapping-tooltip-group demos - Update Popover documentation Made-with: Cursor
1 parent 5a5221c commit 28eea9f

File tree

5 files changed

+193
-9
lines changed

5 files changed

+193
-9
lines changed

src/Tooltip/TooltipInGroup.tsx

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,90 @@ import { useMergedTooltipProps } from './useMergedTooltipProps';
1414
const DEFAULT_OPEN_DELAY = 400;
1515
const DEFAULT_CLOSE_DELAY = 100;
1616

17-
export const TooltipInGroup: FC<TooltipProps> = ({ children, ref: refProp, ...props }) => {
17+
export const TooltipInGroup: FC<TooltipProps> = ({
18+
children,
19+
ref: refProp,
20+
arrow,
21+
className,
22+
classNames,
23+
closeDelay,
24+
defaultOpen,
25+
disabled: disabledProp,
26+
getPopupContainer,
27+
hotkey,
28+
hotkeyProps,
29+
mouseEnterDelay,
30+
mouseLeaveDelay,
31+
onOpenChange,
32+
open,
33+
openDelay,
34+
placement,
35+
popupContainer,
36+
popupProps,
37+
portalProps,
38+
positionerProps,
39+
standalone: _standalone,
40+
styles,
41+
title,
42+
triggerProps: triggerPropsProp,
43+
zIndex,
44+
...restProps
45+
}) => {
46+
const tooltipProps = useMemo(
47+
() => ({
48+
arrow,
49+
className,
50+
classNames,
51+
closeDelay,
52+
defaultOpen,
53+
disabled: disabledProp,
54+
getPopupContainer,
55+
hotkey,
56+
hotkeyProps,
57+
mouseEnterDelay,
58+
mouseLeaveDelay,
59+
onOpenChange,
60+
open,
61+
openDelay,
62+
placement,
63+
popupContainer,
64+
popupProps,
65+
portalProps,
66+
positionerProps,
67+
styles,
68+
title,
69+
triggerProps: triggerPropsProp,
70+
zIndex,
71+
}),
72+
[
73+
arrow,
74+
className,
75+
classNames,
76+
closeDelay,
77+
defaultOpen,
78+
disabledProp,
79+
getPopupContainer,
80+
hotkey,
81+
hotkeyProps,
82+
mouseEnterDelay,
83+
mouseLeaveDelay,
84+
onOpenChange,
85+
open,
86+
openDelay,
87+
placement,
88+
popupContainer,
89+
popupProps,
90+
portalProps,
91+
positionerProps,
92+
styles,
93+
title,
94+
triggerPropsProp,
95+
zIndex,
96+
],
97+
);
98+
1899
const group = use(TooltipGroupHandleContext);
19-
const item = useMergedTooltipProps(props);
100+
const item = useMergedTooltipProps(tooltipProps);
20101

21102
const resolvedOpenDelay = useMemo(() => {
22103
if (item.openDelay !== undefined) return item.openDelay;
@@ -45,17 +126,17 @@ export const TooltipInGroup: FC<TooltipProps> = ({ children, ref: refProp, ...pr
45126
const resolvedProps = (() => {
46127
if (isNativeButtonTriggerElement) return renderProps as any;
47128
// eslint-disable-next-line unused-imports/no-unused-vars
48-
const { type, ref: triggerRef, ...restProps } = renderProps as any;
49-
return restProps;
129+
const { type, ref: triggerRef, ...triggerRest } = renderProps as any;
130+
return triggerRest;
50131
})();
51132

52-
const mergedProps = mergeProps((childElement as any).props, resolvedProps);
133+
const mergedProps = mergeProps(restProps, (childElement as any).props, resolvedProps);
53134
return cloneElement(childElement as any, {
54135
...mergedProps,
55136
ref: mergeRefs([(childElement as any).ref, (renderProps as any).ref, refProp]),
56137
});
57138
},
58-
[childElement, isNativeButtonTriggerElement, refProp],
139+
[childElement, isNativeButtonTriggerElement, refProp, restProps],
59140
);
60141

61142
// Don't render trigger behavior if no content

src/Tooltip/TooltipStandalone.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export const TooltipStandalone = memo<TooltipProps>(
5151
triggerProps,
5252
popupProps,
5353
portalProps,
54+
standalone: _standalone,
55+
...restProps
5456
}) => {
5557
const isClient = useIsClient();
5658
const [uncontrolledOpen, setUncontrolledOpen] = useState(Boolean(defaultOpen));
@@ -144,11 +146,11 @@ export const TooltipStandalone = memo<TooltipProps>(
144146
const resolvedProps = (() => {
145147
if (isNativeButtonTriggerElement) return props as any;
146148
// eslint-disable-next-line unused-imports/no-unused-vars
147-
const { type, ref: triggerRef, ...restProps } = props as any;
148-
return restProps;
149+
const { type, ref: triggerRef, ...triggerRest } = props as any;
150+
return triggerRest;
149151
})();
150152

151-
const mergedProps = mergeProps((children as any).props, resolvedProps);
153+
const mergedProps = mergeProps(restProps, (children as any).props, resolvedProps);
152154
return cloneElement(children as any, {
153155
...mergedProps,
154156
ref: mergeRefs([
@@ -175,6 +177,7 @@ export const TooltipStandalone = memo<TooltipProps>(
175177
refProp,
176178
resolvedCloseDelay,
177179
resolvedOpenDelay,
180+
restProps,
178181
triggerCallbackRef,
179182
triggerProps,
180183
]);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Button, Flexbox, Popover, Tooltip, TooltipGroup } from '@lobehub/ui';
2+
import { HelpCircle, Info } from 'lucide-react';
3+
4+
/**
5+
* Edge case: Popover wraps Tooltip inside a TooltipGroup (singleton mode).
6+
* Hover shows the shared tooltip; click opens the popover.
7+
*/
8+
export default () => {
9+
return (
10+
<Flexbox align="center" gap={16} height={280} justify="center" style={{ padding: 24 }}>
11+
<TooltipGroup>
12+
<Flexbox horizontal gap={12}>
13+
<Popover
14+
arrow
15+
placement="bottom"
16+
trigger="click"
17+
content={
18+
<Flexbox gap={8} style={{ padding: '12px 16px', width: 260 }}>
19+
<div style={{ fontSize: 15, fontWeight: 600 }}>Popover A</div>
20+
<div style={{ color: 'var(--lobe-color-text-3)', fontSize: 13, lineHeight: 1.6 }}>
21+
This popover opens on click, while the tooltip (singleton) shows on hover.
22+
</div>
23+
</Flexbox>
24+
}
25+
>
26+
<Tooltip title="Tooltip A (singleton)">
27+
<Button icon={<HelpCircle size={16} />} type="primary">
28+
Button A
29+
</Button>
30+
</Tooltip>
31+
</Popover>
32+
33+
<Popover
34+
arrow
35+
placement="bottom"
36+
trigger="click"
37+
content={
38+
<Flexbox gap={8} style={{ padding: '12px 16px', width: 260 }}>
39+
<div style={{ fontSize: 15, fontWeight: 600 }}>Popover B</div>
40+
<div style={{ color: 'var(--lobe-color-text-3)', fontSize: 13, lineHeight: 1.6 }}>
41+
Hovering between buttons smoothly transitions the shared tooltip. Clicking still
42+
opens each button's own popover.
43+
</div>
44+
</Flexbox>
45+
}
46+
>
47+
<Tooltip title="Tooltip B (singleton)">
48+
<Button icon={<Info size={16} />}>Button B</Button>
49+
</Tooltip>
50+
</Popover>
51+
</Flexbox>
52+
</TooltipGroup>
53+
</Flexbox>
54+
);
55+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Button, Flexbox, Popover, Tooltip } from '@lobehub/ui';
2+
import { HelpCircle } from 'lucide-react';
3+
4+
/**
5+
* Edge case: Popover wraps Tooltip — the trigger of the Popover is a Tooltip-wrapped element.
6+
* Hover shows tooltip first; click (or hover, depending on trigger) opens the popover.
7+
*/
8+
export default () => {
9+
return (
10+
<Flexbox align="center" height={280} justify="center" style={{ padding: 24 }}>
11+
<Popover
12+
arrow
13+
placement="bottom"
14+
trigger="click"
15+
content={
16+
<Flexbox gap={8} style={{ padding: '12px 16px', width: 260 }}>
17+
<div style={{ fontSize: 15, fontWeight: 600 }}>Popover wrapping Tooltip</div>
18+
<div style={{ color: 'var(--lobe-color-text-3)', fontSize: 13, lineHeight: 1.6 }}>
19+
The trigger is a button wrapped by Tooltip. Tooltip shows on hover; click opens this
20+
popover.
21+
</div>
22+
</Flexbox>
23+
}
24+
>
25+
<Tooltip title="Hover: tooltip · Click: popover">
26+
<Button icon={<HelpCircle size={16} />} size="large" type="primary">
27+
Hover for tooltip / Click for popover
28+
</Button>
29+
</Tooltip>
30+
</Popover>
31+
</Flexbox>
32+
);
33+
};

src/base-ui/Popover/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ When using Tooltip inside Popover, the Tooltip will automatically render into th
6161

6262
<code src="./demos/tooltip-hover-stay.tsx" nopadding></code>
6363

64+
## Popover wrapping Tooltip (Edge Case)
65+
66+
Edge case where the Popover trigger is a Tooltip-wrapped element: `<Popover><Tooltip>...</Tooltip></Popover>`. Hover shows the tooltip; click (or hover, depending on trigger) opens the popover.
67+
68+
<code src="./demos/popover-wrapping-tooltip.tsx" nopadding></code>
69+
70+
## Popover wrapping Tooltip in Group (Edge Case)
71+
72+
Same as above, but with `TooltipGroup` (singleton mode). Hovering between buttons smoothly transitions the shared tooltip; clicking opens each button's own popover.
73+
74+
<code src="./demos/popover-wrapping-tooltip-group.tsx" nopadding></code>
75+
6476
## Native Button Auto Detection
6577

6678
<code src="./demos/native-button.tsx" nopadding></code>

0 commit comments

Comments
 (0)