Просмотр исходного кода

Merge pull request #14500 from grafana/select-refactor

Select refactor
Torkel Ödegaard 7 лет назад
Родитель
Сommit
cf1cd4d60f
44 измененных файлов с 449 добавлено и 528 удалено
  1. 2 2
      public/app/core/components/CustomScrollbar/CustomScrollbar.tsx
  2. 4 4
      public/app/core/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap
  3. 9 9
      public/app/core/components/PermissionList/AddPermission.tsx
  4. 8 7
      public/app/core/components/PermissionList/DisabledPermissionListItem.tsx
  5. 9 7
      public/app/core/components/PermissionList/PermissionListItem.tsx
  6. 0 26
      public/app/core/components/Picker/DescriptionOption.tsx
  7. 0 52
      public/app/core/components/Picker/DescriptionPicker.tsx
  8. 0 18
      public/app/core/components/Picker/NoOptionsMessage.tsx
  9. 0 60
      public/app/core/components/Picker/Select.tsx
  10. 0 52
      public/app/core/components/Picker/SimplePicker.tsx
  11. 0 22
      public/app/core/components/Picker/Unit/UnitOption.tsx
  12. 0 81
      public/app/core/components/Picker/Unit/UnitPicker.tsx
  13. 6 17
      public/app/core/components/Select/DataSourcePicker.tsx
  14. 0 0
      public/app/core/components/Select/IndicatorsContainer.tsx
  15. 20 0
      public/app/core/components/Select/NoOptionsMessage.tsx
  16. 5 5
      public/app/core/components/Select/OptionGroup.tsx
  17. 0 0
      public/app/core/components/Select/PickerOption.test.tsx
  18. 0 0
      public/app/core/components/Select/PickerOption.tsx
  19. 0 0
      public/app/core/components/Select/ResetStyles.tsx
  20. 232 0
      public/app/core/components/Select/Select.tsx
  21. 0 0
      public/app/core/components/Select/TeamPicker.test.tsx
  22. 3 17
      public/app/core/components/Select/TeamPicker.tsx
  23. 51 0
      public/app/core/components/Select/UnitPicker.tsx
  24. 0 0
      public/app/core/components/Select/UserPicker.test.tsx
  25. 10 17
      public/app/core/components/Select/UserPicker.tsx
  26. 0 0
      public/app/core/components/Select/__snapshots__/PickerOption.test.tsx.snap
  27. 0 0
      public/app/core/components/Select/__snapshots__/TeamPicker.test.tsx.snap
  28. 0 0
      public/app/core/components/Select/__snapshots__/UserPicker.test.tsx.snap
  29. 13 15
      public/app/core/components/SharedPreferences/SharedPreferences.tsx
  30. 3 3
      public/app/core/components/TagFilter/TagFilter.tsx
  31. 1 1
      public/app/core/components/code_editor/code_editor.ts
  32. 3 9
      public/app/features/dashboard/dashgrid/QueriesTab.tsx
  33. 8 26
      public/app/features/explore/Explore.tsx
  34. 2 2
      public/app/features/panel/panel_header.ts
  35. 1 1
      public/app/features/teams/TeamMembers.tsx
  36. 14 17
      public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts
  37. 4 5
      public/app/plugins/panel/gauge/MappingRow.tsx
  38. 16 18
      public/app/plugins/panel/gauge/ValueOptions.tsx
  39. 2 6
      public/app/types/explore.ts
  40. 0 1
      public/sass/_grafana.scss
  41. 19 2
      public/sass/components/_form_select_box.scss
  42. 2 0
      public/sass/components/_query_editor.scss
  43. 2 2
      public/sass/components/_switch.scss
  44. 0 24
      public/sass/components/_unit-picker.scss

+ 2 - 2
public/app/core/components/CustomScrollbar/CustomScrollbar.tsx

@@ -28,8 +28,8 @@ class CustomScrollbar extends PureComponent<Props> {
       <Scrollbars
         className={customClassName}
         autoHeight={true}
-        autoHeightMin={'100%'}
-        autoHeightMax={'100%'}
+        autoHeightMin={'inherit'}
+        autoHeightMax={'inherit'}
         renderTrackHorizontal={props => <div {...props} className="track-horizontal" />}
         renderTrackVertical={props => <div {...props} className="track-vertical" />}
         renderThumbHorizontal={props => <div {...props} className="thumb-horizontal" />}

+ 4 - 4
public/app/core/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap

@@ -6,8 +6,8 @@ exports[`CustomScrollbar renders correctly 1`] = `
   style={
     Object {
       "height": "auto",
-      "maxHeight": "100%",
-      "minHeight": "100%",
+      "maxHeight": "inherit",
+      "minHeight": "inherit",
       "overflow": "hidden",
       "position": "relative",
       "width": "100%",
@@ -23,8 +23,8 @@ exports[`CustomScrollbar renders correctly 1`] = `
         "left": undefined,
         "marginBottom": 0,
         "marginRight": 0,
-        "maxHeight": "calc(100% + 0px)",
-        "minHeight": "calc(100% + 0px)",
+        "maxHeight": "calc(inherit + 0px)",
+        "minHeight": "calc(inherit + 0px)",
         "overflow": "scroll",
         "position": "relative",
         "right": undefined,

+ 9 - 9
public/app/core/components/PermissionList/AddPermission.tsx

@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
-import { UserPicker } from 'app/core/components/Picker/UserPicker';
-import { TeamPicker, Team } from 'app/core/components/Picker/TeamPicker';
-import DescriptionPicker, { OptionWithDescription } from 'app/core/components/Picker/DescriptionPicker';
+import { UserPicker } from 'app/core/components/Select/UserPicker';
+import { TeamPicker, Team } from 'app/core/components/Select/TeamPicker';
+import { Select, SelectOptionItem } from 'app/core/components/Select/Select';
 import { User } from 'app/types';
 import {
   dashboardPermissionLevels,
@@ -61,7 +61,7 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
     this.setState({ teamId: team && !Array.isArray(team) ? team.id : 0 });
   };
 
-  onPermissionChanged = (permission: OptionWithDescription) => {
+  onPermissionChanged = (permission: SelectOptionItem) => {
     this.setState({ permission: permission.value });
   };
 
@@ -121,11 +121,11 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
             ) : null}
 
             <div className="gf-form">
-              <DescriptionPicker
-                optionsWithDesc={dashboardPermissionLevels}
-                onSelected={this.onPermissionChanged}
-                disabled={false}
-                className={'gf-form-select-box__control--menu-right'}
+              <Select
+                isSearchable={false}
+                options={dashboardPermissionLevels}
+                onChange={this.onPermissionChanged}
+                className="gf-form-select-box__control--menu-right"
               />
             </div>
 

