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,
|
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> = {
|
export const ComparisonToSelect: StoryObj<PropsAndCustomArgs> = {
|
||||||
args: {
|
args: {
|
||||||
numberOfOptions: 100,
|
numberOfOptions: 100,
|
||||||
|
@ -10,7 +10,7 @@ import { AutoSizeInput } from '../Input/AutoSizeInput';
|
|||||||
import { Input, Props as InputProps } from '../Input/Input';
|
import { Input, Props as InputProps } from '../Input/Input';
|
||||||
|
|
||||||
import { getComboboxStyles } from './getComboboxStyles';
|
import { getComboboxStyles } from './getComboboxStyles';
|
||||||
import { estimateSize, useComboboxFloat } from './useComboboxFloat';
|
import { useComboboxFloat, OPTION_HEIGHT } from './useComboboxFloat';
|
||||||
import { StaleResultError, useLatestAsyncCall } from './useLatestAsyncCall';
|
import { StaleResultError, useLatestAsyncCall } from './useLatestAsyncCall';
|
||||||
|
|
||||||
export type ComboboxOption<T extends string | number = string> = {
|
export type ComboboxOption<T extends string | number = string> = {
|
||||||
@ -129,7 +129,7 @@ export const Combobox = <T extends string | number>({
|
|||||||
const virtualizerOptions = {
|
const virtualizerOptions = {
|
||||||
count: items.length,
|
count: items.length,
|
||||||
getScrollElement: () => floatingRef.current,
|
getScrollElement: () => floatingRef.current,
|
||||||
estimateSize,
|
estimateSize: () => OPTION_HEIGHT,
|
||||||
overscan: 4,
|
overscan: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@ import { css } from '@emotion/css';
|
|||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
const MAX_HEIGHT = 400;
|
|
||||||
|
|
||||||
// We need a px font size to accurately measure the width of items.
|
// 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.
|
// This should be in sync with the body font size in the theme.
|
||||||
export const MENU_ITEM_FONT_SIZE = 14;
|
export const MENU_ITEM_FONT_SIZE = 14;
|
||||||
@ -20,7 +18,6 @@ export const getComboboxStyles = (theme: GrafanaTheme2) => {
|
|||||||
background: theme.components.dropdown.background,
|
background: theme.components.dropdown.background,
|
||||||
boxShadow: theme.shadows.z3,
|
boxShadow: theme.shadows.z3,
|
||||||
zIndex: theme.zIndex.dropdown,
|
zIndex: theme.zIndex.dropdown,
|
||||||
maxHeight: MAX_HEIGHT,
|
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
position: 'relative',
|
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.
|
// Only consider the first n items when calculating the width of the popover.
|
||||||
const WIDTH_CALCULATION_LIMIT_ITEMS = 100_000;
|
const WIDTH_CALCULATION_LIMIT_ITEMS = 100_000;
|
||||||
|
|
||||||
/**
|
// Used with Downshift to get the height of each item
|
||||||
* Used with Downshift to get the height of each item
|
export const OPTION_HEIGHT = 45;
|
||||||
*/
|
const POPOVER_MAX_HEIGHT = OPTION_HEIGHT * 8.5;
|
||||||
export function estimateSize() {
|
|
||||||
return 45;
|
// Clearance around the popover to prevent it from being too close to the edge of the viewport
|
||||||
}
|
const POPOVER_PADDING = 16;
|
||||||
|
|
||||||
export const useComboboxFloat = (
|
export const useComboboxFloat = (
|
||||||
items: Array<ComboboxOption<string | number>>,
|
items: Array<ComboboxOption<string | number>>,
|
||||||
@ -23,7 +23,7 @@ export const useComboboxFloat = (
|
|||||||
) => {
|
) => {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const floatingRef = useRef<HTMLDivElement>(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(), []);
|
const scrollbarWidth = useMemo(() => getScrollbarWidth(), []);
|
||||||
|
|
||||||
@ -35,8 +35,14 @@ export const useComboboxFloat = (
|
|||||||
boundary: document.body,
|
boundary: document.body,
|
||||||
}),
|
}),
|
||||||
size({
|
size({
|
||||||
apply({ availableWidth }) {
|
apply({ availableWidth, availableHeight }) {
|
||||||
setPopoverMaxWidth(availableWidth);
|
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 = {
|
const floatStyles = {
|
||||||
...floatingStyles,
|
...floatingStyles,
|
||||||
width: longestItemWidth,
|
width: longestItemWidth,
|
||||||
maxWidth: popoverMaxWidth,
|
maxWidth: popoverMaxSize?.width,
|
||||||
minWidth: inputRef.current?.offsetWidth,
|
minWidth: inputRef.current?.offsetWidth,
|
||||||
|
|
||||||
|
maxHeight: popoverMaxSize?.height,
|
||||||
};
|
};
|
||||||
|
|
||||||
return { inputRef, floatingRef, floatStyles };
|
return { inputRef, floatingRef, floatStyles };
|
||||||
|
Loading…
Reference in New Issue
Block a user