mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
326 lines
8.9 KiB
TypeScript
326 lines
8.9 KiB
TypeScript
// Libraries
|
|
import classNames from 'classnames';
|
|
import React, { PureComponent } from 'react';
|
|
|
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
|
// @ts-ignore
|
|
import { default as ReactSelect } from '@torkelo/react-select';
|
|
// @ts-ignore
|
|
import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
|
|
// @ts-ignore
|
|
import { components } from '@torkelo/react-select';
|
|
|
|
// Components
|
|
import { SelectOption, SingleValue } from './SelectOption';
|
|
import SelectOptionGroup from './SelectOptionGroup';
|
|
import IndicatorsContainer from './IndicatorsContainer';
|
|
import NoOptionsMessage from './NoOptionsMessage';
|
|
import resetSelectStyles from './resetSelectStyles';
|
|
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
|
import { PopperContent } from '../Tooltip/PopperController';
|
|
import { Tooltip } from '../Tooltip/Tooltip';
|
|
import { SelectableValue } from '@grafana/data';
|
|
|
|
export interface CommonProps<T> {
|
|
defaultValue?: any;
|
|
getOptionLabel?: (item: SelectableValue<T>) => string;
|
|
getOptionValue?: (item: SelectableValue<T>) => string;
|
|
onChange: (item: SelectableValue<T>) => {} | void;
|
|
placeholder?: string;
|
|
width?: number;
|
|
value?: SelectableValue<T>;
|
|
className?: string;
|
|
isDisabled?: boolean;
|
|
isSearchable?: boolean;
|
|
isClearable?: boolean;
|
|
autoFocus?: boolean;
|
|
openMenuOnFocus?: boolean;
|
|
onBlur?: () => void;
|
|
maxMenuHeight?: number;
|
|
isLoading?: boolean;
|
|
noOptionsMessage?: () => string;
|
|
isMulti?: boolean;
|
|
backspaceRemovesValue?: boolean;
|
|
isOpen?: boolean;
|
|
components?: any;
|
|
tooltipContent?: PopperContent<any>;
|
|
onOpenMenu?: () => void;
|
|
onCloseMenu?: () => void;
|
|
tabSelectsValue?: boolean;
|
|
}
|
|
|
|
export interface SelectProps<T> extends CommonProps<T> {
|
|
options: Array<SelectableValue<T>>;
|
|
}
|
|
|
|
interface AsyncProps<T> extends CommonProps<T> {
|
|
defaultOptions: boolean;
|
|
loadOptions: (query: string) => Promise<Array<SelectableValue<T>>>;
|
|
loadingMessage?: () => string;
|
|
}
|
|
|
|
export const MenuList = (props: any) => {
|
|
return (
|
|
<components.MenuList {...props}>
|
|
<CustomScrollbar autoHide={false} autoHeightMax="inherit">
|
|
{props.children}
|
|
</CustomScrollbar>
|
|
</components.MenuList>
|
|
);
|
|
};
|
|
|
|
export class Select<T> extends PureComponent<SelectProps<T>> {
|
|
static defaultProps: Partial<SelectProps<any>> = {
|
|
className: '',
|
|
isDisabled: false,
|
|
isSearchable: true,
|
|
isClearable: false,
|
|
isMulti: false,
|
|
openMenuOnFocus: false,
|
|
autoFocus: false,
|
|
isLoading: false,
|
|
backspaceRemovesValue: true,
|
|
maxMenuHeight: 300,
|
|
tabSelectsValue: true,
|
|
components: {
|
|
Option: SelectOption,
|
|
SingleValue,
|
|
IndicatorsContainer,
|
|
MenuList,
|
|
Group: SelectOptionGroup,
|
|
},
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
defaultValue,
|
|
getOptionLabel,
|
|
getOptionValue,
|
|
onChange,
|
|
options,
|
|
placeholder,
|
|
width,
|
|
value,
|
|
className,
|
|
isDisabled,
|
|
isLoading,
|
|
isSearchable,
|
|
isClearable,
|
|
backspaceRemovesValue,
|
|
isMulti,
|
|
autoFocus,
|
|
openMenuOnFocus,
|
|
onBlur,
|
|
maxMenuHeight,
|
|
noOptionsMessage,
|
|
isOpen,
|
|
components,
|
|
tooltipContent,
|
|
tabSelectsValue,
|
|
onCloseMenu,
|
|
onOpenMenu,
|
|
} = this.props;
|
|
|
|
let widthClass = '';
|
|
if (width) {
|
|
widthClass = 'width-' + width;
|
|
}
|
|
|
|
const selectClassNames = classNames('gf-form-input', 'gf-form-input--form-dropdown', widthClass, className);
|
|
const selectComponents = { ...Select.defaultProps.components, ...components };
|
|
|
|
return (
|
|
<WrapInTooltip onCloseMenu={onCloseMenu} onOpenMenu={onOpenMenu} tooltipContent={tooltipContent} isOpen={isOpen}>
|
|
{(onOpenMenuInternal, onCloseMenuInternal) => {
|
|
return (
|
|
<ReactSelect
|
|
classNamePrefix="gf-form-select-box"
|
|
className={selectClassNames}
|
|
components={selectComponents}
|
|
defaultValue={defaultValue}
|
|
value={value}
|
|
getOptionLabel={getOptionLabel}
|
|
getOptionValue={getOptionValue}
|
|
menuShouldScrollIntoView={false}
|
|
isSearchable={isSearchable}
|
|
onChange={onChange}
|
|
options={options}
|
|
placeholder={placeholder || 'Choose'}
|
|
styles={resetSelectStyles()}
|
|
isDisabled={isDisabled}
|
|
isLoading={isLoading}
|
|
isClearable={isClearable}
|
|
autoFocus={autoFocus}
|
|
onBlur={onBlur}
|
|
openMenuOnFocus={openMenuOnFocus}
|
|
maxMenuHeight={maxMenuHeight}
|
|
noOptionsMessage={noOptionsMessage}
|
|
isMulti={isMulti}
|
|
backspaceRemovesValue={backspaceRemovesValue}
|
|
menuIsOpen={isOpen}
|
|
onMenuOpen={onOpenMenuInternal}
|
|
onMenuClose={onCloseMenuInternal}
|
|
tabSelectsValue={tabSelectsValue}
|
|
/>
|
|
);
|
|
}}
|
|
</WrapInTooltip>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class AsyncSelect<T> extends PureComponent<AsyncProps<T>> {
|
|
static defaultProps: Partial<AsyncProps<any>> = {
|
|
className: '',
|
|
components: {},
|
|
loadingMessage: () => 'Loading...',
|
|
isDisabled: false,
|
|
isClearable: false,
|
|
isMulti: false,
|
|
isSearchable: true,
|
|
backspaceRemovesValue: true,
|
|
autoFocus: false,
|
|
openMenuOnFocus: false,
|
|
maxMenuHeight: 300,
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
defaultValue,
|
|
getOptionLabel,
|
|
getOptionValue,
|
|
onChange,
|
|
placeholder,
|
|
width,
|
|
value,
|
|
className,
|
|
loadOptions,
|
|
defaultOptions,
|
|
isLoading,
|
|
loadingMessage,
|
|
noOptionsMessage,
|
|
isDisabled,
|
|
isSearchable,
|
|
isClearable,
|
|
backspaceRemovesValue,
|
|
autoFocus,
|
|
onBlur,
|
|
openMenuOnFocus,
|
|
maxMenuHeight,
|
|
isMulti,
|
|
tooltipContent,
|
|
onCloseMenu,
|
|
onOpenMenu,
|
|
isOpen,
|
|
} = this.props;
|
|
|
|
let widthClass = '';
|
|
if (width) {
|
|
widthClass = 'width-' + width;
|
|
}
|
|
|
|
const selectClassNames = classNames('gf-form-input', 'gf-form-input--form-dropdown', widthClass, className);
|
|
|
|
return (
|
|
<WrapInTooltip onCloseMenu={onCloseMenu} onOpenMenu={onOpenMenu} tooltipContent={tooltipContent} isOpen={isOpen}>
|
|
{(onOpenMenuInternal, onCloseMenuInternal) => {
|
|
return (
|
|
<ReactAsyncSelect
|
|
classNamePrefix="gf-form-select-box"
|
|
className={selectClassNames}
|
|
components={{
|
|
Option: SelectOption,
|
|
SingleValue,
|
|
IndicatorsContainer,
|
|
NoOptionsMessage,
|
|
}}
|
|
defaultValue={defaultValue}
|
|
value={value}
|
|
getOptionLabel={getOptionLabel}
|
|
getOptionValue={getOptionValue}
|
|
menuShouldScrollIntoView={false}
|
|
onChange={onChange}
|
|
loadOptions={loadOptions}
|
|
isLoading={isLoading}
|
|
defaultOptions={defaultOptions}
|
|
placeholder={placeholder || 'Choose'}
|
|
styles={resetSelectStyles()}
|
|
loadingMessage={loadingMessage}
|
|
noOptionsMessage={noOptionsMessage}
|
|
isDisabled={isDisabled}
|
|
isSearchable={isSearchable}
|
|
isClearable={isClearable}
|
|
autoFocus={autoFocus}
|
|
onBlur={onBlur}
|
|
openMenuOnFocus={openMenuOnFocus}
|
|
maxMenuHeight={maxMenuHeight}
|
|
isMulti={isMulti}
|
|
backspaceRemovesValue={backspaceRemovesValue}
|
|
/>
|
|
);
|
|
}}
|
|
</WrapInTooltip>
|
|
);
|
|
}
|
|
}
|
|
|
|
export interface TooltipWrapperProps {
|
|
children: (onOpenMenu: () => void, onCloseMenu: () => void) => React.ReactNode;
|
|
onOpenMenu?: () => void;
|
|
onCloseMenu?: () => void;
|
|
isOpen?: boolean;
|
|
tooltipContent?: PopperContent<any>;
|
|
}
|
|
|
|
export interface TooltipWrapperState {
|
|
isOpenInternal: boolean;
|
|
}
|
|
|
|
export class WrapInTooltip extends PureComponent<TooltipWrapperProps, TooltipWrapperState> {
|
|
state: TooltipWrapperState = {
|
|
isOpenInternal: false,
|
|
};
|
|
|
|
onOpenMenu = () => {
|
|
const { onOpenMenu } = this.props;
|
|
if (onOpenMenu) {
|
|
onOpenMenu();
|
|
}
|
|
this.setState({ isOpenInternal: true });
|
|
};
|
|
|
|
onCloseMenu = () => {
|
|
const { onCloseMenu } = this.props;
|
|
if (onCloseMenu) {
|
|
onCloseMenu();
|
|
}
|
|
this.setState({ isOpenInternal: false });
|
|
};
|
|
|
|
render() {
|
|
const { children, isOpen, tooltipContent } = this.props;
|
|
const { isOpenInternal } = this.state;
|
|
|
|
let showTooltip: boolean | undefined = undefined;
|
|
|
|
if (isOpenInternal || isOpen) {
|
|
showTooltip = false;
|
|
}
|
|
|
|
if (tooltipContent) {
|
|
return (
|
|
<Tooltip show={showTooltip} content={tooltipContent} placement="bottom">
|
|
<div>
|
|
{/* div needed for tooltip */}
|
|
{children(this.onOpenMenu, this.onCloseMenu)}
|
|
</div>
|
|
</Tooltip>
|
|
);
|
|
} else {
|
|
return <div>{children(this.onOpenMenu, this.onCloseMenu)}</div>;
|
|
}
|
|
}
|
|
}
|
|
|
|
export default Select;
|