+ 8 - 7
public/app/core/components/PermissionList/DisabledPermissionListItem.tsx

@@ -1,5 +1,5 @@
 import React, { Component } from 'react';
-import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker';
+import Select from 'app/core/components/Select/Select';
 import { dashboardPermissionLevels } from 'app/types/acl';
 
 export interface Props {
@@ -9,6 +9,7 @@ export interface Props {
 export default class DisabledPermissionListItem extends Component<Props, any> {
   render() {
     const { item } = this.props;
+    const currentPermissionLevel = dashboardPermissionLevels.find(dp => dp.value === item.permission);
 
     return (
       <tr className="gf-form-disabled">
@@ -23,12 +24,12 @@ export default class DisabledPermissionListItem extends Component<Props, any> {
         <td className="query-keyword">Can</td>
         <td>
           <div className="gf-form">
-            <DescriptionPicker
-              optionsWithDesc={dashboardPermissionLevels}
-              onSelected={() => {}}
-              disabled={true}
-              className={'gf-form-select-box__control--menu-right'}
-              value={item.permission}
+            <Select
+              options={dashboardPermissionLevels}
+              onChange={() => {}}
+              isDisabled={true}
+              className="gf-form-select-box__control--menu-right"
+              value={currentPermissionLevel}
             />
           </div>
         </td>

+ 9 - 7
public/app/core/components/PermissionList/PermissionListItem.tsx

@@ -1,5 +1,5 @@
 import React, { PureComponent } from 'react';
-import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker';
+import { Select } from 'app/core/components/Select/Select';
 import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl';
 import { FolderInfo } from 'app/types';
 
@@ -50,6 +50,7 @@ export default class PermissionsListItem extends PureComponent<Props> {
   render() {
     const { item, folderInfo } = this.props;
     const inheritedFromRoot = item.dashboardId === -1 && !item.inherited;
+    const currentPermissionLevel = dashboardPermissionLevels.find(dp => dp.value === item.permission);
 
     return (
       <tr className={setClassNameHelper(item.inherited)}>
@@ -74,12 +75,13 @@ export default class PermissionsListItem extends PureComponent<Props> {
         <td className="query-keyword">Can</td>
         <td>
           <div className="gf-form">
-            <DescriptionPicker
-              optionsWithDesc={dashboardPermissionLevels}
-              onSelected={this.onPermissionChanged}
-              disabled={item.inherited}
-              className={'gf-form-select-box__control--menu-right'}
-              value={item.permission}
+            <Select
+              isSearchable={false}
+              options={dashboardPermissionLevels}
+              onChange={this.onPermissionChanged}
+              isDisabled={item.inherited}
+              className="gf-form-select-box__control--menu-right"
+              value={currentPermissionLevel}
             />
           </div>
         </td>

+ 0 - 26
public/app/core/components/Picker/DescriptionOption.tsx

@@ -1,26 +0,0 @@
-import React from 'react';
-import { components } from 'react-select';
-import { OptionProps } from 'react-select/lib/components/Option';
-
-// https://github.com/JedWatson/react-select/issues/3038
-interface ExtendedOptionProps extends OptionProps<any> {
-  data: any;
-}
-
-export const Option = (props: ExtendedOptionProps) => {
-  const { children, isSelected, data } = props;
-
-  return (
-    <components.Option {...props}>
-      <div className="gf-form-select-box__desc-option">
-        <div className="gf-form-select-box__desc-option__body">
-          <div>{children}</div>
-          {data.description && <div className="gf-form-select-box__desc-option__desc">{data.description}</div>}
-        </div>
-        {isSelected && <i className="fa fa-check" aria-hidden="true" />}
-      </div>
-    </components.Option>
-  );
-};
-
-export default Option;

+ 0 - 52
public/app/core/components/Picker/DescriptionPicker.tsx

@@ -1,52 +0,0 @@
-import React, { Component } from 'react';
-import Select from 'react-select';
-import DescriptionOption from './DescriptionOption';
-import IndicatorsContainer from './IndicatorsContainer';
-import ResetStyles from './ResetStyles';
-import NoOptionsMessage from './NoOptionsMessage';
-
-export interface OptionWithDescription {
-  value: any;
-  label: string;
-  description: string;
-}
-
-export interface Props {
-  optionsWithDesc: OptionWithDescription[];
-  onSelected: (permission) => void;
-  disabled: boolean;
-  className?: string;
-  value?: any;
-}
-
-const getSelectedOption = (optionsWithDesc, value) => optionsWithDesc.find(option => option.value === value);
-
-class DescriptionPicker extends Component<Props, any> {
-  render() {
-    const { optionsWithDesc, onSelected, disabled, className, value } = this.props;
-    const selectedOption = getSelectedOption(optionsWithDesc, value);
-    return (
-      <div className="permissions-picker">
-        <Select
-          placeholder="Choose"
-          classNamePrefix={`gf-form-select-box`}
-          className={`width-7 gf-form-input gf-form-input--form-dropdown ${className || ''}`}
-          options={optionsWithDesc}
-          components={{
-            Option: DescriptionOption,
-            IndicatorsContainer,
-            NoOptionsMessage,
-          }}
-          styles={ResetStyles}
-          isDisabled={disabled}
-          onChange={onSelected}
-          getOptionValue={i => i.value}
-          getOptionLabel={i => i.label}
-          value={selectedOption}
-        />
-      </div>
-    );
-  }
-}
-
-export default DescriptionPicker;

+ 0 - 18
public/app/core/components/Picker/NoOptionsMessage.tsx

@@ -1,18 +0,0 @@
-import React from 'react';
-import { components } from 'react-select';
-import { OptionProps } from 'react-select/lib/components/Option';
-
-export interface Props {
-  children: Element;
-}
-
-export const PickerOption = (props: OptionProps<any>) => {
-  const { children, className } = props;
-  return (
-    <components.Option {...props}>
-      <div className={`description-picker-option__button btn btn-link ${className}`}>{children}</div>
-    </components.Option>
-  );
-};
-
-export default PickerOption;

+ 0 - 60
public/app/core/components/Picker/Select.tsx

@@ -1,60 +0,0 @@
-// import React, { PureComponent } from 'react';
-// import Select as ReactSelect from 'react-select';
-// import DescriptionOption from './DescriptionOption';
-// import IndicatorsContainer from './IndicatorsContainer';
-// import ResetStyles from './ResetStyles';
-//
-// export interface OptionType {
-//   label: string;
-//   value: string;
-// }
-//
-// interface Props {
-//   defaultValue?: any;
-//   getOptionLabel: (item: T) => string;
-//   getOptionValue: (item: T) => string;
-//   onChange: (item: T) => {} | void;
-//   options: T[];
-//   placeholder?: string;
-//   width?: number;
-//   value: T;
-//   className?: string;
-// }
-//
-// export class Select<T> extends PureComponent<Props<T>> {
-//   static defaultProps = {
-//     width: null,
-//     className: '',
-//   }
-//
-//   render() {
-//     const { defaultValue, getOptionLabel, getOptionValue, onSelected, options, placeholder, width, value, className } = this.props;
-//     let widthClass = '';
-//     if (width) {
-//       widthClass = 'width-'+width;
-//     }
-//
-//   return (
-//     <ReactSelect
-//       classNamePrefix="gf-form-select-box"
-//       className={`gf-form-input gf-form-input--form-dropdown ${widthClass} ${className}`}
-//       components={{
-//         Option: DescriptionOption,
-//         IndicatorsContainer,
-//       }}
-//       defaultValue={defaultValue}
-//       value={value}
-//       getOptionLabel={getOptionLabel}
-//       getOptionValue={getOptionValue}
-//       menuShouldScrollIntoView={false}
-//       isSearchable={false}
-//       onChange={onSelected}
-//       options={options}
-//       placeholder={placeholder || 'Choose'}
-//       styles={ResetStyles}
-//     />
-//   );
-// }
-// }
-//
-// export default Select;

+ 0 - 52
public/app/core/components/Picker/SimplePicker.tsx

@@ -1,52 +0,0 @@
-import React, { SFC } from 'react';
-import Select from 'react-select';
-import DescriptionOption from './DescriptionOption';
-import IndicatorsContainer from './IndicatorsContainer';
-import ResetStyles from './ResetStyles';
-
-interface Props {
-  className?: string;
-  defaultValue?: any;
-  getOptionLabel: (item: any) => string;
-  getOptionValue: (item: any) => string;
-  onSelected: (item: any) => {} | void;
-  options: any[];
-  placeholder?: string;
-  width?: number;
-  value: any;
-}
-
-const SimplePicker: SFC<Props> = ({
-  className,
-  defaultValue,
-  getOptionLabel,
-  getOptionValue,
-  onSelected,
-  options,
-  placeholder,
-  width,
-  value,
-}) => {
-  return (
-    <Select
-      classNamePrefix="gf-form-select-box"
-      className={`${width ? 'width-' + width : ''} gf-form-input gf-form-input--form-dropdown ${className || ''}`}
-      components={{
-        Option: DescriptionOption,
-        IndicatorsContainer,
-      }}
-      defaultValue={defaultValue}
-      value={value}
-      getOptionLabel={getOptionLabel}
-      getOptionValue={getOptionValue}
-      menuShouldScrollIntoView={false}
-      isSearchable={false}
-      onChange={onSelected}
-      options={options}
-      placeholder={placeholder || 'Choose'}
-      styles={ResetStyles}
-    />
-  );
-};
-
-export default SimplePicker;

+ 0 - 22
public/app/core/components/Picker/Unit/UnitOption.tsx

@@ -1,22 +0,0 @@
-import React, { SFC } from 'react';
-import { components } from 'react-select';
-import { OptionProps } from 'react-select/lib/components/Option';
-
-interface ExtendedOptionProps extends OptionProps<any> {
-  data: any;
-}
-
-const UnitOption: SFC<ExtendedOptionProps> = props => {
-  const { children, isSelected, className } = props;
-
-  return (
-    <components.Option {...props}>
-      <div className={`unit-picker-option__button btn btn-link ${className}`}>
-        {isSelected && <i className="fa fa-check pull-right" aria-hidden="true" />}
-        <div className="gf-form">{children}</div>
-      </div>
-    </components.Option>
-  );
-};
-
-export default UnitOption;

+ 0 - 81
public/app/core/components/Picker/Unit/UnitPicker.tsx

@@ -1,81 +0,0 @@
-import React, { PureComponent } from 'react';
-import Select from 'react-select';
-import UnitGroup from './UnitGroup';
-import UnitOption from './UnitOption';
-import ResetStyles from '../ResetStyles';
-import kbn from '../../../utils/kbn';
-
-interface Props {
-  onSelected: (item: any) => {} | void;
-  defaultValue?: string;
-  width?: number;
-}
-
-export default class UnitPicker extends PureComponent<Props> {
-  static defaultProps = {
-    width: 12,
-  };
-
-  render() {
-    const { defaultValue, onSelected, width } = this.props;
-
-    const unitGroups = kbn.getUnitFormats();
-
-    // Need to transform the data structure to work well with Select
-    const groupOptions = unitGroups.map(group => {
-      const options = group.submenu.map(unit => {
-        return {
-          label: unit.text,
-          value: unit.value,
-        };
-      });
-
-      return {
-        label: group.text,
-        options,
-      };
-    });
-
-    // const styles = {
-    //   ...ResetStyles,
-    //   menu: () => ({
-    //     maxHeight: '75%',
-    //     overflow: 'scroll',
-    //   }),
-    //   menuList: () =>
-    //     ({
-    //       overflowY: 'auto',
-    //       position: 'relative',
-    //     } as React.CSSProperties),
-    //   valueContainer: () =>
-    //     ({
-    //       overflow: 'hidden',
-    //       textOverflow: 'ellipsis',
-    //       maxWidth: '90px',
-    //       whiteSpace: 'nowrap',
-    //     } as React.CSSProperties),
-    // };
-
-    const value = groupOptions.map(group => {
-      return group.options.find(option => option.value === defaultValue);
-    });
-
-    return (
-      <Select
-        classNamePrefix="gf-form-select-box"
-        className={`width-${width} gf-form-input gf-form-input--form-dropdown`}
-        defaultValue={value}
-        isSearchable={true}
-        menuShouldScrollIntoView={false}
-        options={groupOptions}
-        placeholder="Choose"
-        onChange={onSelected}
-        components={{
-          Group: UnitGroup,
-          Option: UnitOption,
-        }}
-        styles={ResetStyles}
-      />
-    );
-  }
-}

+ 6 - 17
public/app/features/dashboard/dashgrid/DataSourcePicker.tsx → public/app/core/components/Select/DataSourcePicker.tsx

@@ -3,16 +3,13 @@ import React, { PureComponent } from 'react';
 import _ from 'lodash';
 
 // Components
-import ResetStyles from 'app/core/components/Picker/ResetStyles';
-import { Option, SingleValue } from 'app/core/components/Picker/PickerOption';
-import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer';
-import Select from 'react-select';
+import Select from './Select';
 
 // Types
 import { DataSourceSelectItem } from 'app/types';
 
 export interface Props {
-  onChangeDataSource: (ds: DataSourceSelectItem) => void;
+  onChange: (ds: DataSourceSelectItem) => void;
   datasources: DataSourceSelectItem[];
   current: DataSourceSelectItem;
   onBlur?: () => void;
@@ -32,7 +29,7 @@ export class DataSourcePicker extends PureComponent<Props> {
 
   onChange = item => {
     const ds = this.props.datasources.find(ds => ds.name === item.value);
-    this.props.onChangeDataSource(ds);
+    this.props.onChange(ds);
   };
 
   render() {
@@ -53,27 +50,19 @@ export class DataSourcePicker extends PureComponent<Props> {
     return (
       <div className="gf-form-inline">
         <Select
-          classNamePrefix={`gf-form-select-box`}
+          className="ds-picker"
           isMulti={false}
-          menuShouldScrollIntoView={false}
           isClearable={false}
-          className="gf-form-input gf-form-input--form-dropdown ds-picker"
-          onChange={item => this.onChange(item)}
+          backspaceRemovesValue={false}
+          onChange={this.onChange}
           options={options}
-          styles={ResetStyles}
           autoFocus={autoFocus}
           onBlur={onBlur}
           openMenuOnFocus={true}
           maxMenuHeight={500}
           placeholder="Select datasource"
-          loadingMessage={() => 'Loading datasources...'}
           noOptionsMessage={() => 'No datasources found'}
           value={value}
-          components={{
-            Option,
-            SingleValue,
-            IndicatorsContainer,
-          }}
         />
       </div>
     );

+ 0 - 0
public/app/core/components/Picker/IndicatorsContainer.tsx → public/app/core/components/Select/IndicatorsContainer.tsx


+ 20 - 0
public/app/core/components/Select/NoOptionsMessage.tsx

@@ -0,0 +1,20 @@
+import React from 'react';
+import { components } from 'react-select';
+import { OptionProps } from 'react-select/lib/components/Option';
+
+export interface Props {
+  children: Element;
+}
+
+export const NoOptionsMessage = (props: OptionProps<any>) => {
+  const { children } = props;
+  return (
+    <components.Option {...props}>
+      <div className="gf-form-select-box__desc-option">
+        <div className="gf-form-select-box__desc-option__body">{children}</div>
+      </div>
+    </components.Option>
+  );
+};
+
+export default NoOptionsMessage;

+ 5 - 5
public/app/core/components/Picker/Unit/UnitGroup.tsx → public/app/core/components/Select/OptionGroup.tsx

@@ -9,7 +9,7 @@ interface State {
   expanded: boolean;
 }
 
-export default class UnitGroup extends PureComponent<ExtendedGroupProps, State> {
+export default class OptionGroup extends PureComponent<ExtendedGroupProps, State> {
   state = {
     expanded: false,
   };
@@ -41,10 +41,10 @@ export default class UnitGroup extends PureComponent<ExtendedGroupProps, State>
     const { expanded } = this.state;
 
     return (
-      <div className="width-21 unit-picker-group" style={{ marginBottom: '5px' }}>
-        <div className="unit-picker-group-item" onClick={this.onToggleChildren}>
-          <span style={{ textTransform: 'capitalize' }}>{label}</span>
-          <i className={`fa ${expanded ? 'fa-minus' : 'fa-plus'}`} />{' '}
+      <div className="gf-form-select-box__option-group">
+        <div className="gf-form-select-box__option-group__header" onClick={this.onToggleChildren}>
+          <span className="flex-grow">{label}</span>
+          <i className={`fa ${expanded ? 'fa-caret-left' : 'fa-caret-down'}`} />{' '}
         </div>
         {expanded && children}
       </div>

+ 0 - 0
public/app/core/components/Picker/PickerOption.test.tsx → public/app/core/components/Select/PickerOption.test.tsx


+ 0 - 0
public/app/core/components/Picker/PickerOption.tsx → public/app/core/components/Select/PickerOption.tsx


+ 0 - 0
public/app/core/components/Picker/ResetStyles.tsx → public/app/core/components/Select/ResetStyles.tsx


+ 232 - 0
public/app/core/components/Select/Select.tsx

@@ -0,0 +1,232 @@
+// Libraries
+import classNames from 'classnames';
+import React, { PureComponent } from 'react';
+import { default as ReactSelect } from 'react-select';
+import { default as ReactAsyncSelect } from 'react-select/lib/Async';
+import { components } from 'react-select';
+
+// Components
+import { Option, SingleValue } from './PickerOption';
+import OptionGroup from './OptionGroup';
+import IndicatorsContainer from './IndicatorsContainer';
+import NoOptionsMessage from './NoOptionsMessage';
+import ResetStyles from './ResetStyles';
+import CustomScrollbar from '../CustomScrollbar/CustomScrollbar';
+
+export interface SelectOptionItem {
+  label?: string;
+  value?: any;
+  imgUrl?: string;
+  description?: string;
+  [key: string]: any;
+}
+
+interface CommonProps {
+  defaultValue?: any;
+  getOptionLabel?: (item: SelectOptionItem) => string;
+  getOptionValue?: (item: SelectOptionItem) => string;
+  onChange: (item: SelectOptionItem) => {} | void;
+  placeholder?: string;
+  width?: number;
+  value?: SelectOptionItem;
+  className?: string;
+  isDisabled?: boolean;
+  isSearchable?: boolean;
+  isClearable?: boolean;
+  autoFocus?: boolean;
+  openMenuOnFocus?: boolean;
+  onBlur?: () => void;
+  maxMenuHeight?: number;
+  isLoading: boolean;
+  noOptionsMessage?: () => string;
+  isMulti?: boolean;
+  backspaceRemovesValue: boolean;
+}
+
+interface SelectProps {
+  options: SelectOptionItem[];
+}
+
+interface AsyncProps {
+  defaultOptions: boolean;
+  loadOptions: (query: string) => Promise<SelectOptionItem[]>;
+  loadingMessage?: () => string;
+}
+
+export const MenuList = props => {
+  return (
+    <components.MenuList {...props}>
+      <CustomScrollbar autoHide={false}>{props.children}</CustomScrollbar>
+    </components.MenuList>
+  );
+};
+
+export class Select extends PureComponent<CommonProps & SelectProps> {
+  static defaultProps = {
+    width: null,
+    className: '',
+    isDisabled: false,
+    isSearchable: true,
+    isClearable: false,
+    isMulti: false,
+    openMenuOnFocus: false,
+    autoFocus: false,
+    isLoading: false,
+    backspaceRemovesValue: true,
+    maxMenuHeight: 300,
+  };
+
+  render() {
+    const {
+      defaultValue,
+      getOptionLabel,
+      getOptionValue,
+      onChange,
+      options,
+      placeholder,
+      width,
+      value,
+      className,
+      isDisabled,
+      isLoading,
+      isSearchable,
+      isClearable,
+      backspaceRemovesValue,
+      isMulti,
+      autoFocus,
+      openMenuOnFocus,
+      onBlur,
+      maxMenuHeight,
+      noOptionsMessage,
+    } = this.props;
+
+    let widthClass = '';
+    if (width) {
+      widthClass = 'width-' + width;
+    }
+
+    const selectClassNames = classNames('gf-form-input', 'gf-form-input--form-dropdown', widthClass, className);
+
+    return (
+      <ReactSelect
+        classNamePrefix="gf-form-select-box"
+        className={selectClassNames}
+        components={{
+          Option,
+          SingleValue,
+          IndicatorsContainer,
+          MenuList,
+          Group: OptionGroup,
+        }}
+        defaultValue={defaultValue}
+        value={value}
+        getOptionLabel={getOptionLabel}
+        getOptionValue={getOptionValue}
+        menuShouldScrollIntoView={false}
+        isSearchable={isSearchable}
+        onChange={onChange}
+        options={options}
+        placeholder={placeholder || 'Choose'}
+        styles={ResetStyles}
+        isDisabled={isDisabled}
+        isLoading={isLoading}
+        isClearable={isClearable}
+        autoFocus={autoFocus}
+        onBlur={onBlur}
+        openMenuOnFocus={openMenuOnFocus}
+        maxMenuHeight={maxMenuHeight}
+        noOptionsMessage={noOptionsMessage}
+        isMulti={isMulti}
+        backspaceRemovesValue={backspaceRemovesValue}
+      />
+    );
+  }
+}
+
+export class AsyncSelect extends PureComponent<CommonProps & AsyncProps> {
+  static defaultProps = {
+    width: null,
+    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,
+    } = this.props;
+
+    let widthClass = '';
+    if (width) {
+      widthClass = 'width-' + width;
+    }
+
+    const selectClassNames = classNames('gf-form-input', 'gf-form-input--form-dropdown', widthClass, className);
+
+    return (
+      <ReactAsyncSelect
+        classNamePrefix="gf-form-select-box"
+        className={selectClassNames}
+        components={{
+          Option,
+          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={ResetStyles}
+        loadingMessage={loadingMessage}
+        noOptionsMessage={noOptionsMessage}
+        isDisabled={isDisabled}
+        isSearchable={isSearchable}
+        isClearable={isClearable}
+        autoFocus={autoFocus}
+        onBlur={onBlur}
+        openMenuOnFocus={openMenuOnFocus}
+        maxMenuHeight={maxMenuHeight}
+        isMulti={isMulti}
+        backspaceRemovesValue={backspaceRemovesValue}
+      />
+    );
+  }
+}
+
+export default Select;

+ 0 - 0
public/app/core/components/Picker/TeamPicker.test.tsx → public/app/core/components/Select/TeamPicker.test.tsx


+ 3 - 17
public/app/core/components/Picker/TeamPicker.tsx → public/app/core/components/Select/TeamPicker.tsx

@@ -1,11 +1,7 @@
 import React, { Component } from 'react';
-import AsyncSelect from 'react-select/lib/Async';
-import PickerOption from './PickerOption';
+import { AsyncSelect } from './Select';
 import { debounce } from 'lodash';
 import { getBackendSrv } from 'app/core/services/backend_srv';
-import ResetStyles from './ResetStyles';
-import IndicatorsContainer from './IndicatorsContainer';
-import NoOptionsMessage from './NoOptionsMessage';
 
 export interface Team {
   id: number;
@@ -45,6 +41,7 @@ export class TeamPicker extends Component<Props, State> {
       const teams = result.teams.map(team => {
         return {
           id: team.id,
+          value: team.id,
           label: team.name,
           name: team.name,
           imgUrl: team.avatarUrl,
@@ -62,24 +59,13 @@ export class TeamPicker extends Component<Props, State> {
     return (
       <div className="user-picker">
         <AsyncSelect
-          classNamePrefix={`gf-form-select-box`}
-          isMulti={false}
           isLoading={isLoading}
           defaultOptions={true}
           loadOptions={this.debouncedSearch}
           onChange={onSelected}
-          className={`gf-form-input gf-form-input--form-dropdown ${className || ''}`}
-          styles={ResetStyles}
-          components={{
-            Option: PickerOption,
-            IndicatorsContainer,
-            NoOptionsMessage,
-          }}
+          className={className}
           placeholder="Select a team"
-          loadingMessage={() => 'Loading...'}
           noOptionsMessage={() => 'No teams found'}
-          getOptionValue={i => i.id}
-          getOptionLabel={i => i.label}
         />
       </div>
     );

+ 51 - 0
public/app/core/components/Select/UnitPicker.tsx

@@ -0,0 +1,51 @@
+import React, { PureComponent } from 'react';
+import Select from './Select';
+import kbn from 'app/core/utils/kbn';
+
+interface Props {
+  onChange: (item: any) => {} | void;
+  defaultValue?: string;
+  width?: number;
+}
+
+export default class UnitPicker extends PureComponent<Props> {
+  static defaultProps = {
+    width: 12,
+  };
+
+  render() {
+    const { defaultValue, onChange, width } = this.props;
+
+    const unitGroups = kbn.getUnitFormats();
+
+    // Need to transform the data structure to work well with Select
+    const groupOptions = unitGroups.map(group => {
+      const options = group.submenu.map(unit => {
+        return {
+          label: unit.text,
+          value: unit.value,
+        };
+      });
+
+      return {
+        label: group.text,
+        options,
+      };
+    });
+
+    const value = groupOptions.map(group => {
+      return group.options.find(option => option.value === defaultValue);
+    });
+
+    return (
+      <Select
+        width={width}
+        defaultValue={value}
+        isSearchable={true}
+        options={groupOptions}
+        placeholder="Choose"
+        onChange={onChange}
+      />
+    );
+  }
+}

+ 0 - 0
public/app/core/components/Picker/UserPicker.test.tsx → public/app/core/components/Select/UserPicker.test.tsx


+ 10 - 17
public/app/core/components/Picker/UserPicker.tsx → public/app/core/components/Select/UserPicker.tsx

@@ -1,12 +1,15 @@
+// Libraries
 import React, { Component } from 'react';
-import AsyncSelect from 'react-select/lib/Async';
-import PickerOption from './PickerOption';
+
+// Components
+import { AsyncSelect } from './Select';
+
+// Utils & Services
 import { debounce } from 'lodash';
 import { getBackendSrv } from 'app/core/services/backend_srv';
+
+// Types
 import { User } from 'app/types';
-import ResetStyles from './ResetStyles';
-import IndicatorsContainer from './IndicatorsContainer';
-import NoOptionsMessage from './NoOptionsMessage';
 
 export interface Props {
   onSelected: (user: User) => void;
@@ -40,6 +43,7 @@ export class UserPicker extends Component<Props, State> {
       .then(result => {
         return result.map(user => ({
           id: user.userId,
+          value: user.userId,
           label: user.login === user.email ? user.login : `${user.login} - ${user.email}`,
           imgUrl: user.avatarUrl,
           login: user.login,
@@ -57,24 +61,13 @@ export class UserPicker extends Component<Props, State> {
     return (
       <div className="user-picker">
         <AsyncSelect
-          classNamePrefix={`gf-form-select-box`}
-          isMulti={false}
+          className={className}
           isLoading={isLoading}
           defaultOptions={true}
           loadOptions={this.debouncedSearch}
           onChange={onSelected}
-          className={`gf-form-input gf-form-input--form-dropdown ${className || ''}`}
-          styles={ResetStyles}
-          components={{
-            Option: PickerOption,
-            IndicatorsContainer,
-            NoOptionsMessage,
-          }}
           placeholder="Select user"
-          loadingMessage={() => 'Loading...'}
           noOptionsMessage={() => 'No users found'}
-          getOptionValue={i => i.id}
-          getOptionLabel={i => i.label}
         />
       </div>
     );

+ 0 - 0
public/app/core/components/Picker/__snapshots__/PickerOption.test.tsx.snap → public/app/core/components/Select/__snapshots__/PickerOption.test.tsx.snap


+ 0 - 0
public/app/core/components/Picker/__snapshots__/TeamPicker.test.tsx.snap → public/app/core/components/Select/__snapshots__/TeamPicker.test.tsx.snap


+ 0 - 0
public/app/core/components/Picker/__snapshots__/UserPicker.test.tsx.snap → public/app/core/components/Select/__snapshots__/UserPicker.test.tsx.snap


+ 13 - 15
public/app/core/components/SharedPreferences/SharedPreferences.tsx

@@ -1,7 +1,7 @@
 import React, { PureComponent } from 'react';
 
 import { Label } from 'app/core/components/Label/Label';
-import SimplePicker from 'app/core/components/Picker/SimplePicker';
+import Select from 'app/core/components/Select/Select';
 import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
 
 import { DashboardSearchHit } from 'app/types';
@@ -17,12 +17,12 @@ export interface State {
   dashboards: DashboardSearchHit[];
 }
 
-const themes = [{ value: '', text: 'Default' }, { value: 'dark', text: 'Dark' }, { value: 'light', text: 'Light' }];
+const themes = [{ value: '', label: 'Default' }, { value: 'dark', label: 'Dark' }, { value: 'light', label: 'Light' }];
 
 const timezones = [
-  { value: '', text: 'Default' },
-  { value: 'browser', text: 'Local browser time' },
-  { value: 'utc', text: 'UTC' },
+  { value: '', label: 'Default' },
+  { value: 'browser', label: 'Local browser time' },
+  { value: 'utc', label: 'UTC' },
 ];
 
 export class SharedPreferences extends PureComponent<Props, State> {
@@ -91,12 +91,11 @@ export class SharedPreferences extends PureComponent<Props, State> {
         <h3 className="page-heading">Preferences</h3>
         <div className="gf-form">
           <span className="gf-form-label width-11">UI Theme</span>
-          <SimplePicker
+          <Select
+            isSearchable={false}
             value={themes.find(item => item.value === theme)}
             options={themes}
-            getOptionValue={i => i.value}
-            getOptionLabel={i => i.text}
-            onSelected={theme => this.onThemeChanged(theme.value)}
+            onChange={theme => this.onThemeChanged(theme.value)}
             width={20}
           />
         </div>
@@ -107,11 +106,11 @@ export class SharedPreferences extends PureComponent<Props, State> {
           >
             Home Dashboard
           </Label>
-          <SimplePicker
+          <Select
             value={dashboards.find(dashboard => dashboard.id === homeDashboardId)}
             getOptionValue={i => i.id}
             getOptionLabel={i => i.title}
-            onSelected={(dashboard: DashboardSearchHit) => this.onHomeDashboardChanged(dashboard.id)}
+            onChange={(dashboard: DashboardSearchHit) => this.onHomeDashboardChanged(dashboard.id)}
             options={dashboards}
             placeholder="Chose default dashboard"
             width={20}
@@ -119,11 +118,10 @@ export class SharedPreferences extends PureComponent<Props, State> {
         </div>
         <div className="gf-form">
           <label className="gf-form-label width-11">Timezone</label>
-          <SimplePicker
+          <Select
+            isSearchable={false}
             value={timezones.find(item => item.value === timezone)}
-            getOptionValue={i => i.value}
-            getOptionLabel={i => i.text}
-            onSelected={timezone => this.onTimeZoneChanged(timezone.value)}
+            onChange={timezone => this.onTimeZoneChanged(timezone.value)}
             options={timezones}
             width={20}
           />

+ 3 - 3
public/app/core/components/TagFilter/TagFilter.tsx

@@ -2,10 +2,10 @@ import React from 'react';
 import AsyncSelect from 'react-select/lib/Async';
 import { TagOption } from './TagOption';
 import { TagBadge } from './TagBadge';
-import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer';
-import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage';
+import IndicatorsContainer from 'app/core/components/Select/IndicatorsContainer';
+import NoOptionsMessage from 'app/core/components/Select/NoOptionsMessage';
 import { components } from 'react-select';
-import ResetStyles from 'app/core/components/Picker/ResetStyles';
+import ResetStyles from 'app/core/components/Select/ResetStyles';
 
 export interface Props {
   tags: string[];

+ 1 - 1
public/app/core/components/code_editor/code_editor.ts

@@ -84,7 +84,7 @@ function link(scope, elem, attrs) {
   // disable depreacation warning
   codeEditor.$blockScrolling = Infinity;
   // Padding hacks
-  (codeEditor.renderer as any).setScrollMargin(15, 15);
+  (codeEditor.renderer as any).setScrollMargin(10, 10);
   codeEditor.renderer.setPadding(10);
 
   setThemeMode();

+ 3 - 9
public/app/features/dashboard/dashgrid/QueriesTab.tsx

@@ -6,7 +6,7 @@ import _ from 'lodash';
 // Components
 import './../../panel/metrics_tab';
 import { EditorTabBody } from './EditorTabBody';
-import { DataSourcePicker } from './DataSourcePicker';
+import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
 import { QueryInspector } from './QueryInspector';
 import { QueryOptions } from './QueryOptions';
 import { AngularQueryComponentScope } from 'app/features/panel/metrics_tab';
@@ -205,20 +205,14 @@ export class QueriesTab extends PureComponent<Props, State> {
   renderToolbar = () => {
     const { currentDS } = this.state;
 
-    return (
-      <DataSourcePicker
-        datasources={this.datasources}
-        onChangeDataSource={this.onChangeDataSource}
-        current={currentDS}
-      />
-    );
+    return <DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />;
   };
 
   renderMixedPicker = () => {
     return (
       <DataSourcePicker
         datasources={this.datasources}
-        onChangeDataSource={this.onAddMixedQuery}
+        onChange={this.onAddMixedQuery}
         current={null}
         autoFocus={true}
         onBlur={this.onMixedPickerBlur}

+ 8 - 26
public/app/features/explore/Explore.tsx

@@ -1,6 +1,5 @@
 import React from 'react';
 import { hot } from 'react-hot-loader';
-import Select from 'react-select';
 import _ from 'lodash';
 
 import { DataSource } from 'app/types/datasources';
@@ -25,10 +24,7 @@ import {
   makeTimeSeriesList,
   updateHistory,
 } from 'app/core/utils/explore';
-import ResetStyles from 'app/core/components/Picker/ResetStyles';
-import PickerOption from 'app/core/components/Picker/PickerOption';
-import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer';
-import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage';
+import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
 import TableModel from 'app/core/table_model';
 import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
 import { Emitter } from 'app/core/utils/emitter';
@@ -158,10 +154,12 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
     if (!datasourceSrv) {
       throw new Error('No datasource service passed as props.');
     }
+
     const datasources = datasourceSrv.getExternal();
     const exploreDatasources = datasources.map(ds => ({
       value: ds.name,
-      label: ds.name,
+      name: ds.name,
+      meta: ds.meta,
     }));
 
     if (datasources.length > 0) {
@@ -885,7 +883,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
     } = this.state;
     const graphHeight = showingGraph && showingTable ? '200px' : '400px';
     const exploreClass = split ? 'explore explore-split' : 'explore';
-    const selectedDatasource = datasource ? exploreDatasources.find(d => d.label === datasource.name) : undefined;
+    const selectedDatasource = datasource ? exploreDatasources.find(d => d.name === datasource.name) : undefined;
     const graphLoading = queryTransactions.some(qt => qt.resultType === 'Graph' && !qt.done);
     const tableLoading = queryTransactions.some(qt => qt.resultType === 'Table' && !qt.done);
     const logsLoading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done);
@@ -910,26 +908,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
           )}
           {!datasourceMissing ? (
             <div className="navbar-buttons">
-              <Select
-                classNamePrefix={`gf-form-select-box`}
-                isMulti={false}
-                menuShouldScrollIntoView={false}
-                isLoading={datasourceLoading}
-                isClearable={false}
-                className="gf-form-input gf-form-input--form-dropdown datasource-picker"
+              <DataSourcePicker
                 onChange={this.onChangeDatasource}
-                options={exploreDatasources}
-                styles={ResetStyles}
-                maxMenuHeight={500}
-                placeholder="Select datasource"
-                loadingMessage={() => 'Loading datasources...'}
-                noOptionsMessage={() => 'No datasources found'}
-                value={selectedDatasource}
-                components={{
-                  Option: PickerOption,
-                  IndicatorsContainer,
-                  NoOptionsMessage,
-                }}
+                datasources={exploreDatasources}
+                current={selectedDatasource}
               />
             </div>
           ) : null}

+ 2 - 2
public/app/features/panel/panel_header.ts

@@ -111,11 +111,11 @@ function panelHeader($compile) {
        */
       function togglePanelStackPosition() {
         const menuOpenClass = 'dropdown-menu-open';
-        const panelGridClass = '.react-grid-item.panel';
+        const panelGridClass = '.react-grid-item';
 
         let panelElem = elem
           .find('[data-toggle=dropdown]')
-          .parentsUntil('.panel')
+          .parentsUntil(panelGridClass)
           .parent();
         const menuElem = elem.find('[data-toggle=dropdown]').parent();
         panelElem = panelElem && panelElem.length ? panelElem[0] : undefined;

+ 1 - 1
public/app/features/teams/TeamMembers.tsx

@@ -1,7 +1,7 @@
 import React, { PureComponent } from 'react';
 import { connect } from 'react-redux';
 import SlideDown from 'app/core/components/Animations/SlideDown';
-import { UserPicker } from 'app/core/components/Picker/UserPicker';
+import { UserPicker } from 'app/core/components/Select/UserPicker';
 import DeleteButton from 'app/core/components/DeleteButton/DeleteButton';
 import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
 import { TeamMember, User } from 'app/types';

+ 14 - 17
public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts

@@ -2,21 +2,6 @@ import angular from 'angular';
 import coreModule from 'app/core/core_module';
 import _ from 'lodash';
 
-export class CloudWatchQueryParameter {
-  constructor() {
-    return {
-      templateUrl: 'public/app/plugins/datasource/cloudwatch/partials/query.parameter.html',
-      controller: 'CloudWatchQueryParameterCtrl',
-      restrict: 'E',
-      scope: {
-        target: '=',
-        datasource: '=',
-        onChange: '&',
-      },
-    };
-  }
-}
-
 export class CloudWatchQueryParameterCtrl {
   /** @ngInject */
   constructor($scope, templateSrv, uiSegmentSrv, datasourceSrv, $q) {
@@ -240,5 +225,17 @@ export class CloudWatchQueryParameterCtrl {
   }
 }
 
-coreModule.directive('cloudwatchQueryParameter', CloudWatchQueryParameter);
-coreModule.controller('CloudWatchQueryParameterCtrl', CloudWatchQueryParameterCtrl);
+export function cloudWatchQueryParameter() {
+  return {
+    templateUrl: 'public/app/plugins/datasource/cloudwatch/partials/query.parameter.html',
+    controller: CloudWatchQueryParameterCtrl,
+    restrict: 'E',
+    scope: {
+      target: '=',
+      datasource: '=',
+      onChange: '&',
+    },
+  };
+}
+
+coreModule.directive('cloudwatchQueryParameter', cloudWatchQueryParameter);

+ 4 - 5
public/app/plugins/panel/gauge/MappingRow.tsx

@@ -1,6 +1,6 @@
 import React, { PureComponent } from 'react';
 import { Label } from 'app/core/components/Label/Label';
-import SimplePicker from 'app/core/components/Picker/SimplePicker';
+import { Select } from 'app/core/components/Select/Select';
 import { MappingType, RangeMap, ValueMap } from 'app/types';
 
 interface Props {
@@ -135,13 +135,12 @@ export default class MappingRow extends PureComponent<Props, State> {
       <div className="mapping-row">
         <div className="gf-form-inline mapping-row-type">
           <Label width={5}>Type</Label>
-          <SimplePicker
+          <Select
             placeholder="Choose type"
+            isSearchable={false}
             options={mappingOptions}
             value={mappingOptions.find(o => o.value === type)}
-            getOptionLabel={i => i.label}
-            getOptionValue={i => i.value}
-            onSelected={type => this.onMappingTypeChange(type.value)}
+            onChange={type => this.onMappingTypeChange(type.value)}
             width={7}
           />
         </div>

+ 16 - 18
public/app/plugins/panel/gauge/ValueOptions.tsx

@@ -1,21 +1,21 @@
 import React, { PureComponent } from 'react';
 import { Label } from 'app/core/components/Label/Label';
-import SimplePicker from 'app/core/components/Picker/SimplePicker';
-import UnitPicker from 'app/core/components/Picker/Unit/UnitPicker';
+import Select from 'app/core/components/Select/Select';
+import UnitPicker from 'app/core/components/Select/UnitPicker';
 import { OptionModuleProps } from './module';
 
 const statOptions = [
-  { value: 'min', text: 'Min' },
-  { value: 'max', text: 'Max' },
-  { value: 'avg', text: 'Average' },
-  { value: 'current', text: 'Current' },
-  { value: 'total', text: 'Total' },
-  { value: 'name', text: 'Name' },
-  { value: 'first', text: 'First' },
-  { value: 'delta', text: 'Delta' },
-  { value: 'diff', text: 'Difference' },
-  { value: 'range', text: 'Range' },
-  { value: 'last_time', text: 'Time of last point' },
+  { value: 'min', label: 'Min' },
+  { value: 'max', label: 'Max' },
+  { value: 'avg', label: 'Average' },
+  { value: 'current', label: 'Current' },
+  { value: 'total', label: 'Total' },
+  { value: 'name', label: 'Name' },
+  { value: 'first', label: 'First' },
+  { value: 'delta', label: 'Delta' },
+  { value: 'diff', label: 'Difference' },
+  { value: 'range', label: 'Range' },
+  { value: 'last_time', label: 'Time of last point' },
 ];
 
 const labelWidth = 6;
@@ -43,18 +43,16 @@ export default class ValueOptions extends PureComponent<OptionModuleProps> {
         <h5 className="page-heading">Value</h5>
         <div className="gf-form-inline">
           <Label width={labelWidth}>Stat</Label>
-          <SimplePicker
+          <Select
             width={12}
             options={statOptions}
-            getOptionLabel={i => i.text}
-            getOptionValue={i => i.value}
-            onSelected={this.onStatChange}
+            onChange={this.onStatChange}
             value={statOptions.find(option => option.value === stat)}
           />
         </div>
         <div className="gf-form-inline">
           <Label width={labelWidth}>Unit</Label>
-          <UnitPicker defaultValue={unit} onSelected={value => this.onUnitChange(value)} />
+          <UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
         </div>
         <div className="gf-form-inline">
           <Label width={labelWidth}>Decimals</Label>

+ 2 - 6
public/app/types/explore.ts

@@ -3,6 +3,7 @@ import { Value } from 'slate';
 import { DataQuery, RawTimeRange } from './series';
 import TableModel from 'app/core/table_model';
 import { LogsModel } from 'app/core/logs_model';
+import { DataSourceSelectItem } from 'app/types/datasources';
 
 export interface CompletionItem {
   /**
@@ -74,11 +75,6 @@ export interface CompletionItemGroup {
   skipSort?: boolean;
 }
 
-interface ExploreDatasource {
-  value: string;
-  label: string;
-}
-
 export interface HistoryItem {
   ts: number;
   query: DataQuery;
@@ -159,7 +155,7 @@ export interface ExploreState {
   datasourceLoading: boolean | null;
   datasourceMissing: boolean;
   datasourceName?: string;
-  exploreDatasources: ExploreDatasource[];
+  exploreDatasources: DataSourceSelectItem[];
   graphInterval: number; // in ms
   graphResult?: any[];
   history: HistoryItem[];

+ 0 - 1
public/sass/_grafana.scss

@@ -101,7 +101,6 @@
 @import 'components/delete_button';
 @import 'components/add_data_source.scss';
 @import 'components/page_loader';
-@import 'components/unit-picker';
 @import 'components/thresholds';
 @import 'components/toggle_button_group';
 @import 'components/value-mappings';

+ 19 - 2
public/sass/components/_form_select_box.scss

@@ -79,6 +79,7 @@ $select-input-bg-disabled: $input-bg-disabled;
 .gf-form-select-box__option {
   border-left: 2px solid transparent;
   white-space: nowrap;
+  background-color: $input-bg;
 
   &.gf-form-select-box__option--is-focused {
     color: $dropdownLinkColorHover;
@@ -116,7 +117,7 @@ $select-input-bg-disabled: $input-bg-disabled;
 .gf-form-select-box__select-arrow {
   border-color: $input-color-select-arrow transparent transparent;
   border-style: solid;
-  border-width: 5px 5px 2.5px;
+  border-width: 4px 4px 2.5px;
   display: inline-block;
   height: 0;
   width: 0;
@@ -125,7 +126,7 @@ $select-input-bg-disabled: $input-bg-disabled;
   &.gf-form-select-box__select-arrow--reversed {
     border-color: transparent transparent $input-color-select-arrow;
     top: -2px;
-    border-width: 0 5px 5px;
+    border-width: 0 4px 4px;
   }
 }
 
@@ -170,3 +171,19 @@ $select-input-bg-disabled: $input-bg-disabled;
   width: 16px;
   margin-right: 10px;
 }
+
+.gf-form-select-box__option-group__header {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  justify-items: center;
+  cursor: pointer;
+  padding: 7px 10px;
+  width: 100%;
+  border-bottom: 1px solid $hr-border-color;
+  text-transform: capitalize;
+
+  .fa {
+    padding-right: 2px;
+  }
+}

+ 2 - 0
public/sass/components/_query_editor.scss

@@ -59,6 +59,8 @@
 }
 
 .gf-form-query-letter-cell {
+  flex-shrink: 0;
+
   .gf-form-query-letter-cell-carret {
     display: inline-block;
     width: 0.7rem;

+ 2 - 2
public/sass/components/_switch.scss

@@ -97,10 +97,10 @@ input:checked + .gf-form-switch__slider::before {
   &--transparent {
     background: transparent;
     border: none;
-    width: 20px;
+    width: 23px;
     height: auto;
     position: relative;
-    padding-left: 7px;
+    padding-left: 8px;
   }
 
   &--table-cell {

+ 0 - 24
public/sass/components/_unit-picker.scss

@@ -1,24 +0,0 @@
-.unit-picker-option {
-  position: relative;
-  width: 100%;
-  display: block;
-  border-radius: 0;
-  white-space: normal;
-
-  i.fa-check {
-    padding-left: 2px;
-  }
-}
-
-.unit-picker-group {
-  margin-bottom: 5px;
-}
-
-.unit-picker-group-item {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 10px;
-  font-size: 14px;
-  border-bottom: 1px solid #555;
-}