mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
wip: Upgrade react-select #13425
This commit is contained in:
@@ -17,6 +17,10 @@ export interface Props {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export interface TeamSelectedAction {
|
||||
action: string;
|
||||
}
|
||||
|
||||
class AddPermissions extends Component<Props, NewDashboardAclItem> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -50,11 +54,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 });
|
||||
onTeamSelected = (team: Team, info: TeamSelectedAction) => {
|
||||
this.setState({ teamId: team && !Array.isArray(team) ? team.id : 0 });
|
||||
};
|
||||
|
||||
onPermissionChanged = (permission: OptionWithDescription) => {
|
||||
@@ -82,7 +86,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 +110,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} value={newItem.userId} 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} value={newItem.teamId} className={pickerClassName} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ export default class PermissionsListItem extends PureComponent<Props> {
|
||||
onSelected={this.onPermissionChanged}
|
||||
value={item.permission}
|
||||
disabled={item.inherited}
|
||||
className={'gf-form-input--form-dropdown-right'}
|
||||
className={'gf-form-select2__control--menu-right'}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -1,56 +1,27 @@
|
||||
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;
|
||||
children: Element;
|
||||
isSelected: boolean;
|
||||
data: any;
|
||||
getStyles: 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: OptionProps<any>) => {
|
||||
const { children, isSelected, data } = props;
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<div className={`description-picker-option__button btn btn-link width-19`}>
|
||||
{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;
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import Select from 'react-select';
|
||||
import DescriptionOption from './DescriptionOption';
|
||||
|
||||
export interface Props {
|
||||
optionsWithDesc: OptionWithDescription[];
|
||||
onSelected: (permission) => void;
|
||||
value: number;
|
||||
disabled: boolean;
|
||||
className?: string;
|
||||
}
|
||||
import IndicatorsContainer from './IndicatorsContainer';
|
||||
import ResetStyles from './ResetStyles';
|
||||
import NoOptionsMessage from './NoOptionsMessage';
|
||||
|
||||
export interface OptionWithDescription {
|
||||
value: any;
|
||||
@@ -16,29 +11,38 @@ export interface OptionWithDescription {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
optionsWithDesc: OptionWithDescription[];
|
||||
onSelected: (permission) => void;
|
||||
disabled: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
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 } = this.props;
|
||||
return (
|
||||
<div className="permissions-picker">
|
||||
<Select
|
||||
value={value}
|
||||
valueKey="value"
|
||||
multi={false}
|
||||
clearable={false}
|
||||
labelKey="label"
|
||||
options={optionsWithDesc}
|
||||
onChange={onSelected}
|
||||
className={`width-7 gf-form-input gf-form-input--form-dropdown ${className || ''}`}
|
||||
optionComponent={DescriptionOption}
|
||||
placeholder="Choose"
|
||||
disabled={disabled}
|
||||
classNamePrefix={`gf-form-select2`}
|
||||
className={`width-7 gf-form-input gf-form-input--form-dropdown ${className || ''}`}
|
||||
options={optionsWithDesc}
|
||||
components={{
|
||||
Option: DescriptionOption,
|
||||
IndicatorsContainer,
|
||||
NoOptionsMessage,
|
||||
}}
|
||||
styles={ResetStyles}
|
||||
isDisabled={disabled}
|
||||
onChange={onSelected}
|
||||
getOptionValue={i => i.value}
|
||||
getOptionLabel={i => i.label}
|
||||
// menuIsOpen={true} // debug
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
13
public/app/core/components/Picker/IndicatorsContainer.tsx
Normal file
13
public/app/core/components/Picker/IndicatorsContainer.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
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-select2__select-arrow ${isOpen ? `gf-form-select2__select-arrow--reversed` : ''}`} />
|
||||
</components.IndicatorsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndicatorsContainer;
|
||||
18
public/app/core/components/Picker/NoOptionsMessage.tsx
Normal file
18
public/app/core/components/Picker/NoOptionsMessage.tsx
Normal file
@@ -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 } = props;
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<div className={`description-picker-option__button btn btn-link width-19`}>{children}</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
export default PickerOption;
|
||||
@@ -1,54 +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;
|
||||
children: Element;
|
||||
isSelected: boolean;
|
||||
data: any;
|
||||
getStyles: any;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
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: OptionProps<any>) => {
|
||||
const { children, data } = props;
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<div className={`description-picker-option__button btn btn-link width-19`}>
|
||||
<img src={data.avatarUrl} alt={data.label} className="user-picker-option__avatar" />
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserPickerOption;
|
||||
export default PickerOption;
|
||||
|
||||
23
public/app/core/components/Picker/ResetStyles.tsx
Normal file
23
public/app/core/components/Picker/ResetStyles.tsx
Normal file
@@ -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: () => ({}),
|
||||
};
|
||||
@@ -1,18 +1,11 @@
|
||||
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';
|
||||
|
||||
export interface Props {
|
||||
onSelected: (team: Team) => void;
|
||||
value?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
isLoading;
|
||||
}
|
||||
import ResetStyles from './ResetStyles';
|
||||
import IndicatorsContainer from './IndicatorsContainer';
|
||||
import NoOptionsMessage from './NoOptionsMessage';
|
||||
|
||||
export interface Team {
|
||||
id: number;
|
||||
@@ -21,6 +14,15 @@ export interface Team {
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
onSelected: (team: Team) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export class TeamPicker extends Component<Props, State> {
|
||||
debouncedSearch: any;
|
||||
|
||||
@@ -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,35 @@ 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-select2`}
|
||||
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}
|
||||
// menuIsOpen={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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`)
|
||||
.get(`/api/org/users?query=${query}&limit=1`)
|
||||
.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,31 @@ 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-select2`}
|
||||
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}
|
||||
// menuIsOpen={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
21
public/app/core/components/Picker/ValueContainer.tsx
Normal file
21
public/app/core/components/Picker/ValueContainer.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { components } from 'react-select';
|
||||
|
||||
export const ValueContainer = props => {
|
||||
const { children, getValue, options } = props;
|
||||
console.log('getValue', getValue());
|
||||
console.log('options', options);
|
||||
const existingValue = getValue();
|
||||
const selectedOption = options.find(i => (existingValue[0] ? i.id === existingValue[0].id : undefined));
|
||||
console.log('selectedOption', selectedOption);
|
||||
return (
|
||||
<components.ValueContainer {...props}>
|
||||
{children}
|
||||
{/* {selectedOption ?
|
||||
<span>{selectedOption.label}</span>
|
||||
: children} */}
|
||||
</components.ValueContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ValueContainer;
|
||||
@@ -85,8 +85,8 @@ export class TeamMembers extends PureComponent<Props, State> {
|
||||
render() {
|
||||
const { newTeamMember, isAdding } = this.state;
|
||||
const { searchMemberQuery, members, syncEnabled } = this.props;
|
||||
const newTeamMemberValue = newTeamMember && newTeamMember.id.toString();
|
||||
|
||||
const newTeamMemberValue = newTeamMember && newTeamMember;
|
||||
console.log('newTeamMemberValue', newTeamMemberValue);
|
||||
return (
|
||||
<div>
|
||||
<div className="page-action-bar">
|
||||
@@ -117,8 +117,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
|
||||
|
||||
Reference in New Issue
Block a user