mirror of https://github.com/grafana/grafana
Chore: Use SelectMenuOptions from @grafana/ui in grafana-prometheus (#89562)
use SelectMenuOptions from @grafana/uipull/89978/head
parent
2a1b620c38
commit
5b6edc96d9
@ -1,129 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/packages/grafana-ui/src/components/Select/SelectBase.tsx
|
||||
import { cx } from '@emotion/css'; |
||||
import { max } from 'lodash'; |
||||
import { RefCallback } from 'react'; |
||||
import * as React from 'react'; |
||||
import { MenuListProps } from 'react-select'; |
||||
import { FixedSizeList as List } from 'react-window'; |
||||
|
||||
import { SelectableValue, toIconName } from '@grafana/data'; |
||||
import { selectors } from '@grafana/e2e-selectors'; |
||||
import { CustomScrollbar, Icon, getSelectStyles, useTheme2 } from '@grafana/ui'; |
||||
|
||||
interface SelectMenuProps { |
||||
maxHeight: number; |
||||
innerRef: RefCallback<HTMLDivElement>; |
||||
innerProps: {}; |
||||
} |
||||
|
||||
export const SelectMenu = ({ children, maxHeight, innerRef, innerProps }: React.PropsWithChildren<SelectMenuProps>) => { |
||||
const theme = useTheme2(); |
||||
const styles = getSelectStyles(theme); |
||||
|
||||
return ( |
||||
<div {...innerProps} className={styles.menu} style={{ maxHeight }} aria-label="Select options menu"> |
||||
<CustomScrollbar scrollRefCallback={innerRef} autoHide={false} autoHeightMax="inherit" hideHorizontalTrack> |
||||
{children} |
||||
</CustomScrollbar> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
SelectMenu.displayName = 'SelectMenu'; |
||||
|
||||
const VIRTUAL_LIST_ITEM_HEIGHT = 37; |
||||
const VIRTUAL_LIST_WIDTH_ESTIMATE_MULTIPLIER = 7; |
||||
|
||||
// A virtualized version of the SelectMenu, descriptions for SelectableValue options not supported since those are of a variable height.
|
||||
//
|
||||
// To support the virtualized list we have to "guess" the width of the menu container based on the longest available option.
|
||||
// the reason for this is because all of the options will be positioned absolute, this takes them out of the document and no space
|
||||
// is created for them, thus the container can't grow to accomodate.
|
||||
//
|
||||
// VIRTUAL_LIST_ITEM_HEIGHT and WIDTH_ESTIMATE_MULTIPLIER are both magic numbers.
|
||||
// Some characters (such as emojis and other unicode characters) may consist of multiple code points in which case the width would be inaccurate (but larger than needed).
|
||||
export const VirtualizedSelectMenu = ({ children, maxHeight, options, getValue }: MenuListProps<SelectableValue>) => { |
||||
const theme = useTheme2(); |
||||
const styles = getSelectStyles(theme); |
||||
const [value] = getValue(); |
||||
|
||||
const valueIndex = value ? options.findIndex((option: SelectableValue<unknown>) => option.value === value.value) : 0; |
||||
const initialOffset = valueIndex * VIRTUAL_LIST_ITEM_HEIGHT; |
||||
|
||||
if (!Array.isArray(children)) { |
||||
return null; |
||||
} |
||||
|
||||
const longestOption = max(options.map((option) => option.label?.length)) ?? 0; |
||||
const widthEstimate = longestOption * VIRTUAL_LIST_WIDTH_ESTIMATE_MULTIPLIER; |
||||
const heightEstimate = Math.min(options.length * VIRTUAL_LIST_ITEM_HEIGHT, maxHeight); |
||||
|
||||
return ( |
||||
<List |
||||
className={styles.menu} |
||||
height={heightEstimate} |
||||
width={widthEstimate} |
||||
aria-label="Select options menu" |
||||
itemCount={children.length} |
||||
itemSize={VIRTUAL_LIST_ITEM_HEIGHT} |
||||
initialScrollOffset={initialOffset} |
||||
> |
||||
{({ index, style }) => <div style={{ ...style, overflow: 'hidden' }}>{children[index]}</div>} |
||||
</List> |
||||
); |
||||
}; |
||||
|
||||
VirtualizedSelectMenu.displayName = 'VirtualizedSelectMenu'; |
||||
|
||||
interface SelectMenuOptionProps<T> { |
||||
isDisabled: boolean; |
||||
isFocused: boolean; |
||||
isSelected: boolean; |
||||
innerProps: JSX.IntrinsicElements['div']; |
||||
innerRef: RefCallback<HTMLDivElement>; |
||||
renderOptionLabel?: (value: SelectableValue<T>) => JSX.Element; |
||||
data: SelectableValue<T>; |
||||
} |
||||
|
||||
export const SelectMenuOptions = ({ |
||||
children, |
||||
data, |
||||
innerProps, |
||||
innerRef, |
||||
isFocused, |
||||
isSelected, |
||||
renderOptionLabel, |
||||
}: React.PropsWithChildren<SelectMenuOptionProps<unknown>>) => { |
||||
const theme = useTheme2(); |
||||
const styles = getSelectStyles(theme); |
||||
const icon = data.icon ? toIconName(data.icon) : undefined; |
||||
// We are removing onMouseMove and onMouseOver from innerProps because they cause the whole
|
||||
// list to re-render everytime the user hovers over an option. This is a performance issue.
|
||||
// See https://github.com/JedWatson/react-select/issues/3128#issuecomment-451936743
|
||||
const { onMouseMove, onMouseOver, ...rest } = innerProps; |
||||
|
||||
return ( |
||||
<div |
||||
ref={innerRef} |
||||
className={cx( |
||||
styles.option, |
||||
isFocused && styles.optionFocused, |
||||
isSelected && styles.optionSelected, |
||||
data.isDisabled && styles.optionDisabled |
||||
)} |
||||
{...rest} |
||||
data-testid={selectors.components.Select.option} |
||||
title={data.title} |
||||
> |
||||
{icon && <Icon name={icon} className={styles.optionIcon} />} |
||||
{data.imgUrl && <img className={styles.optionImage} src={data.imgUrl} alt={data.label || String(data.value)} />} |
||||
<div className={styles.optionBody}> |
||||
<span>{renderOptionLabel ? renderOptionLabel(data) : children}</span> |
||||
{data.description && <div className={styles.optionDescription}>{data.description}</div>} |
||||
{data.component && <data.component />} |
||||
</div> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
SelectMenuOptions.displayName = 'SelectMenuOptions'; |
Loading…
Reference in new issue