Skip to content

Commit

Permalink
[MOL-16654][RL] Enhance popover interaction
Browse files Browse the repository at this point in the history
  • Loading branch information
qroll committed Sep 24, 2024
1 parent c4cb151 commit b028b06
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 72 deletions.
135 changes: 65 additions & 70 deletions src/popover-v2/popover-trigger.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import {
FloatingFocusManager,
FloatingPortal,
autoUpdate,
flip,
limitShift,
offset,
shift,
useClick,
useDismiss,
useFloating,
useHover,
useInteractions,
} from "@floating-ui/react";
import { useEffect, useRef, useState } from "react";
import { useRef, useState } from "react";
import { useMediaQuery } from "react-responsive";
import { MediaWidths } from "../media";
import { useFloatingChild } from "../overlay/use-floating-context";
import { PopoverV2 } from "./popover";
import { TriggerContainer } from "./popover-trigger.styles";
import { PopoverV2TriggerProps } from "./types";
import { PopoverV2TriggerProps, PopoverV2TriggerType } from "./types";

export const PopoverTrigger = ({
children,
popoverContent,
trigger = "click",
trigger: _trigger = "click",
position = "top",
zIndex,
rootNode,
Expand All @@ -35,7 +40,7 @@ export const PopoverTrigger = ({
const isMobile = useMediaQuery({
maxWidth: MediaWidths.mobileL,
});
const { refs, floatingStyles } = useFloating({
const { refs, floatingStyles, context } = useFloating({
open: visible,
placement: position,
whileElementsMounted: autoUpdate,
Expand All @@ -46,63 +51,46 @@ export const PopoverTrigger = ({
limiter: limitShift(),
}),
],
onOpenChange: (isOpen) => {
setVisible(isOpen);

// this callback is triggering twice on hover, the check here prevents extra calls
if (visible !== isOpen) {
handleVisibilityChange(isOpen);
}
},
});
const parentZIndex = useFloatingChild();

// =========================================================================
// EFFECTS
// =========================================================================
useEffect(() => {
// NOTE: Do not add mouse down event if it's mobile
if (isMobile || !visible) {
return;
}
document.addEventListener("mousedown", handleMouseDownEvent);
const trigger: PopoverV2TriggerType = isMobile ? "click" : _trigger;
const click = useClick(context, {
// allow trigger by Space/Enter, but disable mouse click in hover mode
ignoreMouse: trigger === "hover",
});
const dismiss = useDismiss(context);
const hover = useHover(context, {
enabled: trigger === "hover",
// short window to enter the floating element without it closing
delay: { close: 500 },
});

return () => {
document.removeEventListener("mousedown", handleMouseDownEvent);
};
}, [visible]);
const { getReferenceProps, getFloatingProps } = useInteractions([
click,
dismiss,
hover,
]);

// =========================================================================
// EVENT HANDLERS
// =========================================================================
const handleMouseDownEvent = (event: MouseEvent) => {
if (
!nodeRef.current?.contains(event.target as Node) &&
!popoverRef.current?.contains(event.target as Node)
) {
// outside click
setVisible(false);

if (onPopoverDismiss) onPopoverDismiss();
}
};

const handleClick = (event: React.MouseEvent) => {
event.preventDefault();
if (trigger === "click" || isMobile) {
setVisible(!visible);

if (!visible && onPopoverAppear) onPopoverAppear();
if (visible && onPopoverDismiss) onPopoverDismiss();
}
};

const handleOnMouseEnter = () => {
if (trigger === "hover" && !isMobile) {
setVisible(true);
}
};

const handleOnMouseLeave = () => {
if (trigger === "hover" && visible && !isMobile) {
setVisible(false);
}
};

const handlePopoverMobileClose = () => {
setVisible(false);
handleVisibilityChange(false);
};

const handleVisibilityChange = (nextVisible: boolean) => {
if (nextVisible && onPopoverAppear) onPopoverAppear();
if (!nextVisible && onPopoverDismiss) onPopoverDismiss();
};

// =========================================================================
Expand All @@ -122,34 +110,41 @@ export const PopoverTrigger = ({

return (
<>
{visible && (
<FloatingPortal root={rootNode}>
<div
ref={(node) => {
popoverRef.current = node;
refs.setFloating(node);
}}
style={{
...floatingStyles,
zIndex: zIndex ?? parentZIndex,
}}
>
{renderPopover()}
</div>
</FloatingPortal>
)}
<TriggerContainer
ref={(node) => {
nodeRef.current = node;
refs.setReference(node);
}}
onClick={handleClick}
onMouseEnter={handleOnMouseEnter}
onMouseLeave={handleOnMouseLeave}
{...getReferenceProps({
onClick: (event) => {
// prevent popover interaction from triggering click events on parents
event.stopPropagation();
event.preventDefault();
},
})}
{...otherProps}
>
{children}
</TriggerContainer>
{visible && (
<FloatingPortal root={rootNode}>
<FloatingFocusManager context={context}>
<div
ref={(node) => {
popoverRef.current = node;
refs.setFloating(node);
}}
style={{
...floatingStyles,
zIndex: zIndex ?? parentZIndex,
}}
{...getFloatingProps()}
>
{renderPopover()}
</div>
</FloatingFocusManager>
</FloatingPortal>
)}
</>
);
};
12 changes: 10 additions & 2 deletions stories/popover-v2/popover.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,20 @@ import { PopoverTrigger } from "@lifesg/react-design-system/popover-v2";

<Canvas of={PopoverStories.HoverInteraction} />

<Heading4>Appearance information</Heading4>
<Heading4>Behaviour</Heading4>

**Appearance**

- The `Popover` component automatically positions itself based on the amount of space available. It will always attempt to display itself at the preferred position by default
- The `Popover` appears as a modal in mobile viewports

The example below illustrates the default behaviour.
**Keyboard support**

The trigger should be included in the page's tab sequence. This way, users who
navigate via keyboard can open the popover with `Space`/`Enter`. On open, focus
will move to the popover. Press `Escape` to dismiss the popover.

The example below illustrates the positioning behaviour when viewed on desktop.

<Canvas of={PopoverStories.Appearance} />

Expand Down

0 comments on commit b028b06

Please sign in to comment.