UNPKG

glass-app-manager

Version:

Informatica's Glass Framework CLI for bootstrapping

150 lines (128 loc) 4.33 kB
// @flow import * as React from "react"; import Positioner from "react-positioner"; export type PopoverProps = { /** * Specify how you want the user to interact with the trigger. */ on?: "click" | "focus" | "hover", /** * Set `true` to open the popup by default. Use this when * you want to _control_ when the popup is open or not. */ open?: boolean, /** * Specify a custom trigger. This makes the popup _uncontrolled_ * and state will be handled internally. */ trigger?: React.ElementType, children: React.Node, /** * Specify the content of the popup. */ content: React.Node, defaultOpen?: boolean, onOpen?: Event => void, onClose?: Event => void, bodyOffset?: number, position?: "top" | "top-left" | "top-right" | "bottom" | "bottom-left" | "bottom-right" | "left" | "right", targetOffset?: number, }; export default function Popover({ bodyOffset = 6, children, content, defaultOpen = false, open, on = "", onOpen, onClose, position = "bottom", targetOffset = 8, trigger, ...rest }: PopoverProps) { const [internalIsOpen, setInternalIsOpen] = React.useState(defaultOpen); const [focus, setFocus] = React.useState(false); // Handle close event const handleClose = React.useCallback( e => { onClose && onClose(e); // Trigger user's custom onClose event setInternalIsOpen(false); }, [onClose, setInternalIsOpen] ); // Handle clicks on content component const handleContentClick = React.useCallback( e => { if (content.props && content.props.onClick) { content.props.onClick(e); // Trigger any onClick events inside content } // Prevent document's click listener from closing content when clicked e.nativeEvent.stopImmediatePropagation(); }, [content.props] ); // Handle open event const handleOpen = React.useCallback( e => { onOpen && onOpen(e); // Trigger setInternalIsOpen(true); }, [onOpen] ); // Handle clicks on trigger component const handleToggle = React.useCallback( e => { if (open || (open === undefined && internalIsOpen)) { handleClose(e); } else { handleOpen(e); } }, [open, internalIsOpen, handleClose, handleOpen] ); const getEventProps = React.useMemo( () => ({ ...(on.indexOf("hover") !== -1 ? { onMouseEnter: handleOpen, onMouseLeave: handleClose, } : {}), ...(on.indexOf("focus") !== -1 ? { onFocus: e => { handleOpen(e); setFocus(true); }, // TODO: Fix the race condition with document click listener onBlur: () => setFocus(false), } : {}), ...(on.indexOf("click") !== -1 ? { onClick: handleToggle, } : {}), }), [on, handleClose, handleOpen, handleToggle, setFocus] ); // Click listener for clicks outside of the trigger component React.useEffect(() => { if ((open || (open === undefined && internalIsOpen)) && !focus) { document.addEventListener("click", handleClose); } return () => document.removeEventListener("click", handleClose); }, [internalIsOpen, open, focus, handleClose]); return ( <Positioner content={React.cloneElement(content, { onClick: handleContentClick })} isShown={open !== undefined ? open : internalIsOpen} position={position} bodyOffset={bodyOffset} targetOffset={targetOffset}> {React.cloneElement(trigger || children, { ...getEventProps })} </Positioner> ); }