Commit 5931d6c8 by Johannes Schill

ux: POC on new select box for the user picker (#10289)

parent 04b97529
......@@ -149,6 +149,7 @@
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-grid-layout": "^0.16.1",
"react-select": "^1.1.0",
"react-sizeme": "^2.3.6",
"remarkable": "^1.7.1",
"rxjs": "^5.4.3",
......
......@@ -4,6 +4,7 @@ import PageHeader from "./components/PageHeader/PageHeader";
import EmptyListCTA from "./components/EmptyListCTA/EmptyListCTA";
import LoginBackground from "./components/Login/LoginBackground";
import { SearchResult } from "./components/search/SearchResult";
import UserPicker from "./components/UserPicker/UserPicker";
export function registerAngularDirectives() {
react2AngularDirective("passwordStrength", PasswordStrength, ["password"]);
......@@ -11,4 +12,9 @@ export function registerAngularDirectives() {
react2AngularDirective("emptyListCta", EmptyListCTA, ["model"]);
react2AngularDirective("loginBackground", LoginBackground, []);
react2AngularDirective("searchResult", SearchResult, []);
react2AngularDirective("userPickerr", UserPicker, [
"backendSrv",
"teamId",
"refreshList"
]);
}
import React, { Component } from "react";
import { debounce } from "lodash";
import Select from "react-select";
import UserPickerOption from "./UserPickerOption";
export interface IProps {
backendSrv: any;
teamId: string;
refreshList: any;
}
export interface User {
id: number;
name: string;
login: string;
email: string;
}
class UserPicker extends Component<IProps, any> {
debouncedSearchUsers: any;
backendSrv: any;
teamId: string;
refreshList: any;
constructor(props) {
super(props);
this.backendSrv = this.props.backendSrv;
this.teamId = this.props.teamId;
this.refreshList = this.props.refreshList;
this.searchUsers = this.searchUsers.bind(this);
this.handleChange = this.handleChange.bind(this);
this.addUser = this.addUser.bind(this);
this.toggleLoading = this.toggleLoading.bind(this);
this.debouncedSearchUsers = debounce(this.searchUsers, 300, {
leading: true,
trailing: false
});
this.state = {
multi: false,
isLoading: false
};
}
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps", nextProps);
}
handleChange(user) {
console.log("user", user);
this.addUser(user.id);
}
toggleLoading(isLoading) {
this.setState(prevState => {
return {
...prevState,
isLoading: isLoading
};
});
}
addUser(userId) {
this.toggleLoading(true);
this.backendSrv
.post(`/api/teams/${this.teamId}/members`, { userId: userId })
.then(() => {
this.refreshList(); // this.get() in the angular controller
this.toggleLoading(false);
// this.$scope.$broadcast('user-picker-reset'); // TODO?
});
}
searchUsers(query) {
this.toggleLoading(true);
return this.backendSrv
.get(`/api/users/search?perpage=10&page=1&query=${query}`)
.then(result => {
const users = result.users.map(user => {
return {
id: user.id,
label: `${user.login} - ${user.email}`,
avatarUrl: user.avatarUrl
};
});
this.toggleLoading(false);
return { options: users };
});
}
render() {
const AsyncComponent = this.state.creatable
? Select.AsyncCreatable
: Select.Async;
return (
<div className="user-picker">
<AsyncComponent
valueKey="id"
multi={this.state.multi}
labelKey="label"
cache={false}
isLoading={this.state.isLoading}
loadOptions={this.debouncedSearchUsers}
loadingPlaceholder="Loading..."
onChange={this.handleChange}
className="width-8 gf-form-input gf-form-input--form-dropdown"
optionComponent={UserPickerOption}
placeholder="Choose"
/>
</div>
);
}
}
export default UserPicker;
import React, { Component } from "react";
class UserPickerOption extends Component {
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"
/>
{children}
</button>
);
}
}
export default UserPickerOption;
......@@ -30,7 +30,14 @@
<form name="ctrl.addMemberForm" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Add member</span>
<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
</div>
</form>
<form name="ctrl.addMemberForm" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Add member</span>
<user-pickerr backendSrv="ctrl.backendSrv" teamId="ctrl.$routeParams.id" refreshList="ctrl.get" teamMembers="ctrl.teamMembers"></user-pickerr>
</div>
</form>
......
import coreModule from 'app/core/core_module';
import coreModule from "app/core/core_module";
export default class TeamDetailsCtrl {
team: Team;
......@@ -6,8 +6,14 @@ export default class TeamDetailsCtrl {
navModel: any;
/** @ngInject **/
constructor(private $scope, private backendSrv, private $routeParams, navModelSrv) {
this.navModel = navModelSrv.getNav('cfg', 'teams', 0);
constructor(
private $scope,
private backendSrv,
private $routeParams,
navModelSrv
) {
this.navModel = navModelSrv.getNav("cfg", "teams", 0);
this.get = this.get.bind(this);
this.get();
}
......@@ -35,7 +41,7 @@ export default class TeamDetailsCtrl {
}
removeMemberConfirmed(teamMember: TeamMember) {
this.backendSrv.delete(`/api/teams/${this.$routeParams.id}/members/${teamMember.userId}`).then(this.get.bind(this));
this.backendSrv.delete(`/api/teams/${this.$routeParams.id}/members/${teamMember.userId}`).then(this.get);
}
update() {
......
import coreModule from "app/core/core_module";
export default class TeamDetailsCtrl {
team: Team;
teamMembers: User[] = [];
navModel: any;
/** @ngInject **/
constructor(
private $scope,
private backendSrv,
private $routeParams,
navModelSrv
) {
this.navModel = navModelSrv.getNav("cfg", "teams", 0);
this.get = this.get.bind(this);
this.get();
}
get() {
if (this.$routeParams && this.$routeParams.id) {
this.backendSrv.get(`/api/teams/${this.$routeParams.id}`).then(result => {
this.team = result;
});
<<<<<<< HEAD
this.backendSrv.get(`/api/teams/${this.$routeParams.id}/members`).then(result => {
this.teamMembers = result;
});
=======
this.backendSrv
.get(`/api/teams/${this.$routeParams.id}/members`)
.then(result => {
this.teamMembers = result;
});
>>>>>>> ux: POC on new select box for the user picker (#10289)
}
}
removeTeamMember(teamMember: TeamMember) {
<<<<<<< HEAD
this.$scope.appEvent('confirm-modal', {
title: 'Remove Member',
text: 'Are you sure you want to remove ' + teamMember.login + ' from this group?',
yesText: 'Remove',
icon: 'fa-warning',
=======
this.$scope.appEvent("confirm-modal", {
title: "Remove Member",
text:
"Are you sure you want to remove " +
teamMember.login +
" from this group?",
yesText: "Remove",
icon: "fa-warning",
>>>>>>> ux: POC on new select box for the user picker (#10289)
onConfirm: () => {
this.removeMemberConfirmed(teamMember);
},
});
}
removeMemberConfirmed(teamMember: TeamMember) {
<<<<<<< HEAD
this.backendSrv.delete(`/api/teams/${this.$routeParams.id}/members/${teamMember.userId}`).then(this.get.bind(this));
=======
this.backendSrv
.delete(`/api/teams/${this.$routeParams.id}/members/${teamMember.userId}`)
.then(this.get);
>>>>>>> ux: POC on new select box for the user picker (#10289)
}
update() {
if (!this.$scope.teamDetailsForm.$valid) {
return;
}
<<<<<<< HEAD
this.backendSrv.put('/api/teams/' + this.team.id, {
name: this.team.name,
email: this.team.email,
});
}
userPicked(user) {
this.backendSrv.post(`/api/teams/${this.$routeParams.id}/members`, { userId: user.id }).then(() => {
this.$scope.$broadcast('user-picker-reset');
this.get();
});
=======
this.backendSrv.put("/api/teams/" + this.team.id, { name: this.team.name });
}
userPicked(user) {
this.backendSrv
.post(`/api/teams/${this.$routeParams.id}/members`, { userId: user.id })
.then(() => {
this.$scope.$broadcast("user-picker-reset");
this.get();
});
>>>>>>> ux: POC on new select box for the user picker (#10289)
}
}
export interface Team {
id: number;
name: string;
email: string;
}
export interface User {
id: number;
name: string;
login: string;
email: string;
}
export interface TeamMember {
userId: number;
name: string;
login: string;
}
<<<<<<< HEAD
coreModule.controller('TeamDetailsCtrl', TeamDetailsCtrl);
=======
coreModule.controller("TeamDetailsCtrl", TeamDetailsCtrl);
>>>>>>> ux: POC on new select box for the user picker (#10289)
import coreModule from 'app/core/core_module';
export default class TeamDetailsCtrl {
team: Team;
teamMembers: User[] = [];
navModel: any;
/** @ngInject **/
constructor(private $scope, private backendSrv, private $routeParams, navModelSrv) {
this.navModel = navModelSrv.getNav('cfg', 'teams', 0);
this.get();
}
get() {
if (this.$routeParams && this.$routeParams.id) {
this.backendSrv.get(`/api/teams/${this.$routeParams.id}`)
.then(result => {
this.team = result;
});
this.backendSrv.get(`/api/teams/${this.$routeParams.id}/members`)
.then(result => {
this.teamMembers = result;
});
}
}
removeTeamMember(teamMember: TeamMember) {
this.$scope.appEvent('confirm-modal', {
title: 'Remove Member',
text: 'Are you sure you want to remove ' + teamMember.login + ' from this group?',
yesText: "Remove",
icon: "fa-warning",
onConfirm: () => {
this.removeMemberConfirmed(teamMember);
}
});
}
removeMemberConfirmed(teamMember: TeamMember) {
this.backendSrv.delete(`/api/teams/${this.$routeParams.id}/members/${teamMember.userId}`)
.then(this.get.bind(this));
}
update() {
if (!this.$scope.teamDetailsForm.$valid) { return; }
this.backendSrv.put('/api/teams/' + this.team.id, {name: this.team.name});
}
userPicked(user) {
this.backendSrv.post(`/api/teams/${this.$routeParams.id}/members`, {userId: user.id}).then(() => {
this.$scope.$broadcast('user-picker-reset');
this.get();
});
}
}
export interface Team {
id: number;
name: string;
}
export interface User {
id: number;
name: string;
login: string;
email: string;
}
export interface TeamMember {
userId: number;
name: string;
login: string;
}
coreModule.controller('TeamDetailsCtrl', TeamDetailsCtrl);
import coreModule from 'app/core/core_module';
export default class TeamDetailsCtrl {
team: Team;
teamMembers: User[] = [];
navModel: any;
/** @ngInject **/
constructor(private $scope, private backendSrv, private $routeParams, navModelSrv) {
this.navModel = navModelSrv.getNav('cfg', 'teams', 0);
this.get();
}
get() {
if (this.$routeParams && this.$routeParams.id) {
this.backendSrv.get(`/api/teams/${this.$routeParams.id}`).then(result => {
this.team = result;
});
this.backendSrv.get(`/api/teams/${this.$routeParams.id}/members`).then(result => {
this.teamMembers = result;
});
}
}
removeTeamMember(teamMember: TeamMember) {
this.$scope.appEvent('confirm-modal', {
title: 'Remove Member',
text: 'Are you sure you want to remove ' + teamMember.login + ' from this group?',
yesText: 'Remove',
icon: 'fa-warning',
onConfirm: () => {
this.removeMemberConfirmed(teamMember);
},
});
}
removeMemberConfirmed(teamMember: TeamMember) {
this.backendSrv.delete(`/api/teams/${this.$routeParams.id}/members/${teamMember.userId}`).then(this.get.bind(this));
}
update() {
if (!this.$scope.teamDetailsForm.$valid) {
return;
}
this.backendSrv.put('/api/teams/' + this.team.id, {
name: this.team.name,
email: this.team.email,
});
}
userPicked(user) {
this.backendSrv.post(`/api/teams/${this.$routeParams.id}/members`, { userId: user.id }).then(() => {
this.$scope.$broadcast('user-picker-reset');
this.get();
});
}
}
export interface Team {
id: number;
name: string;
email: string;
}
export interface User {
id: number;
name: string;
login: string;
email: string;
}
export interface TeamMember {
userId: number;
name: string;
login: string;
}
coreModule.controller('TeamDetailsCtrl', TeamDetailsCtrl);
import coreModule from "app/core/core_module";
export default class TeamDetailsCtrl {
team: Team;
teamMembers: User[] = [];
navModel: any;
/** @ngInject **/
constructor(
private $scope,
private backendSrv,
private $routeParams,
navModelSrv
) {
this.navModel = navModelSrv.getNav("cfg", "teams", 0);
this.get = this.get.bind(this);
this.get();
}
get() {
if (this.$routeParams && this.$routeParams.id) {
this.backendSrv.get(`/api/teams/${this.$routeParams.id}`).then(result => {
this.team = result;
});
this.backendSrv
.get(`/api/teams/${this.$routeParams.id}/members`)
.then(result => {
this.teamMembers = result;
});
}
}
removeTeamMember(teamMember: TeamMember) {
this.$scope.appEvent("confirm-modal", {
title: "Remove Member",
text:
"Are you sure you want to remove " +
teamMember.login +
" from this group?",
yesText: "Remove",
icon: "fa-warning",
onConfirm: () => {
this.removeMemberConfirmed(teamMember);
}
});
}
removeMemberConfirmed(teamMember: TeamMember) {
this.backendSrv
.delete(`/api/teams/${this.$routeParams.id}/members/${teamMember.userId}`)
.then(this.get);
}
update() {
if (!this.$scope.teamDetailsForm.$valid) {
return;
}
this.backendSrv.put("/api/teams/" + this.team.id, { name: this.team.name });
}
userPicked(user) {
this.backendSrv
.post(`/api/teams/${this.$routeParams.id}/members`, { userId: user.id })
.then(() => {
this.$scope.$broadcast("user-picker-reset");
this.get();
});
}
}
export interface Team {
id: number;
name: string;
}
export interface User {
id: number;
name: string;
login: string;
email: string;
}
export interface TeamMember {
userId: number;
name: string;
login: string;
}
coreModule.controller("TeamDetailsCtrl", TeamDetailsCtrl);
......@@ -88,7 +88,8 @@
@import "components/page_header";
@import "components/dashboard_settings";
@import "components/empty_list_cta";
@import "components/user-picker";
@import "components/form_dropdown";
// PAGES
@import "pages/login";
@import "pages/dashboard";
......
$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: $dropdownBackground;
$select-option-color: $input-color;
@import "../../../node_modules/react-select/scss/default.scss";
@mixin select-control() {
width: 100%;
margin-right: $gf-form-margin;
@include border-radius($input-border-radius-sm);
background-color: $input-bg;
}
@mixin select-control-focus() {
border-color: $input-border-focus;
outline: none;
$shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px $input-box-shadow-focus;
@include box-shadow($shadow);
}
// gf-
// .form-dropdown {
// }
.gf-form-input--form-dropdown {
padding: 0;
border: 0;
overflow: visible;
.Select-placeholder {
color: #d8d9da;
}
> .Select-control {
@include select-control();
border-color: #262628;
}
&.is-open > .Select-control {
background: transparent;
border-color: #262628;
}
&.is-focused > .Select-control {
background-color: $input-bg;
@include select-control-focus();
}
.Select-menu-outer {
border: 0;
width: auto;
}
.Select-option.is-focused {
background-color: $dropdownLinkBackgroundHover;
color: $dropdownLinkColorHover;
&::before {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 2px;
display: block;
content: "";
background-image: linear-gradient(
to bottom,
#ffd500 0%,
#ff4400 99%,
#ff4400 100%
);
}
}
}
// gf-form-input--dropdown
// @mixin select-control() {
// width: 100%;
// margin-right: $gf-form-margin;
// @include border-radius($input-border-radius-sm);
// }
// @mixin select-control-focus() {
// border-color: $input-border-focus;
// outline: none;
// $shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px $input-box-shadow-focus;
// @include box-shadow($shadow);
// }
// .gf-form-dropdown-react {
// padding: 0px;
// }
// .Select {
// &.is-focused > .Select-control {
// background-color: $input-bg;
// @include select-control-focus();
// }
// &.is-focused:not(.is-open)>.Select-control,
// &.is-focused:not(.is-open).is-pseudo-focused>.Select-control {
// background-color: $input-label-bg;
// border: none;
// box-shadow: none;
// }
// &.has-value.Select--single>.Select-control .Select-value,
// &.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value {
// .Select-value-label {
// color: $input-color;
// }
// }
// }
// .gf-form-label {
// .Select-control {
// @include select-control();
// font-size: $font-size-sm;
// color: $input-color;
// background-color: $input-label-bg; // padding: $input-padding-y $input-padding-x;
// display: block;
// border: $input-btn-border-width solid transparent;
// }
// }
// .gf-form-input {
// overflow: visible;
// .Select-control {
// @include select-control();
// background-color: $input-bg;
// color: $input-color;
// border: $input-btn-border-width solid $input-border-color;
// }
// }
// .Select-menu-outer {
// margin: 2px 0 0;
// border: 1px solid $dropdownBorder;
// background: $dropdownBackground;
// @include border-radius($input-border-radius-sm);
// }
// .Select-option {
// font-size: $font-size-sm;
// padding: 3px 20px 3px 15px;
// &:last-child {
// @include border-radius($input-border-radius-sm);
// }
// &.is-selected {
// background-color: $dropdownLinkBackgroundHover;
// color: $dropdownLinkColorHover;
// }
// &.is-focused {
// background-color: $dropdownLinkBackgroundHover;
// color: $dropdownLinkColorHover;
// }
// &.is-disabled {
// color: $gray-2;
// }
// }
.user-picker-option__button {
position: relative;
text-align: left;
width: 100%;
background-color: #171819;
display: block;
border-radius: 0;
// &:hover {
// background-color: #262628;
// // background-color: blue;
// &::before {
// position: absolute;
// left: 0;
// top: 0;
// height: 100%;
// width: 2px;
// display: block;
// content: '';
// background-image: linear-gradient(to bottom, #ffd500 0%, #ff4400 99%, #ff4400 100%);
// }
// }
}
.user-picker-option__avatar {
width: 20px;
display: inline-block;
margin-right: 10px;
}
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