mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 17:15:40 -06:00
Forms migration: Old Select to Legacy namespace (#23200)
* Export other components as Legacy * More Select Legacy * Add namespacing to more files * Export new elements * Move Legacy Select folder * Let's not forget the scss file * Move new Select folder * Move new Select from Forms namespace * Little oopsie * Fix errors * Fix merge issues
This commit is contained in:
parent
15bff3114f
commit
b34281e250
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import RCCascader from 'rc-cascader';
|
||||
|
||||
import { Select } from '../Forms/Select/Select';
|
||||
import { Select } from '../Select/Select';
|
||||
import { FormInputSize } from '../Forms/types';
|
||||
import { Input } from '../Forms/Input/Input';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
@ -8,7 +8,7 @@ import { TLSAuthSettings } from './TLSAuthSettings';
|
||||
import { DataSourceSettings } from '@grafana/data';
|
||||
import { HttpSettingsProps } from './types';
|
||||
import { CustomHeadersSettings } from './CustomHeadersSettings';
|
||||
import { Select } from '../Select/Select';
|
||||
import { Select } from '../Forms/Legacy/Select/Select';
|
||||
import { Input } from '../Input/Input';
|
||||
import { FormField } from '../FormField/FormField';
|
||||
import { FormLabel } from '../FormLabel/FormLabel';
|
||||
|
@ -11,7 +11,7 @@ import { Switch } from './Switch';
|
||||
import { Checkbox } from './Checkbox';
|
||||
|
||||
import { RadioButtonGroup } from './RadioButtonGroup/RadioButtonGroup';
|
||||
import { Select } from './Select/Select';
|
||||
import { Select } from '../Select/Select';
|
||||
import Forms from './index';
|
||||
import mdx from './Form.mdx';
|
||||
import { ValidateResult } from 'react-hook-form';
|
||||
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { withKnobs, object, text } from '@storybook/addon-knobs';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { UseState } from '../../utils/storybook/UseState';
|
||||
import { withCenteredStory } from '../../../../utils/storybook/withCenteredStory';
|
||||
import { UseState } from '../../../../utils/storybook/UseState';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { ButtonSelect } from './ButtonSelect';
|
||||
|
@ -0,0 +1,97 @@
|
||||
import React, { PureComponent, ReactElement } from 'react';
|
||||
import Select from './Select';
|
||||
import { PopoverContent } from '../../../Tooltip/Tooltip';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
interface ButtonComponentProps {
|
||||
label: ReactElement | string | undefined;
|
||||
className: string | undefined;
|
||||
iconClass?: string;
|
||||
}
|
||||
|
||||
const ButtonComponent = (buttonProps: ButtonComponentProps) => (props: any) => {
|
||||
const { label, className, iconClass } = buttonProps;
|
||||
|
||||
return (
|
||||
<div // changed to div because of FireFox on MacOs issue below
|
||||
ref={props.innerRef}
|
||||
className={`btn navbar-button navbar-button--tight ${className}`}
|
||||
onClick={props.selectProps.menuIsOpen ? props.selectProps.onMenuClose : props.selectProps.onMenuOpen}
|
||||
onBlur={props.selectProps.onMenuClose}
|
||||
tabIndex={0} // necessary to get onBlur to work https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
|
||||
>
|
||||
<div className="select-button">
|
||||
{iconClass && <i className={`select-button-icon ${iconClass}`} />}
|
||||
<span className="select-button-value">{label ? label : ''}</span>
|
||||
{!props.menuIsOpen && <i className="fa fa-caret-down fa-fw" />}
|
||||
{props.menuIsOpen && <i className="fa fa-caret-up fa-fw" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface Props<T> {
|
||||
className: string | undefined;
|
||||
options: Array<SelectableValue<T>>;
|
||||
value?: SelectableValue<T>;
|
||||
label?: ReactElement | string;
|
||||
iconClass?: string;
|
||||
components?: any;
|
||||
maxMenuHeight?: number;
|
||||
onChange: (item: SelectableValue<T>) => void;
|
||||
tooltipContent?: PopoverContent;
|
||||
isMenuOpen?: boolean;
|
||||
onOpenMenu?: () => void;
|
||||
onCloseMenu?: () => void;
|
||||
tabSelectsValue?: boolean;
|
||||
autoFocus?: boolean;
|
||||
}
|
||||
|
||||
export class ButtonSelect<T> extends PureComponent<Props<T>> {
|
||||
onChange = (item: SelectableValue<T>) => {
|
||||
const { onChange } = this.props;
|
||||
onChange(item);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
options,
|
||||
value,
|
||||
label,
|
||||
iconClass,
|
||||
components,
|
||||
maxMenuHeight,
|
||||
tooltipContent,
|
||||
isMenuOpen,
|
||||
onOpenMenu,
|
||||
onCloseMenu,
|
||||
tabSelectsValue,
|
||||
autoFocus = true,
|
||||
} = this.props;
|
||||
const combinedComponents = {
|
||||
...components,
|
||||
Control: ButtonComponent({ label, className, iconClass }),
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
autoFocus={autoFocus}
|
||||
backspaceRemovesValue={false}
|
||||
isClearable={false}
|
||||
isSearchable={false}
|
||||
options={options}
|
||||
onChange={this.onChange}
|
||||
value={value}
|
||||
isOpen={isMenuOpen}
|
||||
onOpenMenu={onOpenMenu}
|
||||
onCloseMenu={onCloseMenu}
|
||||
maxMenuHeight={maxMenuHeight}
|
||||
components={combinedComponents}
|
||||
className="gf-form-select-box-button-select"
|
||||
tooltipContent={tooltipContent}
|
||||
tabSelectsValue={tabSelectsValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||
// @ts-ignore
|
||||
import { components } from '@torkelo/react-select';
|
||||
|
||||
export const IndicatorsContainer = (props: any) => {
|
||||
const isOpen = props.selectProps.menuIsOpen;
|
||||
return (
|
||||
<components.IndicatorsContainer {...props}>
|
||||
<span
|
||||
className={`gf-form-select-box__select-arrow ${isOpen ? `gf-form-select-box__select-arrow--reversed` : ''}`}
|
||||
/>
|
||||
</components.IndicatorsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndicatorsContainer;
|
@ -0,0 +1,101 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { withKnobs, object } from '@storybook/addon-knobs';
|
||||
import { withCenteredStory } from '../../../../utils/storybook/withCenteredStory';
|
||||
import { UseState } from '../../../../utils/storybook/UseState';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select, AsyncSelect } from './Select';
|
||||
|
||||
export default {
|
||||
title: 'General/Select/Select',
|
||||
component: Select,
|
||||
decorators: [withCenteredStory, withKnobs],
|
||||
};
|
||||
|
||||
const intialState: SelectableValue<string> = { label: 'A label', value: 'A value' };
|
||||
|
||||
const options = object<Array<SelectableValue<string>>>('Options:', [
|
||||
intialState,
|
||||
{ label: 'Another label', value: 'Another value 1' },
|
||||
{ label: 'Another label', value: 'Another value 2' },
|
||||
{ label: 'Another label', value: 'Another value 3' },
|
||||
{ label: 'Another label', value: 'Another value 4' },
|
||||
{ label: 'Another label', value: 'Another value 5' },
|
||||
{ label: 'Another label', value: 'Another value ' },
|
||||
]);
|
||||
|
||||
export const basic = () => {
|
||||
const value = object<SelectableValue<string>>('Selected Value:', intialState);
|
||||
|
||||
return (
|
||||
<UseState initialState={value}>
|
||||
{(value, updateValue) => {
|
||||
return (
|
||||
<Select
|
||||
placeholder="Choose..."
|
||||
options={options}
|
||||
width={20}
|
||||
onChange={value => {
|
||||
action('onChanged fired')(value);
|
||||
updateValue(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</UseState>
|
||||
);
|
||||
};
|
||||
|
||||
export const withAllowCustomValue = () => {
|
||||
// @ts-ignore
|
||||
const value = object<SelectableValue<string>>('Selected Value:', null);
|
||||
|
||||
return (
|
||||
<UseState initialState={value}>
|
||||
{(value, updateValue) => {
|
||||
return (
|
||||
<Select
|
||||
// value={value}
|
||||
placeholder="Choose..."
|
||||
options={options}
|
||||
width={20}
|
||||
allowCustomValue={true}
|
||||
onChange={value => {
|
||||
action('onChanged fired')(value);
|
||||
updateValue(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</UseState>
|
||||
);
|
||||
};
|
||||
|
||||
export const asyncSelect = () => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [value, setValue] = useState();
|
||||
const loadAsyncOptions = useCallback(
|
||||
inputValue => {
|
||||
return new Promise<Array<SelectableValue<string>>>(resolve => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
resolve(options.filter(option => option.label && option.label.includes(inputValue)));
|
||||
}, 1000);
|
||||
});
|
||||
},
|
||||
[value]
|
||||
);
|
||||
return (
|
||||
<AsyncSelect
|
||||
value={value}
|
||||
defaultOptions
|
||||
width={20}
|
||||
isLoading={isLoading}
|
||||
loadOptions={loadAsyncOptions}
|
||||
onChange={value => {
|
||||
action('onChange')(value);
|
||||
setValue(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,327 @@
|
||||
// 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 Creatable from '@torkelo/react-select/creatable';
|
||||
// @ts-ignore
|
||||
import { CreatableProps } from 'react-select';
|
||||
// @ts-ignore
|
||||
import { default as ReactAsyncSelect } from '@torkelo/react-select/async';
|
||||
// @ts-ignore
|
||||
import { components } from '@torkelo/react-select';
|
||||
|
||||
// Components
|
||||
import { SelectOption } from './SelectOption';
|
||||
import { SelectOptionGroup } from '../../../Select/SelectOptionGroup';
|
||||
import { SingleValue } from '../../../Select/SingleValue';
|
||||
import { SelectCommonProps, SelectAsyncProps } from '../../../Select/types';
|
||||
import IndicatorsContainer from './IndicatorsContainer';
|
||||
import NoOptionsMessage from './NoOptionsMessage';
|
||||
import resetSelectStyles from '../../../Select/resetSelectStyles';
|
||||
import { CustomScrollbar } from '../../../CustomScrollbar/CustomScrollbar';
|
||||
import { PopoverContent } from '../../../Tooltip/Tooltip';
|
||||
import { Tooltip } from '../../../Tooltip/Tooltip';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* Changes in new selects:
|
||||
* - noOptionsMessage & loadingMessage is of string type
|
||||
* - isDisabled is renamed to disabled
|
||||
*/
|
||||
type LegacyCommonProps<T> = Omit<SelectCommonProps<T>, 'noOptionsMessage' | 'disabled' | 'value'>;
|
||||
|
||||
interface AsyncProps<T> extends LegacyCommonProps<T>, Omit<SelectAsyncProps<T>, 'loadingMessage'> {
|
||||
loadingMessage?: () => string;
|
||||
noOptionsMessage?: () => string;
|
||||
tooltipContent?: PopoverContent;
|
||||
isDisabled?: boolean;
|
||||
value?: SelectableValue<T>;
|
||||
}
|
||||
|
||||
interface LegacySelectProps<T> extends LegacyCommonProps<T> {
|
||||
tooltipContent?: PopoverContent;
|
||||
noOptionsMessage?: () => string;
|
||||
isDisabled?: boolean;
|
||||
value?: SelectableValue<T>;
|
||||
}
|
||||
|
||||
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<LegacySelectProps<T>> {
|
||||
static defaultProps: Partial<LegacySelectProps<any>> = {
|
||||
className: '',
|
||||
isDisabled: false,
|
||||
isSearchable: true,
|
||||
isClearable: false,
|
||||
isMulti: false,
|
||||
openMenuOnFocus: false,
|
||||
autoFocus: false,
|
||||
isLoading: false,
|
||||
backspaceRemovesValue: true,
|
||||
maxMenuHeight: 300,
|
||||
tabSelectsValue: true,
|
||||
allowCustomValue: false,
|
||||
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,
|
||||
allowCustomValue,
|
||||
formatCreateLabel,
|
||||
} = this.props;
|
||||
|
||||
let widthClass = '';
|
||||
if (width) {
|
||||
widthClass = 'width-' + width;
|
||||
}
|
||||
|
||||
let SelectComponent: ReactSelect | Creatable = ReactSelect;
|
||||
const creatableOptions: any = {};
|
||||
|
||||
if (allowCustomValue) {
|
||||
SelectComponent = Creatable;
|
||||
creatableOptions.formatCreateLabel = formatCreateLabel ?? ((input: string) => input);
|
||||
}
|
||||
|
||||
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 (
|
||||
<SelectComponent
|
||||
captureMenuScroll={false}
|
||||
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}
|
||||
{...creatableOptions}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</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
|
||||
captureMenuScroll={false}
|
||||
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?: PopoverContent;
|
||||
}
|
||||
|
||||
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;
|
@ -1,89 +0,0 @@
|
||||
import React from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
import { Button, ButtonVariant, ButtonProps } from '../../Button';
|
||||
import { ComponentSize } from '../../../types/size';
|
||||
import { SelectCommonProps, CustomControlProps } from './types';
|
||||
import { SelectBase } from './SelectBase';
|
||||
import { stylesFactory, useTheme } from '../../../themes';
|
||||
import { Icon } from '../../Icon/Icon';
|
||||
import { IconType } from '../../Icon/types';
|
||||
|
||||
interface ButtonSelectProps<T> extends Omit<SelectCommonProps<T>, 'renderControl' | 'size' | 'prefix'> {
|
||||
icon?: IconType;
|
||||
variant?: ButtonVariant;
|
||||
size?: ComponentSize;
|
||||
}
|
||||
|
||||
interface SelectButtonProps extends Omit<ButtonProps, 'icon'> {
|
||||
icon?: IconType;
|
||||
isOpen?: boolean;
|
||||
}
|
||||
|
||||
const SelectButton = React.forwardRef<HTMLButtonElement, SelectButtonProps>(
|
||||
({ icon, children, isOpen, ...buttonProps }, ref) => {
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
wrapper: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 200px;
|
||||
text-overflow: ellipsis;
|
||||
`,
|
||||
iconWrap: css`
|
||||
padding: 0 15px 0 0;
|
||||
`,
|
||||
caretWrap: css`
|
||||
padding-left: ${theme.spacing.sm};
|
||||
margin-left: ${theme.spacing.sm};
|
||||
margin-right: -${theme.spacing.sm};
|
||||
height: 100%;
|
||||
`,
|
||||
}));
|
||||
const styles = getStyles(useTheme());
|
||||
const buttonIcon = `fa fa-${icon}`;
|
||||
const caretIcon = isOpen ? 'caret-up' : 'caret-down';
|
||||
return (
|
||||
<Button {...buttonProps} ref={ref} icon={buttonIcon}>
|
||||
<span className={styles.wrapper}>
|
||||
<span>{children}</span>
|
||||
<span className={styles.caretWrap}>
|
||||
<Icon name={caretIcon} />
|
||||
</span>
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export function ButtonSelect<T>({
|
||||
placeholder,
|
||||
icon,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
className,
|
||||
disabled,
|
||||
...selectProps
|
||||
}: ButtonSelectProps<T>) {
|
||||
const buttonProps = {
|
||||
icon,
|
||||
variant,
|
||||
size,
|
||||
className,
|
||||
disabled,
|
||||
};
|
||||
|
||||
return (
|
||||
<SelectBase
|
||||
{...selectProps}
|
||||
renderControl={React.forwardRef<any, CustomControlProps<T>>(({ onBlur, onClick, value, isOpen }, ref) => {
|
||||
return (
|
||||
<SelectButton {...buttonProps} ref={ref} onBlur={onBlur} onClick={onClick} isOpen={isOpen}>
|
||||
{value ? value.label : placeholder}
|
||||
</SelectButton>
|
||||
);
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../../themes/ThemeContext';
|
||||
import { getInputStyles } from '../Input/Input';
|
||||
import { cx, css } from 'emotion';
|
||||
|
||||
export const IndicatorsContainer = React.forwardRef<HTMLDivElement, React.PropsWithChildren<any>>((props, ref) => {
|
||||
const { children } = props;
|
||||
const theme = useTheme();
|
||||
const styles = getInputStyles({ theme, invalid: false });
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
styles.suffix,
|
||||
css`
|
||||
position: relative;
|
||||
`
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,300 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Select, AsyncSelect, MultiSelect, AsyncMultiSelect } from './Select';
|
||||
import { withCenteredStory, withHorizontallyCenteredStory } from '../../../utils/storybook/withCenteredStory';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { getAvailableIcons, IconType } from '../../Icon/types';
|
||||
import { select, boolean } from '@storybook/addon-knobs';
|
||||
import { Icon } from '../../Icon/Icon';
|
||||
import { Button } from '../../Button';
|
||||
import { ButtonSelect } from './ButtonSelect';
|
||||
import { getIconKnob } from '../../../utils/storybook/knobs';
|
||||
import kebabCase from 'lodash/kebabCase';
|
||||
import { generateOptions } from './mockOptions';
|
||||
|
||||
export default {
|
||||
title: 'Forms/Select',
|
||||
component: Select,
|
||||
decorators: [withCenteredStory, withHorizontallyCenteredStory],
|
||||
};
|
||||
|
||||
const loadAsyncOptions = () => {
|
||||
return new Promise<Array<SelectableValue<string>>>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(generateOptions());
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
const getKnobs = () => {
|
||||
const BEHAVIOUR_GROUP = 'Behaviour props';
|
||||
const disabled = boolean('Disabled', false, BEHAVIOUR_GROUP);
|
||||
const invalid = boolean('Invalid', false, BEHAVIOUR_GROUP);
|
||||
const loading = boolean('Loading', false, BEHAVIOUR_GROUP);
|
||||
const prefixSuffixOpts = {
|
||||
None: null,
|
||||
Text: '$',
|
||||
...getAvailableIcons().reduce<Record<string, string>>((prev, c) => {
|
||||
return {
|
||||
...prev,
|
||||
[`Icon: ${c}`]: `icon-${c}`,
|
||||
};
|
||||
}, {}),
|
||||
};
|
||||
const VISUAL_GROUP = 'Visual options';
|
||||
// ---
|
||||
const prefix = select('Prefix', prefixSuffixOpts, null, VISUAL_GROUP);
|
||||
|
||||
let prefixEl: any = prefix;
|
||||
if (prefix && prefix.match(/icon-/g)) {
|
||||
prefixEl = <Icon name={prefix.replace(/icon-/g, '') as IconType} />;
|
||||
}
|
||||
|
||||
return {
|
||||
disabled,
|
||||
invalid,
|
||||
loading,
|
||||
prefixEl,
|
||||
};
|
||||
};
|
||||
|
||||
const getDynamicProps = () => {
|
||||
const knobs = getKnobs();
|
||||
return {
|
||||
disabled: knobs.disabled,
|
||||
isLoading: knobs.loading,
|
||||
invalid: knobs.invalid,
|
||||
prefix: knobs.prefixEl,
|
||||
};
|
||||
};
|
||||
|
||||
export const basic = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses plain values instead of SelectableValue<T>
|
||||
*/
|
||||
export const basicSelectPlainValue = () => {
|
||||
const [value, setValue] = useState<string>();
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v.value);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses plain values instead of SelectableValue<T>
|
||||
*/
|
||||
export const SelectWithOptionDescriptions = () => {
|
||||
// TODO this is not working with new Select
|
||||
|
||||
const [value, setValue] = useState<number>();
|
||||
const options = [
|
||||
{ label: 'Basic option', value: 0 },
|
||||
{ label: 'Option with description', value: 1, description: 'this is a description' },
|
||||
{
|
||||
label: 'Option with description and image',
|
||||
value: 2,
|
||||
description: 'This is a very elaborate description, describing all the wonders in the world.',
|
||||
imgUrl: 'https://placekitten.com/40/40',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v.value);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses plain values instead of SelectableValue<T>
|
||||
*/
|
||||
export const multiPlainValue = () => {
|
||||
const [value, setValue] = useState<string[]>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MultiSelect
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v.map((v: any) => v.value));
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const multiSelect = () => {
|
||||
const [value, setValue] = useState<Array<SelectableValue<string>>>([]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MultiSelect
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const multiSelectAsync = () => {
|
||||
const [value, setValue] = useState<Array<SelectableValue<string>>>();
|
||||
|
||||
return (
|
||||
<AsyncMultiSelect
|
||||
loadOptions={loadAsyncOptions}
|
||||
defaultOptions
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
allowCustomValue
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const buttonSelect = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
const icon = getIconKnob();
|
||||
return (
|
||||
<ButtonSelect
|
||||
placeholder="Select all the things..."
|
||||
value={value}
|
||||
options={generateOptions()}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
allowCustomValue
|
||||
icon={icon}
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const basicSelectAsync = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
|
||||
return (
|
||||
<AsyncSelect
|
||||
loadOptions={loadAsyncOptions}
|
||||
defaultOptions
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const customizedControl = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
|
||||
return (
|
||||
<Select
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
renderControl={React.forwardRef(({ isOpen, value, ...otherProps }, ref) => {
|
||||
return (
|
||||
<Button {...otherProps} ref={ref}>
|
||||
{' '}
|
||||
{isOpen ? 'Open' : 'Closed'}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const autoMenuPlacement = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ height: '95vh', display: 'flex', alignItems: 'flex-end' }}>
|
||||
<Select
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const customValueCreation = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
const [customOptions, setCustomOptions] = useState<Array<SelectableValue<string>>>([]);
|
||||
const options = generateOptions();
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
options={[...options, ...customOptions]}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
allowCustomValue
|
||||
onCreateOption={v => {
|
||||
const customValue: SelectableValue<string> = { value: kebabCase(v), label: v };
|
||||
setCustomOptions([...customOptions, customValue]);
|
||||
setValue(customValue);
|
||||
}}
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { SelectCommonProps, MultiSelectCommonProps, SelectAsyncProps } from './types';
|
||||
import { SelectBase } from './SelectBase';
|
||||
|
||||
export function Select<T>(props: SelectCommonProps<T>) {
|
||||
return <SelectBase {...props} />;
|
||||
}
|
||||
|
||||
export function MultiSelect<T>(props: MultiSelectCommonProps<T>) {
|
||||
// @ts-ignore
|
||||
return <SelectBase {...props} isMulti />;
|
||||
}
|
||||
|
||||
interface AsyncSelectProps<T> extends Omit<SelectCommonProps<T>, 'options'>, SelectAsyncProps<T> {
|
||||
// AsyncSelect has options stored internally. We cannot enable plain values as we don't have access to the fetched options
|
||||
value?: SelectableValue<T>;
|
||||
invalid?: boolean;
|
||||
}
|
||||
|
||||
export function AsyncSelect<T>(props: AsyncSelectProps<T>) {
|
||||
return <SelectBase {...props} />;
|
||||
}
|
||||
|
||||
interface AsyncMultiSelectProps<T> extends Omit<MultiSelectCommonProps<T>, 'options'>, SelectAsyncProps<T> {
|
||||
// AsyncSelect has options stored internally. We cannot enable plain values as we don't have access to the fetched options
|
||||
value?: Array<SelectableValue<T>>;
|
||||
}
|
||||
|
||||
export function AsyncMultiSelect<T>(props: AsyncMultiSelectProps<T>) {
|
||||
// @ts-ignore
|
||||
return <SelectBase {...props} isMulti />;
|
||||
}
|
@ -2,15 +2,15 @@ import { Controller as InputControl } from 'react-hook-form';
|
||||
import { getFormStyles } from './getFormStyles';
|
||||
import { Label } from './Label';
|
||||
import { Input } from './Input/Input';
|
||||
import { ButtonSelect } from './Select/ButtonSelect';
|
||||
import { RadioButtonGroup } from './RadioButtonGroup/RadioButtonGroup';
|
||||
import { AsyncSelect, Select } from './Select/Select';
|
||||
import { Form } from './Form';
|
||||
import { Field } from './Field';
|
||||
import { Switch } from './Switch';
|
||||
import { Legend } from './Legend';
|
||||
import { TextArea } from './TextArea/TextArea';
|
||||
import { Checkbox } from './Checkbox';
|
||||
//Remove after Enterprise migrations have been merged
|
||||
import { Select } from '../Select/Select';
|
||||
|
||||
const Forms = {
|
||||
RadioButtonGroup,
|
||||
@ -20,13 +20,11 @@ const Forms = {
|
||||
Input,
|
||||
Form,
|
||||
Field,
|
||||
Select,
|
||||
ButtonSelect,
|
||||
InputControl,
|
||||
AsyncSelect,
|
||||
TextArea,
|
||||
Checkbox,
|
||||
Legend,
|
||||
Select,
|
||||
};
|
||||
|
||||
export default Forms;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types';
|
||||
import { FieldMatcherID, fieldMatchers } from '@grafana/data';
|
||||
import Forms from '../Forms';
|
||||
import { Select } from '../Select/Select';
|
||||
|
||||
export class FieldNameMatcherEditor extends React.PureComponent<MatcherUIProps<string>> {
|
||||
render() {
|
||||
@ -23,12 +23,7 @@ export class FieldNameMatcherEditor extends React.PureComponent<MatcherUIProps<s
|
||||
const selectedOption = selectOptions.find(v => v.value === options);
|
||||
|
||||
return (
|
||||
<Forms.Select
|
||||
allowCustomValue
|
||||
value={selectedOption}
|
||||
options={selectOptions}
|
||||
onChange={o => onChange(o.value!)}
|
||||
/>
|
||||
<Select allowCustomValue value={selectedOption} options={selectOptions} onChange={o => onChange(o.value!)} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { FieldConfigEditorProps, SelectFieldConfigSettings } from '@grafana/data';
|
||||
import Forms from '../Forms';
|
||||
import { Select } from '../Select/Select';
|
||||
|
||||
export function SelectValueEditor<T>({
|
||||
value,
|
||||
onChange,
|
||||
item,
|
||||
}: FieldConfigEditorProps<T, SelectFieldConfigSettings<T>>) {
|
||||
return <Forms.Select<T> defaultValue={value} onChange={e => onChange(e.value)} options={item.settings?.options} />;
|
||||
return <Select<T> defaultValue={value} onChange={e => onChange(e.value)} options={item.settings?.options} />;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import classNames from 'classnames';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
import { Tooltip } from '../Tooltip/Tooltip';
|
||||
import { ButtonSelect } from '../Select/ButtonSelect';
|
||||
import { ButtonSelect } from '../Forms/Legacy/Select/ButtonSelect';
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { withTheme } from '../../themes';
|
||||
|
@ -2,7 +2,7 @@ import React, { useRef } from 'react';
|
||||
import { css, cx } from 'emotion';
|
||||
import useClickAway from 'react-use/lib/useClickAway';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '../Select/Select';
|
||||
import { Select } from '../Forms/Legacy/Select/Select';
|
||||
|
||||
export interface Props<T> {
|
||||
value?: SelectableValue<T>;
|
||||
|
@ -1,97 +1,89 @@
|
||||
import React, { PureComponent, ReactElement } from 'react';
|
||||
import Select from './Select';
|
||||
import { PopoverContent } from '../Tooltip/Tooltip';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import React from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
interface ButtonComponentProps {
|
||||
label: ReactElement | string | undefined;
|
||||
className: string | undefined;
|
||||
iconClass?: string;
|
||||
import { Button, ButtonVariant, ButtonProps } from '../Button';
|
||||
import { ComponentSize } from '../../types/size';
|
||||
import { SelectCommonProps, CustomControlProps } from './types';
|
||||
import { SelectBase } from './SelectBase';
|
||||
import { stylesFactory, useTheme } from '../../themes';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { IconType } from '../Icon/types';
|
||||
|
||||
interface ButtonSelectProps<T> extends Omit<SelectCommonProps<T>, 'renderControl' | 'size' | 'prefix'> {
|
||||
icon?: IconType;
|
||||
variant?: ButtonVariant;
|
||||
size?: ComponentSize;
|
||||
}
|
||||
|
||||
const ButtonComponent = (buttonProps: ButtonComponentProps) => (props: any) => {
|
||||
const { label, className, iconClass } = buttonProps;
|
||||
|
||||
return (
|
||||
<div // changed to div because of FireFox on MacOs issue below
|
||||
ref={props.innerRef}
|
||||
className={`btn navbar-button navbar-button--tight ${className}`}
|
||||
onClick={props.selectProps.menuIsOpen ? props.selectProps.onMenuClose : props.selectProps.onMenuOpen}
|
||||
onBlur={props.selectProps.onMenuClose}
|
||||
tabIndex={0} // necessary to get onBlur to work https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
|
||||
>
|
||||
<div className="select-button">
|
||||
{iconClass && <i className={`select-button-icon ${iconClass}`} />}
|
||||
<span className="select-button-value">{label ? label : ''}</span>
|
||||
{!props.menuIsOpen && <i className="fa fa-caret-down fa-fw" />}
|
||||
{props.menuIsOpen && <i className="fa fa-caret-up fa-fw" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface Props<T> {
|
||||
className: string | undefined;
|
||||
options: Array<SelectableValue<T>>;
|
||||
value?: SelectableValue<T>;
|
||||
label?: ReactElement | string;
|
||||
iconClass?: string;
|
||||
components?: any;
|
||||
maxMenuHeight?: number;
|
||||
onChange: (item: SelectableValue<T>) => void;
|
||||
tooltipContent?: PopoverContent;
|
||||
isMenuOpen?: boolean;
|
||||
onOpenMenu?: () => void;
|
||||
onCloseMenu?: () => void;
|
||||
tabSelectsValue?: boolean;
|
||||
autoFocus?: boolean;
|
||||
interface SelectButtonProps extends Omit<ButtonProps, 'icon'> {
|
||||
icon?: IconType;
|
||||
isOpen?: boolean;
|
||||
}
|
||||
|
||||
export class ButtonSelect<T> extends PureComponent<Props<T>> {
|
||||
onChange = (item: SelectableValue<T>) => {
|
||||
const { onChange } = this.props;
|
||||
onChange(item);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
options,
|
||||
value,
|
||||
label,
|
||||
iconClass,
|
||||
components,
|
||||
maxMenuHeight,
|
||||
tooltipContent,
|
||||
isMenuOpen,
|
||||
onOpenMenu,
|
||||
onCloseMenu,
|
||||
tabSelectsValue,
|
||||
autoFocus = true,
|
||||
} = this.props;
|
||||
const combinedComponents = {
|
||||
...components,
|
||||
Control: ButtonComponent({ label, className, iconClass }),
|
||||
};
|
||||
|
||||
const SelectButton = React.forwardRef<HTMLButtonElement, SelectButtonProps>(
|
||||
({ icon, children, isOpen, ...buttonProps }, ref) => {
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
wrapper: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 200px;
|
||||
text-overflow: ellipsis;
|
||||
`,
|
||||
iconWrap: css`
|
||||
padding: 0 15px 0 0;
|
||||
`,
|
||||
caretWrap: css`
|
||||
padding-left: ${theme.spacing.sm};
|
||||
margin-left: ${theme.spacing.sm};
|
||||
margin-right: -${theme.spacing.sm};
|
||||
height: 100%;
|
||||
`,
|
||||
}));
|
||||
const styles = getStyles(useTheme());
|
||||
const buttonIcon = `fa fa-${icon}`;
|
||||
const caretIcon = isOpen ? 'caret-up' : 'caret-down';
|
||||
return (
|
||||
<Select
|
||||
autoFocus={autoFocus}
|
||||
backspaceRemovesValue={false}
|
||||
isClearable={false}
|
||||
isSearchable={false}
|
||||
options={options}
|
||||
onChange={this.onChange}
|
||||
value={value}
|
||||
isOpen={isMenuOpen}
|
||||
onOpenMenu={onOpenMenu}
|
||||
onCloseMenu={onCloseMenu}
|
||||
maxMenuHeight={maxMenuHeight}
|
||||
components={combinedComponents}
|
||||
className="gf-form-select-box-button-select"
|
||||
tooltipContent={tooltipContent}
|
||||
tabSelectsValue={tabSelectsValue}
|
||||
/>
|
||||
<Button {...buttonProps} ref={ref} icon={buttonIcon}>
|
||||
<span className={styles.wrapper}>
|
||||
<span>{children}</span>
|
||||
<span className={styles.caretWrap}>
|
||||
<Icon name={caretIcon} />
|
||||
</span>
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export function ButtonSelect<T>({
|
||||
placeholder,
|
||||
icon,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
className,
|
||||
disabled,
|
||||
...selectProps
|
||||
}: ButtonSelectProps<T>) {
|
||||
const buttonProps = {
|
||||
icon,
|
||||
variant,
|
||||
size,
|
||||
className,
|
||||
disabled,
|
||||
};
|
||||
|
||||
return (
|
||||
<SelectBase
|
||||
{...selectProps}
|
||||
renderControl={React.forwardRef<any, CustomControlProps<T>>(({ onBlur, onClick, value, isOpen }, ref) => {
|
||||
return (
|
||||
<SelectButton {...buttonProps} ref={ref} onBlur={onBlur} onClick={onClick} isOpen={isOpen}>
|
||||
{value ? value.label : placeholder}
|
||||
</SelectButton>
|
||||
);
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '../../Icon/Icon';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
|
||||
interface DropdownIndicatorProps {
|
||||
isOpen: boolean;
|
@ -1,18 +1,24 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../themes/ThemeContext';
|
||||
import { getInputStyles } from '../Forms/Input/Input';
|
||||
import { cx, css } from 'emotion';
|
||||
|
||||
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||
// @ts-ignore
|
||||
import { components } from '@torkelo/react-select';
|
||||
export const IndicatorsContainer = React.forwardRef<HTMLDivElement, React.PropsWithChildren<any>>((props, ref) => {
|
||||
const { children } = props;
|
||||
const theme = useTheme();
|
||||
const styles = getInputStyles({ theme, invalid: false });
|
||||
|
||||
export const IndicatorsContainer = (props: any) => {
|
||||
const isOpen = props.selectProps.menuIsOpen;
|
||||
return (
|
||||
<components.IndicatorsContainer {...props}>
|
||||
<span
|
||||
className={`gf-form-select-box__select-arrow ${isOpen ? `gf-form-select-box__select-arrow--reversed` : ''}`}
|
||||
/>
|
||||
</components.IndicatorsContainer>
|
||||
<div
|
||||
className={cx(
|
||||
styles.suffix,
|
||||
css`
|
||||
position: relative;
|
||||
`
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndicatorsContainer;
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../../themes/ThemeContext';
|
||||
import { getFocusCss, sharedInputStyle } from '../commonStyles';
|
||||
import { getInputStyles } from '../Input/Input';
|
||||
import { useTheme } from '../../themes/ThemeContext';
|
||||
import { getFocusCss, sharedInputStyle } from '../Forms/commonStyles';
|
||||
import { getInputStyles } from '../Forms/Input/Input';
|
||||
import { cx, css } from 'emotion';
|
||||
import { stylesFactory } from '../../../themes';
|
||||
import { stylesFactory } from '../../themes';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
interface InputControlProps {
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../../themes';
|
||||
import { useTheme } from '../../themes';
|
||||
import { getSelectStyles } from './getSelectStyles';
|
||||
import { Icon } from '../../Icon/Icon';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
|
||||
interface MultiValueContainerProps {
|
||||
innerProps: any;
|
@ -1,101 +1,300 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { withKnobs, object } from '@storybook/addon-knobs';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { UseState } from '../../utils/storybook/UseState';
|
||||
import React, { useState } from 'react';
|
||||
import { Select, AsyncSelect, MultiSelect, AsyncMultiSelect } from './Select';
|
||||
import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select, AsyncSelect } from './Select';
|
||||
import { getAvailableIcons, IconType } from '../Icon/types';
|
||||
import { select, boolean } from '@storybook/addon-knobs';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { Button } from '../Button';
|
||||
import { ButtonSelect } from './ButtonSelect';
|
||||
import { getIconKnob } from '../../utils/storybook/knobs';
|
||||
import kebabCase from 'lodash/kebabCase';
|
||||
import { generateOptions } from './mockOptions';
|
||||
|
||||
export default {
|
||||
title: 'General/Select/Select',
|
||||
title: 'Forms/Select',
|
||||
component: Select,
|
||||
decorators: [withCenteredStory, withKnobs],
|
||||
decorators: [withCenteredStory, withHorizontallyCenteredStory],
|
||||
};
|
||||
|
||||
const intialState: SelectableValue<string> = { label: 'A label', value: 'A value' };
|
||||
const loadAsyncOptions = () => {
|
||||
return new Promise<Array<SelectableValue<string>>>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(generateOptions());
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
const options = object<Array<SelectableValue<string>>>('Options:', [
|
||||
intialState,
|
||||
{ label: 'Another label', value: 'Another value 1' },
|
||||
{ label: 'Another label', value: 'Another value 2' },
|
||||
{ label: 'Another label', value: 'Another value 3' },
|
||||
{ label: 'Another label', value: 'Another value 4' },
|
||||
{ label: 'Another label', value: 'Another value 5' },
|
||||
{ label: 'Another label', value: 'Another value ' },
|
||||
]);
|
||||
const getKnobs = () => {
|
||||
const BEHAVIOUR_GROUP = 'Behaviour props';
|
||||
const disabled = boolean('Disabled', false, BEHAVIOUR_GROUP);
|
||||
const invalid = boolean('Invalid', false, BEHAVIOUR_GROUP);
|
||||
const loading = boolean('Loading', false, BEHAVIOUR_GROUP);
|
||||
const prefixSuffixOpts = {
|
||||
None: null,
|
||||
Text: '$',
|
||||
...getAvailableIcons().reduce<Record<string, string>>((prev, c) => {
|
||||
return {
|
||||
...prev,
|
||||
[`Icon: ${c}`]: `icon-${c}`,
|
||||
};
|
||||
}, {}),
|
||||
};
|
||||
const VISUAL_GROUP = 'Visual options';
|
||||
// ---
|
||||
const prefix = select('Prefix', prefixSuffixOpts, null, VISUAL_GROUP);
|
||||
|
||||
let prefixEl: any = prefix;
|
||||
if (prefix && prefix.match(/icon-/g)) {
|
||||
prefixEl = <Icon name={prefix.replace(/icon-/g, '') as IconType} />;
|
||||
}
|
||||
|
||||
return {
|
||||
disabled,
|
||||
invalid,
|
||||
loading,
|
||||
prefixEl,
|
||||
};
|
||||
};
|
||||
|
||||
const getDynamicProps = () => {
|
||||
const knobs = getKnobs();
|
||||
return {
|
||||
disabled: knobs.disabled,
|
||||
isLoading: knobs.loading,
|
||||
invalid: knobs.invalid,
|
||||
prefix: knobs.prefixEl,
|
||||
};
|
||||
};
|
||||
|
||||
export const basic = () => {
|
||||
const value = object<SelectableValue<string>>('Selected Value:', intialState);
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
|
||||
return (
|
||||
<UseState initialState={value}>
|
||||
{(value, updateValue) => {
|
||||
return (
|
||||
<Select
|
||||
placeholder="Choose..."
|
||||
options={options}
|
||||
width={20}
|
||||
onChange={value => {
|
||||
action('onChanged fired')(value);
|
||||
updateValue(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</UseState>
|
||||
<>
|
||||
<Select
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const withAllowCustomValue = () => {
|
||||
// @ts-ignore
|
||||
const value = object<SelectableValue<string>>('Selected Value:', null);
|
||||
|
||||
/**
|
||||
* Uses plain values instead of SelectableValue<T>
|
||||
*/
|
||||
export const basicSelectPlainValue = () => {
|
||||
const [value, setValue] = useState<string>();
|
||||
return (
|
||||
<UseState initialState={value}>
|
||||
{(value, updateValue) => {
|
||||
return (
|
||||
<Select
|
||||
// value={value}
|
||||
placeholder="Choose..."
|
||||
options={options}
|
||||
width={20}
|
||||
allowCustomValue={true}
|
||||
onChange={value => {
|
||||
action('onChanged fired')(value);
|
||||
updateValue(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</UseState>
|
||||
<>
|
||||
<Select
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v.value);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const asyncSelect = () => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [value, setValue] = useState();
|
||||
const loadAsyncOptions = useCallback(
|
||||
inputValue => {
|
||||
return new Promise<Array<SelectableValue<string>>>(resolve => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
resolve(options.filter(option => option.label && option.label.includes(inputValue)));
|
||||
}, 1000);
|
||||
});
|
||||
/**
|
||||
* Uses plain values instead of SelectableValue<T>
|
||||
*/
|
||||
export const SelectWithOptionDescriptions = () => {
|
||||
// TODO this is not working with new Select
|
||||
|
||||
const [value, setValue] = useState<number>();
|
||||
const options = [
|
||||
{ label: 'Basic option', value: 0 },
|
||||
{ label: 'Option with description', value: 1, description: 'this is a description' },
|
||||
{
|
||||
label: 'Option with description and image',
|
||||
value: 2,
|
||||
description: 'This is a very elaborate description, describing all the wonders in the world.',
|
||||
imgUrl: 'https://placekitten.com/40/40',
|
||||
},
|
||||
[value]
|
||||
);
|
||||
];
|
||||
|
||||
return (
|
||||
<AsyncSelect
|
||||
value={value}
|
||||
defaultOptions
|
||||
width={20}
|
||||
isLoading={isLoading}
|
||||
<>
|
||||
<Select
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v.value);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses plain values instead of SelectableValue<T>
|
||||
*/
|
||||
export const multiPlainValue = () => {
|
||||
const [value, setValue] = useState<string[]>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MultiSelect
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v.map((v: any) => v.value));
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const multiSelect = () => {
|
||||
const [value, setValue] = useState<Array<SelectableValue<string>>>([]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MultiSelect
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const multiSelectAsync = () => {
|
||||
const [value, setValue] = useState<Array<SelectableValue<string>>>();
|
||||
|
||||
return (
|
||||
<AsyncMultiSelect
|
||||
loadOptions={loadAsyncOptions}
|
||||
onChange={value => {
|
||||
action('onChange')(value);
|
||||
setValue(value);
|
||||
defaultOptions
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
allowCustomValue
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const buttonSelect = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
const icon = getIconKnob();
|
||||
return (
|
||||
<ButtonSelect
|
||||
placeholder="Select all the things..."
|
||||
value={value}
|
||||
options={generateOptions()}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
allowCustomValue
|
||||
icon={icon}
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const basicSelectAsync = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
|
||||
return (
|
||||
<AsyncSelect
|
||||
loadOptions={loadAsyncOptions}
|
||||
defaultOptions
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const customizedControl = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
|
||||
return (
|
||||
<Select
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
renderControl={React.forwardRef(({ isOpen, value, ...otherProps }, ref) => {
|
||||
return (
|
||||
<Button {...otherProps} ref={ref}>
|
||||
{' '}
|
||||
{isOpen ? 'Open' : 'Closed'}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const autoMenuPlacement = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ height: '95vh', display: 'flex', alignItems: 'flex-end' }}>
|
||||
<Select
|
||||
options={generateOptions()}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const customValueCreation = () => {
|
||||
const [value, setValue] = useState<SelectableValue<string>>();
|
||||
const [customOptions, setCustomOptions] = useState<Array<SelectableValue<string>>>([]);
|
||||
const options = generateOptions();
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
options={[...options, ...customOptions]}
|
||||
value={value}
|
||||
onChange={v => {
|
||||
setValue(v);
|
||||
}}
|
||||
size="md"
|
||||
allowCustomValue
|
||||
onCreateOption={v => {
|
||||
const customValue: SelectableValue<string> = { value: kebabCase(v), label: v };
|
||||
setCustomOptions([...customOptions, customValue]);
|
||||
setValue(customValue);
|
||||
}}
|
||||
{...getDynamicProps()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,327 +1,33 @@
|
||||
// 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 Creatable from '@torkelo/react-select/creatable';
|
||||
// @ts-ignore
|
||||
import { CreatableProps } from 'react-select';
|
||||
// @ts-ignore
|
||||
import { default as ReactAsyncSelect } from '@torkelo/react-select/async';
|
||||
// @ts-ignore
|
||||
import { components } from '@torkelo/react-select';
|
||||
|
||||
// Components
|
||||
import { SelectOption } from './SelectOption';
|
||||
import { SelectOptionGroup } from '../Forms/Select/SelectOptionGroup';
|
||||
import { SingleValue } from '../Forms/Select/SingleValue';
|
||||
import { SelectCommonProps, SelectAsyncProps } from '../Forms/Select/types';
|
||||
import IndicatorsContainer from './IndicatorsContainer';
|
||||
import NoOptionsMessage from './NoOptionsMessage';
|
||||
import resetSelectStyles from '../Forms/Select/resetSelectStyles';
|
||||
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
||||
import { PopoverContent } from '../Tooltip/Tooltip';
|
||||
import { Tooltip } from '../Tooltip/Tooltip';
|
||||
import React from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { SelectCommonProps, MultiSelectCommonProps, SelectAsyncProps } from './types';
|
||||
import { SelectBase } from './SelectBase';
|
||||
|
||||
/**
|
||||
* Changes in new selects:
|
||||
* - noOptionsMessage & loadingMessage is of string type
|
||||
* - isDisabled is renamed to disabled
|
||||
*/
|
||||
type LegacyCommonProps<T> = Omit<SelectCommonProps<T>, 'noOptionsMessage' | 'disabled' | 'value'>;
|
||||
export function Select<T>(props: SelectCommonProps<T>) {
|
||||
return <SelectBase {...props} />;
|
||||
}
|
||||
|
||||
interface AsyncProps<T> extends LegacyCommonProps<T>, Omit<SelectAsyncProps<T>, 'loadingMessage'> {
|
||||
loadingMessage?: () => string;
|
||||
noOptionsMessage?: () => string;
|
||||
tooltipContent?: PopoverContent;
|
||||
isDisabled?: boolean;
|
||||
export function MultiSelect<T>(props: MultiSelectCommonProps<T>) {
|
||||
// @ts-ignore
|
||||
return <SelectBase {...props} isMulti />;
|
||||
}
|
||||
|
||||
interface AsyncSelectProps<T> extends Omit<SelectCommonProps<T>, 'options'>, SelectAsyncProps<T> {
|
||||
// AsyncSelect has options stored internally. We cannot enable plain values as we don't have access to the fetched options
|
||||
value?: SelectableValue<T>;
|
||||
invalid?: boolean;
|
||||
}
|
||||
|
||||
interface LegacySelectProps<T> extends LegacyCommonProps<T> {
|
||||
tooltipContent?: PopoverContent;
|
||||
noOptionsMessage?: () => string;
|
||||
isDisabled?: boolean;
|
||||
value?: SelectableValue<T>;
|
||||
export function AsyncSelect<T>(props: AsyncSelectProps<T>) {
|
||||
return <SelectBase {...props} />;
|
||||
}
|
||||
|
||||
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<LegacySelectProps<T>> {
|
||||
static defaultProps: Partial<LegacySelectProps<any>> = {
|
||||
className: '',
|
||||
isDisabled: false,
|
||||
isSearchable: true,
|
||||
isClearable: false,
|
||||
isMulti: false,
|
||||
openMenuOnFocus: false,
|
||||
autoFocus: false,
|
||||
isLoading: false,
|
||||
backspaceRemovesValue: true,
|
||||
maxMenuHeight: 300,
|
||||
tabSelectsValue: true,
|
||||
allowCustomValue: false,
|
||||
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,
|
||||
allowCustomValue,
|
||||
formatCreateLabel,
|
||||
} = this.props;
|
||||
|
||||
let widthClass = '';
|
||||
if (width) {
|
||||
widthClass = 'width-' + width;
|
||||
}
|
||||
|
||||
let SelectComponent: ReactSelect | Creatable = ReactSelect;
|
||||
const creatableOptions: any = {};
|
||||
|
||||
if (allowCustomValue) {
|
||||
SelectComponent = Creatable;
|
||||
creatableOptions.formatCreateLabel = formatCreateLabel ?? ((input: string) => input);
|
||||
}
|
||||
|
||||
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 (
|
||||
<SelectComponent
|
||||
captureMenuScroll={false}
|
||||
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}
|
||||
{...creatableOptions}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</WrapInTooltip>
|
||||
);
|
||||
}
|
||||
interface AsyncMultiSelectProps<T> extends Omit<MultiSelectCommonProps<T>, 'options'>, SelectAsyncProps<T> {
|
||||
// AsyncSelect has options stored internally. We cannot enable plain values as we don't have access to the fetched options
|
||||
value?: Array<SelectableValue<T>>;
|
||||
}
|
||||
|
||||
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
|
||||
captureMenuScroll={false}
|
||||
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 function AsyncMultiSelect<T>(props: AsyncMultiSelectProps<T>) {
|
||||
// @ts-ignore
|
||||
return <SelectBase {...props} isMulti />;
|
||||
}
|
||||
|
||||
export interface TooltipWrapperProps {
|
||||
children: (onOpenMenu: () => void, onCloseMenu: () => void) => React.ReactNode;
|
||||
onOpenMenu?: () => void;
|
||||
onCloseMenu?: () => void;
|
||||
isOpen?: boolean;
|
||||
tooltipContent?: PopoverContent;
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -9,9 +9,9 @@ import { default as ReactAsyncSelect } from '@torkelo/react-select/async';
|
||||
// @ts-ignore
|
||||
import { default as AsyncCreatable } from '@torkelo/react-select/async-creatable';
|
||||
|
||||
import { Icon } from '../../Icon/Icon';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { css, cx } from 'emotion';
|
||||
import { inputSizesPixels } from '../commonStyles';
|
||||
import { inputSizesPixels } from '../Forms/commonStyles';
|
||||
import resetSelectStyles from './resetSelectStyles';
|
||||
import { SelectMenu, SelectMenuOptions } from './SelectMenu';
|
||||
import { IndicatorsContainer } from './IndicatorsContainer';
|
||||
@ -21,7 +21,7 @@ import { DropdownIndicator } from './DropdownIndicator';
|
||||
import { SelectOptionGroup } from './SelectOptionGroup';
|
||||
import { SingleValue } from './SingleValue';
|
||||
import { MultiValueContainer, MultiValueRemove } from './MultiValue';
|
||||
import { useTheme } from '../../../themes';
|
||||
import { useTheme } from '../../themes';
|
||||
import { getSelectStyles } from './getSelectStyles';
|
||||
import { cleanValue } from './utils';
|
||||
import { SelectBaseProps, SelectValue } from './types';
|
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../../themes/ThemeContext';
|
||||
import { useTheme } from '../../themes/ThemeContext';
|
||||
import { getSelectStyles } from './getSelectStyles';
|
||||
import { cx } from 'emotion';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Icon } from '../../Icon/Icon';
|
||||
import { CustomScrollbar } from '../../CustomScrollbar/CustomScrollbar';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
||||
|
||||
interface SelectMenuProps {
|
||||
maxHeight: number;
|
@ -2,8 +2,8 @@ import React, { PureComponent } from 'react';
|
||||
import { css, cx } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { GroupProps } from 'react-select';
|
||||
import { stylesFactory, withTheme, selectThemeVariant } from '../../../themes';
|
||||
import { Themeable } from '../../../types';
|
||||
import { stylesFactory, withTheme, selectThemeVariant } from '../../themes';
|
||||
import { Themeable } from '../../types';
|
||||
|
||||
interface ExtendedGroupProps extends GroupProps<any>, Themeable {
|
||||
data: {
|
@ -4,11 +4,11 @@ import { css, cx } from 'emotion';
|
||||
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||
// @ts-ignore
|
||||
import { components } from '@torkelo/react-select';
|
||||
import { useDelayedSwitch } from '../../../utils/useDelayedSwitch';
|
||||
import { stylesFactory, useTheme } from '../../../themes';
|
||||
import { SlideOutTransition } from '../../transitions/SlideOutTransition';
|
||||
import { FadeTransition } from '../../transitions/FadeTransition';
|
||||
import { Spinner } from '../../Spinner/Spinner';
|
||||
import { useDelayedSwitch } from '../../utils/useDelayedSwitch';
|
||||
import { stylesFactory, useTheme } from '../../themes';
|
||||
import { SlideOutTransition } from '../transitions/SlideOutTransition';
|
||||
import { FadeTransition } from '../transitions/FadeTransition';
|
||||
import { Spinner } from '../Spinner/Spinner';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { cx } from 'emotion';
|
||||
import { useTheme } from '../../../themes/ThemeContext';
|
||||
import { useTheme } from '../../themes/ThemeContext';
|
||||
import { getSelectStyles } from './getSelectStyles';
|
||||
|
||||
export const ValueContainer = (props: any) => {
|
@ -1,5 +1,5 @@
|
||||
import { stylesFactory } from '../../../themes/stylesFactory';
|
||||
import { selectThemeVariant as stv } from '../../../themes/selectThemeVariant';
|
||||
import { stylesFactory } from '../../themes/stylesFactory';
|
||||
import { selectThemeVariant as stv } from '../../themes/selectThemeVariant';
|
||||
import { css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import React from 'react';
|
||||
import { FormInputSize } from '../types';
|
||||
import { FormInputSize } from '../Forms/types';
|
||||
|
||||
export type SelectValue<T> = T | SelectableValue<T> | T[] | Array<SelectableValue<T>>;
|
||||
|
||||
@ -18,6 +18,8 @@ export interface SelectCommonProps<T> {
|
||||
onKeyDown?: (event: React.KeyboardEvent) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
//To be removed, is here to make Enterprise mergable
|
||||
isDisabled?: boolean;
|
||||
isSearchable?: boolean;
|
||||
isClearable?: boolean;
|
||||
autoFocus?: boolean;
|
@ -42,7 +42,7 @@ const options: SelectableValue[] = [
|
||||
{ label: 'Option 6', value: 6 },
|
||||
];
|
||||
|
||||
describe('Forms.Select utils', () => {
|
||||
describe('Select utils', () => {
|
||||
describe('findSelected value', () => {
|
||||
it('should find value of type number in array of optgroups', () => {
|
||||
expect(findSelectedValue(11, optGroup)).toEqual({ label: 'Group 2 - Option 1', value: 11 });
|
@ -7,7 +7,7 @@ import { FormField } from '../FormField/FormField';
|
||||
import { StatsPicker } from '../StatsPicker/StatsPicker';
|
||||
|
||||
// Types
|
||||
import Select from '../Select/Select';
|
||||
import Select from '../Forms/Legacy/Select/Select';
|
||||
import {
|
||||
ReduceDataOptions,
|
||||
DEFAULT_FIELD_DISPLAY_VALUES_LIMIT,
|
||||
|
@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
|
||||
import isArray from 'lodash/isArray';
|
||||
import difference from 'lodash/difference';
|
||||
|
||||
import { Select } from '../Forms/Select/Select';
|
||||
import { Select } from '../Select/Select';
|
||||
|
||||
import { fieldReducers, SelectableValue } from '@grafana/data';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DataTransformerID, DataTransformerConfig, DataFrame, transformDataFrame } from '@grafana/data';
|
||||
import { Select } from '../Select/Select';
|
||||
import { Select } from '../Forms/Legacy/Select/Select';
|
||||
import { transformersUIRegistry } from './transformers';
|
||||
import React from 'react';
|
||||
import { TransformationRow } from './TransformationRow';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { Select } from '../Select/Select';
|
||||
import { Select } from '../Forms/Legacy/Select/Select';
|
||||
import { Cascader, CascaderOption } from '../Cascader/Cascader';
|
||||
import { getValueFormats, SelectableValue } from '@grafana/data';
|
||||
|
||||
|
@ -3,7 +3,7 @@ import React, { ChangeEvent, PureComponent } from 'react';
|
||||
import { FormField } from '../FormField/FormField';
|
||||
import { FormLabel } from '../FormLabel/FormLabel';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Select } from '../Select/Select';
|
||||
import { Select } from '../Forms/Legacy/Select/Select';
|
||||
|
||||
import { MappingType, ValueMapping } from '@grafana/data';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
import { Select } from '../index';
|
||||
import Forms from '../Forms';
|
||||
import { MappingType, RangeMap, ValueMap, ValueMapping } from '@grafana/data';
|
||||
import * as styleMixins from '../../themes/mixins';
|
||||
@ -80,7 +81,7 @@ export const MappingRow: React.FC<Props> = ({ valueMapping, updateValueMapping,
|
||||
<div className={styles.wrapper}>
|
||||
<FieldConfigItemHeaderTitle title="Mapping type" onRemove={removeValueMapping}>
|
||||
<div className={styles.itemContent}>
|
||||
<Forms.Select
|
||||
<Select
|
||||
placeholder="Choose type"
|
||||
isSearchable={false}
|
||||
options={MAPPING_OPTIONS}
|
||||
|
@ -2,7 +2,7 @@ import { text } from '@storybook/addon-knobs';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { ValuePicker } from './ValuePicker';
|
||||
import React from 'react';
|
||||
import { generateOptions } from '../Forms/Select/mockOptions';
|
||||
import { generateOptions } from '../Select/mockOptions';
|
||||
|
||||
export default {
|
||||
title: 'General/ValuePicker',
|
||||
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||
import { IconType } from '../Icon/types';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Button, ButtonVariant } from '../Button';
|
||||
import { Select } from '../Forms/Select/Select';
|
||||
import { Select } from '../Select/Select';
|
||||
import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer';
|
||||
|
||||
interface ValuePickerProps<T> {
|
||||
|
@ -8,7 +8,7 @@
|
||||
@import 'PanelOptionsGrid/PanelOptionsGrid';
|
||||
@import 'PanelOptionsGroup/PanelOptionsGroup';
|
||||
@import 'RefreshPicker/RefreshPicker';
|
||||
@import 'Select/Select';
|
||||
@import 'Forms/Legacy/Select/Select';
|
||||
@import 'TableInputCSV/TableInputCSV';
|
||||
@import 'ThresholdsEditor/ThresholdsEditor';
|
||||
@import 'TimePicker/TimeOfDayPicker';
|
||||
|
@ -7,15 +7,8 @@ export { Portal } from './Portal/Portal';
|
||||
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
|
||||
|
||||
export { ClipboardButton } from './ClipboardButton/ClipboardButton';
|
||||
|
||||
// Select
|
||||
export { Select, AsyncSelect } from './Select/Select';
|
||||
export { IndicatorsContainer } from './Select/IndicatorsContainer';
|
||||
export { NoOptionsMessage } from './Select/NoOptionsMessage';
|
||||
export { default as resetSelectStyles } from './Forms/Select/resetSelectStyles';
|
||||
export { ButtonSelect } from './Select/ButtonSelect';
|
||||
export { ButtonCascader } from './ButtonCascader/ButtonCascader';
|
||||
export { Cascader, CascaderOption } from './Cascader/Cascader';
|
||||
export { ButtonCascader } from './ButtonCascader/ButtonCascader';
|
||||
|
||||
// Forms
|
||||
export { FormLabel } from './FormLabel/FormLabel';
|
||||
@ -140,5 +133,27 @@ export * from './Button';
|
||||
export { ValuePicker } from './ValuePicker/ValuePicker';
|
||||
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
|
||||
|
||||
export { default as resetSelectStyles } from './Select/resetSelectStyles';
|
||||
export * from './Select/Select';
|
||||
export { ButtonSelect } from './Select/ButtonSelect';
|
||||
|
||||
export { HorizontalGroup, VerticalGroup, Container } from './Layout/Layout';
|
||||
export { RadioButtonGroup } from './Forms/RadioButtonGroup/RadioButtonGroup';
|
||||
|
||||
// Legacy forms
|
||||
|
||||
// Select
|
||||
import { Select, AsyncSelect } from './Forms/Legacy/Select/Select';
|
||||
import { IndicatorsContainer } from './Forms/Legacy/Select/IndicatorsContainer';
|
||||
import { NoOptionsMessage } from './Forms/Legacy/Select/NoOptionsMessage';
|
||||
import { ButtonSelect } from './Forms/Legacy/Select/ButtonSelect';
|
||||
|
||||
const LegacyForms = {
|
||||
Select,
|
||||
AsyncSelect,
|
||||
IndicatorsContainer,
|
||||
NoOptionsMessage,
|
||||
ButtonSelect,
|
||||
};
|
||||
|
||||
export { LegacyForms };
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
ValueMappingFieldConfigSettings,
|
||||
valueMappingsOverrideProcessor,
|
||||
} from '@grafana/data';
|
||||
import { NumberValueEditor, Forms, StringValueEditor } from '../components';
|
||||
import { NumberValueEditor, Forms, StringValueEditor, Select } from '../components';
|
||||
import { ValueMappingsValueEditor } from '../components/OptionsUI/mappings';
|
||||
import { ThresholdsValueEditor } from '../components/OptionsUI/thresholds';
|
||||
import { UnitValueEditor } from '../components/OptionsUI/units';
|
||||
@ -216,11 +216,7 @@ export const getStandardOptionEditors = () => {
|
||||
name: 'Select',
|
||||
description: 'Allows option selection',
|
||||
editor: props => (
|
||||
<Forms.Select
|
||||
value={props.value}
|
||||
onChange={e => props.onChange(e.value)}
|
||||
options={props.item.settings?.options}
|
||||
/>
|
||||
<Select value={props.value} onChange={e => props.onChange(e.value)} options={props.item.settings?.options} />
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
||||
import { TeamPicker, Team } from 'app/core/components/Select/TeamPicker';
|
||||
import { Select } from '@grafana/ui';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { User } from 'app/types';
|
||||
import {
|
||||
@ -12,6 +12,7 @@ import {
|
||||
NewDashboardAclItem,
|
||||
OrgRole,
|
||||
} from 'app/types/acl';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
export interface Props {
|
||||
onAddPermission: (item: NewDashboardAclItem) => void;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Select } from '@grafana/ui';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
import { dashboardPermissionLevels } from 'app/types/acl';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
export interface Props {
|
||||
item: any;
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Select } from '@grafana/ui';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl';
|
||||
import { FolderInfo } from 'app/types';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
const setClassNameHelper = (inherited: boolean) => {
|
||||
return inherited ? 'gf-form-disabled' : '';
|
||||
|
@ -2,7 +2,7 @@ import React, { FC } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Forms } from '@grafana/ui';
|
||||
import { AsyncSelect } from '@grafana/ui';
|
||||
import { FormInputSize } from '@grafana/ui/src/components/Forms/types';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { DashboardSearchHit, DashboardDTO } from 'app/types';
|
||||
@ -42,7 +42,7 @@ export const DashboardPicker: FC<Props> = ({
|
||||
const [state, searchDashboards] = useAsyncFn(debouncedSearch, []);
|
||||
|
||||
return (
|
||||
<Forms.AsyncSelect
|
||||
<AsyncSelect
|
||||
size={size}
|
||||
isLoading={state.loading}
|
||||
isClearable={isClearable}
|
||||
|
@ -2,8 +2,9 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { Select } from '@grafana/ui';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
import { SelectableValue, DataSourceSelectItem } from '@grafana/data';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
export interface Props {
|
||||
onChange: (ds: DataSourceSelectItem) => void;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Forms } from '@grafana/ui';
|
||||
import { AsyncSelect } from '@grafana/ui';
|
||||
import { AppEvents, SelectableValue } from '@grafana/data';
|
||||
import { debounce } from 'lodash';
|
||||
import appEvents from '../../app_events';
|
||||
@ -150,7 +150,7 @@ export class FolderPicker extends PureComponent<Props, State> {
|
||||
return (
|
||||
<>
|
||||
{useNewForms && (
|
||||
<Forms.AsyncSelect
|
||||
<AsyncSelect
|
||||
loadingMessage="Loading folders..."
|
||||
defaultOptions
|
||||
defaultValue={folder}
|
||||
@ -167,7 +167,7 @@ export class FolderPicker extends PureComponent<Props, State> {
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-label width-7">Folder</label>
|
||||
<Forms.AsyncSelect
|
||||
<AsyncSelect
|
||||
loadingMessage="Loading folders..."
|
||||
defaultOptions
|
||||
defaultValue={folder}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Select } from '@grafana/ui';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Variable } from 'app/types/templates';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
export interface Props {
|
||||
onChange: (value: string) => void;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Forms } from '@grafana/ui';
|
||||
import { AsyncSelect } from '@grafana/ui';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { Organization } from 'app/types';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
@ -54,7 +54,7 @@ export class OrgPicker extends PureComponent<Props, State> {
|
||||
const { isLoading } = this.state;
|
||||
|
||||
return (
|
||||
<Forms.AsyncSelect
|
||||
<AsyncSelect
|
||||
className={className}
|
||||
isLoading={isLoading}
|
||||
defaultOptions={true}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { AsyncSelect } from '@grafana/ui';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
import { debounce } from 'lodash';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
const { AsyncSelect } = LegacyForms;
|
||||
|
||||
export interface Team {
|
||||
id: number;
|
||||
|
@ -3,7 +3,8 @@ import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
// Components
|
||||
import { AsyncSelect } from '@grafana/ui';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
const { AsyncSelect } = LegacyForms;
|
||||
|
||||
// Utils & Services
|
||||
import { debounce } from 'lodash';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { FormLabel, Select } from '@grafana/ui';
|
||||
import { FormLabel, LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
import { DashboardSearchHit, DashboardSearchHitType } from 'app/types';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
@ -8,7 +8,8 @@ import { escapeStringForRegex } from '@grafana/data';
|
||||
// Components
|
||||
import { TagOption } from './TagOption';
|
||||
import { TagBadge } from './TagBadge';
|
||||
import { IndicatorsContainer, NoOptionsMessage, resetSelectStyles } from '@grafana/ui';
|
||||
import { resetSelectStyles, LegacyForms } from '@grafana/ui';
|
||||
const { IndicatorsContainer, NoOptionsMessage } = LegacyForms;
|
||||
|
||||
export interface TermCount {
|
||||
term: string;
|
||||
|
@ -6,7 +6,17 @@ import { css } from 'emotion';
|
||||
import { InspectHeader } from './InspectHeader';
|
||||
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { JSONFormatter, Drawer, Select, Table, TabContent, stylesFactory, CustomScrollbar, Button } from '@grafana/ui';
|
||||
import {
|
||||
JSONFormatter,
|
||||
Drawer,
|
||||
LegacyForms,
|
||||
Table,
|
||||
TabContent,
|
||||
stylesFactory,
|
||||
CustomScrollbar,
|
||||
Button,
|
||||
} from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime';
|
||||
import {
|
||||
DataFrame,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { PanelModel } from '../../state';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { DataLinksInlineEditor, Forms } from '@grafana/ui';
|
||||
import { Forms, Select, DataLinksInlineEditor } from '@grafana/ui';
|
||||
import { OptionsGroup } from './OptionsGroup';
|
||||
import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
||||
import { getVariables } from '../../../variables/state/selectors';
|
||||
@ -54,7 +54,7 @@ export const GeneralPanelOptions: FC<{
|
||||
This is not visible while in edit mode. You need to go back to dashboard and then update the variable or
|
||||
reload the dashboard."
|
||||
>
|
||||
<Forms.Select
|
||||
<Select
|
||||
value={panel.repeat}
|
||||
onChange={value => onPanelConfigChange('repeat', value.value)}
|
||||
options={variableOptions}
|
||||
@ -72,7 +72,7 @@ export const GeneralPanelOptions: FC<{
|
||||
|
||||
{panel.repeat && panel.repeatDirection === 'h' && (
|
||||
<Forms.Field label="Max per row">
|
||||
<Forms.Select
|
||||
<Select
|
||||
options={maxPerRowOptions}
|
||||
value={panel.maxPerRow}
|
||||
onChange={value => onPanelConfigChange('maxPerRow', value.value)}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin, SelectableValue } from '@grafana/data';
|
||||
import { Forms, stylesFactory, Icon } from '@grafana/ui';
|
||||
import { Select, stylesFactory, Icon } from '@grafana/ui';
|
||||
import { css, cx } from 'emotion';
|
||||
import config from 'app/core/config';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
@ -205,7 +205,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
</DashNavButton>
|
||||
</div>
|
||||
<div className={styles.toolbarItem}>
|
||||
<Forms.Select
|
||||
<Select
|
||||
value={displayModes.find(v => v.value === uiState.mode)}
|
||||
options={displayModes}
|
||||
onChange={this.onDiplayModeChange}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Switch, Select } from '@grafana/ui';
|
||||
import { Switch, LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { buildIframeHtml } from './utils';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
import { Switch, Select, ClipboardButton } from '@grafana/ui';
|
||||
import { Switch, LegacyForms, ClipboardButton } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { SelectableValue, PanelModel, AppEvents } from '@grafana/data';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { buildImageUrl, buildShareUrl } from './utils';
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Button, ClipboardButton, Input, LinkButton, Select } from '@grafana/ui';
|
||||
import { Button, ClipboardButton, Input, LinkButton, LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { AppEvents, SelectableValue } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Select, ThemeContext } from '@grafana/ui';
|
||||
import { LegacyForms, ThemeContext } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { css, cx } from 'emotion';
|
||||
import { GrafanaTheme, SelectableValue } from '@grafana/data';
|
||||
|
||||
|
@ -7,7 +7,8 @@ import classNames from 'classnames';
|
||||
import { css } from 'emotion';
|
||||
|
||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||
import { ToggleButtonGroup, ToggleButton, Tooltip, ButtonSelect, SetInterval } from '@grafana/ui';
|
||||
import { ToggleButtonGroup, ToggleButton, Tooltip, LegacyForms, SetInterval } from '@grafana/ui';
|
||||
const { ButtonSelect } = LegacyForms;
|
||||
import { RawTimeRange, TimeZone, TimeRange, DataQuery, ExploreMode } from '@grafana/data';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { StoreState } from 'app/types/store';
|
||||
|
@ -21,7 +21,8 @@ import {
|
||||
// Components
|
||||
import RichHistoryCard from './RichHistoryCard';
|
||||
import { sortOrderOptions } from './RichHistory';
|
||||
import { Select, Slider } from '@grafana/ui';
|
||||
import { LegacyForms, Slider } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
export interface Props {
|
||||
queries: RichHistoryQuery[];
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { RichHistorySettings, RichHistorySettingsProps } from './RichHistorySettings';
|
||||
import { Forms } from '@grafana/ui';
|
||||
import { Forms, Select } from '@grafana/ui';
|
||||
|
||||
const setup = (propOverrides?: Partial<RichHistorySettingsProps>) => {
|
||||
const props: RichHistorySettingsProps = {
|
||||
@ -23,7 +23,7 @@ const setup = (propOverrides?: Partial<RichHistorySettingsProps>) => {
|
||||
describe('RichHistorySettings', () => {
|
||||
it('should render component with correct retention period', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(Forms.Select).text()).toEqual('2 weeks');
|
||||
expect(wrapper.find(Select).text()).toEqual('2 weeks');
|
||||
});
|
||||
it('should render component with correctly checked starredTabAsFirstTab settings', () => {
|
||||
const wrapper = setup();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { stylesFactory, useTheme, Forms, Button } from '@grafana/ui';
|
||||
import { stylesFactory, useTheme, Forms, Select, Button } from '@grafana/ui';
|
||||
import { GrafanaTheme, AppEvents } from '@grafana/data';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { CoreEvents } from 'app/types';
|
||||
@ -79,11 +79,7 @@ export function RichHistorySettings(props: RichHistorySettingsProps) {
|
||||
className="space-between"
|
||||
>
|
||||
<div className={styles.input}>
|
||||
<Forms.Select
|
||||
value={selectedOption}
|
||||
options={retentionPeriodOptions}
|
||||
onChange={onChangeRetentionPeriod}
|
||||
></Forms.Select>
|
||||
<Select value={selectedOption} options={retentionPeriodOptions} onChange={onChangeRetentionPeriod}></Select>
|
||||
</div>
|
||||
</Forms.Field>
|
||||
<Forms.Field label="Default active tab" description=" " className="space-between">
|
||||
|
@ -15,7 +15,8 @@ import { sortQueries, createDatasourcesList } from '../../../core/utils/richHist
|
||||
// Components
|
||||
import RichHistoryCard from './RichHistoryCard';
|
||||
import { sortOrderOptions } from './RichHistory';
|
||||
import { Select } from '@grafana/ui';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
export interface Props {
|
||||
queries: RichHistoryQuery[];
|
||||
|
@ -1,7 +1,8 @@
|
||||
// Libraries
|
||||
import React, { PureComponent, ChangeEvent } from 'react';
|
||||
|
||||
import { FormLabel, Select, FormField } from '@grafana/ui';
|
||||
import { FormLabel, LegacyForms, FormField } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { SelectableValue, ReducerID, QueryEditorProps } from '@grafana/data';
|
||||
|
||||
// Types
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Select, DeleteButton } from '@grafana/ui';
|
||||
import { LegacyForms, DeleteButton } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { TeamMember, teamsPermissionLevels, TeamPermissionLevel } from 'app/types';
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { FormLabel, Select, Input, Button } from '@grafana/ui';
|
||||
import { FormLabel, LegacyForms, Input, Button } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import {
|
||||
DataSourcePluginOptionsEditorProps,
|
||||
onUpdateDatasourceJsonDataOptionSelect,
|
||||
|
@ -2,7 +2,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Types
|
||||
import { Select } from '@grafana/ui';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
import { DataQuery, DataQueryError, PanelData, DataFrame, SelectableValue } from '@grafana/data';
|
||||
import { DashboardQuery } from './types';
|
||||
import config from 'app/core/config';
|
||||
@ -12,6 +12,7 @@ import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { SHARED_DASHBODARD_QUERY } from './types';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { filterPanelDataToQuery } from 'app/features/dashboard/panel_editor/QueryEditorRow';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
type ResultInfo = {
|
||||
img: string; // The Datasource
|
||||
|
@ -3,7 +3,8 @@ import { last } from 'lodash';
|
||||
import { mount } from 'enzyme';
|
||||
import { ElasticDetails } from './ElasticDetails';
|
||||
import { createDefaultConfigOptions } from './mocks';
|
||||
import { Select } from '@grafana/ui';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
describe('ElasticDetails', () => {
|
||||
it('should render without error', () => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { EventsWithValidation, FormField, Input, regexValidation, Select } from '@grafana/ui';
|
||||
import { EventsWithValidation, FormField, Input, regexValidation, LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { ElasticsearchOptions } from '../types';
|
||||
import { DataSourceSettings, SelectableValue } from '@grafana/data';
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { PureComponent, ChangeEvent } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { AzureCredentialsForm } from './AzureCredentialsForm';
|
||||
import { Switch, FormLabel, Select, Button } from '@grafana/ui';
|
||||
import { Switch, FormLabel, LegacyForms, Button } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { AzureDataSourceSettings } from '../types';
|
||||
|
||||
export interface State {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { ChangeEvent, PureComponent } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Input, FormLabel, Select, Button } from '@grafana/ui';
|
||||
import { Input, FormLabel, LegacyForms, Button } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
export interface Props {
|
||||
selectedAzureCloud?: string;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DataSourceHttpSettings, FormLabel, Select, Switch } from '@grafana/ui';
|
||||
import { DataSourceHttpSettings, FormLabel, LegacyForms, Switch } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import {
|
||||
DataSourcePluginOptionsEditorProps,
|
||||
onUpdateDatasourceJsonDataOptionSelect,
|
||||
|
@ -8,7 +8,8 @@ import {
|
||||
onUpdateDatasourceJsonDataOptionSelect,
|
||||
onUpdateDatasourceSecureJsonDataOption,
|
||||
} from '@grafana/data';
|
||||
import { DataSourceHttpSettings, FormLabel, Input, SecretFormField, Select } from '@grafana/ui';
|
||||
import { DataSourceHttpSettings, FormLabel, Input, SecretFormField, LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { InfluxOptions, InfluxSecureJsonData } from '../types';
|
||||
|
||||
const httpModes = [
|
||||
|
@ -5,7 +5,8 @@ import React, { PureComponent } from 'react';
|
||||
import { InputDatasource, describeDataFrame } from './InputDatasource';
|
||||
import { InputQuery, InputOptions } from './types';
|
||||
|
||||
import { FormLabel, Select, TableInputCSV } from '@grafana/ui';
|
||||
import { FormLabel, LegacyForms, TableInputCSV } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { DataFrame, toCSV, SelectableValue, MutableDataFrame, QueryEditorProps } from '@grafana/data';
|
||||
|
||||
import { dataFrameToCSV } from './utils';
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { SyntheticEvent } from 'react';
|
||||
import { FormLabel, Select, Input } from '@grafana/ui';
|
||||
import { FormLabel, LegacyForms, Input } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { DataSourceSettings, SelectableValue } from '@grafana/data';
|
||||
import { OpenTsdbOptions } from '../types';
|
||||
|
||||
|
@ -2,9 +2,11 @@ import _ from 'lodash';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Types
|
||||
import { FormLabel, Select, Switch } from '@grafana/ui';
|
||||
import { FormLabel, LegacyForms, Switch } from '@grafana/ui';
|
||||
import { SelectableValue, QueryEditorProps } from '@grafana/data';
|
||||
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
import { PrometheusDatasource } from '../datasource';
|
||||
import { PromQuery, PromOptions } from '../types';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { SyntheticEvent } from 'react';
|
||||
import { EventsWithValidation, FormField, FormLabel, Input, regexValidation, Select } from '@grafana/ui';
|
||||
import { EventsWithValidation, FormField, FormLabel, Input, regexValidation, LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { DataSourceSettings, SelectableValue } from '@grafana/data';
|
||||
import { PromOptions } from '../types';
|
||||
|
||||
|
@ -6,7 +6,8 @@ import _ from 'lodash';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
// Components
|
||||
import { FormLabel, Select } from '@grafana/ui';
|
||||
import { FormLabel, LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { QueryEditorProps, SelectableValue } from '@grafana/data';
|
||||
|
||||
// Types
|
||||
|
@ -6,13 +6,14 @@ import {
|
||||
FieldDisplayEditor,
|
||||
PanelOptionsGroup,
|
||||
FormLabel,
|
||||
Select,
|
||||
LegacyForms,
|
||||
Switch,
|
||||
FieldPropertiesEditor,
|
||||
ThresholdsEditor,
|
||||
LegacyValueMappingsEditor,
|
||||
DataLinksEditor,
|
||||
} from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import {
|
||||
DataLink,
|
||||
FieldConfig,
|
||||
|
@ -10,9 +10,10 @@ import {
|
||||
GraphTooltipOptions,
|
||||
PanelOptionsGrid,
|
||||
PanelOptionsGroup,
|
||||
Select,
|
||||
LegacyForms,
|
||||
FieldPropertiesEditor,
|
||||
} from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { Options, GraphOptions } from './types';
|
||||
import { GraphLegendEditor } from './GraphLegendEditor';
|
||||
import { NewPanelEditorContext } from 'app/features/dashboard/components/PanelEditor/PanelEditor';
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Switch, PanelOptionsGrid, PanelOptionsGroup, FormLabel, Select } from '@grafana/ui';
|
||||
import { Switch, PanelOptionsGrid, PanelOptionsGroup, FormLabel, LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
// Types
|
||||
import { Options } from './types';
|
||||
|
@ -2,7 +2,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { Select, FormLabel, PanelOptionsGroup } from '@grafana/ui';
|
||||
import { LegacyForms, FormLabel, PanelOptionsGroup } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
// Types
|
||||
import { PanelEditorProps } from '@grafana/data';
|
||||
|
@ -6,13 +6,15 @@ import {
|
||||
FieldDisplayEditor,
|
||||
PanelOptionsGroup,
|
||||
FormLabel,
|
||||
Select,
|
||||
LegacyForms,
|
||||
FieldPropertiesEditor,
|
||||
ThresholdsEditor,
|
||||
LegacyValueMappingsEditor,
|
||||
DataLinksEditor,
|
||||
} from '@grafana/ui';
|
||||
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
import {
|
||||
PanelEditorProps,
|
||||
ReduceDataOptions,
|
||||
|
@ -2,7 +2,8 @@
|
||||
import React, { PureComponent, ChangeEvent } from 'react';
|
||||
|
||||
// Components
|
||||
import { PanelOptionsGroup, Select } from '@grafana/ui';
|
||||
import { PanelOptionsGroup, LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
import { PanelEditorProps, SelectableValue } from '@grafana/data';
|
||||
|
||||
// Types
|
||||
|
Loading…
Reference in New Issue
Block a user