From cf2063f19afe29b69c688086e9b0b0d32a7d472f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= Date: Mon, 13 Dec 2021 14:59:51 +0100 Subject: [PATCH] Select: Infer dropdown menu position (#42446) * Infer dropdown menu position when using asynchronously loaded options * Infer position only when the menu is opened when the component is created * Add link to react-select bug * Update docs --- .../src/components/Select/SelectBase.tsx | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index 3f71f5416b3..c6e22c66e85 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -1,4 +1,4 @@ -import React, { ComponentProps, useCallback } from 'react'; +import React, { ComponentProps, useCallback, useEffect, useRef, useState } from 'react'; import { default as ReactSelect } from 'react-select'; import Creatable from 'react-select/creatable'; import { default as ReactAsyncSelect } from 'react-select/async'; @@ -144,6 +144,27 @@ export function SelectBase({ } const theme = useTheme2(); const styles = getSelectStyles(theme); + + const reactSelectRef = useRef<{ controlRef: HTMLElement }>(null); + const [closeToBottom, setCloseToBottom] = useState(false); + + // Infer the menu position for asynchronously loaded options. menuPlacement="auto" doesn't work when the menu is + // automatically opened when the component is created (it happens in SegmentSelect by setting menuIsOpen={true}). + // We can remove this workaround when the bug in react-select is fixed: https://github.com/JedWatson/react-select/issues/4936 + // Note: we use useEffect instead of hooking into onMenuOpen due to another bug: https://github.com/JedWatson/react-select/issues/3375 + useEffect(() => { + if ( + loadOptions && + isOpen && + reactSelectRef.current && + reactSelectRef.current.controlRef && + menuPlacement === 'auto' + ) { + const distance = window.innerHeight - reactSelectRef.current.controlRef.getBoundingClientRect().bottom; + setCloseToBottom(distance < maxMenuHeight); + } + }, [maxMenuHeight, menuPlacement, loadOptions, isOpen]); + const onChangeWithEmpty = useCallback( (value: SelectValue, action: ActionMeta) => { if (isMulti && (value === undefined || value === null)) { @@ -204,7 +225,7 @@ export function SelectBase({ minMenuHeight, maxVisibleValues, menuIsOpen: isOpen, - menuPlacement, + menuPlacement: menuPlacement === 'auto' && closeToBottom ? 'top' : menuPlacement, menuPosition, menuShouldBlockScroll: true, menuPortalTarget: menuShouldPortal ? document.body : undefined, @@ -246,6 +267,7 @@ export function SelectBase({ return ( <>