UNPKG

@wix/design-system

Version:

@wix/design-system

175 lines 7.49 kB
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