Commit 69e0311c by Torkel Ödegaard

Merge branch '13425-team-picker-bug'

parents b49f4a8c c368c72d
...@@ -126,7 +126,7 @@ jobs: ...@@ -126,7 +126,7 @@ jobs:
build-all: build-all:
docker: docker:
- image: grafana/build-container:1.1.0 - image: grafana/build-container:1.2.0
working_directory: /go/src/github.com/grafana/grafana working_directory: /go/src/github.com/grafana/grafana
steps: steps:
- checkout - checkout
...@@ -173,7 +173,7 @@ jobs: ...@@ -173,7 +173,7 @@ jobs:
build: build:
docker: docker:
- image: grafana/build-container:1.1.0 - image: grafana/build-container:1.2.0
working_directory: /go/src/github.com/grafana/grafana working_directory: /go/src/github.com/grafana/grafana
steps: steps:
- checkout - checkout
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"@types/react": "^16.4.14", "@types/react": "^16.4.14",
"@types/react-custom-scrollbars": "^4.0.5", "@types/react-custom-scrollbars": "^4.0.5",
"@types/react-dom": "^16.0.7", "@types/react-dom": "^16.0.7",
"@types/react-select": "^2.0.4",
"angular-mocks": "1.6.6", "angular-mocks": "1.6.6",
"autoprefixer": "^6.4.0", "autoprefixer": "^6.4.0",
"axios": "^0.17.1", "axios": "^0.17.1",
...@@ -86,7 +87,7 @@ ...@@ -86,7 +87,7 @@
"tslint-loader": "^3.5.3", "tslint-loader": "^3.5.3",
"typescript": "^3.0.3", "typescript": "^3.0.3",
"uglifyjs-webpack-plugin": "^1.2.7", "uglifyjs-webpack-plugin": "^1.2.7",
"webpack": "^4.8.0", "webpack": "4.19.1",
"webpack-bundle-analyzer": "^2.9.0", "webpack-bundle-analyzer": "^2.9.0",
"webpack-cleanup-plugin": "^0.5.1", "webpack-cleanup-plugin": "^0.5.1",
"webpack-cli": "^2.1.4", "webpack-cli": "^2.1.4",
...@@ -157,7 +158,7 @@ ...@@ -157,7 +158,7 @@
"react-highlight-words": "^0.10.0", "react-highlight-words": "^0.10.0",
"react-popper": "^0.7.5", "react-popper": "^0.7.5",
"react-redux": "^5.0.7", "react-redux": "^5.0.7",
"react-select": "^1.1.0", "react-select": "2.1.0",
"react-sizeme": "^2.3.6", "react-sizeme": "^2.3.6",
"react-transition-group": "^2.2.1", "react-transition-group": "^2.2.1",
"redux": "^4.0.0", "redux": "^4.0.0",
......
...@@ -45,7 +45,7 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response { ...@@ -45,7 +45,7 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
// GET /api/org/users // GET /api/org/users
func GetOrgUsersForCurrentOrg(c *m.ReqContext) Response { 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 // GET /api/orgs/:orgId/users
......
...@@ -35,8 +35,6 @@ enterpriseIndex.keys().forEach(key => { ...@@ -35,8 +35,6 @@ enterpriseIndex.keys().forEach(key => {
enterpriseIndex(key); enterpriseIndex(key);
}); });
declare var System: any;
export class GrafanaApp { export class GrafanaApp {
registerFunctions: any; registerFunctions: any;
ngModuleDependencies: any[]; ngModuleDependencies: any[];
...@@ -125,7 +123,7 @@ export class GrafanaApp { ...@@ -125,7 +123,7 @@ export class GrafanaApp {
coreModule.config(setupAngularRoutes); coreModule.config(setupAngularRoutes);
registerAngularDirectives(); registerAngularDirectives();
const preBootRequires = [System.import('app/features/all')]; const preBootRequires = [import('app/features/all')];
Promise.all(preBootRequires) Promise.all(preBootRequires)
.then(() => { .then(() => {
......
...@@ -50,11 +50,11 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> { ...@@ -50,11 +50,11 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
}; };
onUserSelected = (user: User) => { onUserSelected = (user: User) => {
this.setState({ userId: user ? user.id : 0 }); this.setState({ userId: user && !Array.isArray(user) ? user.id : 0 });
}; };
onTeamSelected = (team: Team) => { onTeamSelected = (team: Team) => {
this.setState({ teamId: team ? team.id : 0 }); this.setState({ teamId: team && !Array.isArray(team) ? team.id : 0 });
}; };
onPermissionChanged = (permission: OptionWithDescription) => { onPermissionChanged = (permission: OptionWithDescription) => {
...@@ -82,7 +82,6 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> { ...@@ -82,7 +82,6 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
const newItem = this.state; const newItem = this.state;
const pickerClassName = 'width-20'; const pickerClassName = 'width-20';
const isValid = this.isValid(); const isValid = this.isValid();
return ( return (
<div className="gf-form-inline cta-form"> <div className="gf-form-inline cta-form">
<button className="cta-form__close btn btn-transparent" onClick={onCancel}> <button className="cta-form__close btn btn-transparent" onClick={onCancel}>
...@@ -107,21 +106,13 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> { ...@@ -107,21 +106,13 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
{newItem.type === AclTarget.User ? ( {newItem.type === AclTarget.User ? (
<div className="gf-form"> <div className="gf-form">
<UserPicker <UserPicker onSelected={this.onUserSelected} className={pickerClassName} />
onSelected={this.onUserSelected}
value={newItem.userId.toString()}
className={pickerClassName}
/>
</div> </div>
) : null} ) : null}
{newItem.type === AclTarget.Team ? ( {newItem.type === AclTarget.Team ? (
<div className="gf-form"> <div className="gf-form">
<TeamPicker <TeamPicker onSelected={this.onTeamSelected} className={pickerClassName} />
onSelected={this.onTeamSelected}
value={newItem.teamId.toString()}
className={pickerClassName}
/>
</div> </div>
) : null} ) : null}
...@@ -129,9 +120,8 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> { ...@@ -129,9 +120,8 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
<DescriptionPicker <DescriptionPicker
optionsWithDesc={dashboardPermissionLevels} optionsWithDesc={dashboardPermissionLevels}
onSelected={this.onPermissionChanged} onSelected={this.onPermissionChanged}
value={newItem.permission}
disabled={false} disabled={false}
className={'gf-form-input--form-dropdown-right'} className={'gf-form-select-box__control--menu-right'}
/> />
</div> </div>
......
...@@ -26,9 +26,9 @@ export default class DisabledPermissionListItem extends Component<Props, any> { ...@@ -26,9 +26,9 @@ export default class DisabledPermissionListItem extends Component<Props, any> {
<DescriptionPicker <DescriptionPicker
optionsWithDesc={dashboardPermissionLevels} optionsWithDesc={dashboardPermissionLevels}
onSelected={() => {}} onSelected={() => {}}
value={item.permission}
disabled={true} disabled={true}
className={'gf-form-input--form-dropdown-right'} className={'gf-form-select-box__control--menu-right'}
value={item.permission}
/> />
</div> </div>
</td> </td>
......
...@@ -77,9 +77,9 @@ export default class PermissionsListItem extends PureComponent<Props> { ...@@ -77,9 +77,9 @@ export default class PermissionsListItem extends PureComponent<Props> {
<DescriptionPicker <DescriptionPicker
optionsWithDesc={dashboardPermissionLevels} optionsWithDesc={dashboardPermissionLevels}
onSelected={this.onPermissionChanged} onSelected={this.onPermissionChanged}
value={item.permission}
disabled={item.inherited} disabled={item.inherited}
className={'gf-form-input--form-dropdown-right'} className={'gf-form-select-box__control--menu-right'}
value={item.permission}
/> />
</div> </div>
</td> </td>
......
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 { // https://github.com/JedWatson/react-select/issues/3038
onSelect: any; interface ExtendedOptionProps extends OptionProps<any> {
onFocus: any; data: any;
option: any;
isFocused: any;
className: any;
} }
class DescriptionOption extends Component<Props, any> { export const Option = (props: ExtendedOptionProps) => {
constructor(props) { const { children, isSelected, data, className } = props;
super(props); return (
this.handleMouseDown = this.handleMouseDown.bind(this); <components.Option {...props}>
this.handleMouseEnter = this.handleMouseEnter.bind(this); <div className={`description-picker-option__button btn btn-link ${className}`}>
this.handleMouseMove = this.handleMouseMove.bind(this); {isSelected && <i className="fa fa-check pull-right" aria-hidden="true" />}
}
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`}
>
<div className="gf-form">{children}</div> <div className="gf-form">{children}</div>
<div className="gf-form"> <div className="gf-form">
<div className="muted width-17">{option.description}</div> <div className="muted width-17">{data.description}</div>
{className.indexOf('is-selected') > -1 && <i className="fa fa-check" aria-hidden="true" />}
</div> </div>
</button> </div>
); </components.Option>
} );
} };
export default DescriptionOption; export default Option;
import React, { Component } from 'react'; import React, { Component } from 'react';
import Select from 'react-select'; import Select from 'react-select';
import DescriptionOption from './DescriptionOption'; 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 { export interface Props {
optionsWithDesc: OptionWithDescription[]; optionsWithDesc: OptionWithDescription[];
onSelected: (permission) => void; onSelected: (permission) => void;
value: number;
disabled: boolean; disabled: boolean;
className?: string; className?: string;
value?: any;
} }
export interface OptionWithDescription { const getSelectedOption = (optionsWithDesc, value) => optionsWithDesc.find(option => option.value === value);
value: any;
label: string;
description: string;
}
class DescriptionPicker extends Component<Props, any> { class DescriptionPicker extends Component<Props, any> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {};
} }
render() { render() {
const { optionsWithDesc, onSelected, value, disabled, className } = this.props; const { optionsWithDesc, onSelected, disabled, className, value } = this.props;
const selectedOption = getSelectedOption(optionsWithDesc, value);
return ( return (
<div className="permissions-picker"> <div className="permissions-picker">
<Select <Select
value={value} placeholder="Choose"
valueKey="value" classNamePrefix={`gf-form-select-box`}
multi={false} className={`width-7 gf-form-input gf-form-input--form-dropdown ${className || ''}`}
clearable={false}
labelKey="label"
options={optionsWithDesc} options={optionsWithDesc}
components={{
Option: DescriptionOption,
IndicatorsContainer,
NoOptionsMessage,
}}
styles={ResetStyles}
isDisabled={disabled}
onChange={onSelected} onChange={onSelected}
className={`width-7 gf-form-input gf-form-input--form-dropdown ${className || ''}`} getOptionValue={i => i.value}
optionComponent={DescriptionOption} getOptionLabel={i => i.label}
placeholder="Choose" value={selectedOption}
disabled={disabled}
/> />
</div> </div>
); );
......
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;
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;
...@@ -3,10 +3,26 @@ import renderer from 'react-test-renderer'; ...@@ -3,10 +3,26 @@ import renderer from 'react-test-renderer';
import PickerOption from './PickerOption'; import PickerOption from './PickerOption';
const model = { const model = {
onSelect: () => {}, cx: jest.fn(),
onFocus: () => {}, clearValue: jest.fn(),
isFocused: () => {}, onSelect: jest.fn(),
option: { 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', title: 'Model title',
avatarUrl: 'url/to/avatar', avatarUrl: 'url/to/avatar',
label: 'User picker label', label: 'User picker label',
......
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 { // https://github.com/JedWatson/react-select/issues/3038
onSelect: any; interface ExtendedOptionProps extends OptionProps<any> {
onFocus: any; data: any;
option: any;
isFocused: any;
className: any;
} }
class UserPickerOption extends Component<Props, any> { export const PickerOption = (props: ExtendedOptionProps) => {
constructor(props) { const { children, data, className } = props;
super(props); return (
this.handleMouseDown = this.handleMouseDown.bind(this); <components.Option {...props}>
this.handleMouseEnter = this.handleMouseEnter.bind(this); <div className={`description-picker-option__button btn btn-link ${className}`}>
this.handleMouseMove = this.handleMouseMove.bind(this); {data.avatarUrl && <img src={data.avatarUrl} alt={data.label} className="user-picker-option__avatar" />}
}
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" />
{children} {children}
</button> </div>
); </components.Option>
} );
} };
export default UserPickerOption; export default PickerOption;
export default {
clearIndicator: () => ({}),
container: () => ({}),
control: () => ({}),
dropdownIndicator: () => ({}),
group: () => ({}),
groupHeading: () => ({}),
indicatorsContainer: () => ({}),
indicatorSeparator: () => ({}),
input: () => ({}),
loadingIndicator: () => ({}),
loadingMessage: () => ({}),
menu: () => ({}),
menuList: () => ({}),
multiValue: () => ({}),
multiValueLabel: () => ({}),
multiValueRemove: () => ({}),
noOptionsMessage: () => ({}),
option: () => ({}),
placeholder: () => ({}),
singleValue: () => ({}),
valueContainer: () => ({}),
};
import React, { Component } from 'react'; import React, { Component } from 'react';
import Select from 'react-select'; import AsyncSelect from 'react-select/lib/Async';
import PickerOption from './PickerOption'; import PickerOption from './PickerOption';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { getBackendSrv } from 'app/core/services/backend_srv'; 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 { export interface Props {
onSelected: (team: Team) => void; onSelected: (team: Team) => void;
value?: string;
className?: string; className?: string;
} }
export interface State { export interface State {
isLoading; isLoading: boolean;
}
export interface Team {
id: number;
label: string;
name: string;
avatarUrl: string;
} }
export class TeamPicker extends Component<Props, State> { export class TeamPicker extends Component<Props, State> {
...@@ -31,7 +33,7 @@ export class TeamPicker extends Component<Props, State> { ...@@ -31,7 +33,7 @@ export class TeamPicker extends Component<Props, State> {
this.debouncedSearch = debounce(this.search, 300, { this.debouncedSearch = debounce(this.search, 300, {
leading: true, leading: true,
trailing: false, trailing: true,
}); });
} }
...@@ -39,7 +41,7 @@ export class TeamPicker extends Component<Props, State> { ...@@ -39,7 +41,7 @@ export class TeamPicker extends Component<Props, State> {
const backendSrv = getBackendSrv(); const backendSrv = getBackendSrv();
this.setState({ isLoading: true }); 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 => { const teams = result.teams.map(team => {
return { return {
id: team.id, id: team.id,
...@@ -50,31 +52,34 @@ export class TeamPicker extends Component<Props, State> { ...@@ -50,31 +52,34 @@ export class TeamPicker extends Component<Props, State> {
}); });
this.setState({ isLoading: false }); this.setState({ isLoading: false });
return { options: teams }; return teams;
}); });
} }
render() { render() {
const { onSelected, value, className } = this.props; const { onSelected, className } = this.props;
const { isLoading } = this.state; const { isLoading } = this.state;
return ( return (
<div className="user-picker"> <div className="user-picker">
<Select.Async <AsyncSelect
valueKey="id" classNamePrefix={`gf-form-select-box`}
multi={false} isMulti={false}
labelKey="label"
cache={false}
isLoading={isLoading} isLoading={isLoading}
defaultOptions={true}
loadOptions={this.debouncedSearch} loadOptions={this.debouncedSearch}
loadingPlaceholder="Loading..."
noResultsText="No teams found"
onChange={onSelected} onChange={onSelected}
className={`gf-form-input gf-form-input--form-dropdown ${className || ''}`} className={`gf-form-input gf-form-input--form-dropdown ${className || ''}`}
optionComponent={PickerOption} styles={ResetStyles}
components={{
Option: PickerOption,
IndicatorsContainer,
NoOptionsMessage,
}}
placeholder="Select a team" placeholder="Select a team"
value={value} loadingMessage={() => 'Loading...'}
autosize={true} noOptionsMessage={() => 'No teams found'}
getOptionValue={i => i.id}
getOptionLabel={i => i.label}
/> />
</div> </div>
); );
......
import React, { Component } from 'react'; import React, { Component } from 'react';
import Select from 'react-select'; import AsyncSelect from 'react-select/lib/Async';
import PickerOption from './PickerOption'; import PickerOption from './PickerOption';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { getBackendSrv } from 'app/core/services/backend_srv'; import { getBackendSrv } from 'app/core/services/backend_srv';
import { User } from 'app/types'; import { User } from 'app/types';
import ResetStyles from './ResetStyles';
import IndicatorsContainer from './IndicatorsContainer';
import NoOptionsMessage from './NoOptionsMessage';
export interface Props { export interface Props {
onSelected: (user: User) => void; onSelected: (user: User) => void;
value?: string;
className?: string; className?: string;
} }
...@@ -31,20 +33,17 @@ export class UserPicker extends Component<Props, State> { ...@@ -31,20 +33,17 @@ export class UserPicker extends Component<Props, State> {
search(query?: string) { search(query?: string) {
const backendSrv = getBackendSrv(); const backendSrv = getBackendSrv();
this.setState({ isLoading: true }); this.setState({ isLoading: true });
return backendSrv return backendSrv
.get(`/api/org/users?query=${query}&limit=10`) .get(`/api/org/users?query=${query}&limit=10`)
.then(result => { .then(result => {
return { return result.map(user => ({
options: result.map(user => ({ id: user.userId,
id: user.userId, label: `${user.login} - ${user.email}`,
label: `${user.login} - ${user.email}`, avatarUrl: user.avatarUrl,
avatarUrl: user.avatarUrl, login: user.login,
login: user.login, }));
})),
};
}) })
.finally(() => { .finally(() => {
this.setState({ isLoading: false }); this.setState({ isLoading: false });
...@@ -52,26 +51,30 @@ export class UserPicker extends Component<Props, State> { ...@@ -52,26 +51,30 @@ export class UserPicker extends Component<Props, State> {
} }
render() { render() {
const { value, className } = this.props; const { className, onSelected } = this.props;
const { isLoading } = this.state; const { isLoading } = this.state;
return ( return (
<div className="user-picker"> <div className="user-picker">
<Select.Async <AsyncSelect
valueKey="id" classNamePrefix={`gf-form-select-box`}
multi={false} isMulti={false}
labelKey="label"
cache={false}
isLoading={isLoading} isLoading={isLoading}
defaultOptions={true}
loadOptions={this.debouncedSearch} loadOptions={this.debouncedSearch}
loadingPlaceholder="Loading..." onChange={onSelected}
noResultsText="No users found"
onChange={this.props.onSelected}
className={`gf-form-input gf-form-input--form-dropdown ${className || ''}`} className={`gf-form-input gf-form-input--form-dropdown ${className || ''}`}
optionComponent={PickerOption} styles={ResetStyles}
components={{
Option: PickerOption,
IndicatorsContainer,
NoOptionsMessage,
}}
placeholder="Select user" placeholder="Select user"
value={value} loadingMessage={() => 'Loading...'}
autosize={true} noOptionsMessage={() => 'No users found'}
getOptionValue={i => i.id}
getOptionLabel={i => i.label}
/> />
</div> </div>
); );
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PickerOption renders correctly 1`] = ` exports[`PickerOption renders correctly 1`] = `
<button <div>
className="user-picker-option__button btn btn-link class-for-user-picker" <div
onMouseDown={[Function]} className="description-picker-option__button btn btn-link class-for-user-picker"
onMouseEnter={[Function]} >
onMouseMove={[Function]} <img
title="Model title" alt="User picker label"
> className="user-picker-option__avatar"
<img src="url/to/avatar"
alt="User picker label" />
className="user-picker-option__avatar" Model title
src="url/to/avatar" </div>
/> </div>
</button>
`; `;
...@@ -5,85 +5,115 @@ exports[`TeamPicker renders correctly 1`] = ` ...@@ -5,85 +5,115 @@ exports[`TeamPicker renders correctly 1`] = `
className="user-picker" className="user-picker"
> >
<div <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 <div
className="Select-control" className="css-0 gf-form-select-box__control"
onKeyDown={[Function]}
onMouseDown={[Function]} onMouseDown={[Function]}
onTouchEnd={[Function]} onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
> >
<div <div
className="Select-multi-value-wrapper" className="css-0 gf-form-select-box__value-container"
id="react-select-2--value"
> >
<div <div
className="Select-placeholder" className="css-0 gf-form-select-box__placeholder"
> >
Loading... Select a team
</div> </div>
<div <div
className="Select-input" className="css-0"
style={
Object {
"display": "inline-block",
}
}
> >
<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 <div
className="gf-form-select-box__input"
style={ style={
Object { Object {
"height": 0, "display": "inline-block",
"left": 0,
"overflow": "scroll",
"position": "absolute",
"top": 0,
"visibility": "hidden",
"whiteSpace": "pre",
} }
} }
> >
<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> </div>
</div> </div>
<span <div
aria-hidden="true" className="css-0 gf-form-select-box__indicators"
className="Select-loading-zone"
>
<span
className="Select-loading"
/>
</span>
<span
className="Select-arrow-zone"
onMouseDown={[Function]}
> >
<span <span
className="Select-arrow" className="gf-form-select-box__select-arrow "
onMouseDown={[Function]}
/> />
</span> </div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -5,85 +5,115 @@ exports[`UserPicker renders correctly 1`] = ` ...@@ -5,85 +5,115 @@ exports[`UserPicker renders correctly 1`] = `
className="user-picker" className="user-picker"
> >
<div <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 <div
className="Select-control" className="css-0 gf-form-select-box__control"
onKeyDown={[Function]}
onMouseDown={[Function]} onMouseDown={[Function]}
onTouchEnd={[Function]} onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
> >
<div <div
className="Select-multi-value-wrapper" className="css-0 gf-form-select-box__value-container"
id="react-select-2--value"
> >
<div <div
className="Select-placeholder" className="css-0 gf-form-select-box__placeholder"
> >
Loading... Select user
</div> </div>
<div <div
className="Select-input" className="css-0"
style={
Object {
"display": "inline-block",
}
}
> >
<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 <div
className="gf-form-select-box__input"
style={ style={
Object { Object {
"height": 0, "display": "inline-block",
"left": 0,
"overflow": "scroll",
"position": "absolute",
"top": 0,
"visibility": "hidden",
"whiteSpace": "pre",
} }
} }
> >
<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> </div>
</div> </div>
<span <div
aria-hidden="true" className="css-0 gf-form-select-box__indicators"
className="Select-loading-zone"
>
<span
className="Select-loading"
/>
</span>
<span
className="Select-arrow-zone"
onMouseDown={[Function]}
> >
<span <span
className="Select-arrow" className="gf-form-select-box__select-arrow "
onMouseDown={[Function]}
/> />
</span> </div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -5,17 +5,12 @@ export interface Props { ...@@ -5,17 +5,12 @@ export interface Props {
label: string; label: string;
removeIcon: boolean; removeIcon: boolean;
count: number; count: number;
onClick: any; onClick?: any;
} }
export class TagBadge extends React.Component<Props, any> { export class TagBadge extends React.Component<Props, any> {
constructor(props) { constructor(props) {
super(props); super(props);
this.onClick = this.onClick.bind(this);
}
onClick(event) {
this.props.onClick(event);
} }
render() { render() {
...@@ -28,7 +23,7 @@ export class TagBadge extends React.Component<Props, any> { ...@@ -28,7 +23,7 @@ export class TagBadge extends React.Component<Props, any> {
const countLabel = count !== 0 && <span className="tag-count-label">{`(${count})`}</span>; const countLabel = count !== 0 && <span className="tag-count-label">{`(${count})`}</span>;
return ( return (
<span className={`label label-tag`} onClick={this.onClick} style={tagStyle}> <span className={`label label-tag`} style={tagStyle}>
{removeIcon && <i className="fa fa-remove" />} {removeIcon && <i className="fa fa-remove" />}
{label} {countLabel} {label} {countLabel}
</span> </span>
......
import _ from 'lodash';
import React from 'react'; import React from 'react';
import { Async } from 'react-select'; import AsyncSelect from 'react-select/lib/Async';
import { TagValue } from './TagValue';
import { TagOption } from './TagOption'; 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 { export interface Props {
tags: string[]; tags: string[];
...@@ -18,15 +21,15 @@ export class TagFilter extends React.Component<Props, any> { ...@@ -18,15 +21,15 @@ export class TagFilter extends React.Component<Props, any> {
this.searchTags = this.searchTags.bind(this); this.searchTags = this.searchTags.bind(this);
this.onChange = this.onChange.bind(this); this.onChange = this.onChange.bind(this);
this.onTagRemove = this.onTagRemove.bind(this);
} }
searchTags(query) { searchTags(query) {
return this.props.tagOptions().then(options => { return this.props.tagOptions().then(options => {
const tags = _.map(options, tagOption => { return options.map(option => ({
return { value: tagOption.term, label: tagOption.term, count: tagOption.count }; value: option.term,
}); label: option.term,
return { options: tags }; count: option.count,
}));
}); });
} }
...@@ -34,33 +37,44 @@ export class TagFilter extends React.Component<Props, any> { ...@@ -34,33 +37,44 @@ export class TagFilter extends React.Component<Props, any> {
this.props.onSelect(newTags); 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() { render() {
const selectOptions = { const selectOptions = {
classNamePrefix: 'gf-form-select-box',
isMulti: true,
defaultOptions: true,
loadOptions: this.searchTags, loadOptions: this.searchTags,
onChange: this.onChange, onChange: this.onChange,
value: this.props.tags,
multi: true,
className: 'gf-form-input gf-form-input--form-dropdown', className: 'gf-form-input gf-form-input--form-dropdown',
placeholder: 'Tags', placeholder: 'Tags',
loadingPlaceholder: 'Loading...', loadingMessage: () => 'Loading...',
noResultsText: 'No tags found', noOptionsMessage: () => 'No tags found',
optionComponent: TagOption, 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 ( return (
<div className="gf-form gf-form--has-input-icon gf-form--grow"> <div className="gf-form gf-form--has-input-icon gf-form--grow">
<div className="tag-filter"> <div className="tag-filter">
<Async {...selectOptions} /> <AsyncSelect {...selectOptions} />
</div> </div>
<i className="gf-form-input-icon fa fa-tag" /> <i className="gf-form-input-icon fa fa-tag" />
</div> </div>
......
import React from 'react'; import React from 'react';
import { components } from 'react-select';
import { OptionProps } from 'react-select/lib/components/Option';
import { TagBadge } from './TagBadge'; import { TagBadge } from './TagBadge';
export interface Props { // https://github.com/JedWatson/react-select/issues/3038
onSelect: any; interface ExtendedOptionProps extends OptionProps<any> {
onFocus: any; data: any;
option: any;
isFocused: any;
className: any;
} }
export class TagOption extends React.Component<Props, any> { export const TagOption = (props: ExtendedOptionProps) => {
constructor(props) { const { data, className, label } = props;
super(props); return (
this.handleMouseDown = this.handleMouseDown.bind(this); <components.Option {...props}>
this.handleMouseEnter = this.handleMouseEnter.bind(this); <div className={`tag-filter-option btn btn-link ${className || ''}`}>
this.handleMouseMove = this.handleMouseMove.bind(this); <TagBadge label={label} removeIcon={false} count={data.count} />
} </div>
</components.Option>
);
};
handleMouseDown(event) { export default TagOption;
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>
);
}
}
...@@ -21,6 +21,6 @@ export class TagValue extends React.Component<Props, any> { ...@@ -21,6 +21,6 @@ export class TagValue extends React.Component<Props, any> {
render() { render() {
const { value } = this.props; 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} />;
} }
} }
...@@ -160,8 +160,12 @@ export class SearchCtrl { ...@@ -160,8 +160,12 @@ export class SearchCtrl {
searchDashboards() { searchDashboards() {
this.currentSearchId = this.currentSearchId + 1; this.currentSearchId = this.currentSearchId + 1;
const localSearchId = this.currentSearchId; 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) { if (localSearchId < this.currentSearchId) {
return; return;
} }
...@@ -196,7 +200,7 @@ export class SearchCtrl { ...@@ -196,7 +200,7 @@ export class SearchCtrl {
} }
onTagSelect(newTags) { onTagSelect(newTags) {
this.query.tag = _.map(newTags, tag => tag.value); this.query.tag = newTags;
this.search(); this.search();
} }
......
...@@ -9,6 +9,10 @@ import store from 'app/core/store'; ...@@ -9,6 +9,10 @@ import store from 'app/core/store';
import TimeSeries from 'app/core/time_series2'; import TimeSeries from 'app/core/time_series2';
import { parse as parseDate } from 'app/core/utils/datemath'; import { parse as parseDate } from 'app/core/utils/datemath';
import { DEFAULT_RANGE } from 'app/core/utils/explore'; 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 ElapsedTime from './ElapsedTime';
import QueryRows from './QueryRows'; import QueryRows from './QueryRows';
...@@ -519,7 +523,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -519,7 +523,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
const logsButtonActive = showingLogs ? 'active' : ''; const logsButtonActive = showingLogs ? 'active' : '';
const tableButtonActive = showingBoth || showingTable ? 'active' : ''; const tableButtonActive = showingBoth || showingTable ? 'active' : '';
const exploreClass = split ? 'explore explore-split' : 'explore'; 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 ( return (
<div className={exploreClass} ref={this.getRef}> <div className={exploreClass} ref={this.getRef}>
...@@ -541,13 +545,23 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -541,13 +545,23 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
{!datasourceMissing ? ( {!datasourceMissing ? (
<div className="navbar-buttons"> <div className="navbar-buttons">
<Select <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" className="gf-form-input gf-form-input--form-dropdown datasource-picker"
onChange={this.onChangeDatasource} onChange={this.onChangeDatasource}
options={exploreDatasources} options={exploreDatasources}
isOpen={true} styles={ResetStyles}
placeholder="Loading datasources..." placeholder="Select datasource"
loadingMessage={() => 'Loading datasources...'}
noOptionsMessage={() => 'No datasources found'}
value={selectedDatasource} value={selectedDatasource}
components={{
Option: PickerOption,
IndicatorsContainer,
NoOptionsMessage,
}}
/> />
</div> </div>
) : null} ) : null}
......
...@@ -83,10 +83,8 @@ export class TeamMembers extends PureComponent<Props, State> { ...@@ -83,10 +83,8 @@ export class TeamMembers extends PureComponent<Props, State> {
} }
render() { render() {
const { newTeamMember, isAdding } = this.state; const { isAdding } = this.state;
const { searchMemberQuery, members, syncEnabled } = this.props; const { searchMemberQuery, members, syncEnabled } = this.props;
const newTeamMemberValue = newTeamMember && newTeamMember.id.toString();
return ( return (
<div> <div>
<div className="page-action-bar"> <div className="page-action-bar">
...@@ -117,8 +115,7 @@ export class TeamMembers extends PureComponent<Props, State> { ...@@ -117,8 +115,7 @@ export class TeamMembers extends PureComponent<Props, State> {
</button> </button>
<h5>Add Team Member</h5> <h5>Add Team Member</h5>
<div className="gf-form-inline"> <div className="gf-form-inline">
<UserPicker onSelected={this.onUserSelected} className="width-30" value={newTeamMemberValue} /> <UserPicker onSelected={this.onUserSelected} className="width-30" />
{this.state.newTeamMember && ( {this.state.newTeamMember && (
<button className="btn btn-success gf-form-btn" type="submit" onClick={this.onAddUserToTeam}> <button className="btn btn-success gf-form-btn" type="submit" onClick={this.onAddUserToTeam}>
Add to team Add to team
......
...@@ -60,7 +60,6 @@ exports[`Render should render component 1`] = ` ...@@ -60,7 +60,6 @@ exports[`Render should render component 1`] = `
<UserPicker <UserPicker
className="width-30" className="width-30"
onSelected={[Function]} onSelected={[Function]}
value={null}
/> />
</div> </div>
</div> </div>
...@@ -155,7 +154,6 @@ exports[`Render should render team members 1`] = ` ...@@ -155,7 +154,6 @@ exports[`Render should render team members 1`] = `
<UserPicker <UserPicker
className="width-30" className="width-30"
onSelected={[Function]} onSelected={[Function]}
value={null}
/> />
</div> </div>
</div> </div>
...@@ -376,7 +374,6 @@ exports[`Render should render team members when sync enabled 1`] = ` ...@@ -376,7 +374,6 @@ exports[`Render should render team members when sync enabled 1`] = `
<UserPicker <UserPicker
className="width-30" className="width-30"
onSelected={[Function]} onSelected={[Function]}
value={null}
/> />
</div> </div>
</div> </div>
......
...@@ -194,6 +194,7 @@ $input-box-shadow-focus: rgba(102, 175, 233, 0.6); ...@@ -194,6 +194,7 @@ $input-box-shadow-focus: rgba(102, 175, 233, 0.6);
$input-color-placeholder: $gray-1 !default; $input-color-placeholder: $gray-1 !default;
$input-label-bg: $gray-blue; $input-label-bg: $gray-blue;
$input-label-border-color: $dark-3; $input-label-border-color: $dark-3;
$input-color-select-arrow: $white;
// Search // Search
$search-shadow: 0 0 30px 0 $black; $search-shadow: 0 0 30px 0 $black;
......
...@@ -190,6 +190,7 @@ $input-box-shadow-focus: $blue !default; ...@@ -190,6 +190,7 @@ $input-box-shadow-focus: $blue !default;
$input-color-placeholder: $gray-4 !default; $input-color-placeholder: $gray-4 !default;
$input-label-bg: $gray-5; $input-label-bg: $gray-5;
$input-label-border-color: $gray-5; $input-label-border-color: $gray-5;
$input-color-select-arrow: $gray-1;
// Sidemenu // Sidemenu
// ------------------------- // -------------------------
......
$select-input-height: 35px; $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-input-bg-disabled: $input-bg-disabled;
$select-option-selected-bg: $dropdownLinkBackgroundActive;
@import '../../../node_modules/react-select/scss/default.scss';
@mixin select-control() { @mixin select-control() {
width: 100%; width: 100%;
...@@ -29,73 +15,113 @@ $select-option-selected-bg: $dropdownLinkBackgroundActive; ...@@ -29,73 +15,113 @@ $select-option-selected-bg: $dropdownLinkBackgroundActive;
@include box-shadow($shadow); @include box-shadow($shadow);
} }
// react-select tweaks .gf-form-select-box__control {
.gf-form-input--form-dropdown { @include select-control();
padding: 0; border: 1px solid $input-border-color;
border: 0; color: $input-color;
overflow: visible; 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 { .gf-form-select-box__control--menu-right {
color: $input-color-placeholder; .gf-form-select-box__menu {
right: 0;
left: unset;
} }
}
> .Select-control { .gf-form-select-box__input {
@include select-control(); padding-left: 5px;
border-color: $dark-3; }
input { .gf-form-select-box__menu {
min-width: 1rem; background: $dropdownBackground;
} position: absolute;
z-index: 2;
}
.Select-clear, .tag-filter .gf-form-select-box__menu {
.Select-arrow { width: 100%;
margin-right: 8px; }
}
.Select-value { .gf-form-select-box__multi-value {
display: inline-block; display: inline;
padding: $input-padding-y $input-padding-x; }
font-size: $font-size-md;
line-height: $input-line-height;
vertical-align: baseline;
white-space: nowrap;
}
}
&.is-open > .Select-control { .gf-form-select-box__option {
background: transparent; border-left: 2px solid transparent;
border-color: $dark-3;
}
&.is-focused > .Select-control { &.gf-form-select-box__option--is-focused {
background-color: $input-bg; color: $dropdownLinkColorHover;
@include select-control-focus(); background-color: $dropdownLinkBackgroundHover;
@include left-brand-border-gradient();
} }
&.is-focused:not(.is-open) > .Select-control { &.gf-form-select-box__option--is-selected {
background-color: $input-bg; .fa {
@include select-control-focus(); color: $input-color-select-arrow;
}
} }
}
.Select-menu-outer { .gf-form-select-box__control--is-focused .gf-form-select-box__placeholder {
border: 0; display: none;
width: auto; }
}
.Select-option { .gf-form-select-box__value-container {
border-left: 2px solid transparent; display: table-cell;
padding: 8px 10px;
> div {
display: inline-block;
} }
}
.Select-option.is-focused { .gf-form-select-box__indicators {
background-color: $dropdownLinkBackgroundHover; display: table-cell;
color: $dropdownLinkColorHover; vertical-align: middle;
@include left-brand-border-gradient(); 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 { .gf-form--has-input-icon {
.Select-menu-outer { .gf-form-select-box__value-container {
right: 0; padding-left: 30px;
left: unset;
} }
} }
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
.tag { .tag {
margin-right: 2px; margin-right: 2px;
color: white; color: $white;
[data-role='remove'] { [data-role='remove'] {
margin-left: 8px; margin-left: 8px;
...@@ -63,13 +63,6 @@ ...@@ -63,13 +63,6 @@
.tag-count-label { .tag-count-label {
margin-left: 3px; margin-left: 3px;
} }
.gf-form-input--form-dropdown {
.Select-menu-outer {
border: 0;
width: 100%;
}
}
} }
.tag-filter-values { .tag-filter-values {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment