Combobox: Squish the menu height in small viewports (#95568)

* Combobox: Squish the menu height in small viewports

* rename constants
This commit is contained in:
Josh Hunt 2024-10-29 17:47:31 +00:00 committed by GitHub
parent 261b4a5564
commit 952957248f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 66 additions and 15 deletions

View File

@ -330,6 +330,52 @@ export const Async: StoryObj<PropsAndCustomArgs> = {
render: AsyncStory,
};
const noop = () => {};
const PositioningTestStory: StoryFn<PropsAndCustomArgs> = (args) => {
if (typeof args.options === 'function') {
throw new Error('This story does not support async options');
}
function renderColumnOfComboboxes(pos: string) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
flex: 1,
}}
>
<Combobox placeholder={`${pos} top`} options={args.options} value={null} onChange={noop} />
<Combobox placeholder={`${pos} middle`} options={args.options} value={null} onChange={noop} />
<Combobox placeholder={`${pos} bottom`} options={args.options} value={null} onChange={noop} />
</div>
);
}
return (
<div
style={{
display: 'flex',
flexDirection: 'row',
// approx the height of the dev alert, and three margins. exact doesn't matter
minHeight: 'calc(100vh - (105px + 16px + 16px + 16px))',
justifyContent: 'space-between',
gap: 32,
}}
>
{renderColumnOfComboboxes('Left')}
{renderColumnOfComboboxes('Middle')}
{renderColumnOfComboboxes('Right')}
</div>
);
};
export const PositioningTest: StoryObj<PropsAndCustomArgs> = {
render: PositioningTestStory,
};
export const ComparisonToSelect: StoryObj<PropsAndCustomArgs> = {
args: {
numberOfOptions: 100,

View File

@ -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<T extends string | number = string> = {
@ -129,7 +129,7 @@ export const Combobox = <T extends string | number>({
const virtualizerOptions = {
count: items.length,
getScrollElement: () => floatingRef.current,
estimateSize,
estimateSize: () => OPTION_HEIGHT,
overscan: 4,
};

View File

@ -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',
}),

View File

@ -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<ComboboxOption<string | number>>,
@ -23,7 +23,7 @@ export const useComboboxFloat = (
) => {
const inputRef = useRef<HTMLInputElement>(null);
const floatingRef = useRef<HTMLDivElement>(null);
const [popoverMaxWidth, setPopoverMaxWidth] = useState<number | undefined>(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 };