UNPKG

@wonderflow/react-components

Version:

UI components from Wonderflow's Wanda design system

113 lines (112 loc) 4.79 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import tkns from '@wonderflow/tokens/platforms/web/tokens.json'; import { useFocusWithin, useKeyPress } from 'ahooks'; import clsx from 'clsx'; import { domMax, LazyMotion, m, } from 'framer-motion'; import { Children, cloneElement, forwardRef, isValidElement, useEffect, useMemo, useRef, useState, } from 'react'; import { createPortal } from 'react-dom'; import { mergeRefs } from 'react-merge-refs'; import { usePopperTooltip } from 'react-popper-tooltip'; import { useUIDSeed } from 'react-uid'; import { isBrowser } from '../../utils/browser'; import * as styles from './popover.module.css'; const cssEasingToArray = (cssEasing) => { const [x1, y1, x2, y2] = cssEasing.replace(/[^0-9.,]+/g, '').split(',').map(i => parseFloat(i)); return [x1, y1, x2, y2]; }; const PopoverAnimation = { visible: { y: 0, opacity: 1, transition: { ease: cssEasingToArray(tkns.easing.entrance), duration: 0.1, }, }, hidden: { y: -5, opacity: 0, transition: { ease: cssEasingToArray(tkns.easing.exit), duration: 0.1, }, }, }; export const Popover = forwardRef(({ children, trigger, offset = 8, placement = 'auto-start', open, disabled, closeOnOutsideClick = true, closeOnInsideClick = false, className, matchTriggerWidth, onOpenChange, root, ...otherProps }, forwardedRef) => { const seedID = useUIDSeed(); const [isOpen, setIsOpen] = useState(false); const popoverContainerRef = useRef(null); const sameWidth = useMemo(() => ({ name: 'sameWidth', enabled: true, phase: 'beforeWrite', requires: ['computeStyles'], fn: ({ state }) => { const s = state; if (matchTriggerWidth) s.styles.popper.width = `${s.rects.reference.width}px`; }, effect: ({ state }) => { const s = state; const referenceElement = s.elements.reference; if (matchTriggerWidth) s.elements.popper.style.width = `${referenceElement.offsetWidth ?? 0}px`; }, }), [matchTriggerWidth]); const handleCloseOnClickInside = (e) => { e.stopPropagation(); if (isOpen && closeOnInsideClick) { setIsOpen(false); } }; const { getTooltipProps, setTooltipRef, setTriggerRef, // eslint-disable-next-line @typescript-eslint/naming-convention visible, } = usePopperTooltip({ delayShow: 0, delayHide: 0, trigger: !disabled ? ['click'] : null, visible: isOpen, closeOnTriggerHidden: true, closeOnOutsideClick, onVisibleChange: (state) => { onOpenChange?.(state); setIsOpen(state); }, }, { placement, modifiers: [ sameWidth, { name: 'offset', options: { offset: [0, offset], }, }, ], }); const isFocusWithin = useFocusWithin(popoverContainerRef, { onBlur: (e) => { if (e.relatedTarget && visible) { setIsOpen(true); } }, }); useKeyPress('esc', () => setIsOpen(false)); useEffect(() => { if (open !== undefined) setIsOpen(open); }, [open]); return (_jsxs("div", { ref: mergeRefs([popoverContainerRef, forwardedRef]), className: clsx(styles.Popover, className), "data-popover-has-focus": isFocusWithin, children: [isValidElement(trigger) && cloneElement(trigger, { ref: setTriggerRef, id: seedID('popover-trigger'), key: seedID('popover-trigger'), 'aria-haspopup': 'true', 'aria-controls': seedID('popover-dialog'), 'aria-expanded': isOpen, ...otherProps, }), isBrowser() && visible && createPortal(_jsx("div", { "aria-hidden": true, "data-testid": "tooltip", ref: setTooltipRef, role: "tooltip", id: seedID('tooltip-content'), ...getTooltipProps({ className: styles.PopUp }), onClick: e => handleCloseOnClickInside(e), children: _jsx(LazyMotion, { features: domMax, strict: true, children: _jsx(m.div, { variants: PopoverAnimation, initial: "hidden", animate: "visible", exit: "hidden", children: _jsx("div", { children: Children.map(children, child => isValidElement(child) && cloneElement(child, { id: seedID('popover-dialog'), 'aria-labelledby': seedID('popover-trigger'), })) }) }) }) }, seedID('tooltip-content')), root ?? document.body)] })); }); Popover.displayName = 'Popover';