diff --git a/src/components/ContextMenu/ContextMenu.react.js b/src/components/ContextMenu/ContextMenu.react.js index fdd309c6e4..278a8aecb7 100644 --- a/src/components/ContextMenu/ContextMenu.react.js +++ b/src/components/ContextMenu/ContextMenu.react.js @@ -5,80 +5,105 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ + import PropTypes from 'lib/PropTypes'; import React, { useState, useEffect, useRef } from 'react'; import styles from 'components/ContextMenu/ContextMenu.scss'; -const getPositionToFitVisibleScreen = ref => { - if (ref.current) { - const elBox = ref.current.getBoundingClientRect(); - const y = elBox.y + elBox.height < window.innerHeight ? 0 : 0 - elBox.y + 100; - - // If there's a previous element show current next to it. - // Try on right side first, then on left if there's no place. - const prevEl = ref.current.previousSibling; - if (prevEl) { - const prevElBox = prevEl.getBoundingClientRect(); - const showOnRight = prevElBox.x + prevElBox.width + elBox.width < window.innerWidth; - return { - x: showOnRight ? prevElBox.width : -elBox.width, - y, - }; - } +const getPositionToFitVisibleScreen = ( + ref, + offset = 0, + mainItemCount = 0, + subItemCount = 0 +) => { + if (!ref.current) { + return; + } + + const elBox = ref.current.getBoundingClientRect(); + const menuHeight = elBox.height; + const footerHeight = 50; + const lowerLimit = window.innerHeight - footerHeight; + const upperLimit = 0; + + const shouldApplyOffset = mainItemCount === 0 || subItemCount > mainItemCount; + const prevEl = ref.current.previousSibling; + + if (prevEl) { + const prevElBox = prevEl.getBoundingClientRect(); + const showOnRight = prevElBox.x + prevElBox.width + elBox.width < window.innerWidth; - return { x: 0, y }; + let proposedTop = shouldApplyOffset + ? prevElBox.top + offset + : prevElBox.top; + + proposedTop = Math.max(upperLimit, Math.min(proposedTop, lowerLimit - menuHeight)); + + return { + x: showOnRight ? prevElBox.width : -elBox.width, + y: proposedTop - elBox.top, + }; } + + const proposedTop = elBox.top + offset; + const clampedTop = Math.max(upperLimit, Math.min(proposedTop, lowerLimit - menuHeight)); + return { + x: 0, + y: clampedTop - elBox.top, + }; }; -const MenuSection = ({ level, items, path, setPath, hide }) => { +const MenuSection = ({ level, items, path, setPath, hide, parentItemCount = 0 }) => { const sectionRef = useRef(null); - const [position, setPosition] = useState(); + const [position, setPosition] = useState(null); + const hasPositioned = useRef(false); useEffect(() => { - const newPosition = getPositionToFitVisibleScreen(sectionRef); - newPosition && setPosition(newPosition); - }, [sectionRef]); + if (!hasPositioned.current) { + const newPosition = getPositionToFitVisibleScreen( + sectionRef, + path[level] * 30, + parentItemCount, + items.length + ); + if (newPosition) { + setPosition(newPosition); + hasPositioned.current = true; + } + } + }, []); const style = position ? { - left: position.x, - top: position.y + path[level] * 30, + transform: `translate(${position.x}px, ${position.y}px)`, maxHeight: '80vh', - overflowY: 'scroll', + overflowY: 'auto', opacity: 1, + position: 'absolute', } : {}; return (