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

Merge branch '13425-team-picker-bug'

Torkel Ödegaard 7 лет назад
Родитель
Сommit
69e0311cbc
32 измененных файлов с 850 добавлено и 627 удалено
  1. 2 2
      .circleci/config.yml
  2. 3 2
      package.json
  3. 1 1
      pkg/api/org_users.go
  4. 1 3
      public/app/app.ts
  5. 5 15
      public/app/core/components/PermissionList/AddPermission.tsx
  6. 2 2
      public/app/core/components/PermissionList/DisabledPermissionListItem.tsx
  7. 2 2
      public/app/core/components/PermissionList/PermissionListItem.tsx
  8. 18 49
      public/app/core/components/Picker/DescriptionOption.tsx
  9. 26 18
      public/app/core/components/Picker/DescriptionPicker.tsx
  10. 15 0
      public/app/core/components/Picker/IndicatorsContainer.tsx
  11. 18 0
      public/app/core/components/Picker/NoOptionsMessage.tsx
  12. 20 4
      public/app/core/components/Picker/PickerOption.test.tsx
  13. 17 49
      public/app/core/components/Picker/PickerOption.tsx
  14. 23 0
      public/app/core/components/Picker/ResetStyles.tsx
  15. 30 25
      public/app/core/components/Picker/TeamPicker.tsx
  16. 26 23
      public/app/core/components/Picker/UserPicker.tsx
  17. 12 13
      public/app/core/components/Picker/__snapshots__/PickerOption.test.tsx.snap
  18. 85 55
      public/app/core/components/Picker/__snapshots__/TeamPicker.test.tsx.snap
  19. 85 55
      public/app/core/components/Picker/__snapshots__/UserPicker.test.tsx.snap
  20. 2 7
      public/app/core/components/TagFilter/TagBadge.tsx
  21. 38 24
      public/app/core/components/TagFilter/TagFilter.tsx
  22. 16 46
      public/app/core/components/TagFilter/TagOption.tsx
  23. 1 1
      public/app/core/components/TagFilter/TagValue.tsx
  24. 6 2
      public/app/core/components/search/search.ts
  25. 18 4
      public/app/features/explore/Explore.tsx
  26. 2 5
      public/app/features/teams/TeamMembers.tsx
  27. 0 3
      public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap
  28. 1 0
      public/sass/_variables.dark.scss
  29. 1 0
      public/sass/_variables.light.scss
  30. 90 64
      public/sass/components/_form_select_box.scss
  31. 1 8
      public/sass/components/_tagsinput.scss
  32. 283 145
      yarn.lock

+ 2 - 2
.circleci/config.yml

@@ -126,7 +126,7 @@ jobs:
 
   build-all:
     docker:
-     - image: grafana/build-container:1.1.0
+     - image: grafana/build-container:1.2.0
     working_directory: /go/src/github.com/grafana/grafana
     steps:
       - checkout
@@ -173,7 +173,7 @@ jobs:
 
   build:
     docker:
-     - image: grafana/build-container:1.1.0
+     - image: grafana/build-container:1.2.0
     working_directory: /go/src/github.com/grafana/grafana
     steps:
       - checkout

+ 3 - 2
package.json

@@ -17,6 +17,7 @@
     "@types/react": "^16.4.14",
     "@types/react-custom-scrollbars": "^4.0.5",
     "@types/react-dom": "^16.0.7",
+    "@types/react-select": "^2.0.4",
     "angular-mocks": "1.6.6",
     "autoprefixer": "^6.4.0",
     "axios": "^0.17.1",
@@ -86,7 +87,7 @@
     "tslint-loader": "^3.5.3",
     "typescript": "^3.0.3",
     "uglifyjs-webpack-plugin": "^1.2.7",
-    "webpack": "^4.8.0",
+    "webpack": "4.19.1",
     "webpack-bundle-analyzer": "^2.9.0",
     "webpack-cleanup-plugin": "^0.5.1",
     "webpack-cli": "^2.1.4",
@@ -157,7 +158,7 @@
     "react-highlight-words": "^0.10.0",
     "react-popper": "^0.7.5",
     "react-redux": "^5.0.7",
-    "react-select": "^1.1.0",
+    "react-select": "2.1.0",
     "react-sizeme": "^2.3.6",
     "react-transition-group": "^2.2.1",
     "redux": "^4.0.0",

+ 1 - 1
pkg/api/org_users.go

@@ -45,7 +45,7 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
 
 // GET /api/org/users
 func GetOrgUsersForCurrentOrg(c *m.ReqContext) Response {
-	return getOrgUsersHelper(c.OrgId, c.Params("query"), c.ParamsInt("limit"))
+	return getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
 }
 
 // GET /api/orgs/:orgId/users

+ 1 - 3
public/app/app.ts

@@ -35,8 +35,6 @@ enterpriseIndex.keys().forEach(key => {
   enterpriseIndex(key);
 });
 
