@chayns-components/core
Version:
A set of beautiful React components for developing your own applications with chayns.
179 lines (178 loc) • 6.13 kB
JavaScript
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