Skip to content

Commit 57ca6f6

Browse files
authored
Popover and Tooltip improvements (#788)
* Popover and Tooltip hover behaviour * Format source * Ensure legacy trigger updates props * Update docstring
1 parent ed6cc3f commit 57ca6f6

File tree

4 files changed

+127
-19
lines changed

4 files changed

+127
-19
lines changed

src/components/popover/Popover.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33

4-
import PopoverTemplate from '../../private/PopoverTemplate';
4+
import {PopoverTemplate} from '../../private/OverlayTemplates';
55
import Overlay from '../../private/Overlay';
66

77
/**
@@ -72,7 +72,8 @@ const Popover = props => {
7272
Popover.defaultProps = {
7373
delay: {show: 0, hide: 50},
7474
placement: 'right',
75-
flip: true
75+
flip: true,
76+
autohide: false
7677
};
7778

7879
Popover.propTypes = {
@@ -208,6 +209,11 @@ Popover.propTypes = {
208209
*/
209210
body: PropTypes.bool,
210211

212+
/**
213+
* Optionally hide popover when hovering over content - default False.
214+
*/
215+
autohide: PropTypes.bool,
216+
211217
/**
212218
* Object that holds the loading state object coming from dash-renderer
213219
*/

src/components/tooltip/Tooltip.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import {omit} from 'ramda';
4-
import RBTooltip from 'react-bootstrap/Tooltip';
54

5+
import {TooltipTemplate} from '../../private/OverlayTemplates';
66
import Overlay from '../../private/Overlay';
77

88
/**
@@ -31,17 +31,22 @@ const Tooltip = props => {
3131
{...omit(['setProps'], otherProps)}
3232
trigger="hover focus"
3333
>
34-
<RBTooltip style={style} className={class_name || className}>
34+
<TooltipTemplate
35+
id={id}
36+
style={style}
37+
className={class_name || className}
38+
>
3539
{children}
36-
</RBTooltip>
40+
</TooltipTemplate>
3741
</Overlay>
3842
);
3943
};
4044

4145
Tooltip.defaultProps = {
4246
delay: {show: 0, hide: 50},
4347
placement: 'auto',
44-
flip: true
48+
flip: true,
49+
autohide: true
4550
};
4651

4752
Tooltip.propTypes = {
@@ -118,6 +123,11 @@ Tooltip.propTypes = {
118123
*/
119124
delay: PropTypes.shape({show: PropTypes.number, hide: PropTypes.number}),
120125

126+
/**
127+
* Optionally hide tooltip when hovering over tooltip content - default True.
128+
*/
129+
autohide: PropTypes.bool,
130+
121131
/**
122132
* Object that holds the loading state object coming from dash-renderer
123133
*/

src/private/Overlay.js

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import React, {useEffect, useRef, useState} from 'react';
1+
import React, {createContext, useEffect, useRef, useState} from 'react';
22
import RBOverlay from 'react-bootstrap/Overlay';
33

4+
const OverlayContext = createContext({});
5+
46
function isInDOMSubtree(element, subtreeRoot) {
57
return (
68
subtreeRoot && (element === subtreeRoot || subtreeRoot.contains(element))
@@ -37,6 +39,7 @@ const Overlay = ({
3739
trigger,
3840
defaultShow,
3941
setProps,
42+
autohide,
4043
...otherProps
4144
}) => {
4245
// isOpen should be false initially, even if defaultShow is true
@@ -67,7 +70,10 @@ const Overlay = ({
6770
};
6871
// Function to create the delay for hiding if currently open
6972
const hideWithDelay = () => {
70-
if (isOpenRef.current) {
73+
if (!isOpenRef.current && showTimeout.current) {
74+
showTimeout.current = clearTimeout(showTimeout.current);
75+
hide();
76+
} else if (isOpenRef.current) {
7177
clearTimeout(hideTimeout.current);
7278
hideTimeout.current = setTimeout(hide, delay.hide);
7379
}
@@ -85,7 +91,10 @@ const Overlay = ({
8591
};
8692
// Function to create the delay for showing, if currently closed
8793
const showWithDelay = () => {
88-
if (!isOpenRef.current) {
94+
if (isOpenRef.current && hideTimeout.current) {
95+
hideTimeout.current = clearTimeout(hideTimeout.current);
96+
show();
97+
} else if (!isOpenRef.current) {
8998
clearTimeout(showTimeout.current);
9099
showTimeout.current = setTimeout(show, delay.show);
91100
}
@@ -110,6 +119,25 @@ const Overlay = ({
110119
}
111120
};
112121

122+
const handleMouseOverTooltipContent = e => {
123+
if (triggers.indexOf('hover') > -1 && !autohide) {
124+
if (hideTimeout.current) {
125+
hideTimeout.current = clearTimeout(hideTimeout.current);
126+
}
127+
show();
128+
}
129+
};
130+
131+
const handleMouseLeaveTooltipContent = e => {
132+
if (triggers.indexOf('hover') > -1 && !autohide) {
133+
if (showTimeout.current) {
134+
showTimeout.current = clearTimeout(showTimeout.current);
135+
}
136+
e.persist();
137+
hideWithDelay();
138+
}
139+
};
140+
113141
// Add event listeners
114142
const addEventListeners = t => {
115143
if (t) {
@@ -178,16 +206,28 @@ const Overlay = ({
178206
}, [targetStr]);
179207

180208
return (
181-
<RBOverlay
182-
show={isOpen}
183-
rootClose={rootClose} // Close when clicking outside the icon
184-
onHide={() => setIsOpen(false)} // Must be defined when using rootClose
185-
target={targetRef.current}
186-
{...otherProps}
209+
<OverlayContext.Provider
210+
value={{handleMouseOverTooltipContent, handleMouseLeaveTooltipContent}}
187211
>
188-
{children}
189-
</RBOverlay>
212+
<RBOverlay
213+
show={isOpen}
214+
rootClose={rootClose} // Close when clicking outside the icon
215+
// Must be defined when using rootClose
216+
// need to make sure that props stay in sync
217+
onHide={() => {
218+
setIsOpen(false);
219+
if (setProps) {
220+
setProps({is_open: false});
221+
}
222+
}}
223+
target={targetRef.current}
224+
{...otherProps}
225+
>
226+
{children}
227+
</RBOverlay>
228+
</OverlayContext.Provider>
190229
);
191230
};
192231

193232
export default Overlay;
233+
export {OverlayContext};

src/private/PopoverTemplate.js renamed to src/private/OverlayTemplates.js

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import React from 'react';
1+
import React, {useContext} from 'react';
22
import classNames from 'classnames';
33
import PopoverBody from 'react-bootstrap/PopoverBody';
4+
import {OverlayContext} from './Overlay';
45

56
// Convert from left/right to start/end
67
const getOverlayDirection = placement => {
@@ -35,6 +36,10 @@ const PopoverTemplate = React.forwardRef(
3536
// Identify the direction and placement
3637
const [primaryPlacement] = placement?.split('-') || [];
3738
const bsDirection = getOverlayDirection(primaryPlacement);
39+
const {
40+
handleMouseOverTooltipContent,
41+
handleMouseLeaveTooltipContent
42+
} = useContext(OverlayContext);
3843

3944
return (
4045
<div
@@ -47,6 +52,8 @@ const PopoverTemplate = React.forwardRef(
4752
'popover',
4853
primaryPlacement && `bs-popover-${bsDirection}`
4954
)}
55+
onMouseOver={handleMouseOverTooltipContent}
56+
onMouseLeave={handleMouseLeaveTooltipContent}
5057
{...props}
5158
>
5259
{!hideArrow && <div className="popover-arrow" {...arrowProps} />}
@@ -56,4 +63,49 @@ const PopoverTemplate = React.forwardRef(
5663
}
5764
);
5865

59-
export default PopoverTemplate;
66+
const TooltipTemplate = React.forwardRef(
67+
(
68+
{
69+
bsPrefix,
70+
placement,
71+
className,
72+
style,
73+
children,
74+
arrowProps,
75+
popper: _,
76+
show: _2,
77+
...props
78+
},
79+
ref
80+
) => {
81+
const [primaryPlacement] = placement?.split('-') || [];
82+
const bsDirection = getOverlayDirection(primaryPlacement);
83+
84+
const {
85+
handleMouseOverTooltipContent,
86+
handleMouseLeaveTooltipContent
87+
} = useContext(OverlayContext);
88+
89+
return (
90+
<div
91+
ref={ref}
92+
style={style}
93+
role="tooltip"
94+
x-placement={primaryPlacement}
95+
className={classNames(
96+
className,
97+
'tooltip',
98+
`bs-tooltip-${bsDirection}`
99+
)}
100+
onMouseOver={handleMouseOverTooltipContent}
101+
onMouseLeave={handleMouseLeaveTooltipContent}
102+
{...props}
103+
>
104+
<div className="tooltip-arrow" {...arrowProps} />
105+
<div className={'tooltip-inner'}>{children}</div>
106+
</div>
107+
);
108+
}
109+
);
110+
111+
export {PopoverTemplate, TooltipTemplate};

0 commit comments

Comments
 (0)