UNPKG

@wix/design-system

Version:

@wix/design-system

243 lines 9.65 kB
import { useContext, useEffect, useMemo } from 'react'; import { useFloating, autoUpdate, flip, shift, useClick, useDismiss, useRole, useInteractions, useHover, size, offset, autoPlacement, safePolygon, } from '@floating-ui/react'; import { APPEND_TO } from '../PopoverNext.constants'; import { ZIndex } from '../../common/ZIndex'; import { usePositioning } from './usePositioning'; import deprecationLog from '../../utils/deprecationLog'; import { useTransition } from './useTransition'; import { useArrow } from './useArrow'; import { WixDesignSystemContext } from '../../WixDesignSystemProvider/context'; const ANIMATED_DURATION = { open: 150, close: 100 }; const formatWidth = (value) => typeof value === 'number' ? `${value}px` : value; export function usePopover({ dataHook, open = false, onOpenChange, focusManagerEnabled = true, appendTo = APPEND_TO.parent, dynamicWidth = false, zIndex = ZIndex.popover, width, minWidth, maxWidth, excludeClass, moveBy, flip: shouldFlip = true, placement = 'bottom', fixed = false, rootRef, showDelay, hideDelay, timeout: duration, skin, transitionSettings, onClickOutside, overlay = false, role, tabIndex, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, id, style, onClick, animate, showArrow = false, customArrow, contentClassName, onMouseEnter, onMouseLeave, autoUpdateOptions, }) { const { contextClassName } = useContext(WixDesignSystemContext); const isHoverMode = Boolean(onMouseEnter && onMouseLeave); const isHybridMode = Boolean(onMouseEnter) !== Boolean(onMouseLeave); useEffect(() => { if (showDelay) { deprecationLog('<PopoverNext /> - prop `showDelay` is deprecated. Use `transitionSettings` instead.'); } if (hideDelay) { deprecationLog('<PopoverNext /> - prop `hideDelay` is deprecated. Use `transitionSettings` instead.'); } if (duration) { deprecationLog('<PopoverNext /> - prop `timeout` is deprecated. Use `transitionSettings` instead.'); } if (animate) { deprecationLog('<PopoverNext /> - prop `animate` is deprecated. Use `transitionSettings` instead.'); } }, [showDelay, hideDelay, duration, animate]); const positioning = usePositioning({ flip: shouldFlip, fixed, placement, appendTo, rootRef, }); const { getArrowProps, arrowMiddleware, getContentWithArrowStyles } = useArrow(showArrow); const { context, middlewareData, floatingStyles, placement: floatingPlacement, } = useFloating({ open, onOpenChange: (newOpen, event, reason) => { onOpenChange(newOpen, reason); if (reason === 'hover' || reason === 'safe-polygon') { if (newOpen && onMouseEnter && event) { onMouseEnter(event); } else if (!newOpen && onMouseLeave && event) { onMouseLeave(event); } } }, whileElementsMounted: (referenceEl, floatingEl, update) => autoUpdate(referenceEl, floatingEl, update, { animationFrame: autoUpdateOptions?.animationFrame ?? false, }), placement: positioning.placement, middleware: [ size({ apply({ rects, elements }) { const styles = { wordBreak: 'normal', display: 'flex', justifyContent: 'center', }; if (width) { styles.width = formatWidth(width); if (minWidth) { styles.minWidth = formatWidth(minWidth); } if (maxWidth) { styles.maxWidth = formatWidth(maxWidth); } } else if (dynamicWidth) { styles.minWidth = formatWidth(rects.reference.width); if (maxWidth) { styles.maxWidth = formatWidth(maxWidth); } } else { if (minWidth) { styles.minWidth = formatWidth(minWidth); } if (maxWidth) { styles.maxWidth = formatWidth(maxWidth); } } if (appendTo === 'window' || appendTo === 'scrollParent') { styles.minWidth = !!minWidth ? formatWidth(minWidth) : 'fit-content'; styles.width = !!width ? formatWidth(width) : formatWidth(rects.reference.width); if (maxWidth) { styles.maxWidth = formatWidth(maxWidth); } } Object.assign(elements.floating.style, styles); }, }), !!positioning.flip ? flip(positioning.flip) : undefined, !!positioning.autoPlacement ? autoPlacement(positioning.autoPlacement) : undefined, shift(positioning.shift), moveBy ? offset({ mainAxis: moveBy?.y ?? 0, crossAxis: moveBy?.x ?? 0, }) : undefined, arrowMiddleware, ].filter(Boolean), }); const hover = useHover(context, { enabled: Boolean(onMouseEnter) || Boolean(onMouseLeave), delay: { open: showDelay ?? transitionSettings?.openDelay ?? 0, close: hideDelay ?? transitionSettings?.closeDelay ?? 0, }, handleClose: safePolygon({ requireIntent: true, }), }); const click = useClick(context, { enabled: !isHoverMode, }); const dismiss = useDismiss(context, { outsidePress: event => { const target = event.target; const classes = target.classList; const referenceElement = context.refs.reference.current; const floatingElement = context.refs.floating.current; if (referenceElement instanceof Element && referenceElement.contains(target)) { return false; } // Under React 16 the floating ref can be unset when Floating UI's // outside-press listener evaluates the click that just opened the // popover, causing it to fire onClickOutside on an inside click. if (floatingElement instanceof Element && floatingElement.contains(target)) { return false; } if (excludeClass && classes.contains(excludeClass)) { return false; } onClickOutside?.(event); return true; }, escapeKey: true, }); const roleSetting = useRole(context, { role }); const interactions = useInteractions([ hover, click, dismiss, roleSetting, { reference: { onClick, }, floating: { id, tabIndex: tabIndex ?? -1, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, ...(isHybridMode ? { onMouseEnter, onMouseLeave } : {}), }, }, ]); const convertToTransitionSettings = (duration) => { if (!duration || typeof duration === 'number') { return duration; } return { open: duration.enter, close: duration.exit }; }; const { isMounted, styles: transitionStyles } = useTransition({ ...transitionSettings, duration: transitionSettings?.duration ?? convertToTransitionSettings(duration) ?? (animate ? ANIMATED_DURATION : undefined), closeDelay: transitionSettings?.closeDelay ?? hideDelay, openDelay: transitionSettings?.openDelay ?? showDelay, context, }); return useMemo(() => ({ dataHook, focusManagerEnabled, interactions, context, appendTo, zIndex, open: isMounted, popoverStyles: { ...context.floatingStyles, ...floatingStyles, ...getContentWithArrowStyles(floatingPlacement), }, transitionStyles, portalRoot: positioning.portalRoot, contextClassName, skin, overlay, style, rootRef, middlewareData, placement, arrowProps: getArrowProps(context), showArrow, contentClassName, customArrow, onMouseEnter, onMouseLeave, }), [ dataHook, focusManagerEnabled, interactions, floatingPlacement, context, onMouseEnter, onMouseLeave, isMounted, zIndex, appendTo, positioning, transitionStyles, contextClassName, skin, overlay, style, middlewareData, placement, showArrow, rootRef, floatingStyles, contentClassName, customArrow, getArrowProps, getContentWithArrowStyles, ]); } //# sourceMappingURL=usePopover.js.map