diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.story.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.story.tsx index 73526a825a7..309a3028098 100644 --- a/packages/grafana-ui/src/components/Combobox/Combobox.story.tsx +++ b/packages/grafana-ui/src/components/Combobox/Combobox.story.tsx @@ -330,6 +330,52 @@ export const Async: StoryObj = { render: AsyncStory, }; +const noop = () => {}; +const PositioningTestStory: StoryFn = (args) => { + if (typeof args.options === 'function') { + throw new Error('This story does not support async options'); + } + + function renderColumnOfComboboxes(pos: string) { + return ( +
+ + + +
+ ); + } + + return ( +
+ {renderColumnOfComboboxes('Left')} + {renderColumnOfComboboxes('Middle')} + {renderColumnOfComboboxes('Right')} +
+ ); +}; + +export const PositioningTest: StoryObj = { + render: PositioningTestStory, +}; + export const ComparisonToSelect: StoryObj = { args: { numberOfOptions: 100, diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.tsx index 6813b634318..ec402f78dc1 100644 --- a/packages/grafana-ui/src/components/Combobox/Combobox.tsx +++ b/packages/grafana-ui/src/components/Combobox/Combobox.tsx @@ -10,7 +10,7 @@ import { AutoSizeInput } from '../Input/AutoSizeInput'; import { Input, Props as InputProps } from '../Input/Input'; import { getComboboxStyles } from './getComboboxStyles'; -import { estimateSize, useComboboxFloat } from './useComboboxFloat'; +import { useComboboxFloat, OPTION_HEIGHT } from './useComboboxFloat'; import { StaleResultError, useLatestAsyncCall } from './useLatestAsyncCall'; export type ComboboxOption = { @@ -129,7 +129,7 @@ export const Combobox = ({ const virtualizerOptions = { count: items.length, getScrollElement: () => floatingRef.current, - estimateSize, + estimateSize: () => OPTION_HEIGHT, overscan: 4, }; diff --git a/packages/grafana-ui/src/components/Combobox/getComboboxStyles.ts b/packages/grafana-ui/src/components/Combobox/getComboboxStyles.ts index e967c260f85..9c24efae0ea 100644 --- a/packages/grafana-ui/src/components/Combobox/getComboboxStyles.ts +++ b/packages/grafana-ui/src/components/Combobox/getComboboxStyles.ts @@ -2,8 +2,6 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; -const MAX_HEIGHT = 400; - // We need a px font size to accurately measure the width of items. // This should be in sync with the body font size in the theme. export const MENU_ITEM_FONT_SIZE = 14; @@ -20,7 +18,6 @@ export const getComboboxStyles = (theme: GrafanaTheme2) => { background: theme.components.dropdown.background, boxShadow: theme.shadows.z3, zIndex: theme.zIndex.dropdown, - maxHeight: MAX_HEIGHT, overflowY: 'auto', position: 'relative', }), diff --git a/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts b/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts index 0dc912f29e4..6b271d081ed 100644 --- a/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts +++ b/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts @@ -9,12 +9,12 @@ import { MENU_ITEM_FONT_SIZE, MENU_ITEM_FONT_WEIGHT, MENU_ITEM_PADDING_X } from // Only consider the first n items when calculating the width of the popover. const WIDTH_CALCULATION_LIMIT_ITEMS = 100_000; -/** - * Used with Downshift to get the height of each item - */ -export function estimateSize() { - return 45; -} +// Used with Downshift to get the height of each item +export const OPTION_HEIGHT = 45; +const POPOVER_MAX_HEIGHT = OPTION_HEIGHT * 8.5; + +// Clearance around the popover to prevent it from being too close to the edge of the viewport +const POPOVER_PADDING = 16; export const useComboboxFloat = ( items: Array>, @@ -23,7 +23,7 @@ export const useComboboxFloat = ( ) => { const inputRef = useRef(null); const floatingRef = useRef(null); - const [popoverMaxWidth, setPopoverMaxWidth] = useState(undefined); + const [popoverMaxSize, setPopoverMaxSize] = useState<{ width: number; height: number } | undefined>(undefined); const scrollbarWidth = useMemo(() => getScrollbarWidth(), []); @@ -35,8 +35,14 @@ export const useComboboxFloat = ( boundary: document.body, }), size({ - apply({ availableWidth }) { - setPopoverMaxWidth(availableWidth); + apply({ availableWidth, availableHeight }) { + const preferredMaxWidth = availableWidth - POPOVER_PADDING; + const preferredMaxHeight = availableHeight - POPOVER_PADDING; + + const width = Math.max(preferredMaxWidth, 0); + const height = Math.min(Math.max(preferredMaxHeight, OPTION_HEIGHT), POPOVER_MAX_HEIGHT); + + setPopoverMaxSize({ width, height }); }, }), ]; @@ -67,8 +73,10 @@ export const useComboboxFloat = ( const floatStyles = { ...floatingStyles, width: longestItemWidth, - maxWidth: popoverMaxWidth, + maxWidth: popoverMaxSize?.width, minWidth: inputRef.current?.offsetWidth, + + maxHeight: popoverMaxSize?.height, }; return { inputRef, floatingRef, floatStyles };