-declare var System: any;
-
 export class GrafanaApp {
   registerFunctions: any;
   ngModuleDependencies: any[];
@@ -125,7 +123,7 @@ export class GrafanaApp {
     coreModule.config(setupAngularRoutes);
     registerAngularDirectives();
 
-    const preBootRequires = [System.import('app/features/all')];
+    const preBootRequires = [import('app/features/all')];
 
     Promise.all(preBootRequires)
       .then(() => {

+ 5 - 15
public/app/core/components/PermissionList/AddPermission.tsx

@@ -50,11 +50,11 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
   };
 
   onUserSelected = (user: User) => {
-    this.setState({ userId: user ? user.id : 0 });
+    this.setState({ userId: user && !Array.isArray(user) ? user.id : 0 });
   };
 
   onTeamSelected = (team: Team) => {
-    this.setState({ teamId: team ? team.id : 0 });
+    this.setState({ teamId: team && !Array.isArray(team) ? team.id : 0 });
   };
 
   onPermissionChanged = (permission: OptionWithDescription) => {
@@ -82,7 +82,6 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
     const newItem = this.state;
     const pickerClassName = 'width-20';
     const isValid = this.isValid();
-
     return (
       <div className="gf-form-inline cta-form">
         <button className="cta-form__close btn btn-transparent" onClick={onCancel}>
@@ -107,21 +106,13 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
 
             {newItem.type === AclTarget.User ? (
               <div className="gf-form">
-                <UserPicker
-                  onSelected={this.onUserSelected}
-                  value={newItem.userId.toString()}
-                  className={pickerClassName}
-                />
+                <UserPicker onSelected={this.onUserSelected} className={pickerClassName} />
               </div>
             ) : null}
 
             {newItem.type === AclTarget.Team ? (
               <div className="gf-form">
-                <TeamPicker
-                  onSelected={this.onTeamSelected}
-                  value={newItem.teamId.toString()}
-                  className={pickerClassName}
-                />
+                <TeamPicker onSelected={this.onTeamSelected} className={pickerClassName} />
               </div>
             ) : null}
 
@@ -129,9 +120,8 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
               <DescriptionPicker
                 optionsWithDesc={dashboardPermissionLevels}
                 onSelected={this.onPermissionChanged}
-                value={newItem.permission}
                 disabled={false}
-                className={'gf-form-input--form-dropdown-right'}
+                className={'gf-form-select-box__control--menu-right'}
               />
             </div>
 

+ 2 - 2
public/app/core/components/PermissionList/DisabledPermissionListItem.tsx

@@ -26,9 +26,9 @@ export default class DisabledPermissionListItem extends Component<Props, any> {
             <DescriptionPicker
               optionsWithDesc={dashboardPermissionLevels}
               onSelected={() => {}}
-              value={item.permission}
               disabled={true}
-              className={'gf-form-input--form-dropdown-right'}
+              className={'gf-form-select-box__control--menu-right'}
+              value={item.permission}
             />
           </div>
         </td>

+ 2 - 2
public/app/core/components/PermissionList/PermissionListItem.tsx

@@ -77,9 +77,9 @@ export default class PermissionsListItem extends PureComponent<Props> {
             <DescriptionPicker
               optionsWithDesc={dashboardPermissionLevels}
               onSelected={this.onPermissionChanged}
-              value={item.permission}
               disabled={item.inherited}
-              className={'gf-form-input--form-dropdown-right'}
+              className={'gf-form-select-box__control--menu-right'}
+              value={item.permission}
             />
           </div>
         </td>

+ 18 - 49
public/app/core/components/Picker/DescriptionOption.tsx

@@ -1,56 +1,25 @@
-import React, { Component } from 'react';
+import React from 'react';
+import { components } from 'react-select';
+import { OptionProps } from 'react-select/lib/components/Option';
 
-export interface Props {
-  onSelect: any;
-  onFocus: any;
-  option: any;
-  isFocused: any;
-  className: any;
+// https://github.com/JedWatson/react-select/issues/3038
+interface ExtendedOptionProps extends OptionProps<any> {
+  data: any;
 }
 
-class DescriptionOption extends Component<Props, any> {
-  constructor(props) {
-    super(props);
-    this.handleMouseDown = this.handleMouseDown.bind(this);
-    this.handleMouseEnter = this.handleMouseEnter.bind(this);
-    this.handleMouseMove = this.handleMouseMove.bind(this);
-  }
-
-  handleMouseDown(event) {
-    event.preventDefault();
-    event.stopPropagation();
-    this.props.onSelect(this.props.option, event);
-  }
-
-  handleMouseEnter(event) {
-    this.props.onFocus(this.props.option, event);
-  }
-
-  handleMouseMove(event) {
-    if (this.props.isFocused) {
-      return;
-    }
-    this.props.onFocus(this.props.option, event);
-  }
-
-  render() {
-    const { option, children, className } = this.props;
-    return (
-      <button
-        onMouseDown={this.handleMouseDown}
-        onMouseEnter={this.handleMouseEnter}
-        onMouseMove={this.handleMouseMove}
-        title={option.title}
-        className={`description-picker-option__button btn btn-link ${className} width-19`}
-      >
+export const Option = (props: ExtendedOptionProps) => {
+  const { children, isSelected, data, className } = props;
+  return (
+    <components.Option {...props}>
+      <div className={`description-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 className="gf-form">
-          <div className="muted width-17">{option.description}</div>
-          {className.indexOf('is-selected') > -1 && <i className="fa fa-check" aria-hidden="true" />}
+          <div className="muted width-17">{data.description}</div>
         </div>
-      </button>
-    );
-  }
-}
+      </div>
+    </components.Option>
+  );
+};
 
-export default DescriptionOption;
+export default Option;

+ 26 - 18
public/app/core/components/Picker/DescriptionPicker.tsx

@@ -1,44 +1,52 @@
 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;
-  value: number;
   disabled: boolean;
   className?: string;
+  value?: any;
 }
 
-export interface OptionWithDescription {
-  value: any;
-  label: string;
-  description: string;
-}
+const getSelectedOption = (optionsWithDesc, value) => optionsWithDesc.find(option => option.value === value);
 
 class DescriptionPicker extends Component<Props, any> {
   constructor(props) {
     super(props);
-    this.state = {};
   }
 
   render() {
-    const { optionsWithDesc, onSelected, value, disabled, className } = this.props;
-
+    const { optionsWithDesc, onSelected, disabled, className, value } = this.props;
+    const selectedOption = getSelectedOption(optionsWithDesc, value);
     return (
       <div className="permissions-picker">
         <Select
-          value={value}
-          valueKey="value"
-          multi={false}
-          clearable={false}
-          labelKey="label"
+          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}
-          className={`width-7 gf-form-input gf-form-input--form-dropdown ${className || ''}`}
-          optionComponent={DescriptionOption}
-          placeholder="Choose"
-          disabled={disabled}
+          getOptionValue={i => i.value}
+          getOptionLabel={i => i.label}
+          value={selectedOption}
         />
       </div>
     );

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

@@ -0,0 +1,15 @@
+import React from 'react';
+import { components } from 'react-select';
+
+export const IndicatorsContainer = props => {
+  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;

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

@@ -0,0 +1,18 @@
+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;

+ 20 - 4
public/app/core/components/Picker/PickerOption.test.tsx

@@ -3,10 +3,26 @@ import renderer from 'react-test-renderer';
 import PickerOption from './PickerOption';
 
 const model = {
-  onSelect: () => {},
-  onFocus: () => {},
-  isFocused: () => {},
-  option: {
+  cx: jest.fn(),
+  clearValue: jest.fn(),
+  onSelect: jest.fn(),
+  getStyles: jest.fn(),
+  getValue: jest.fn(),
+  hasValue: true,
+  isMulti: false,
+  options: [],
+  selectOption: jest.fn(),
+  selectProps: {},
+  setValue: jest.fn(),
+  isDisabled: false,
+  isFocused: false,
+  isSelected: false,
+  innerRef: null,
+  innerProps: null,
+  label: 'Option label',
+  type: null,
+  children: 'Model title',
+  data: {
     title: 'Model title',
     avatarUrl: 'url/to/avatar',
     label: 'User picker label',

+ 17 - 49
public/app/core/components/Picker/PickerOption.tsx

@@ -1,54 +1,22 @@
-import React, { Component } from 'react';
+import React from 'react';
+import { components } from 'react-select';
+import { OptionProps } from 'react-select/lib/components/Option';
 
-export interface Props {
-  onSelect: any;
-  onFocus: any;
-  option: any;
-  isFocused: any;
-  className: any;
+// https://github.com/JedWatson/react-select/issues/3038
+interface ExtendedOptionProps extends OptionProps<any> {
+  data: any;
 }
 
-class UserPickerOption extends Component<Props, any> {
-  constructor(props) {
-    super(props);
-    this.handleMouseDown = this.handleMouseDown.bind(this);
-    this.handleMouseEnter = this.handleMouseEnter.bind(this);
-    this.handleMouseMove = this.handleMouseMove.bind(this);
-  }
-
-  handleMouseDown(event) {
-    event.preventDefault();
-    event.stopPropagation();
-    this.props.onSelect(this.props.option, event);
-  }
-
-  handleMouseEnter(event) {
-    this.props.onFocus(this.props.option, event);
-  }
-
-  handleMouseMove(event) {
-    if (this.props.isFocused) {
-      return;
-    }
-    this.props.onFocus(this.props.option, event);
-  }
-
-  render() {
-    const { option, children, className } = this.props;
-
-    return (
-      <button
-        onMouseDown={this.handleMouseDown}
-        onMouseEnter={this.handleMouseEnter}
-        onMouseMove={this.handleMouseMove}
-        title={option.title}
-        className={`user-picker-option__button btn btn-link ${className}`}
-      >
-        <img src={option.avatarUrl} alt={option.label} className="user-picker-option__avatar" />
+export const PickerOption = (props: ExtendedOptionProps) => {
+  const { children, data, className } = props;
+  return (
+    <components.Option {...props}>
+      <div className={`description-picker-option__button btn btn-link ${className}`}>
+        {data.avatarUrl && <img src={data.avatarUrl} alt={data.label} className="user-picker-option__avatar" />}
         {children}
-      </button>
-    );
-  }
-}
+      </div>
+    </components.Option>
+  );
+};
 
-export default UserPickerOption;
+export default PickerOption;

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

@@ -0,0 +1,23 @@
+export default {
+  clearIndicator: () => ({}),
+  container: () => ({}),
+  control: () => ({}),
+  dropdownIndicator: () => ({}),
+  group: () => ({}),
+  groupHeading: () => ({}),
+  indicatorsContainer: () => ({}),
+  indicatorSeparator: () => ({}),
+  input: () => ({}),
+  loadingIndicator: () => ({}),
+  loadingMessage: () => ({}),
+  menu: () => ({}),
+  menuList: () => ({}),
+  multiValue: () => ({}),
+  multiValueLabel: () => ({}),
+  multiValueRemove: () => ({}),
+  noOptionsMessage: () => ({}),
+  option: () => ({}),
+  placeholder: () => ({}),
+  singleValue: () => ({}),
+  valueContainer: () => ({}),
+};

+ 30 - 25
public/app/core/components/Picker/TeamPicker.tsx

@@ -1,24 +1,26 @@
 import React, { Component } from 'react';
-import Select from 'react-select';
+import AsyncSelect from 'react-select/lib/Async';
 import PickerOption from './PickerOption';
 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;
+  label: string;
+  name: string;
+  avatarUrl: string;
+}
 
 export interface Props {
   onSelected: (team: Team) => void;
-  value?: string;
   className?: string;
 }
 
 export interface State {
-  isLoading;
-}
-
-export interface Team {
-  id: number;
-  label: string;
-  name: string;
-  avatarUrl: string;
+  isLoading: boolean;
 }
 
 export class TeamPicker extends Component<Props, State> {
@@ -31,7 +33,7 @@ export class TeamPicker extends Component<Props, State> {
 
     this.debouncedSearch = debounce(this.search, 300, {
       leading: true,
-      trailing: false,
+      trailing: true,
     });
   }
 
@@ -39,7 +41,7 @@ export class TeamPicker extends Component<Props, State> {
     const backendSrv = getBackendSrv();
     this.setState({ isLoading: true });
 
-    return backendSrv.get(`/api/teams/search?perpage=50&page=1&query=${query}`).then(result => {
+    return backendSrv.get(`/api/teams/search?perpage=10&page=1&query=${query}`).then(result => {
       const teams = result.teams.map(team => {
         return {
           id: team.id,
@@ -50,31 +52,34 @@ export class TeamPicker extends Component<Props, State> {
       });
 
       this.setState({ isLoading: false });
-      return { options: teams };
+      return teams;
     });
   }
 
   render() {
-    const { onSelected, value, className } = this.props;
+    const { onSelected, className } = this.props;
     const { isLoading } = this.state;
-
     return (
       <div className="user-picker">
-        <Select.Async
-          valueKey="id"
-          multi={false}
-          labelKey="label"
-          cache={false}
+        <AsyncSelect
+          classNamePrefix={`gf-form-select-box`}
+          isMulti={false}
           isLoading={isLoading}
+          defaultOptions={true}
           loadOptions={this.debouncedSearch}
-          loadingPlaceholder="Loading..."
-          noResultsText="No teams found"
           onChange={onSelected}
           className={`gf-form-input gf-form-input--form-dropdown ${className || ''}`}
-          optionComponent={PickerOption}
+          styles={ResetStyles}
+          components={{
+            Option: PickerOption,
+            IndicatorsContainer,
+            NoOptionsMessage,
+          }}
           placeholder="Select a team"
-          value={value}
-          autosize={true}
+          loadingMessage={() => 'Loading...'}
+          noOptionsMessage={() => 'No teams found'}
+          getOptionValue={i => i.id}
+          getOptionLabel={i => i.label}
         />
       </div>
     );

+ 26 - 23
public/app/core/components/Picker/UserPicker.tsx

@@ -1,13 +1,15 @@
 import React, { Component } from 'react';
-import Select from 'react-select';
+import AsyncSelect from 'react-select/lib/Async';
 import PickerOption from './PickerOption';
 import { debounce } from 'lodash';
 import { getBackendSrv } from 'app/core/services/backend_srv';
 import { User } from 'app/types';
+import ResetStyles from './ResetStyles';
+import IndicatorsContainer from './IndicatorsContainer';
+import NoOptionsMessage from './NoOptionsMessage';
 
 export interface Props {
   onSelected: (user: User) => void;
-  value?: string;
   className?: string;
 }
 
@@ -31,20 +33,17 @@ export class UserPicker extends Component<Props, State> {
 
   search(query?: string) {
     const backendSrv = getBackendSrv();
-
     this.setState({ isLoading: true });
 
     return backendSrv
       .get(`/api/org/users?query=${query}&limit=10`)
       .then(result => {
-        return {
-          options: result.map(user => ({
-            id: user.userId,
-            label: `${user.login} - ${user.email}`,
-            avatarUrl: user.avatarUrl,
-            login: user.login,
-          })),
-        };
+        return result.map(user => ({
+          id: user.userId,
+          label: `${user.login} - ${user.email}`,
+          avatarUrl: user.avatarUrl,
+          login: user.login,
+        }));
       })
       .finally(() => {
         this.setState({ isLoading: false });
@@ -52,26 +51,30 @@ export class UserPicker extends Component<Props, State> {
   }
 
   render() {
-    const { value, className } = this.props;
+    const { className, onSelected } = this.props;
     const { isLoading } = this.state;
 
     return (
       <div className="user-picker">
-        <Select.Async
-          valueKey="id"
-          multi={false}
-          labelKey="label"
-          cache={false}
+        <AsyncSelect
+          classNamePrefix={`gf-form-select-box`}
+          isMulti={false}
           isLoading={isLoading}
+          defaultOptions={true}
           loadOptions={this.debouncedSearch}
-          loadingPlaceholder="Loading..."
-          noResultsText="No users found"
-          onChange={this.props.onSelected}
+          onChange={onSelected}
           className={`gf-form-input gf-form-input--form-dropdown ${className || ''}`}
-          optionComponent={PickerOption}
+          styles={ResetStyles}
+          components={{
+            Option: PickerOption,
+            IndicatorsContainer,
+            NoOptionsMessage,
+          }}
           placeholder="Select user"
-          value={value}
-          autosize={true}
+          loadingMessage={() => 'Loading...'}
+          noOptionsMessage={() => 'No users found'}
+          getOptionValue={i => i.id}
+          getOptionLabel={i => i.label}
         />
       </div>
     );

+ 12 - 13
public/app/core/components/Picker/__snapshots__/PickerOption.test.tsx.snap

@@ -1,17 +1,16 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`PickerOption renders correctly 1`] = `
-<button
-  className="user-picker-option__button btn btn-link class-for-user-picker"
-  onMouseDown={[Function]}
-  onMouseEnter={[Function]}
-  onMouseMove={[Function]}
-  title="Model title"
->
-  <img
-    alt="User picker label"
-    className="user-picker-option__avatar"
-    src="url/to/avatar"
-  />
-</button>
+<div>
+  <div
+    className="description-picker-option__button btn btn-link class-for-user-picker"
+  >
+    <img
+      alt="User picker label"
+      className="user-picker-option__avatar"
+      src="url/to/avatar"
+    />
+    Model title
+  </div>
+</div>
 `;

+ 85 - 55
public/app/core/components/Picker/__snapshots__/TeamPicker.test.tsx.snap

@@ -5,85 +5,115 @@ exports[`TeamPicker renders correctly 1`] = `
   className="user-picker"
 >
   <div
-    className="Select gf-form-input gf-form-input--form-dropdown  is-clearable is-loading is-searchable Select--single"
+    className="css-0 gf-form-input gf-form-input--form-dropdown"
+    onKeyDown={[Function]}
   >
     <div
-      className="Select-control"
-      onKeyDown={[Function]}
+      className="css-0 gf-form-select-box__control"
       onMouseDown={[Function]}
       onTouchEnd={[Function]}
-      onTouchMove={[Function]}
-      onTouchStart={[Function]}
     >
       <div
-        className="Select-multi-value-wrapper"
-        id="react-select-2--value"
+        className="css-0 gf-form-select-box__value-container"
       >
         <div
-          className="Select-placeholder"
+          className="css-0 gf-form-select-box__placeholder"
         >
-          Loading...
+          Select a team
         </div>
         <div
-          className="Select-input"
-          style={
-            Object {
-              "display": "inline-block",
-            }
-          }
+          className="css-0"
         >
-          <input
-            aria-activedescendant="react-select-2--value"
-            aria-expanded="false"
-            aria-haspopup="false"
-            aria-owns=""
-            onBlur={[Function]}
-            onChange={[Function]}
-            onFocus={[Function]}
-            required={false}
-            role="combobox"
-            style={
-              Object {
-                "boxSizing": "content-box",
-                "width": "5px",
-              }
-            }
-            value=""
-          />
           <div
+            className="gf-form-select-box__input"
             style={
               Object {
-                "height": 0,
-                "left": 0,
-                "overflow": "scroll",
-                "position": "absolute",
-                "top": 0,
-                "visibility": "hidden",
-                "whiteSpace": "pre",
+                "display": "inline-block",
               }
             }
           >
-            
+            <input
+              aria-autocomplete="list"
+              autoCapitalize="none"
+              autoComplete="off"
+              autoCorrect="off"
+              disabled={false}
+              id="react-select-2-input"
+              onBlur={[Function]}
+              onChange={[Function]}
+              onFocus={[Function]}
+              spellCheck="false"
+              style={
+                Object {
+                  "background": 0,
+                  "border": 0,
+                  "boxSizing": "content-box",
+                  "color": "inherit",
+                  "fontSize": "inherit",
+                  "opacity": 1,
+                  "outline": 0,
+                  "padding": 0,
+                  "width": "1px",
+                }
+              }
+              tabIndex="0"
+              theme={
+                Object {
+                  "borderRadius": 4,
+                  "colors": Object {
+                    "danger": "#DE350B",
+                    "dangerLight": "#FFBDAD",
+                    "neutral0": "hsl(0, 0%, 100%)",
+                    "neutral10": "hsl(0, 0%, 90%)",
+                    "neutral20": "hsl(0, 0%, 80%)",
+                    "neutral30": "hsl(0, 0%, 70%)",
+                    "neutral40": "hsl(0, 0%, 60%)",
+                    "neutral5": "hsl(0, 0%, 95%)",
+                    "neutral50": "hsl(0, 0%, 50%)",
+                    "neutral60": "hsl(0, 0%, 40%)",
+                    "neutral70": "hsl(0, 0%, 30%)",
+                    "neutral80": "hsl(0, 0%, 20%)",
+                    "neutral90": "hsl(0, 0%, 10%)",
+                    "primary": "#2684FF",
+                    "primary25": "#DEEBFF",
+                    "primary50": "#B2D4FF",
+                    "primary75": "#4C9AFF",
+                  },
+                  "spacing": Object {
+                    "baseUnit": 4,
+                    "controlHeight": 38,
+                    "menuGutter": 8,
+                  },
+                }
+              }
+              type="text"
+              value=""
+            />
+            <div
+              style={
+                Object {
+                  "height": 0,
+                  "left": 0,
+                  "overflow": "scroll",
+                  "position": "absolute",
+                  "top": 0,
+                  "visibility": "hidden",
+                  "whiteSpace": "pre",
+                }
+              }
+            >
+              
+            </div>
           </div>
         </div>
       </div>
-      <span
-        aria-hidden="true"
-        className="Select-loading-zone"
-      >
-        <span
-          className="Select-loading"
-        />
-      </span>
-      <span
-        className="Select-arrow-zone"
-        onMouseDown={[Function]}
+      <div
+        className="css-0 gf-form-select-box__indicators"
       >
         <span
-          className="Select-arrow"
-          onMouseDown={[Function]}
+          className="gf-form-select-box__select-arrow "
         />
-      </span>
+      </div>
     </div>
   </div>
 </div>

+ 85 - 55
public/app/core/components/Picker/__snapshots__/UserPicker.test.tsx.snap

@@ -5,85 +5,115 @@ exports[`UserPicker renders correctly 1`] = `
   className="user-picker"
 >
   <div
-    className="Select gf-form-input gf-form-input--form-dropdown  is-clearable is-loading is-searchable Select--single"
+    className="css-0 gf-form-input gf-form-input--form-dropdown"
+    onKeyDown={[Function]}
   >
     <div
-      className="Select-control"
-      onKeyDown={[Function]}
+      className="css-0 gf-form-select-box__control"
       onMouseDown={[Function]}
       onTouchEnd={[Function]}
-      onTouchMove={[Function]}
-      onTouchStart={[Function]}
     >
       <div
-        className="Select-multi-value-wrapper"
-        id="react-select-2--value"
+        className="css-0 gf-form-select-box__value-container"
       >
         <div
-          className="Select-placeholder"
+          className="css-0 gf-form-select-box__placeholder"
         >
-          Loading...
+          Select user
         </div>
         <div
-          className="Select-input"
-          style={
-            Object {
-              "display": "inline-block",
-            }
-          }
+          className="css-0"
         >
-          <input
-            aria-activedescendant="react-select-2--value"
-            aria-expanded="false"
-            aria-haspopup="false"
-            aria-owns=""
-            onBlur={[Function]}
-            onChange={[Function]}
-            onFocus={[Function]}
-            required={false}
-            role="combobox"
-            style={
-              Object {
-                "boxSizing": "content-box",
-                "width": "5px",
-              }
-            }
-            value=""
-          />
           <div
+            className="gf-form-select-box__input"
             style={
               Object {
-                "height": 0,
-                "left": 0,
-                "overflow": "scroll",
-                "position": "absolute",
-                "top": 0,
-                "visibility": "hidden",
-                "whiteSpace": "pre",
+                "display": "inline-block",
               }
             }
           >
-            
+            <input
+              aria-autocomplete="list"
+              autoCapitalize="none"
+              autoComplete="off"
+              autoCorrect="off"
+              disabled={false}
+              id="react-select-2-input"
+              onBlur={[Function]}
+              onChange={[Function]}
+              onFocus={[Function]}
+              spellCheck="false"
+              style={
+                Object {
+                  "background": 0,
+                  "border": 0,
+                  "boxSizing": "content-box",
+                  "color": "inherit",
+                  "fontSize": "inherit",
+                  "opacity": 1,
+                  "outline": 0,
+                  "padding": 0,
+                  "width": "1px",
+                }
+              }
+              tabIndex="0"
+              theme={
+                Object {
+                  "borderRadius": 4,
+                  "colors": Object {
+                    "danger": "#DE350B",
+                    "dangerLight": "#FFBDAD",
+                    "neutral0": "hsl(0, 0%, 100%)",
+                    "neutral10": "hsl(0, 0%, 90%)",
+                    "neutral20": "hsl(0, 0%, 80%)",
+                    "neutral30": "hsl(0, 0%, 70%)",
+                    "neutral40": "hsl(0, 0%, 60%)",
+                    "neutral5": "hsl(0, 0%, 95%)",
+                    "neutral50": "hsl(0, 0%, 50%)",
+                    "neutral60": "hsl(0, 0%, 40%)",
+                    "neutral70": "hsl(0, 0%, 30%)",
+                    "neutral80": "hsl(0, 0%, 20%)",
+                    "neutral90": "hsl(0, 0%, 10%)",
+                    "primary": "#2684FF",
+                    "primary25": "#DEEBFF",
+                    "primary50": "#B2D4FF",
+                    "primary75": "#4C9AFF",
+                  },
+                  "spacing": Object {
+                    "baseUnit": 4,
+                    "controlHeight": 38,
+                    "menuGutter": 8,
+                  },
+                }
+              }
+              type="text"
+              value=""
+            />
+            <div
+              style={
+                Object {
+                  "height": 0,
+                  "left": 0,
+                  "overflow": "scroll",
+                  "position": "absolute",
+                  "top": 0,
+                  "visibility": "hidden",
+                  "whiteSpace": "pre",
+                }
+              }
+            >
+              
+            </div>
           </div>
         </div>
       </div>
-      <span
-        aria-hidden="true"
-        className="Select-loading-zone"
-      >
-        <span
-          className="Select-loading"
-        />
-      </span>
-      <span
-        className="Select-arrow-zone"
-        onMouseDown={[Function]}
+      <div
+        className="css-0 gf-form-select-box__indicators"
       >
         <span
-          className="Select-arrow"
-          onMouseDown={[Function]}
+          className="gf-form-select-box__select-arrow "
         />
-      </span>
+      </div>
     </div>
   </div>
 </div>

+ 2 - 7
public/app/core/components/TagFilter/TagBadge.tsx

@@ -5,17 +5,12 @@ export interface Props {
   label: string;
   removeIcon: boolean;
   count: number;
-  onClick: any;
+  onClick?: any;
 }
 
 export class TagBadge extends React.Component<Props, any> {
   constructor(props) {
     super(props);
-    this.onClick = this.onClick.bind(this);
-  }
-
-  onClick(event) {
-    this.props.onClick(event);
   }
 
   render() {
@@ -28,7 +23,7 @@ export class TagBadge extends React.Component<Props, any> {
     const countLabel = count !== 0 && <span className="tag-count-label">{`(${count})`}</span>;
 
     return (
-      <span className={`label label-tag`} onClick={this.onClick} style={tagStyle}>
+      <span className={`label label-tag`} style={tagStyle}>
         {removeIcon && <i className="fa fa-remove" />}
         {label} {countLabel}
       </span>

+ 38 - 24
public/app/core/components/TagFilter/TagFilter.tsx

@@ -1,8 +1,11 @@
-import _ from 'lodash';
 import React from 'react';
-import { Async } from 'react-select';
-import { TagValue } from './TagValue';
+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 { components } from 'react-select';
+import ResetStyles from 'app/core/components/Picker/ResetStyles';
 
 export interface Props {
   tags: string[];
@@ -18,15 +21,15 @@ export class TagFilter extends React.Component<Props, any> {
 
     this.searchTags = this.searchTags.bind(this);
     this.onChange = this.onChange.bind(this);
-    this.onTagRemove = this.onTagRemove.bind(this);
   }
 
   searchTags(query) {
     return this.props.tagOptions().then(options => {
-      const tags = _.map(options, tagOption => {
-        return { value: tagOption.term, label: tagOption.term, count: tagOption.count };
-      });
-      return { options: tags };
+      return options.map(option => ({
+        value: option.term,
+        label: option.term,
+        count: option.count,
+      }));
     });
   }
 
@@ -34,33 +37,44 @@ export class TagFilter extends React.Component<Props, any> {
     this.props.onSelect(newTags);
   }
 
-  onTagRemove(tag) {
-    let newTags = _.without(this.props.tags, tag.label);
-    newTags = _.map(newTags, tag => {
-      return { value: tag };
-    });
-    this.props.onSelect(newTags);
-  }
-
   render() {
     const selectOptions = {
+      classNamePrefix: 'gf-form-select-box',
+      isMulti: true,
+      defaultOptions: true,
       loadOptions: this.searchTags,
       onChange: this.onChange,
-      value: this.props.tags,
-      multi: true,
       className: 'gf-form-input gf-form-input--form-dropdown',
       placeholder: 'Tags',
-      loadingPlaceholder: 'Loading...',
-      noResultsText: 'No tags found',
-      optionComponent: TagOption,
-    };
+      loadingMessage: () => 'Loading...',
+      noOptionsMessage: () => 'No tags found',
+      getOptionValue: i => i.value,
+      getOptionLabel: i => i.label,
+      value: this.props.tags,
+      styles: ResetStyles,
+      components: {
+        Option: TagOption,
+        IndicatorsContainer,
+        NoOptionsMessage,
+        MultiValueLabel: () => {
+          return null; // We want the whole tag to be clickable so we use MultiValueRemove instead
+        },
+        MultiValueRemove: props => {
+          const { data } = props;
 
-    selectOptions['valueComponent'] = TagValue;
+          return (
+            <components.MultiValueRemove {...props}>
+              <TagBadge key={data.label} label={data.label} removeIcon={true} count={data.count} />
+            </components.MultiValueRemove>
+          );
+        },
+      },
+    };
 
     return (
       <div className="gf-form gf-form--has-input-icon gf-form--grow">
         <div className="tag-filter">
-          <Async {...selectOptions} />
+          <AsyncSelect {...selectOptions} />
         </div>
         <i className="gf-form-input-icon fa fa-tag" />
       </div>

+ 16 - 46
public/app/core/components/TagFilter/TagOption.tsx

@@ -1,52 +1,22 @@
 import React from 'react';
+import { components } from 'react-select';
+import { OptionProps } from 'react-select/lib/components/Option';
 import { TagBadge } from './TagBadge';
 
-export interface Props {
-  onSelect: any;
-  onFocus: any;
-  option: any;
-  isFocused: any;
-  className: any;
+// https://github.com/JedWatson/react-select/issues/3038
+interface ExtendedOptionProps extends OptionProps<any> {
+  data: any;
 }
 
-export class TagOption extends React.Component<Props, any> {
-  constructor(props) {
-    super(props);
-    this.handleMouseDown = this.handleMouseDown.bind(this);
-    this.handleMouseEnter = this.handleMouseEnter.bind(this);
-    this.handleMouseMove = this.handleMouseMove.bind(this);
-  }
+export const TagOption = (props: ExtendedOptionProps) => {
+  const { data, className, label } = props;
+  return (
+    <components.Option {...props}>
+      <div className={`tag-filter-option btn btn-link ${className || ''}`}>
+        <TagBadge label={label} removeIcon={false} count={data.count} />
+      </div>
+    </components.Option>
+  );
+};
 
-  handleMouseDown(event) {
-    event.preventDefault();
-    event.stopPropagation();
-    this.props.onSelect(this.props.option, event);
-  }
-
-  handleMouseEnter(event) {
-    this.props.onFocus(this.props.option, event);
-  }
-
-  handleMouseMove(event) {
-    if (this.props.isFocused) {
-      return;
-    }
-    this.props.onFocus(this.props.option, event);
-  }
-
-  render() {
-    const { option, className } = this.props;
-
-    return (
-      <button
-        onMouseDown={this.handleMouseDown}
-        onMouseEnter={this.handleMouseEnter}
-        onMouseMove={this.handleMouseMove}
-        title={option.title}
-        className={`tag-filter-option btn btn-link ${className || ''}`}
-      >
-        <TagBadge label={option.label} removeIcon={false} count={option.count} onClick={this.handleMouseDown} />
-      </button>
-    );
-  }
-}
+export default TagOption;

+ 1 - 1
public/app/core/components/TagFilter/TagValue.tsx

@@ -21,6 +21,6 @@ export class TagValue extends React.Component<Props, any> {
   render() {
     const { value } = this.props;
 
-    return <TagBadge label={value.label} removeIcon={true} count={0} onClick={this.onClick} />;
+    return <TagBadge label={value.label} removeIcon={false} count={0} onClick={this.onClick} />;
   }
 }

+ 6 - 2
public/app/core/components/search/search.ts

@@ -160,8 +160,12 @@ export class SearchCtrl {
   searchDashboards() {
     this.currentSearchId = this.currentSearchId + 1;
     const localSearchId = this.currentSearchId;
+    const query = {
+      ...this.query,
+      tag: this.query.tag.map(i => i.value),
+    };
 
-    return this.searchSrv.search(this.query).then(results => {
+    return this.searchSrv.search(query).then(results => {
       if (localSearchId < this.currentSearchId) {
         return;
       }
@@ -196,7 +200,7 @@ export class SearchCtrl {
   }
 
   onTagSelect(newTags) {
-    this.query.tag = _.map(newTags, tag => tag.value);
+    this.query.tag = newTags;
     this.search();
   }
 

+ 18 - 4
public/app/features/explore/Explore.tsx

@@ -9,6 +9,10 @@ import store from 'app/core/store';
 import TimeSeries from 'app/core/time_series2';
 import { parse as parseDate } from 'app/core/utils/datemath';
 import { DEFAULT_RANGE } 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 ElapsedTime from './ElapsedTime';
 import QueryRows from './QueryRows';
@@ -519,7 +523,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
     const logsButtonActive = showingLogs ? 'active' : '';
     const tableButtonActive = showingBoth || showingTable ? 'active' : '';
     const exploreClass = split ? 'explore explore-split' : 'explore';
-    const selectedDatasource = datasource ? datasource.name : undefined;
+    const selectedDatasource = datasource ? exploreDatasources.find(d => d.label === datasource.name) : undefined;
 
     return (
       <div className={exploreClass} ref={this.getRef}>
@@ -541,13 +545,23 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
           {!datasourceMissing ? (
             <div className="navbar-buttons">
               <Select
-                clearable={false}
+                classNamePrefix={`gf-form-select-box`}
+                isMulti={false}
+                isLoading={datasourceLoading}
+                isClearable={false}
                 className="gf-form-input gf-form-input--form-dropdown datasource-picker"
                 onChange={this.onChangeDatasource}
                 options={exploreDatasources}
-                isOpen={true}
-                placeholder="Loading datasources..."
+                styles={ResetStyles}
+                placeholder="Select datasource"
+                loadingMessage={() => 'Loading datasources...'}
+                noOptionsMessage={() => 'No datasources found'}
                 value={selectedDatasource}
+                components={{
+                  Option: PickerOption,
+                  IndicatorsContainer,
+                  NoOptionsMessage,
+                }}
               />
             </div>
           ) : null}

+ 2 - 5
public/app/features/teams/TeamMembers.tsx

@@ -83,10 +83,8 @@ export class TeamMembers extends PureComponent<Props, State> {
   }
 
   render() {
-    const { newTeamMember, isAdding } = this.state;
+    const { isAdding } = this.state;
     const { searchMemberQuery, members, syncEnabled } = this.props;
-    const newTeamMemberValue = newTeamMember && newTeamMember.id.toString();
-
     return (
       <div>
         <div className="page-action-bar">
@@ -117,8 +115,7 @@ export class TeamMembers extends PureComponent<Props, State> {
             </button>
             <h5>Add Team Member</h5>
             <div className="gf-form-inline">
-              <UserPicker onSelected={this.onUserSelected} className="width-30" value={newTeamMemberValue} />
-
+              <UserPicker onSelected={this.onUserSelected} className="width-30" />
               {this.state.newTeamMember && (
                 <button className="btn btn-success gf-form-btn" type="submit" onClick={this.onAddUserToTeam}>
                   Add to team

+ 0 - 3
public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap

@@ -60,7 +60,6 @@ exports[`Render should render component 1`] = `
         <UserPicker
           className="width-30"
           onSelected={[Function]}
-          value={null}
         />
       </div>
     </div>
@@ -155,7 +154,6 @@ exports[`Render should render team members 1`] = `
         <UserPicker
           className="width-30"
           onSelected={[Function]}
-          value={null}
         />
       </div>
     </div>
@@ -376,7 +374,6 @@ exports[`Render should render team members when sync enabled 1`] = `
         <UserPicker
           className="width-30"
           onSelected={[Function]}
-          value={null}
         />
       </div>
     </div>

+ 1 - 0
public/sass/_variables.dark.scss

@@ -194,6 +194,7 @@ $input-box-shadow-focus: rgba(102, 175, 233, 0.6);
 $input-color-placeholder: $gray-1 !default;
 $input-label-bg: $gray-blue;
 $input-label-border-color: $dark-3;
+$input-color-select-arrow: $white;
 
 // Search
 $search-shadow: 0 0 30px 0 $black;

+ 1 - 0
public/sass/_variables.light.scss

@@ -190,6 +190,7 @@ $input-box-shadow-focus: $blue !default;
 $input-color-placeholder: $gray-4 !default;
 $input-label-bg: $gray-5;
 $input-label-border-color: $gray-5;
+$input-color-select-arrow: $gray-1;
 
 // Sidemenu
 // -------------------------

+ 90 - 64
public/sass/components/_form_select_box.scss

@@ -1,19 +1,5 @@
 $select-input-height: 35px;
-$select-menu-max-height: 300px;
-$select-item-font-size: $font-size-base;
-$select-item-bg: $dropdownBackground;
-$select-item-fg: $input-color;
-$select-option-bg: $menu-dropdown-bg;
-$select-option-color: $input-color;
-$select-noresults-color: $text-color;
-$select-input-bg: $input-bg;
-$select-input-border-color: $input-border-color;
-$select-menu-box-shadow: $menu-dropdown-shadow;
-$select-text-color: $text-color;
 $select-input-bg-disabled: $input-bg-disabled;
-$select-option-selected-bg: $dropdownLinkBackgroundActive;
-
-@import '../../../node_modules/react-select/scss/default.scss';
 
 @mixin select-control() {
   width: 100%;
@@ -29,73 +15,113 @@ $select-option-selected-bg: $dropdownLinkBackgroundActive;
   @include box-shadow($shadow);
 }
 
-// react-select tweaks
-.gf-form-input--form-dropdown {
-  padding: 0;
-  border: 0;
-  overflow: visible;
+.gf-form-select-box__control {
+  @include select-control();
+  border: 1px solid $input-border-color;
+  color: $input-color;
+  cursor: default;
+  display: table;
+  border-spacing: 0;
+  border-collapse: separate;
+  height: $select-input-height;
+  outline: none;
+  overflow: hidden;
+  position: relative;
+}
+
+.gf-form-select-box__control--is-focused {
+  background-color: $input-bg;
+  @include select-control-focus();
+}
+
+.gf-form-select-box__control--is-disabled {
+  background-color: $select-input-bg-disabled;
+}
 
-  .Select-placeholder {
-    color: $input-color-placeholder;
+.gf-form-select-box__control--menu-right {
+  .gf-form-select-box__menu {
+    right: 0;
+    left: unset;
   }
+}
 
-  > .Select-control {
-    @include select-control();
-    border-color: $dark-3;
+.gf-form-select-box__input {
+  padding-left: 5px;
+}
 
-    input {
-      min-width: 1rem;
-    }
+.gf-form-select-box__menu {
+  background: $dropdownBackground;
+  position: absolute;
+  z-index: 2;
+}
 
-    .Select-clear,
-    .Select-arrow {
-      margin-right: 8px;
-    }
+.tag-filter .gf-form-select-box__menu {
+  width: 100%;
+}
 
-    .Select-value {
-      display: inline-block;
-      padding: $input-padding-y $input-padding-x;
-      font-size: $font-size-md;
-      line-height: $input-line-height;
-      vertical-align: baseline;
-      white-space: nowrap;
-    }
-  }
+.gf-form-select-box__multi-value {
+  display: inline;
+}
 
-  &.is-open > .Select-control {
-    background: transparent;
-    border-color: $dark-3;
-  }
+.gf-form-select-box__option {
+  border-left: 2px solid transparent;
 
-  &.is-focused > .Select-control {
-    background-color: $input-bg;
-    @include select-control-focus();
+  &.gf-form-select-box__option--is-focused {
+    color: $dropdownLinkColorHover;
+    background-color: $dropdownLinkBackgroundHover;
+    @include left-brand-border-gradient();
   }
 
-  &.is-focused:not(.is-open) > .Select-control {
-    background-color: $input-bg;
-    @include select-control-focus();
+  &.gf-form-select-box__option--is-selected {
+    .fa {
+      color: $input-color-select-arrow;
+    }
   }
+}
 
-  .Select-menu-outer {
-    border: 0;
-    width: auto;
-  }
+.gf-form-select-box__control--is-focused .gf-form-select-box__placeholder {
+  display: none;
+}
 
-  .Select-option {
-    border-left: 2px solid transparent;
+.gf-form-select-box__value-container {
+  display: table-cell;
+  padding: 8px 10px;
+  > div {
+    display: inline-block;
   }
+}
 
-  .Select-option.is-focused {
-    background-color: $dropdownLinkBackgroundHover;
-    color: $dropdownLinkColorHover;
-    @include left-brand-border-gradient();
+.gf-form-select-box__indicators {
+  display: table-cell;
+  vertical-align: middle;
+  text-align: right;
+  padding-right: 10px;
+  width: 20px;
+}
+
+.gf-form-select-box__select-arrow {
+  border-color: $input-color-select-arrow transparent transparent;
+  border-style: solid;
+  border-width: 5px 5px 2.5px;
+  display: inline-block;
+  height: 0;
+  width: 0;
+  position: relative;
+
+  &.gf-form-select-box__select-arrow--reversed {
+    border-color: transparent transparent $input-color-select-arrow;
+    top: -2px;
+    border-width: 0 5px 5px;
   }
 }
+.gf-form-input--form-dropdown {
+  padding: 0;
+  border: 0;
+  overflow: visible;
+}
 
-.gf-form-input--form-dropdown-right {
-  .Select-menu-outer {
-    right: 0;
-    left: unset;
+.gf-form--has-input-icon {
+  .gf-form-select-box__value-container {
+    padding-left: 30px;
   }
 }

+ 1 - 8
public/sass/components/_tagsinput.scss

@@ -19,7 +19,7 @@
 
   .tag {
     margin-right: 2px;
-    color: white;
+    color: $white;
 
     [data-role='remove'] {
       margin-left: 8px;
@@ -63,13 +63,6 @@
   .tag-count-label {
     margin-left: 3px;
   }
-
-  .gf-form-input--form-dropdown {
-    .Select-menu-outer {
-      border: 0;
-      width: 100%;
-    }
-  }
 }
 
 .tag-filter-values {

Разница между файлами не показана из-за своего большого размера
+ 283 - 145
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов