]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6690 update the style of select list component
authorStas Vilchik <vilchiks@gmail.com>
Fri, 21 Aug 2015 11:35:08 +0000 (13:35 +0200)
committerStas Vilchik <vilchiks@gmail.com>
Mon, 24 Aug 2015 07:44:55 +0000 (09:44 +0200)
14 files changed:
server/sonar-web/src/main/js/api/permissions.jsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/global-permissions/groups-view.js
server/sonar-web/src/main/js/apps/global-permissions/users-view.js
server/sonar-web/src/main/js/apps/project-permissions/groups-view.js
server/sonar-web/src/main/js/apps/project-permissions/users-view.js
server/sonar-web/src/main/js/components/select-list/controls.jsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/select-list/footer.jsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/select-list/item.jsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/select-list/list.jsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/select-list/main.jsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/shared/checkbox.jsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/shared/radio-toggle.jsx [new file with mode: 0644]
server/sonar-web/src/main/js/libs/third-party/classNames.js [new file with mode: 0644]
server/sonar-web/src/main/less/components/select-list.less

diff --git a/server/sonar-web/src/main/js/api/permissions.jsx b/server/sonar-web/src/main/js/api/permissions.jsx
new file mode 100644 (file)
index 0000000..fb565c2
--- /dev/null
@@ -0,0 +1,92 @@
+function _request(options) {
+  let $ = jQuery;
+  return $.ajax(options);
+}
+
+function _url(path) {
+  return window.baseUrl + path;
+}
+
+function _typeError(method, message) {
+  throw new TypeError(`permissions#${method}: ${message}`);
+}
+
+
+export function getUsers(data) {
+  let url = _url('/api/permissions/users');
+  return _request({ type: 'GET', url: url, data: data });
+}
+
+
+export function grantToUser(permission, user, project) {
+  if (typeof permission !== 'string' || !permission.length) {
+    return _typeError('grantToUser', 'please provide permission');
+  }
+  if (typeof user !== 'string' || !user.length) {
+    return _typeError('grantToUser', 'please provide user login');
+  }
+
+  let url = _url('/api/permissions/add_user');
+  let data = { permission: permission, login: user };
+  if (project) {
+    data.projectId = project;
+  }
+  return _request({ type: 'POST', url: url, data: data });
+}
+
+
+export function revokeFromUser(permission, user, project) {
+  if (typeof permission !== 'string' || !permission.length) {
+    return _typeError('revokeFromUser', 'please provide permission');
+  }
+  if (typeof user !== 'string' || !user.length) {
+    return _typeError('revokeFromUser', 'please provide user login');
+  }
+
+  let url = _url('/api/permissions/remove_user');
+  let data = { permission: permission, login: user };
+  if (project) {
+    data.projectId = project;
+  }
+  return _request({ type: 'POST', url: url, data: data });
+}
+
+
+export function getGroups(data) {
+  let url = _url('/api/permissions/groups');
+  return _request({ type: 'GET', url: url, data: data });
+}
+
+
+export function grantToGroup(permission, group, project) {
+  if (typeof permission !== 'string' || !permission.length) {
+    return _typeError('grantToGroup', 'please provide permission');
+  }
+  if (typeof group !== 'string' || !group.length) {
+    return _typeError('grantToGroup', 'please provide group name');
+  }
+
+  let url = _url('/api/permissions/add_group');
+  let data = { permission: permission, groupName: group };
+  if (project) {
+    data.projectId = project;
+  }
+  return _request({ type: 'POST', url: url, data: data });
+}
+
+
+export function revokeFromGroup(permission, group, project) {
+  if (typeof permission !== 'string' || !permission.length) {
+    return _typeError('revokeFromGroup', 'please provide permission');
+  }
+  if (typeof group !== 'string' || !group.length) {
+    return _typeError('revokeFromGroup', 'please provide group name');
+  }
+
+  let url = _url('/api/permissions/remove_group');
+  let data = { permission: permission, groupName: group };
+  if (project) {
+    data.projectId = project;
+  }
+  return _request({ type: 'POST', url: url, data: data });
+}
index 5b25ec62db27be447062f66499d8a9a76c75c486..3e515d9403154fe619ddbb99171701e7a8073a9e 100644 (file)
@@ -1,36 +1,43 @@
 define([
   'components/common/modals',
-  'components/common/select-list',
+  'react',
+  'components/select-list/main',
+  '../../api/permissions',
   './templates'
-], function (Modal) {
+], function (Modal, React, SelectList, Permissions) {
 
   return Modal.extend({
     template: Templates['global-permissions-groups'],
 
     onRender: function () {
+      var that = this;
       this._super();
-      new window.SelectList({
-        el: this.$('#global-permissions-groups'),
-        width: '100%',
-        readOnly: false,
-        focusSearch: false,
-        format: function (item) {
-          return item.name;
+      var props = {
+        loadItems: function (options, callback) {
+          var _data = { permission: that.options.permission, p: options.page, ps: 100 };
+          options.query ? _.extend(_data, { q: options.query }) : _.extend(_data, { selected: options.selection });
+          Permissions.getGroups(_data).done(function (r) {
+            var paging = _.defaults({}, r.paging, { total: 0, pageIndex: 1 });
+            callback(r.groups, paging);
+          });
         },
-        queryParam: 'q',
-        searchUrl: baseUrl + '/api/permissions/groups?ps=100&permission=' + this.options.permission,
-        selectUrl: baseUrl + '/api/permissions/add_group',
-        deselectUrl: baseUrl + '/api/permissions/remove_group',
-        extra: {
-          permission: this.options.permission
+        renderItem: function (group) {
+          return group.name;
         },
-        selectParameter: 'groupName',
-        selectParameterValue: 'name',
-        parse: function (r) {
-          this.more = false;
-          return r.groups;
+        getItemKey: function (group) {
+          return group.name;
+        },
+        selectItem: function (group, callback) {
+          Permissions.grantToGroup(that.options.permission, group.name).done(callback);
+        },
+        deselectItem: function (group, callback) {
+          Permissions.revokeFromGroup(that.options.permission, group.name).done(callback);
         }
-      });
+      };
+      React.render(
+          React.createElement(SelectList, props),
+          this.$('#global-permissions-groups')[0]
+      );
     },
 
     onDestroy: function () {
index b5660c650da6392b5187340a3b561a54e8a30af1..54c413421270a2a5f1c82b6c4e1c09152c23cb6b 100644 (file)
@@ -1,36 +1,43 @@
 define([
   'components/common/modals',
-  'components/common/select-list',
+  'react',
+  'components/select-list/main',
+  '../../api/permissions',
   './templates'
-], function (Modal) {
+], function (Modal, React, SelectList, Permissions) {
 
   return Modal.extend({
     template: Templates['global-permissions-users'],
 
     onRender: function () {
+      var that = this;
       this._super();
-      new window.SelectList({
-        el: this.$('#global-permissions-users'),
-        width: '100%',
-        readOnly: false,
-        focusSearch: false,
-        format: function (item) {
-          return item.name + '<br><span class="note">' + item.login + '</span>';
+      var props = {
+        loadItems: function (options, callback) {
+          var data = { permission: that.options.permission, p: options.page, ps: 100 };
+          options.query ? _.extend(data, { q: options.query }) : _.extend(data, { selected: options.selection });
+          Permissions.getUsers(data).done(function (r) {
+            var paging = _.defaults({}, r.paging, { total: 0, pageIndex: 1 });
+            callback(r.users, paging);
+          });
         },
-        queryParam: 'q',
-        searchUrl: baseUrl + '/api/permissions/users?ps=100&permission=' + this.options.permission,
-        selectUrl: baseUrl + '/api/permissions/add_user',
-        deselectUrl: baseUrl + '/api/permissions/remove_user',
-        extra: {
-          permission: this.options.permission
+        renderItem: function (user) {
+          return user.name + '<br><span class="note">' + user.login + '</span>';
         },
-        selectParameter: 'login',
-        selectParameterValue: 'login',
-        parse: function (r) {
-          this.more = false;
-          return r.users;
+        getItemKey: function (user) {
+          return user.login;
+        },
+        selectItem: function (user, callback) {
+          Permissions.grantToUser(that.options.permission, user.login).done(callback);
+        },
+        deselectItem: function (user, callback) {
+          Permissions.revokeFromUser(that.options.permission, user.login).done(callback);
         }
-      });
+      };
+      React.render(
+          React.createElement(SelectList, props),
+          this.$('#global-permissions-users')[0]
+      );
     },
 
     onDestroy: function () {
index ba98ca7e52514180a15cb1ff60ed3e859a00de93..0c49e3c55849e132cf111f0d14c0fe6dd442badb 100644 (file)
@@ -1,37 +1,43 @@
 define([
   'components/common/modals',
-  'components/common/select-list',
+  'react',
+  'components/select-list/main',
+  '../../api/permissions',
   './templates'
-], function (Modal) {
+], function (Modal, React, SelectList, Permissions) {
 
   return Modal.extend({
     template: Templates['project-permissions-groups'],
 
     onRender: function () {
+      var that = this;
       this._super();
-      new window.SelectList({
-        el: this.$('#project-permissions-groups'),
-        width: '100%',
-        readOnly: false,
-        focusSearch: false,
-        format: function (item) {
-          return item.name;
+      var props = {
+        loadItems: function (options, callback) {
+          var _data = { permission: that.options.permission, projectId: that.options.project, p: options.page, ps: 100 };
+          options.query ? _.extend(_data, { q: options.query }) : _.extend(_data, { selected: options.selection });
+          Permissions.getGroups(_data).done(function (r) {
+            var paging = _.defaults({}, r.paging, { total: 0, pageIndex: 1 });
+            callback(r.groups, paging);
+          });
         },
-        queryParam: 'q',
-        searchUrl: baseUrl + '/api/permissions/groups?ps=100&permission=' + this.options.permission + '&projectId=' + this.options.project,
-        selectUrl: baseUrl + '/api/permissions/add_group',
-        deselectUrl: baseUrl + '/api/permissions/remove_group',
-        extra: {
-          permission: this.options.permission,
-          projectId: this.options.project
+        renderItem: function (group) {
+          return group.name;
         },
-        selectParameter: 'groupName',
-        selectParameterValue: 'name',
-        parse: function (r) {
-          this.more = false;
-          return r.groups;
+        getItemKey: function (group) {
+          return group.name;
+        },
+        selectItem: function (group, callback) {
+          Permissions.grantToGroup(that.options.permission, group.name, that.options.project).done(callback);
+        },
+        deselectItem: function (group, callback) {
+          Permissions.revokeFromGroup(that.options.permission, group.name, that.options.project).done(callback);
         }
-      });
+      };
+      React.render(
+          React.createElement(SelectList, props),
+          this.$('#project-permissions-groups')[0]
+      );
     },
 
     onDestroy: function () {
index 6a715835ba75db9ce55a09052254c575395dd65e..0ed4f2cc000e446cbf2b49e1377ab43226dee0d2 100644 (file)
@@ -1,37 +1,43 @@
 define([
   'components/common/modals',
-  'components/common/select-list',
+  'react',
+  'components/select-list/main',
+  '../../api/permissions',
   './templates'
-], function (Modal) {
+], function (Modal, React, SelectList, Permissions) {
 
   return Modal.extend({
     template: Templates['project-permissions-users'],
 
     onRender: function () {
+      var that = this;
       this._super();
-      new window.SelectList({
-        el: this.$('#project-permissions-users'),
-        width: '100%',
-        readOnly: false,
-        focusSearch: false,
-        format: function (item) {
-          return item.name + '<br><span class="note">' + item.login + '</span>';
+      var props = {
+        loadItems: function (options, callback) {
+          var data = { permission: that.options.permission, projectId: that.options.project, p: options.page, ps: 100 };
+          options.query ? _.extend(data, { q: options.query }) : _.extend(data, { selected: options.selection });
+          Permissions.getUsers(data).done(function (r) {
+            var paging = _.defaults({}, r.paging, { total: 0, pageIndex: 1 });
+            callback(r.users, paging);
+          });
         },
-        queryParam: 'q',
-        searchUrl: baseUrl + '/api/permissions/users?ps=100&permission=' + this.options.permission + '&projectId=' + this.options.project,
-        selectUrl: baseUrl + '/api/permissions/add_user',
-        deselectUrl: baseUrl + '/api/permissions/remove_user',
-        extra: {
-          permission: this.options.permission,
-          projectId: this.options.project
+        renderItem: function (user) {
+          return user.name + '<br><span class="note">' + user.login + '</span>';
         },
-        selectParameter: 'login',
-        selectParameterValue: 'login',
-        parse: function (r) {
-          this.more = false;
-          return r.users;
+        getItemKey: function (user) {
+          return user.login;
+        },
+        selectItem: function (user, callback) {
+          Permissions.grantToUser(that.options.permission, user.login, that.options.project).done(callback);
+        },
+        deselectItem: function (user, callback) {
+          Permissions.revokeFromUser(that.options.permission, user.login, that.options.project).done(callback);
         }
-      });
+      };
+      React.render(
+          React.createElement(SelectList, props),
+          this.$('#project-permissions-users')[0]
+      );
     },
 
     onDestroy: function () {
diff --git a/server/sonar-web/src/main/js/components/select-list/controls.jsx b/server/sonar-web/src/main/js/components/select-list/controls.jsx
new file mode 100644 (file)
index 0000000..47c5bb4
--- /dev/null
@@ -0,0 +1,53 @@
+import React from 'react';
+import classNames from '../../libs/third-party/classNames';
+import RadioToggle from '../shared/radio-toggle';
+
+export default React.createClass({
+  componentWillMount() {
+    this.search = _.debounce(this.search, 100);
+  },
+
+  search() {
+    let query = React.findDOMNode(this.refs.search).value;
+    this.props.search(query);
+  },
+
+  onCheck(value) {
+    switch (value) {
+      case 'selected':
+        this.props.loadSelected();
+        break;
+      case 'deselected':
+        this.props.loadDeselected();
+        break;
+      default:
+        this.props.loadAll()
+    }
+  },
+
+  render() {
+    let selectionDisabled = !!this.props.query;
+
+    let selectionOptions = [
+      { value: 'selected', label: 'Selected' },
+      { value: 'deselected', label: 'Not Selected' },
+      { value: 'all', label: 'All' }
+    ];
+
+    return (
+        <div className="select-list-control">
+          <div className="pull-left">
+            <RadioToggle
+                name="select-list-selection"
+                options={selectionOptions}
+                onCheck={this.onCheck}
+                value={this.props.selection}
+                disabled={selectionDisabled}/>
+          </div>
+          <div className="pull-right">
+            <input onChange={this.search} ref="search" type="search" placeholder="Search" initialValue={this.props.query}/>
+          </div>
+        </div>
+    );
+  }
+});
diff --git a/server/sonar-web/src/main/js/components/select-list/footer.jsx b/server/sonar-web/src/main/js/components/select-list/footer.jsx
new file mode 100644 (file)
index 0000000..b60e32e
--- /dev/null
@@ -0,0 +1,32 @@
+import React from 'react';
+import Checkbox from '../shared/checkbox';
+
+export default React.createClass({
+  propTypes: {
+    count: React.PropTypes.number.isRequired,
+    total: React.PropTypes.number.isRequired,
+    loadMore: React.PropTypes.func.isRequired
+  },
+
+  loadMore(e) {
+    e.preventDefault();
+    this.props.loadMore();
+  },
+
+  renderLoadMoreLink() {
+    let hasMore = this.props.total > this.props.count;
+    if (!hasMore) {
+      return null;
+    }
+    return <a onClick={this.loadMore} className="spacer-left" href="#">show more</a>;
+  },
+
+  render() {
+    return (
+        <footer className="spacer-top note text-center">
+          {this.props.count}/{this.props.total} shown
+          {this.renderLoadMoreLink()}
+        </footer>
+    );
+  }
+});
diff --git a/server/sonar-web/src/main/js/components/select-list/item.jsx b/server/sonar-web/src/main/js/components/select-list/item.jsx
new file mode 100644 (file)
index 0000000..2f0fe6d
--- /dev/null
@@ -0,0 +1,27 @@
+import React from 'react';
+import Checkbox from '../shared/checkbox';
+
+export default React.createClass({
+  propTypes: {
+    item: React.PropTypes.any.isRequired,
+    renderItem: React.PropTypes.func.isRequired,
+    selectItem: React.PropTypes.func.isRequired,
+    deselectItem: React.PropTypes.func.isRequired
+  },
+
+  onCheck(checked) {
+    checked ? this.props.selectItem(this.props.item) : this.props.deselectItem(this.props.item);
+  },
+
+  render() {
+    let renderedItem = this.props.renderItem(this.props.item);
+    return (
+        <li className="panel panel-vertical">
+          <div className="display-inline-block text-middle spacer-right">
+            <Checkbox onCheck={this.onCheck} initiallyChecked={!!this.props.item.selected}/>
+          </div>
+          <div className="display-inline-block text-middle" dangerouslySetInnerHTML={{ __html: renderedItem }}/>
+        </li>
+    );
+  }
+});
diff --git a/server/sonar-web/src/main/js/components/select-list/list.jsx b/server/sonar-web/src/main/js/components/select-list/list.jsx
new file mode 100644 (file)
index 0000000..f8182f5
--- /dev/null
@@ -0,0 +1,22 @@
+import React from 'react';
+import Item from './item';
+
+export default React.createClass({
+  propTypes: {
+    items: React.PropTypes.array.isRequired,
+    renderItem: React.PropTypes.func.isRequired,
+    getItemKey: React.PropTypes.func.isRequired,
+    selectItem: React.PropTypes.func.isRequired,
+    deselectItem: React.PropTypes.func.isRequired
+  },
+
+  render() {
+    let renderedItems = this.props.items.map(item => {
+      let key = this.props.getItemKey(item);
+      return <Item key={key} {...this.props} item={item} />;
+    });
+    return (
+        <ul>{renderedItems}</ul>
+    );
+  }
+});
diff --git a/server/sonar-web/src/main/js/components/select-list/main.jsx b/server/sonar-web/src/main/js/components/select-list/main.jsx
new file mode 100644 (file)
index 0000000..bc043a8
--- /dev/null
@@ -0,0 +1,82 @@
+import React from 'react';
+import Controls from './controls';
+import List from './list';
+import Footer from './footer';
+
+export default React.createClass({
+  propTypes: {
+    loadItems: React.PropTypes.func.isRequired,
+    renderItem: React.PropTypes.func.isRequired,
+    getItemKey: React.PropTypes.func.isRequired,
+    selectItem: React.PropTypes.func.isRequired,
+    deselectItem: React.PropTypes.func.isRequired
+  },
+
+
+  getInitialState() {
+    return { items: [], total: 0, selection: 'selected', query: null };
+  },
+
+  componentDidMount() {
+    this.loadItems();
+  },
+
+  loadItems() {
+    let options = {
+      selection: this.state.selection,
+      query: this.state.query,
+      page: 1
+    };
+    this.props.loadItems(options, (items, paging) => {
+      this.setState({ items: items, total: paging.total, page: paging.pageIndex });
+    });
+  },
+
+  loadMoreItems() {
+    let options = {
+      selection: this.state.selection,
+      query: this.state.query,
+      page: this.state.page + 1
+    };
+    this.props.loadItems(options, (items, paging) => {
+      let newItems = [].concat(this.state.items, items);
+      this.setState({ items: newItems, total: paging.total, page: paging.pageIndex });
+    });
+  },
+
+  loadSelected() {
+    this.setState({ selection: 'selected', query: null }, this.loadItems);
+  },
+
+  loadDeselected() {
+    this.setState({ selection: 'deselected', query: null }, this.loadItems);
+  },
+
+  loadAll() {
+    this.setState({ selection: 'all', query: null }, this.loadItems);
+  },
+
+  search(query) {
+    this.setState({ query: query }, this.loadItems);
+  },
+
+  render() {
+    return (
+        <div className="select-list-container">
+          <Controls
+              selection={this.state.selection}
+              query={this.state.query}
+              loadSelected={this.loadSelected}
+              loadDeselected={this.loadDeselected}
+              loadAll={this.loadAll}
+              search={this.search}/>
+
+          <div className="select-list-wrapper">
+            <List {...this.props} items={this.state.items}/>
+          </div>
+
+          <Footer count={this.state.items.length} total={this.state.total} loadMore={this.loadMoreItems}/>
+        </div>
+    );
+  }
+});
diff --git a/server/sonar-web/src/main/js/components/shared/checkbox.jsx b/server/sonar-web/src/main/js/components/shared/checkbox.jsx
new file mode 100644 (file)
index 0000000..8504da5
--- /dev/null
@@ -0,0 +1,29 @@
+import React from 'react';
+
+export default React.createClass({
+  propTypes: {
+    onCheck: React.PropTypes.func.isRequired,
+    initiallyChecked: React.PropTypes.bool
+  },
+
+  getInitialState() {
+    return { checked: this.props.initiallyChecked || false };
+  },
+
+  componentWillReceiveProps(nextProps) {
+    if (nextProps.initiallyChecked != null) {
+      this.setState({ checked: nextProps.initiallyChecked });
+    }
+  },
+
+  toggle(e) {
+    e.preventDefault();
+    this.props.onCheck(!this.state.checked);
+    this.setState({ checked: !this.state.checked });
+  },
+
+  render() {
+    const className = this.state.checked ? 'icon-checkbox icon-checkbox-checked' : 'icon-checkbox';
+    return <a onClick={this.toggle} className={className} href="#"/>;
+  }
+});
diff --git a/server/sonar-web/src/main/js/components/shared/radio-toggle.jsx b/server/sonar-web/src/main/js/components/shared/radio-toggle.jsx
new file mode 100644 (file)
index 0000000..541a906
--- /dev/null
@@ -0,0 +1,37 @@
+import React from 'react';
+
+export default React.createClass({
+  propTypes: {
+    options: React.PropTypes.array.isRequired,
+    name: React.PropTypes.string.isRequired,
+    onCheck: React.PropTypes.func.isRequired
+  },
+
+  getDefaultProps: function () {
+    return { disabled: false, value: null };
+  },
+
+  onChange(e) {
+    let newValue = e.currentTarget.value;
+    this.props.onCheck(newValue);
+  },
+
+  renderOption(option) {
+    let checked = option.value === this.props.value;
+    let htmlId = this.props.name + '__' + option.value;
+    return (
+        <li key={option.value}>
+          <input onChange={this.onChange} type="radio" name={this.props.name} value={option.value} id={htmlId}
+                 checked={checked} disabled={this.props.disabled}/>
+          <label htmlFor={htmlId}>{option.label}</label>
+        </li>
+    );
+  },
+
+  render() {
+    let options = this.props.options.map(this.renderOption);
+    return (
+        <ul className="radio-toggle">{options}</ul>
+    );
+  }
+});
diff --git a/server/sonar-web/src/main/js/libs/third-party/classNames.js b/server/sonar-web/src/main/js/libs/third-party/classNames.js
new file mode 100644 (file)
index 0000000..afb8eed
--- /dev/null
@@ -0,0 +1,49 @@
+/*!
+  Copyright (c) 2015 Jed Watson.
+  Licensed under the MIT License (MIT), see
+  http://jedwatson.github.io/classnames
+*/
+
+(function () {
+       'use strict';
+
+       function classNames () {
+
+               var classes = '';
+
+               for (var i = 0; i < arguments.length; i++) {
+                       var arg = arguments[i];
+                       if (!arg) continue;
+
+                       var argType = typeof arg;
+
+                       if ('string' === argType || 'number' === argType) {
+                               classes += ' ' + arg;
+
+                       } else if (Array.isArray(arg)) {
+                               classes += ' ' + classNames.apply(null, arg);
+
+                       } else if ('object' === argType) {
+                               for (var key in arg) {
+                                       if (arg.hasOwnProperty(key) && arg[key]) {
+                                               classes += ' ' + key;
+                                       }
+                               }
+                       }
+               }
+
+               return classes.substr(1);
+       }
+
+       if (typeof module !== 'undefined' && module.exports) {
+               module.exports = classNames;
+       } else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd){
+               // AMD. Register as an anonymous module.
+               define(function () {
+                       return classNames;
+               });
+       } else {
+               window.classNames = classNames;
+       }
+
+}());
index 25b35d46c714c3ec585495e418c0657687ea21c4..98c07e2b2cd5f6e1727c93c38dc505fb3e060593 100644 (file)
   float: left;
   margin-left: 20px;
 }
+
+.select-list-wrapper {
+  height: 30vw;
+  border-top: 1px solid @barBorderColor;
+  border-bottom: 1px solid @barBorderColor;
+  overflow: auto;
+}