UNPKG

@chayns-components/core

Version:

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

231 lines (227 loc) • 8.05 kB
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 { PopupAlignment } from '../../types/popup'; import AreaContextProvider from '../area-provider/AreaContextProvider'; import PopupContentWrapper from './popup-content-wrapper/PopupContentWrapper'; import { StyledPopup } from './Popup.styles'; import { useMeasuredClone } from '../../hooks/element'; const Popup = /*#__PURE__*/forwardRef((_ref, ref) => { let { alignment, content, onShow, container, onHide, children, shouldHideOnChildrenLeave, shouldShowOnHover = false, shouldUseChildrenWidth = true, shouldScrollWithContent = false, shouldUseFullWidth = false, yOffset = 0 } = _ref; const [coordinates, setCoordinates] = useState({ x: 0, y: 0 }); const [internalAlignment, setInternalAlignment] = useState(PopupAlignment.TopLeft); const [offset, setOffset] = useState(0); const [isOpen, setIsOpen] = useState(false); const [portal, setPortal] = useState(); const [pseudoSize, setPseudoSize] = useState(); const [newContainer, setNewContainer] = useState(container ?? null); const timeout = useRef(); const uuid = useUuid(); const { height, width, measuredElement } = useMeasuredClone({ content }); // ToDo: Replace with hook if new chayns api is ready const popupContentRef = useRef(null); const popupRef = useRef(null); useEffect(() => { if (popupRef.current && !container) { const el = popupRef.current; const element = el.closest('.dialog-inner, .page-provider, .tapp, body'); setNewContainer(element); } }, [container]); useEffect(() => { if (container instanceof Element) { setNewContainer(container); } }, [container]); useEffect(() => { setPseudoSize({ height, width }); }, [height, width]); const handleShow = useCallback(() => { if (popupRef.current && pseudoSize) { if (!newContainer) { return; } const { height: pseudoHeight, width: pseudoWidth } = pseudoSize; const { height: childrenHeight, left: childrenLeft, top: childrenTop, width: childrenWidth } = popupRef.current.getBoundingClientRect(); const { height: containerHeight, width: containerWidth, top, left } = newContainer.getBoundingClientRect(); const zoomX = containerWidth / newContainer.offsetWidth; const zoomY = containerHeight / newContainer.offsetHeight; if (pseudoHeight > childrenTop - 25 || alignment === PopupAlignment.BottomLeft || alignment === PopupAlignment.BottomRight) { let isRight = false; if (pseudoWidth > childrenLeft + childrenWidth / 2 - 25 || alignment === PopupAlignment.BottomRight) { setInternalAlignment(PopupAlignment.BottomRight); isRight = true; } else { setInternalAlignment(PopupAlignment.BottomLeft); } const x = (childrenLeft + childrenWidth / 2 - left) / zoomX + newContainer.scrollLeft; const y = (childrenTop + childrenHeight / 2 - top) / zoomY + newContainer.scrollTop - yOffset; let newOffset; if (isRight) { newOffset = x + pseudoWidth >= window.innerWidth ? x + pseudoWidth - window.innerWidth : 0; } else { newOffset = 0; const right = window.innerWidth - (childrenLeft + childrenWidth / 2); newOffset = right + pseudoWidth >= window.innerWidth ? right + pseudoWidth - window.innerWidth : 0; } setOffset(newOffset); const newX = x - newOffset; setCoordinates({ x: newX < 23 ? 23 : newX, y }); } else { let isRight = false; if (pseudoWidth > childrenLeft + childrenWidth / 2 - 25 || alignment === PopupAlignment.TopRight) { setInternalAlignment(PopupAlignment.TopRight); isRight = true; } else { setInternalAlignment(PopupAlignment.TopLeft); } const x = (childrenLeft + childrenWidth / 2 - left) / zoomX + newContainer.scrollLeft; const y = (childrenTop + childrenHeight / 2 - top) / zoomY + newContainer.scrollTop - yOffset; let newOffset; if (isRight) { newOffset = x + pseudoWidth >= window.innerWidth ? x + pseudoWidth - window.innerWidth : 0; } else { newOffset = 0; const right = window.innerWidth - (childrenLeft + childrenWidth / 2); newOffset = right + pseudoWidth >= window.innerWidth ? right + pseudoWidth - window.innerWidth : 0; } setOffset(newOffset); const newX = x - newOffset; setCoordinates({ x: newX < 23 ? 23 : newX, y }); } setIsOpen(true); } }, [alignment, newContainer, pseudoSize, yOffset]); const handleChildrenClick = () => { handleShow(); }; const handleHide = useCallback(() => { setIsOpen(false); }, []); const handleMouseEnter = useCallback(() => { if (shouldShowOnHover) { window.clearTimeout(timeout.current); handleShow(); } }, [handleShow, shouldShowOnHover]); const handleMouseLeave = useCallback(() => { if (!shouldShowOnHover) { return; } if (shouldHideOnChildrenLeave) { handleHide(); return; } timeout.current = window.setTimeout(() => { handleHide(); }, 500); }, [handleHide, shouldHideOnChildrenLeave, shouldShowOnHover]); const handleDocumentClick = useCallback(event => { if (!popupContentRef.current?.contains(event.target)) { handleHide(); } }, [handleHide]); useImperativeHandle(ref, () => ({ hide: handleHide, show: handleShow }), [handleHide, handleShow]); // useEffect(() => { // void getWindowMetrics().then((result) => { // if (result.topBarHeight) { // setMenuHeight(result.topBarHeight); // } // }); // }, []); useEffect(() => { if (isOpen) { 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, isOpen, onHide, onShow]); useEffect(() => { if (!newContainer) { return; } setPortal(() => /*#__PURE__*/createPortal(/*#__PURE__*/React.createElement(AnimatePresence, { initial: false }, isOpen && /*#__PURE__*/React.createElement(PopupContentWrapper, { width: pseudoSize?.width ?? 0, offset: offset, shouldScrollWithContent: shouldScrollWithContent, coordinates: coordinates, key: `tooltip_${uuid}`, alignment: internalAlignment, ref: popupContentRef, onMouseLeave: handleMouseLeave, onMouseEnter: handleMouseEnter }, /*#__PURE__*/React.createElement(AreaContextProvider, { shouldChangeColor: false }, content))), newContainer)); }, [internalAlignment, newContainer, content, coordinates, handleMouseEnter, handleMouseLeave, isOpen, offset, pseudoSize?.width, uuid, shouldScrollWithContent]); return /*#__PURE__*/React.createElement(React.Fragment, null, measuredElement, /*#__PURE__*/React.createElement(StyledPopup, { className: "beta-chayns-popup", ref: popupRef, onClick: handleChildrenClick, onMouseLeave: handleMouseLeave, onMouseEnter: handleMouseEnter, $shouldUseChildrenWidth: shouldUseChildrenWidth, $shouldUseFullWidth: shouldUseFullWidth }, children), portal); }); Popup.displayName = 'Popup'; export default Popup; //# sourceMappingURL=Popup.js.map