From 3c6e2aaac686a68b0393adf13067913eae0ab02c Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 18 Jul 2023 21:31:43 -0400 Subject: [PATCH] fix: Opening contextual menus sometimes change scroll position --- app/components/ContextMenu/index.tsx | 9 ++++-- app/components/InputSelect.tsx | 10 +++---- app/hooks/useMenuHeight.ts | 44 ++++++++++++++++++++-------- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/app/components/ContextMenu/index.tsx b/app/components/ContextMenu/index.tsx index f44f9afd6..64547c873 100644 --- a/app/components/ContextMenu/index.tsx +++ b/app/components/ContextMenu/index.tsx @@ -56,7 +56,10 @@ const ContextMenu: React.FC = ({ ...rest }) => { const previousVisible = usePrevious(rest.visible); - const maxHeight = useMenuHeight(rest.visible, rest.unstable_disclosureRef); + const maxHeight = useMenuHeight({ + visible: rest.visible, + elementRef: rest.unstable_disclosureRef, + }); const backgroundRef = React.useRef(null); const { ui } = useStores(); const { t } = useTranslation(); @@ -147,9 +150,9 @@ const ContextMenu: React.FC = ({ ref={backgroundRef} hiddenScrollbars style={ - maxHeight && topAnchor + topAnchor ? { - maxHeight: `min(${maxHeight}px, 75vh)`, + maxHeight, } : undefined } diff --git a/app/components/InputSelect.tsx b/app/components/InputSelect.tsx index d926ea04f..ee345c73c 100644 --- a/app/components/InputSelect.tsx +++ b/app/components/InputSelect.tsx @@ -85,11 +85,11 @@ const InputSelect = (props: Props) => { const contentRef = React.useRef(null); const minWidth = buttonRef.current?.offsetWidth || 0; const margin = 8; - const menuMaxHeight = useMenuHeight( - select.visible, - select.unstable_disclosureRef, - margin - ); + const menuMaxHeight = useMenuHeight({ + visible: select.visible, + elementRef: select.unstable_disclosureRef, + margin, + }); const maxHeight = Math.min( menuMaxHeight ?? 0, window.innerHeight - diff --git a/app/hooks/useMenuHeight.ts b/app/hooks/useMenuHeight.ts index 2d544eb66..0e298df87 100644 --- a/app/hooks/useMenuHeight.ts +++ b/app/hooks/useMenuHeight.ts @@ -2,26 +2,44 @@ import * as React from "react"; import useMobile from "~/hooks/useMobile"; import useWindowSize from "~/hooks/useWindowSize"; -const useMenuHeight = ( - visible: void | boolean, - unstable_disclosureRef?: React.RefObject, - margin = 8 -) => { - const [maxHeight, setMaxHeight] = React.useState(); +const useMenuHeight = ({ + visible, + elementRef, + maxViewportHeight = 70, + margin = 8, +}: { + /** Whether the menu is visible. */ + visible: void | boolean; + /** The maximum height of the menu as a percentage of the viewport. */ + maxViewportHeight?: number; + /** A ref pointing to the element for the menu disclosure. */ + elementRef?: React.RefObject; + /** The margin to apply to the positioning. */ + margin?: number; +}) => { + const [maxHeight, setMaxHeight] = React.useState(10); const isMobile = useMobile(); const { height: windowHeight } = useWindowSize(); - React.useEffect(() => { + React.useLayoutEffect(() => { if (visible && !isMobile) { + const maxHeight = (windowHeight / 100) * maxViewportHeight; + setMaxHeight( - unstable_disclosureRef?.current - ? windowHeight - - unstable_disclosureRef.current.getBoundingClientRect().bottom - - margin - : undefined + Math.min( + maxHeight, + elementRef?.current + ? windowHeight - + elementRef.current.getBoundingClientRect().bottom - + margin + : 0 + ) ); + } else { + setMaxHeight(0); } - }, [visible, unstable_disclosureRef, windowHeight, margin, isMobile]); + }, [visible, elementRef, windowHeight, margin, isMobile, maxViewportHeight]); + return maxHeight; };