mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Combobox: Squish the menu height in small viewports (#95568)
* Combobox: Squish the menu height in small viewports * rename constants
This commit is contained in:
parent
261b4a5564
commit
952957248f
@ -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,
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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',
|
||||
}),
|
||||
|
@ -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 };
|
||||
|
Loading…
Reference in New Issue
Block a user