@wix/design-system
Version:
@wix/design-system
243 lines • 9.65 kB
JavaScript
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