import React, { FC, ReactElement, cloneElement, forwardRef, ReactNode, useLayoutEffect, } from "react"; import classNames from "classnames"; import { Manager as PopperManager, Reference as PopperReference, Popper, PopperArrowProps, } from "react-popper-2"; import { Box, BoxProps } from "../Box"; import { Portal } from "../Portal"; import { bemHOF } from "../../utilities/bem"; import { PopperPlacement } from "../../types"; const cn = bemHOF("Popover"); interface InlineSharedProps { /** * Removes the padding from inside the popover */ removePadding?: boolean; } export interface PopoverTooltipSharedProps extends BoxProps { /** * The element that the popover should be attached to. The component * as `referenceElement` must be able to accept a `ref`. If you’re * using a component that does not allow ref forwarding, wrap the * component in a `Box`. */ referenceElement: ReactElement; /** * Position where the popover should appear */ placement?: PopperPlacement; /** * Array of Popper.js modifiers */ popperModifiers?: Record[]; /** * Manually control the popover’s visibility */ isVisible?: boolean; } interface PopoverDefaultProps extends PopoverTooltipSharedProps, InlineSharedProps { /** * Whether or not to render the popover inline */ inline?: false; /** * Toggles the popover arrow on and off */ showArrow?: boolean; } interface PopoverInlineProps extends InlineSharedProps, BoxProps { inline: true; placement?: "top" | "right" | "bottom" | "left"; } interface InlinePopoverProps extends BoxProps { "data-placement"?: PopperPlacement; hasPortal?: boolean; removePadding: boolean; } type PopoverProps = PopoverDefaultProps | PopoverInlineProps; const InlinePopover: FC = forwardRef< HTMLDivElement, InlinePopoverProps >(({ className, hasPortal = false, removePadding, ...rest }, ref) => { const Container = hasPortal ? Portal : React.Fragment; return ( ); }); const ChildrenWithArrow = ({ children, popperPlacement, arrowProps, update, showArrow, }: { children: ReactNode; popperPlacement: PopperPlacement; arrowProps: PopperArrowProps; update: () => void; showArrow: boolean; }) => { useLayoutEffect(() => { update(); }, [children, update]); return ( <> {children} {showArrow && (
)} ); }; export const Popover = ({ removePadding = false, ...props }: PopoverProps) => { if (props.inline) { const { placement, className, children, style: styleProp, inline, ...rest } = props; return ( {children} ); } if (!props.inline) { const { referenceElement, popperModifiers, isVisible, placement, className, style: styleProp, children, inline, showArrow = false, ...rest } = props; const offset = showArrow ? [0, 18] : [0, 8]; const defaultPopperModifiers = [ { name: "offset", options: { offset, }, }, ]; const propModifiers = popperModifiers || []; return ( {isVisible && ( {({ ref, style, placement: popperPlacement, arrowProps, update, }) => ( {children} )} )} {({ ref }) => cloneElement(referenceElement, { ref, }) } ); } return null; };