@wix/design-system
Version:
@wix/design-system
175 lines • 7.49 kB
JavaScript
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
import DropdownBase from '../DropdownBase';
import { st, classes } from './PopoverMenu.st.css.js';
import { listItemSectionBuilder, } from '../ListItemSection';
import { PLACEMENT, TEXT_SIZES } from './PopoverMenu.constants';
import { APPEND_TO } from '../PopoverNext/PopoverNext.constants';
import { mergeRefs } from '../utils/mergeRefs';
const DEFAULT_MAX_WIDTH_PX = 204;
const DEFAULT_MIN_WIDTH_PX = 144;
/** PopoverMenu */
export const PopoverMenu = forwardRef(function PopoverMenu(props, forwardedRef) {
const { maxWidth = DEFAULT_MAX_WIDTH_PX, minWidth = DEFAULT_MIN_WIDTH_PX, placement = PLACEMENT.bottom, appendTo = APPEND_TO.window, textSize = TEXT_SIZES.medium, fixed = false, flip = true, showArrow = true, ellipsis = false, maxHeight = 'auto', triggerElement, children, dataHook, moveBy, zIndex, className, fluid, onHide, onShow, fixedFooter, } = props;
const dropdownRef = useRef(null);
useImperativeHandle(forwardedRef, () => ({
_open: () => {
if (dropdownRef.current) {
dropdownRef.current._open();
}
},
}));
const filterChildren = (children) => {
return (React.Children.map(children, child => child)?.filter(child => typeof child !== 'string' && !!child && React.isValidElement(child)) ?? []);
};
const buildOptions = (children) => {
return children.map((child, id) => {
const displayName = child.type && child.type.displayName;
if (displayName && displayName === 'PopoverMenu.Divider') {
return {
id,
type: 'divider',
dataHook: child.props.dataHook,
};
}
if (displayName && displayName === 'PopoverMenu.SectionTitle') {
return {
id,
type: 'title',
title: child.props.title,
dataHook: child.props.dataHook,
};
}
if (displayName && displayName === 'PopoverMenu.MenuItem') {
return {
id,
title: child.props.text,
onClick: child.props.onClick,
skin: child.props.skin,
dataHook: child.props.dataHook,
prefixIcon: child.props.prefixIcon,
disabled: child.props.disabled,
disabledDescription: child.props.disabledDescription,
subtitle: child.props.subtitle,
suffixIcon: child.props.suffixIcon,
};
}
return {
id,
value: child,
type: 'custom',
overrideStyle: true,
};
});
};
const renderOptions = () => {
const filteredChildren = filterChildren(children);
const options = buildOptions(filteredChildren);
return options.map(option => {
const { type, ...optionRest } = option;
// Custom
if (type === 'custom') {
return optionRest;
}
// Divider
if (type === 'divider') {
return listItemSectionBuilder({
...optionRest,
type: option.type,
});
}
// SectionTitle
if (type === 'title') {
return listItemSectionBuilder({
...optionRest,
type: option.type,
});
}
const { id, disabled, dataHook, skin, subtitle, title, ...rest } = optionRest;
return {
...rest,
id,
disabled,
as: 'button',
dataHook: dataHook || `popover-menu-${id}`,
skin: skin || 'standard',
size: textSize,
className: classes.listItem,
ellipsis,
subtitle,
shouldFocusWithoutScroll: true,
value: 'action',
title: false,
optionTitle: title,
};
});
};
const renderTriggerElement = ({ toggle, open, close, isOpen, ref, }) => {
if (!triggerElement) {
return null;
}
const handleTriggerClick = (e) => {
if (React.isValidElement(triggerElement) &&
triggerElement.props?.onClick) {
triggerElement.props.onClick(e);
}
toggle();
};
const commonProps = {
onClick: handleTriggerClick,
ref,
'aria-haspopup': 'menu',
'aria-expanded': isOpen,
};
// When cloning a React element, merge the user's ref with the internal ref
if (React.isValidElement(triggerElement)) {
const elementWithRef = triggerElement;
const userRef = elementWithRef.ref;
const mergedRef = mergeRefs(ref, userRef);
return React.cloneElement(triggerElement, {
...commonProps,
ref: mergedRef,
});
}
// When using a function, pass the ref directly
if (typeof triggerElement === 'function') {
return triggerElement({
toggle,
open,
close,
isOpen,
...commonProps,
});
}
return null;
};
/**
* If the trigger element is disabled, the popover menu should not trigger open or close.
* Otherwise, the popover menu should use DropdownBase's default behavior.
*/
const getDropdownOpenProps = () => {
// Not bulletproof. We should use popovermenu disabled prop instead.
// TODO: introduce disabled prop for popovermenu.
let isDropdownDisabled = false;
if (React.isValidElement(triggerElement)) {
isDropdownDisabled = triggerElement.props?.disabled ?? false;
}
return isDropdownDisabled ? { open: false } : {};
};
return (React.createElement(DropdownBase, { ...getDropdownOpenProps(), ref: dropdownRef, className: st(classes.root, className), popoverContentClassName: st(classes.popoverContent, {
withWidth: Boolean(minWidth || maxWidth),
}), dataHook: dataHook, animate: true, options: renderOptions(), appendTo: appendTo, placement: placement, minWidth: minWidth, maxWidth: maxWidth, width: "fit-content", flip: flip, fixed: fixed, showArrow: showArrow, tabIndex: -1, moveBy: moveBy, maxHeight: maxHeight, zIndex: zIndex, fluid: fluid, onHide: onHide, onShow: onShow, listType: "action", autoFocus: true, fixedFooter: fixedFooter }, ({ toggle, open, close, isOpen, ref }) => renderTriggerElement({ toggle, open, close, isOpen, ref })));
});
const MenuItem = () => React.createElement(React.Fragment, null);
MenuItem.displayName = 'PopoverMenu.MenuItem';
const Divider = () => React.createElement(React.Fragment, null);
Divider.displayName = 'PopoverMenu.Divider';
const SectionTitle = () => React.createElement(React.Fragment, null);
SectionTitle.displayName = 'PopoverMenu.SectionTitle';
Object.assign(PopoverMenu, {
displayName: 'PopoverMenu',
MenuItem,
Divider,
SectionTitle,
});
export default PopoverMenu;
//# sourceMappingURL=PopoverMenu.js.map