UNPKG

@chayns-components/core

Version:

A set of beautiful React components for developing your own applications with chayns.

179 lines (178 loc) 6.13 kB
import { createDialog, DialogType } from 'chayns-api'; import { AnimatePresence } from 'motion/react'; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { useUuid } from '../../hooks/uuid'; import { ContextMenuAlignment } from '../../types/contextMenu'; import { getIsTouch } from '../../utils/environment'; import Icon from '../icon/Icon'; import ContextMenuContent from './context-menu-content/ContextMenuContent'; import { StyledContextMenu } from './ContextMenu.styles'; const ContextMenu = /*#__PURE__*/forwardRef((_ref, ref) => { let { alignment, children = /*#__PURE__*/React.createElement(Icon, { icons: ['ts-ellipsis_v'], size: 18 }), container, coordinates, shouldHidePopupArrow = false, items, headline, onHide, onShow, shouldCloseOnPopupClick = true, shouldSeparateLastItem = false, shouldShowHoverEffect = false, zIndex = 20 } = _ref; const [internalCoordinates, setInternalCoordinates] = useState({ x: 0, y: 0 }); const [newContainer, setNewContainer] = useState(container ?? null); const [internalAlignment, setInternalAlignment] = useState(ContextMenuAlignment.TopLeft); const [isContentShown, setIsContentShown] = useState(false); const [portal, setPortal] = useState(); const uuid = useUuid(); // ToDo: Replace with hook if new chayns api is ready const contextMenuContentRef = useRef(null); const contextMenuRef = useRef(null); const isTouch = getIsTouch(); useEffect(() => { if (contextMenuRef.current && !container) { const el = contextMenuRef.current; const element = el.closest('.dialog-inner, .page-provider, .tapp, body'); setNewContainer(element); } }, [container]); useEffect(() => { if (container instanceof Element) { setNewContainer(container); } }, [container]); const handleHide = useCallback(() => { setIsContentShown(false); }, []); const handleShow = useCallback(async () => { if (isTouch) { const { result } = await createDialog({ type: DialogType.SELECT, buttons: [], list: items.map((_ref2, index) => { let { icons, text, isSelected } = _ref2; return { name: text, id: index, isSelected, icon: icons[0] }; }) }).open(); if (result && typeof result[0] === 'number') { void items[result[0]]?.onClick(); } } else if (contextMenuRef.current) { if (!newContainer) { return; } const { height: childrenHeight, left: childrenLeft, top: childrenTop, width: childrenWidth } = contextMenuRef.current.getBoundingClientRect(); const { height, width, top, left } = newContainer.getBoundingClientRect(); const zoomX = width / newContainer.offsetWidth; const zoomY = height / newContainer.offsetHeight; const x = (childrenLeft + childrenWidth / 2 - left) / zoomX + newContainer.scrollLeft; const y = (childrenTop + childrenHeight / 2 - top) / zoomY + newContainer.scrollTop; setInternalCoordinates({ x, y }); if (x < width / 2) { if (y < height / 2) { setInternalAlignment(ContextMenuAlignment.BottomRight); } else { setInternalAlignment(ContextMenuAlignment.TopRight); } } else if (y < height / 2) { setInternalAlignment(ContextMenuAlignment.BottomLeft); } else { setInternalAlignment(ContextMenuAlignment.TopLeft); } setIsContentShown(true); } }, [isTouch, items, newContainer]); const handleClick = useCallback(event => { event.preventDefault(); event.stopPropagation(); void handleShow(); }, [handleShow]); const handleDocumentClick = useCallback(event => { if (!shouldCloseOnPopupClick && contextMenuContentRef.current?.contains(event.target)) { return; } handleHide(); }, [handleHide, shouldCloseOnPopupClick]); useImperativeHandle(ref, () => ({ hide: handleHide, show: handleShow }), [handleHide, handleShow]); useEffect(() => { if (isContentShown) { document.addEventListener('click', handleDocumentClick, true); window.addEventListener('blur', handleHide); if (typeof onShow === 'function') { onShow(); } } else if (typeof onHide === 'function') { onHide(); } return () => { document.removeEventListener('click', handleDocumentClick, true); window.removeEventListener('blur', handleHide); }; }, [handleDocumentClick, handleHide, isContentShown, onHide, onShow]); useEffect(() => { if (!newContainer) { return; } setPortal(() => /*#__PURE__*/createPortal(/*#__PURE__*/React.createElement(AnimatePresence, { initial: false }, isContentShown && /*#__PURE__*/React.createElement(ContextMenuContent, { coordinates: coordinates ?? internalCoordinates, items: items, shouldSeparateLastItem: shouldSeparateLastItem, zIndex: zIndex, headline: headline, shouldHidePopupArrow: shouldHidePopupArrow, key: `contextMenu_${uuid}`, alignment: alignment ?? internalAlignment, ref: contextMenuContentRef })), newContainer)); }, [alignment, newContainer, coordinates, internalAlignment, internalCoordinates, isContentShown, items, uuid, zIndex, shouldHidePopupArrow, headline, shouldSeparateLastItem]); return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(StyledContextMenu, { className: "beta-chayns-context-menu", $isActive: isContentShown && shouldShowHoverEffect, $shouldAddHoverEffect: !isTouch && shouldShowHoverEffect, onClick: handleClick, ref: contextMenuRef }, children), portal); }); ContextMenu.displayName = 'ContextMenu'; export default ContextMenu; //# sourceMappingURL=ContextMenu.js.map