mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Select: Overflow ellipsis and control over multi value wrapping (#76405)
* Select: Better overflow and wrapping behavior and control * Update * truncate * minor update * review fixes * Remove legacy big
This commit is contained in:
parent
ea37a116f7
commit
867ff52b38
@ -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"],
|
||||
|
@ -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 <Icon name={icon} size={size} />;
|
||||
};
|
||||
}
|
||||
|
@ -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<Array<SelectableValue<string>>>([]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ maxWidth: '450px' }}>
|
||||
<MultiSelect
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
@ -236,13 +235,15 @@ export const MultiSelectBasic: Story = (args) => {
|
||||
prefix={getPrefix(args.icon)}
|
||||
{...args}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
MultiSelectBasic.args = {
|
||||
isClearable: false,
|
||||
closeMenuOnSelect: false,
|
||||
maxVisibleValues: 5,
|
||||
noMultiValueWrap: false,
|
||||
};
|
||||
|
||||
export const MultiSelectAsync: Story = (args) => {
|
||||
|
@ -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 (
|
||||
<span key="excess-values" id="excess-values">
|
||||
(+{selectedValuesCount - maxVisibleValues})
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
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<T, Rest = {}>({
|
||||
allowCustomValue = false,
|
||||
allowCreateWhileLoading = false,
|
||||
@ -145,6 +126,7 @@ export function SelectBase<T, Rest = {}>({
|
||||
tabSelectsValue = true,
|
||||
value,
|
||||
virtualized = false,
|
||||
noMultiValueWrap,
|
||||
width,
|
||||
isValidNewOption,
|
||||
formatOptionLabel,
|
||||
@ -274,6 +256,7 @@ export function SelectBase<T, Rest = {}>({
|
||||
showAllSelectedWhenOpen,
|
||||
tabSelectsValue,
|
||||
value: isMulti ? selectedValue : selectedValue?.[0],
|
||||
noMultiValueWrap,
|
||||
};
|
||||
|
||||
if (allowCustomValue) {
|
||||
@ -305,31 +288,8 @@ export function SelectBase<T, Rest = {}>({
|
||||
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 <IndicatorsContainer {...props}>{indicatorChildren}</IndicatorsContainer>;
|
||||
}
|
||||
|
||||
return <IndicatorsContainer {...props} />;
|
||||
},
|
||||
IndicatorSeparator() {
|
||||
return <></>;
|
||||
},
|
||||
IndicatorsContainer: CustomIndicatorsContainer,
|
||||
IndicatorSeparator: IndicatorSeparator,
|
||||
Control: CustomControl,
|
||||
Option: SelectMenuOptions,
|
||||
ClearIndicator(props: any) {
|
||||
@ -361,9 +321,7 @@ export function SelectBase<T, Rest = {}>({
|
||||
</div>
|
||||
);
|
||||
},
|
||||
DropdownIndicator(props) {
|
||||
return <DropdownIndicator isOpen={props.selectProps.menuIsOpen} />;
|
||||
},
|
||||
DropdownIndicator: DropdownIndicator,
|
||||
SingleValue(props: any) {
|
||||
return <SingleValue {...props} isDisabled={disabled} />;
|
||||
},
|
||||
@ -394,3 +352,37 @@ function defaultFormatCreateLabel(input: string) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
<span key="excess-values" id="excess-values">
|
||||
(+{selectedValuesCount - maxVisibleValues})
|
||||
</span>
|
||||
);
|
||||
|
||||
return <IndicatorsContainer {...props}>{indicatorChildren}</IndicatorsContainer>;
|
||||
}
|
||||
}
|
||||
|
||||
return <IndicatorsContainer {...props} />;
|
||||
}
|
||||
|
||||
function IndicatorSeparator() {
|
||||
return <></>;
|
||||
}
|
||||
|
@ -30,8 +30,15 @@ class UnthemedValueContainer extends Component<any & { theme: GrafanaTheme2 }> {
|
||||
|
||||
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 <div className={className}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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<SelectableValue<string>>((name) => ({
|
||||
|
@ -30,7 +30,10 @@ export default function resetSelectStyles(theme: GrafanaTheme2) {
|
||||
maxHeight,
|
||||
}),
|
||||
multiValue: () => ({}),
|
||||
multiValueLabel: () => ({}),
|
||||
multiValueLabel: () => ({
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}),
|
||||
multiValueRemove: () => ({}),
|
||||
noOptionsMessage: () => ({}),
|
||||
option: () => ({}),
|
||||
|
@ -99,6 +99,8 @@ export interface SelectCommonProps<T> {
|
||||
) => boolean;
|
||||
/** Message to display isLoading=true*/
|
||||
loadingMessage?: string;
|
||||
/** Disables wrapping of multi value values when closed */
|
||||
noMultiValueWrap?: boolean;
|
||||
}
|
||||
|
||||
export interface SelectAsyncProps<T> {
|
||||
|
@ -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';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user