diff --git a/.betterer.results b/.betterer.results index 4c02c626b98..cd5eb5be4d0 100644 --- a/.betterer.results +++ b/.betterer.results @@ -860,8 +860,7 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "8"], [0, 0, 0, "Unexpected any. Specify a different type.", "9"], [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "Unexpected any. Specify a different type.", "11"], - [0, 0, 0, "Unexpected any. Specify a different type.", "12"] + [0, 0, 0, "Unexpected any. Specify a different type.", "11"] ], "packages/grafana-ui/src/components/Select/SelectOptionGroup.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx b/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx index e54058ff53f..5e56ce531d6 100644 --- a/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx +++ b/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx @@ -1,13 +1,11 @@ import React from 'react'; +import { DropdownIndicatorProps } from 'react-select'; import { Icon } from '../Icon/Icon'; -interface DropdownIndicatorProps { - isOpen: boolean; -} - -export const DropdownIndicator = ({ isOpen }: DropdownIndicatorProps) => { +export function DropdownIndicator({ selectProps }: DropdownIndicatorProps) { + const isOpen = selectProps.menuIsOpen; const icon = isOpen ? 'search' : 'angle-down'; const size = isOpen ? 'sm' : 'md'; return ; -}; +} diff --git a/packages/grafana-ui/src/components/Select/Select.story.tsx b/packages/grafana-ui/src/components/Select/Select.story.tsx index 2c8751b54ee..7bb9537e7d6 100644 --- a/packages/grafana-ui/src/components/Select/Select.story.tsx +++ b/packages/grafana-ui/src/components/Select/Select.story.tsx @@ -45,7 +45,6 @@ const meta: Meta = { 'renderControl', 'options', 'isOptionDisabled', - 'maxVisibleValues', 'aria-label', 'noOptionsMessage', 'menuPosition', @@ -225,7 +224,7 @@ export const MultiSelectBasic: Story = (args) => { const [value, setValue] = useState>>([]); return ( - <> +
{ prefix={getPrefix(args.icon)} {...args} /> - +
); }; + MultiSelectBasic.args = { isClearable: false, closeMenuOnSelect: false, maxVisibleValues: 5, + noMultiValueWrap: false, }; export const MultiSelectAsync: Story = (args) => { diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index c47d9cf3c5c..661c97995e8 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -1,6 +1,6 @@ import { t } from 'i18next'; import React, { ComponentProps, useCallback, useEffect, useRef, useState } from 'react'; -import { default as ReactSelect } from 'react-select'; +import { default as ReactSelect, IndicatorsContainerProps, Props as ReactSelectProps } from 'react-select'; import { default as ReactAsyncSelect } from 'react-select/async'; import { default as AsyncCreatable } from 'react-select/async-creatable'; import Creatable from 'react-select/creatable'; @@ -25,31 +25,6 @@ import { useCustomSelectStyles } from './resetSelectStyles'; import { ActionMeta, InputActionMeta, SelectBaseProps } from './types'; import { cleanValue, findSelectedValue, omitDescriptions } from './utils'; -interface ExtraValuesIndicatorProps { - maxVisibleValues?: number | undefined; - selectedValuesCount: number; - menuIsOpen: boolean; - showAllSelectedWhenOpen: boolean; -} - -const renderExtraValuesIndicator = (props: ExtraValuesIndicatorProps) => { - const { maxVisibleValues, selectedValuesCount, menuIsOpen, showAllSelectedWhenOpen } = props; - - if ( - maxVisibleValues !== undefined && - selectedValuesCount > maxVisibleValues && - !(showAllSelectedWhenOpen && menuIsOpen) - ) { - return ( - - (+{selectedValuesCount - maxVisibleValues}) - - ); - } - - return null; -}; - const CustomControl = (props: any) => { const { children, @@ -88,6 +63,12 @@ const CustomControl = (props: any) => { ); }; +interface SelectPropsWithExtras extends ReactSelectProps { + maxVisibleValues?: number | undefined; + showAllSelectedWhenOpen: boolean; + noMultiValueWrap?: boolean; +} + export function SelectBase({ allowCustomValue = false, allowCreateWhileLoading = false, @@ -145,6 +126,7 @@ export function SelectBase({ tabSelectsValue = true, value, virtualized = false, + noMultiValueWrap, width, isValidNewOption, formatOptionLabel, @@ -274,6 +256,7 @@ export function SelectBase({ showAllSelectedWhenOpen, tabSelectsValue, value: isMulti ? selectedValue : selectedValue?.[0], + noMultiValueWrap, }; if (allowCustomValue) { @@ -305,31 +288,8 @@ export function SelectBase({ MenuList: SelectMenuComponent, Group: SelectOptionGroup, ValueContainer, - IndicatorsContainer(props: any) { - const { selectProps } = props; - const { value, showAllSelectedWhenOpen, maxVisibleValues, menuIsOpen } = selectProps; - - if (maxVisibleValues !== undefined) { - const selectedValuesCount = value.length; - const indicatorChildren = [...props.children]; - indicatorChildren.splice( - -1, - 0, - renderExtraValuesIndicator({ - maxVisibleValues, - selectedValuesCount, - showAllSelectedWhenOpen, - menuIsOpen, - }) - ); - return {indicatorChildren}; - } - - return ; - }, - IndicatorSeparator() { - return <>; - }, + IndicatorsContainer: CustomIndicatorsContainer, + IndicatorSeparator: IndicatorSeparator, Control: CustomControl, Option: SelectMenuOptions, ClearIndicator(props: any) { @@ -361,9 +321,7 @@ export function SelectBase({ ); }, - DropdownIndicator(props) { - return ; - }, + DropdownIndicator: DropdownIndicator, SingleValue(props: any) { return ; }, @@ -394,3 +352,37 @@ function defaultFormatCreateLabel(input: string) { ); } + +type CustomIndicatorsContainerProps = IndicatorsContainerProps & { + selectProps: SelectPropsWithExtras; + children: React.ReactNode; +}; + +function CustomIndicatorsContainer(props: CustomIndicatorsContainerProps) { + const { showAllSelectedWhenOpen, maxVisibleValues, menuIsOpen } = props.selectProps; + + const value = props.getValue(); + + if (maxVisibleValues !== undefined && Array.isArray(props.children)) { + const selectedValuesCount = value.length; + + if (selectedValuesCount > maxVisibleValues && !(showAllSelectedWhenOpen && menuIsOpen)) { + const indicatorChildren = [...props.children]; + indicatorChildren.splice( + -1, + 0, + + (+{selectedValuesCount - maxVisibleValues}) + + ); + + return {indicatorChildren}; + } + } + + return ; +} + +function IndicatorSeparator() { + return <>; +} diff --git a/packages/grafana-ui/src/components/Select/ValueContainer.tsx b/packages/grafana-ui/src/components/Select/ValueContainer.tsx index b7c6f957fcb..9025a4a1d0d 100644 --- a/packages/grafana-ui/src/components/Select/ValueContainer.tsx +++ b/packages/grafana-ui/src/components/Select/ValueContainer.tsx @@ -30,8 +30,15 @@ class UnthemedValueContainer extends Component { renderContainer(children?: ReactNode) { const { isMulti, theme } = this.props; + const noWrap = this.props.selectProps?.noMultiValueWrap && !this.props.selectProps?.menuIsOpen; const styles = getSelectStyles(theme); - const className = cx(styles.valueContainer, isMulti && styles.valueContainerMulti); + + const className = cx( + styles.valueContainer, + isMulti && styles.valueContainerMulti, + noWrap && styles.valueContainerMultiNoWrap + ); + return
{children}
; } } diff --git a/packages/grafana-ui/src/components/Select/getSelectStyles.ts b/packages/grafana-ui/src/components/Select/getSelectStyles.ts index 420ccdfb15b..84ce715aab2 100644 --- a/packages/grafana-ui/src/components/Select/getSelectStyles.ts +++ b/packages/grafana-ui/src/components/Select/getSelectStyles.ts @@ -96,6 +96,9 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme2) => { flexWrap: 'wrap', display: 'flex', }), + valueContainerMultiNoWrap: css({ + flexWrap: 'nowrap', + }), loadingMessage: css({ label: 'grafana-select-loading-message', padding: theme.spacing(1), @@ -113,6 +116,8 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme2) => { padding: theme.spacing(0.25, 0, 0.25, 1), color: theme.colors.text.primary, fontSize: theme.typography.size.sm, + overflow: 'hidden', + whiteSpace: 'nowrap', '&:hover': { background: theme.colors.emphasize(theme.colors.background.secondary), diff --git a/packages/grafana-ui/src/components/Select/mockOptions.tsx b/packages/grafana-ui/src/components/Select/mockOptions.tsx index 12d56b0bdc0..7af01826de6 100644 --- a/packages/grafana-ui/src/components/Select/mockOptions.tsx +++ b/packages/grafana-ui/src/components/Select/mockOptions.tsx @@ -24,6 +24,7 @@ export const generateOptions = (desc = false) => { 'Ok Vicente', 'Garry Spitz', 'Han Harnish', + 'A very long value that is very long and takes up a lot of space and should be truncated preferrably if it does not fit', ]; return values.map>((name) => ({ diff --git a/packages/grafana-ui/src/components/Select/resetSelectStyles.ts b/packages/grafana-ui/src/components/Select/resetSelectStyles.ts index fb844575d66..3f1e31abff7 100644 --- a/packages/grafana-ui/src/components/Select/resetSelectStyles.ts +++ b/packages/grafana-ui/src/components/Select/resetSelectStyles.ts @@ -30,7 +30,10 @@ export default function resetSelectStyles(theme: GrafanaTheme2) { maxHeight, }), multiValue: () => ({}), - multiValueLabel: () => ({}), + multiValueLabel: () => ({ + overflow: 'hidden', + textOverflow: 'ellipsis', + }), multiValueRemove: () => ({}), noOptionsMessage: () => ({}), option: () => ({}), diff --git a/packages/grafana-ui/src/components/Select/types.ts b/packages/grafana-ui/src/components/Select/types.ts index e3fe5c6b766..0cbe644ed70 100644 --- a/packages/grafana-ui/src/components/Select/types.ts +++ b/packages/grafana-ui/src/components/Select/types.ts @@ -99,6 +99,8 @@ export interface SelectCommonProps { ) => boolean; /** Message to display isLoading=true*/ loadingMessage?: string; + /** Disables wrapping of multi value values when closed */ + noMultiValueWrap?: boolean; } export interface SelectAsyncProps { diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 72c8be994c5..e4240c1daf2 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -227,7 +227,6 @@ export { FieldArray } from './Forms/FieldArray'; // Select export { default as resetSelectStyles } from './Select/resetSelectStyles'; export * from './Select/Select'; -export { DropdownIndicator } from './Select/DropdownIndicator'; export { getSelectStyles } from './Select/getSelectStyles'; export * from './Select/types';