New Select: decrease item height (#95952)

This commit is contained in:
Laura Fernández 2024-11-14 13:24:02 +01:00 committed by GitHub
parent c8aaefd54a
commit 6a8755a8af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 34 additions and 19 deletions

View File

@ -166,7 +166,7 @@ describe('Combobox', () => {
expect(onChangeHandler).toHaveBeenCalledWith(expect.objectContaining({ value: 'custom value' })); expect(onChangeHandler).toHaveBeenCalledWith(expect.objectContaining({ value: 'custom value' }));
}); });
it('should proivde custom string when all options are numbers', async () => { it('should provide custom string when all options are numbers', async () => {
const options = [ const options = [
{ label: '1', value: 1 }, { label: '1', value: 1 },
{ label: '2', value: 2 }, { label: '2', value: 2 },
@ -333,7 +333,7 @@ describe('Combobox', () => {
jest.advanceTimersByTime(500); // Custom value while typing jest.advanceTimersByTime(500); // Custom value while typing
}); });
const customItem = screen.queryByRole('option', { name: 'fir Create custom value' }); const customItem = screen.queryByRole('option', { name: 'Custom value: fir' });
expect(customItem).toBeInTheDocument(); expect(customItem).toBeInTheDocument();
}); });

View File

@ -13,8 +13,8 @@ import { Box } from '../Layout/Box/Box';
import { Stack } from '../Layout/Stack/Stack'; import { Stack } from '../Layout/Stack/Stack';
import { ScrollContainer } from '../ScrollContainer/ScrollContainer'; import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
import { getComboboxStyles } from './getComboboxStyles'; import { getComboboxStyles, MENU_OPTION_HEIGHT } from './getComboboxStyles';
import { useComboboxFloat, OPTION_HEIGHT } from './useComboboxFloat'; import { useComboboxFloat } 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> = {
@ -69,6 +69,9 @@ type AutoSizeConditionals =
type ComboboxProps<T extends string | number> = ComboboxBaseProps<T> & AutoSizeConditionals; type ComboboxProps<T extends string | number> = ComboboxBaseProps<T> & AutoSizeConditionals;
function itemToString<T extends string | number>(item: ComboboxOption<T> | null) { function itemToString<T extends string | number>(item: ComboboxOption<T> | null) {
if (item?.label?.includes('Custom value: ')) {
return item?.value.toString();
}
return item?.label ?? item?.value.toString() ?? ''; return item?.label ?? item?.value.toString() ?? '';
} }
@ -122,13 +125,18 @@ export const Combobox = <T extends string | number>(props: ComboboxProps<T>) =>
let itemsToSet = items; let itemsToSet = items;
if (inputValue && createCustomValue) { if (inputValue && createCustomValue) {
const optionMatchingInput = items.find((opt) => opt.label === inputValue || opt.value === inputValue); const optionMatchingInput = items.find(
(opt) => opt.label === 'Custom value: ' + inputValue || opt.value === inputValue
);
if (!optionMatchingInput) { if (!optionMatchingInput) {
const customValueOption = { const customValueOption = {
label: t('combobox.custom-value.label', 'Custom value: ') + inputValue,
// Type casting needed to make this work when T is a number // Type casting needed to make this work when T is a number
value: inputValue as unknown as T, value: inputValue as unknown as T,
/* TODO: Add this back when we do support descriptions and have need for it
description: t('combobox.custom-value.create', 'Create custom value'), description: t('combobox.custom-value.create', 'Create custom value'),
*/
}; };
itemsToSet = items.slice(0); itemsToSet = items.slice(0);
@ -174,7 +182,7 @@ export const Combobox = <T extends string | number>(props: ComboboxProps<T>) =>
const virtualizerOptions = { const virtualizerOptions = {
count: items.length, count: items.length,
getScrollElement: () => scrollRef.current, getScrollElement: () => scrollRef.current,
estimateSize: () => OPTION_HEIGHT, estimateSize: () => MENU_OPTION_HEIGHT,
overscan: 4, overscan: 4,
}; };

View File

@ -6,7 +6,12 @@ import { GrafanaTheme2 } from '@grafana/data';
// 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;
export const MENU_ITEM_FONT_WEIGHT = 500; export const MENU_ITEM_FONT_WEIGHT = 500;
export const MENU_ITEM_PADDING_X = 8; export const MENU_ITEM_PADDING = 8;
export const MENU_ITEM_LINE_HEIGHT = 1.5;
// Used with Downshift to get the height of each item
export const MENU_OPTION_HEIGHT = MENU_ITEM_PADDING * 2 + MENU_ITEM_FONT_SIZE * MENU_ITEM_LINE_HEIGHT;
export const POPOVER_MAX_HEIGHT = MENU_OPTION_HEIGHT * 8.5;
export const getComboboxStyles = (theme: GrafanaTheme2) => { export const getComboboxStyles = (theme: GrafanaTheme2) => {
return { return {
@ -27,7 +32,7 @@ export const getComboboxStyles = (theme: GrafanaTheme2) => {
}), }),
option: css({ option: css({
label: 'grafana-select-option', label: 'grafana-select-option',
padding: MENU_ITEM_PADDING_X, padding: MENU_ITEM_PADDING,
position: 'absolute', position: 'absolute',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@ -65,7 +70,7 @@ export const getComboboxStyles = (theme: GrafanaTheme2) => {
fontWeight: 'normal', fontWeight: 'normal',
fontSize: theme.typography.bodySmall.fontSize, fontSize: theme.typography.bodySmall.fontSize,
color: theme.colors.text.secondary, color: theme.colors.text.secondary,
lineHeight: theme.typography.body.lineHeight, lineHeight: MENU_ITEM_LINE_HEIGHT,
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
overflow: 'hidden', overflow: 'hidden',
}), }),
@ -84,7 +89,7 @@ export const getComboboxStyles = (theme: GrafanaTheme2) => {
borderRadius: theme.shape.radius.default, borderRadius: theme.shape.radius.default,
content: '" "', content: '" "',
display: 'block', display: 'block',
height: '100%', height: MENU_OPTION_HEIGHT,
position: 'absolute', position: 'absolute',
width: theme.spacing(0.5), width: theme.spacing(0.5),
left: 0, left: 0,

View File

@ -4,15 +4,17 @@ import { useMemo, useRef, useState } from 'react';
import { measureText } from '../../utils'; import { measureText } from '../../utils';
import { ComboboxOption } from './Combobox'; import { ComboboxOption } from './Combobox';
import { MENU_ITEM_FONT_SIZE, MENU_ITEM_FONT_WEIGHT, MENU_ITEM_PADDING_X } from './getComboboxStyles'; import {
MENU_ITEM_FONT_SIZE,
MENU_ITEM_FONT_WEIGHT,
MENU_ITEM_PADDING,
MENU_OPTION_HEIGHT,
POPOVER_MAX_HEIGHT,
} from './getComboboxStyles';
// 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
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 // Clearance around the popover to prevent it from being too close to the edge of the viewport
const POPOVER_PADDING = 16; const POPOVER_PADDING = 16;
@ -41,7 +43,7 @@ export const useComboboxFloat = (
const preferredMaxHeight = availableHeight - POPOVER_PADDING; const preferredMaxHeight = availableHeight - POPOVER_PADDING;
const width = Math.max(preferredMaxWidth, 0); const width = Math.max(preferredMaxWidth, 0);
const height = Math.min(Math.max(preferredMaxHeight, OPTION_HEIGHT * 6), POPOVER_MAX_HEIGHT); const height = Math.min(Math.max(preferredMaxHeight, MENU_OPTION_HEIGHT * 6), POPOVER_MAX_HEIGHT);
setPopoverMaxSize({ width, height }); setPopoverMaxSize({ width, height });
}, },
@ -68,7 +70,7 @@ export const useComboboxFloat = (
const size = measureText(longestItem, MENU_ITEM_FONT_SIZE, MENU_ITEM_FONT_WEIGHT).width; const size = measureText(longestItem, MENU_ITEM_FONT_SIZE, MENU_ITEM_FONT_WEIGHT).width;
return size + MENU_ITEM_PADDING_X * 2 + scrollbarWidth; return size + MENU_ITEM_PADDING * 2 + scrollbarWidth;
}, [items, scrollbarWidth]); }, [items, scrollbarWidth]);
const floatStyles = { const floatStyles = {

View File

@ -455,7 +455,7 @@
"title": "Clear value" "title": "Clear value"
}, },
"custom-value": { "custom-value": {
"create": "Create custom value" "label": "Custom value: "
}, },
"options": { "options": {
"no-found": "No options found." "no-found": "No options found."

View File

@ -455,7 +455,7 @@
"title": "Cľęäř väľūę" "title": "Cľęäř väľūę"
}, },
"custom-value": { "custom-value": {
"create": "Cřęäŧę čūşŧőm väľūę" "label": "Cūşŧőm väľūę: "
}, },
"options": { "options": {
"no-found": "Ńő őpŧįőʼnş ƒőūʼnđ." "no-found": "Ńő őpŧįőʼnş ƒőūʼnđ."