Skip to content

Commit 6d413f0

Browse files
feat(Popover) add option to trigger popover on hover
1 parent 6b44876 commit 6d413f0

File tree

3 files changed

+81
-15
lines changed

3 files changed

+81
-15
lines changed

packages/react-core/src/components/Popover/Popover.tsx

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ export interface PopoverProps {
203203
shouldOpen?: (event: MouseEvent | KeyboardEvent, showFunction?: () => void) => void;
204204
/** Flag indicating whether the close button should be shown. */
205205
showClose?: boolean;
206+
/** Sets an interaction to open popover, defaults to "click" */
207+
variant?: 'click' | 'hover';
206208
/** Whether to trap focus in the popover. */
207209
withFocusTrap?: boolean;
208210
/** The z-index of the popover. */
@@ -241,6 +243,7 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
241243
onShown = (): void => null,
242244
onMount = (): void => null,
243245
zIndex = 9999,
246+
variant = 'click',
244247
minWidth = popoverMinWidth && popoverMinWidth.value,
245248
maxWidth = popoverMaxWidth && popoverMaxWidth.value,
246249
closeBtnAriaLabel = 'Close',
@@ -279,6 +282,7 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
279282
const showTimerRef = React.useRef(null);
280283
const hideTimerRef = React.useRef(null);
281284
const popoverRef = React.useRef(null);
285+
const hideShowTime = variant === 'hover' ? animationDuration : 0;
282286

283287
React.useEffect(() => {
284288
onMount();
@@ -306,7 +310,7 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
306310
setOpacity(1);
307311
propWithFocusTrap !== false && withFocusTrap && setFocusTrapActive(true);
308312
onShown();
309-
}, 0);
313+
}, hideShowTime);
310314
};
311315
const hide = (event?: MouseEvent | KeyboardEvent) => {
312316
event && onHide(event);
@@ -320,7 +324,7 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
320324
transitionTimerRef.current = setTimeout(() => {
321325
onHidden();
322326
}, animationDuration);
323-
}, 0);
327+
}, hideShowTime);
324328
};
325329
const positionModifiers = {
326330
top: styles.modifiers.top,
@@ -363,25 +367,49 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
363367
}
364368
};
365369
const onTriggerClick = (event: MouseEvent) => {
366-
if (triggerManually) {
367-
if (visible) {
368-
shouldClose(event, hide);
369-
} else {
370-
shouldOpen(event, show);
371-
}
372-
} else {
373-
if (visible) {
374-
hide(event);
370+
if (variant === 'click') {
371+
if (triggerManually) {
372+
if (visible) {
373+
shouldClose(event, hide);
374+
} else {
375+
shouldOpen(event, show);
376+
}
375377
} else {
376-
show(event, true);
378+
if (visible) {
379+
hide(event);
380+
} else {
381+
show(event, true);
382+
}
377383
}
378384
}
379385
};
386+
380387
const onContentMouseDown = () => {
381388
if (focusTrapActive) {
382389
setFocusTrapActive(false);
383390
}
384391
};
392+
393+
const onMouseEnter = (event: any) => {
394+
if (variant === 'hover') {
395+
if (triggerManually) {
396+
shouldOpen(event as MouseEvent, show);
397+
} else {
398+
show(event as MouseEvent, false);
399+
}
400+
}
401+
};
402+
403+
const onMouseLeave = (event: any) => {
404+
if (variant === 'hover') {
405+
if (triggerManually) {
406+
shouldClose(event as MouseEvent, hide);
407+
} else {
408+
hide(event);
409+
}
410+
}
411+
};
412+
385413
const closePopover = (event: MouseEvent) => {
386414
event.stopPropagation();
387415
if (triggerManually) {
@@ -424,6 +452,8 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
424452
aria-labelledby={headerContent ? `popover-${uniqueId}-header` : undefined}
425453
aria-describedby={`popover-${uniqueId}-body`}
426454
onMouseDown={onContentMouseDown}
455+
onMouseLeave={onMouseLeave}
456+
onMouseEnter={onMouseEnter}
427457
style={{
428458
minWidth: hasCustomMinWidth ? minWidth : null,
429459
maxWidth: hasCustomMaxWidth ? maxWidth : null,
@@ -434,7 +464,9 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
434464
>
435465
<PopoverArrow />
436466
<PopoverContent>
437-
{showClose && <PopoverCloseButton onClose={closePopover} aria-label={closeBtnAriaLabel} />}
467+
{showClose && variant === 'click' && (
468+
<PopoverCloseButton onClose={closePopover} aria-label={closeBtnAriaLabel} />
469+
)}
438470
{headerContent && (
439471
<PopoverHeader
440472
id={`popover-${uniqueId}-header`}
@@ -468,6 +500,8 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
468500
minWidth="revert"
469501
appendTo={appendTo}
470502
isVisible={visible}
503+
onMouseEnter={onMouseEnter}
504+
onMouseLeave={onMouseLeave}
471505
positionModifiers={positionModifiers}
472506
distance={distance}
473507
placement={position}

packages/react-core/src/components/Popover/examples/Popover.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,57 +19,73 @@ By default, the `appendTo` prop of the popover will append to the document body
1919
### Basic
2020

2121
```ts file="./PopoverBasic.tsx"
22+
23+
```
24+
25+
### Hoverable
26+
27+
```ts file="./PopoverHover.tsx"
28+
2229
```
2330

2431
### Close popover from content (controlled)
2532

2633
```ts file="./PopoverCloseControlled.tsx"
34+
2735
```
2836

2937
### Close popover from content (uncontrolled)
3038

3139
Note: If you use the isVisible prop, either refer to the example above or if you want to use the hide callback from the content then be sure to keep isVisible in-sync.
3240

3341
```ts file="./PopoverCloseUncontrolled.tsx"
42+
3443
```
3544

3645
### Without header/footer/close and no padding
3746

3847
```ts file="./PopoverWithoutHeaderFooterCloseNoPadding.tsx"
48+
3949
```
4050

4151
### Width auto
4252

4353
Here the popover goes over the navigation, so the prop `appendTo` is set to the documents body.
4454

4555
```ts file="./PopoverWidthAuto.tsx"
56+
4657
```
4758

4859
### Popover react ref
4960

5061
```ts file="./PopoverReactRef.tsx"
62+
5163
```
5264

5365
### Popover selector ref
5466

5567
```ts file="./PopoverSelectorRef.tsx"
68+
5669
```
5770

5871
### Advanced
5972

6073
```ts file="./PopoverAdvanced.tsx"
74+
6175
```
6276

6377
### Popover with icon in the title
6478

6579
Here the popover goes over the navigation, so the prop `appendTo` is set to the documents body.
6680

67-
```ts file="./PopoverWithIconInTheTitle.tsx"
81+
```ts file="./PopoverWithIconInTheTitle.tsx"
82+
6883
```
6984

7085
### Alert popover
7186

7287
Here the popover goes over the navigation, so the prop `appendTo` is set to the documents body.
7388

74-
```ts file="./PopoverAlert.tsx"
89+
```ts file="./PopoverAlert.tsx"
90+
7591
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
import { Popover, Button } from '@patternfly/react-core';
3+
4+
export const PopoverHover: React.FunctionComponent = () => (
5+
<div style={{ margin: '50px' }}>
6+
<Popover
7+
variant="hover"
8+
aria-label="Hoverable popover"
9+
headerContent={<div>Popover header</div>}
10+
bodyContent={<div>This popover opens on hover.</div>}
11+
footerContent="Popover footer"
12+
>
13+
<Button disabled>Hover to trigger popover</Button>
14+
</Popover>
15+
</div>
16+
);

0 commit comments

Comments
 (0)