Merge branch 'winh-new-branch-dropdown-style' into 'master'

Cleanup new branch/merge request form in issues

Closes #41938

See merge request gitlab-org/gitlab-ce!16854
This commit is contained in:
Phil Hughes 2018-02-13 09:45:36 +00:00
commit 8925671a06
10 changed files with 135 additions and 139 deletions

View file

@ -180,6 +180,7 @@ export default class CreateMergeRequestDropdown {
valueAttribute: 'data-text',
},
],
hideOnClick: false,
};
}

View file

@ -3,7 +3,6 @@ const DATA_DROPDOWN = 'data-dropdown';
const SELECTED_CLASS = 'droplab-item-selected';
const ACTIVE_CLASS = 'droplab-item-active';
const IGNORE_CLASS = 'droplab-item-ignore';
const IGNORE_HIDING_CLASS = 'droplab-item-ignore-hiding';
// Matches `{{anything}}` and `{{ everything }}`.
const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g;
@ -14,5 +13,4 @@ export {
ACTIVE_CLASS,
TEMPLATE_REGEX,
IGNORE_CLASS,
IGNORE_HIDING_CLASS,
};

View file

@ -1,13 +1,14 @@
import utils from './utils';
import { SELECTED_CLASS, IGNORE_CLASS, IGNORE_HIDING_CLASS } from './constants';
import { SELECTED_CLASS, IGNORE_CLASS } from './constants';
class DropDown {
constructor(list, config = {}) {
constructor(list, config = { }) {
this.currentIndex = 0;
this.hidden = true;
this.list = typeof list === 'string' ? document.querySelector(list) : list;
this.items = [];
this.eventWrapper = {};
this.hideOnClick = config.hideOnClick !== false;
if (config.addActiveClassToDropdownButton) {
this.dropdownToggle = this.list.parentNode.querySelector('.js-dropdown-toggle');
@ -37,15 +38,17 @@ class DropDown {
clickEvent(e) {
if (e.target.tagName === 'UL') return;
if (e.target.classList.contains(IGNORE_CLASS)) return;
if (e.target.closest(`.${IGNORE_CLASS}`)) return;
const selected = utils.closest(e.target, 'LI');
const selected = e.target.closest('li');
if (!selected) return;
this.addSelectedClass(selected);
e.preventDefault();
if (!e.target.classList.contains(IGNORE_HIDING_CLASS)) this.hide();
if (this.hideOnClick) {
this.hide();
}
const listEvent = new CustomEvent('click.dl', {
detail: {

View file

@ -449,9 +449,11 @@ img.emoji {
.prepend-top-10 { margin-top: 10px; }
.prepend-top-15 { margin-top: 15px; }
.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-16 { margin-top: 16px; }
.prepend-top-20 { margin-top: 20px; }
.prepend-left-4 { margin-left: 4px; }
.prepend-left-5 { margin-left: 5px; }
.prepend-left-8 { margin-left: 8px; }
.prepend-left-10 { margin-left: 10px; }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; }

View file

@ -736,10 +736,6 @@
}
}
.droplab-item-ignore {
pointer-events: none;
}
.pika-single.animate-picker.is-bound,
.pika-single.animate-picker.is-bound.is-hidden {
/*

View file

@ -182,6 +182,7 @@ label {
.help-block {
margin-bottom: 0;
margin-top: #{$grid-size / 2};
}
.gl-field-error {

View file

@ -181,11 +181,6 @@ ul.related-merge-requests > li {
}
.create-mr-dropdown-wrap {
.branch-message,
.ref-message {
display: none;
}
.ref::selection {
color: $placeholder-text-color;
}
@ -216,6 +211,17 @@ ul.related-merge-requests > li {
transform: translateY(0);
display: none;
margin-top: 4px;
// override dropdown item styles
.btn.btn-success {
@include btn-default;
@include btn-green;
border-style: solid;
border-width: 1px;
line-height: $line-height-base;
width: auto;
}
}
.create-merge-request-dropdown-toggle {
@ -225,66 +231,6 @@ ul.related-merge-requests > li {
margin-left: 0;
}
}
.droplab-item-ignore {
pointer-events: auto;
}
.create-item {
cursor: pointer;
margin: 0 1px;
&:hover,
&:focus {
background-color: $dropdown-item-hover-bg;
color: $gl-text-color;
}
}
li.divider {
margin: 8px 10px;
}
li:not(.divider) {
padding: 8px 9px;
&:last-child {
padding-bottom: 8px;
}
&.droplab-item-selected {
.icon-container {
i {
visibility: visible;
}
}
.description {
display: block;
}
}
&.droplab-item-ignore {
padding-top: 8px;
}
.icon-container {
float: left;
i {
visibility: hidden;
}
}
.description {
padding-left: 22px;
}
input,
span {
margin: 4px 0 0;
}
}
}
.discussion-reply-holder .note-edit-form {

View file

@ -21,30 +21,33 @@
%button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' } } }
= icon('caret-down')
%ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-align-right.gl-show-field-errors{ data: { dropdown: true } }
- if can_create_merge_request
%li.create-item.droplab-item-selected.droplab-item-ignore-hiding{ role: 'button', data: { value: 'create-mr', text: 'Create merge request' } }
.menu-item.droplab-item-ignore-hiding
.icon-container.droplab-item-ignore-hiding= icon('check')
.description.droplab-item-ignore-hiding Create merge request and branch
.droplab-dropdown
%ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-align-right.gl-show-field-errors{ data: { dropdown: true } }
- if can_create_merge_request
%li.droplab-item-selected{ role: 'button', data: { value: 'create-mr', text: _('Create merge request') } }
.menu-item
= icon('check', class: 'icon')
= _('Create merge request and branch')
%li.create-item.droplab-item-ignore-hiding{ class: [!can_create_merge_request && 'droplab-item-selected'], role: 'button', data: { value: 'create-branch', text: 'Create branch' } }
.menu-item.droplab-item-ignore-hiding
.icon-container.droplab-item-ignore-hiding= icon('check')
.description.droplab-item-ignore-hiding Create branch
%li.divider
%li{ class: [!can_create_merge_request && 'droplab-item-selected'], role: 'button', data: { value: 'create-branch', text: _('Create branch') } }
.menu-item
= icon('check', class: 'icon')
= _('Create branch')
%li.divider.droplab-item-ignore
%li.droplab-item-ignore
Branch name
%input.js-branch-name.form-control.droplab-item-ignore{ type: 'text', placeholder: "#{@issue.to_branch_name}", value: "#{@issue.to_branch_name}" }
%span.js-branch-message.branch-message.droplab-item-ignore
%li.droplab-item-ignore.prepend-left-8.append-right-8.prepend-top-16
.form-group
%label{ for: 'new-branch-name' }
= _('Branch name')
%input#new-branch-name.js-branch-name.form-control{ type: 'text', placeholder: "#{@issue.to_branch_name}", value: "#{@issue.to_branch_name}" }
%span.js-branch-message.help-block
%li.droplab-item-ignore
Source (branch or tag)
%input.js-ref.ref.form-control.droplab-item-ignore{ type: 'text', placeholder: "#{@project.default_branch}", value: "#{@project.default_branch}", data: { value: "#{@project.default_branch}" } }
%span.js-ref-message.ref-message.droplab-item-ignore
%li.droplab-item-ignore
%button.btn.btn-success.js-create-target.droplab-item-ignore{ type: 'button', data: { action: 'create-mr' } }
Create merge request
.form-group
%label{ for: 'source-name' }
= _('Source (branch or tag)')
%input#source-name.js-ref.ref.form-control{ type: 'text', placeholder: "#{@project.default_branch}", value: "#{@project.default_branch}", data: { value: "#{@project.default_branch}" } }
%span.js-ref-message.help-block
.form-group
%button.btn.btn-success.js-create-target{ type: 'button', data: { action: 'create-mr' } }
= _('Create merge request')

View file

@ -0,0 +1,5 @@
---
title: Cleanup new branch/merge request form in issues
merge_request: 16854
author:
type: fixed

View file

@ -1,8 +1,8 @@
import DropDown from '~/droplab/drop_down';
import utils from '~/droplab/utils';
import { SELECTED_CLASS, IGNORE_CLASS } from '~/droplab/constants';
import { SELECTED_CLASS } from '~/droplab/constants';
describe('DropDown', function () {
describe('DropLab DropDown', function () {
describe('class constructor', function () {
beforeEach(function () {
spyOn(DropDown.prototype, 'getItems');
@ -128,93 +128,131 @@ describe('DropDown', function () {
beforeEach(function () {
this.classList = jasmine.createSpyObj('classList', ['contains']);
this.list = { dispatchEvent: () => {} };
this.dropdown = { hide: () => {}, list: this.list, addSelectedClass: () => {} };
this.event = { preventDefault: () => {}, target: { classList: this.classList } };
this.dropdown = {
hideOnClick: true,
hide: () => {},
list: this.list,
addSelectedClass: () => {},
};
this.event = {
preventDefault: () => {},
target: {
classList: this.classList,
closest: () => null,
},
};
this.customEvent = {};
this.closestElement = {};
this.dummyListItem = document.createElement('li');
spyOn(this.event.target, 'closest').and.callFake((selector) => {
if (selector === 'li') {
return this.dummyListItem;
}
return null;
});
spyOn(this.dropdown, 'hide');
spyOn(this.dropdown, 'addSelectedClass');
spyOn(this.list, 'dispatchEvent');
spyOn(this.event, 'preventDefault');
spyOn(window, 'CustomEvent').and.returnValue(this.customEvent);
spyOn(utils, 'closest').and.returnValues(this.closestElement, undefined);
this.classList.contains.and.returnValue(false);
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
});
it('should call utils.closest', function () {
expect(utils.closest).toHaveBeenCalledWith(this.event.target, 'LI');
it('should call event.target.closest', function () {
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
expect(this.event.target.closest).toHaveBeenCalledWith('.droplab-item-ignore');
expect(this.event.target.closest).toHaveBeenCalledWith('li');
});
it('should call addSelectedClass', function () {
expect(this.dropdown.addSelectedClass).toHaveBeenCalledWith(this.closestElement);
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
expect(this.dropdown.addSelectedClass).toHaveBeenCalledWith(this.dummyListItem);
});
it('should call .preventDefault', function () {
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
expect(this.event.preventDefault).toHaveBeenCalled();
});
it('should call .hide', function () {
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
expect(this.dropdown.hide).toHaveBeenCalled();
});
it('should construct CustomEvent', function () {
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
expect(window.CustomEvent).toHaveBeenCalledWith('click.dl', jasmine.any(Object));
});
it('should call .classList.contains checking for IGNORE_CLASS', function () {
expect(this.classList.contains).toHaveBeenCalledWith(IGNORE_CLASS);
});
it('should call .dispatchEvent with the customEvent', function () {
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
expect(this.list.dispatchEvent).toHaveBeenCalledWith(this.customEvent);
});
describe('if the target is a UL element', function () {
beforeEach(function () {
this.event = { preventDefault: () => {}, target: { tagName: 'UL', classList: this.classList } };
this.event.target = document.createElement('ul');
spyOn(this.event, 'preventDefault');
utils.closest.calls.reset();
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
spyOn(this.event.target, 'closest');
});
it('should return immediately', function () {
expect(utils.closest).not.toHaveBeenCalled();
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
expect(this.event.target.closest).not.toHaveBeenCalled();
expect(this.dropdown.addSelectedClass).not.toHaveBeenCalled();
});
});
describe('if the target has the IGNORE_CLASS class', function () {
describe('if the target has the droplab-item-ignore class', function () {
beforeEach(function () {
this.event = { preventDefault: () => {}, target: { tagName: 'LI', classList: this.classList } };
this.ignoredButton = document.createElement('button');
this.ignoredButton.classList.add('droplab-item-ignore');
this.event.target = this.ignoredButton;
spyOn(this.event, 'preventDefault');
this.classList.contains.and.returnValue(true);
utils.closest.calls.reset();
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
spyOn(this.ignoredButton, 'closest').and.callThrough();
});
it('should return immediately', function () {
expect(utils.closest).not.toHaveBeenCalled();
it('does not select element', function () {
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
expect(this.ignoredButton.closest.calls.count()).toBe(1);
expect(this.ignoredButton.closest).toHaveBeenCalledWith('.droplab-item-ignore');
expect(this.dropdown.addSelectedClass).not.toHaveBeenCalled();
});
});
describe('if no selected element exists', function () {
beforeEach(function () {
this.event.preventDefault.calls.reset();
this.clickEvent = DropDown.prototype.clickEvent.call(this.dropdown, this.event);
});
it('should return undefined', function () {
expect(this.clickEvent).toBe(undefined);
this.dummyListItem = null;
});
it('should return before .preventDefault is called', function () {
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
expect(this.event.preventDefault).not.toHaveBeenCalled();
expect(this.dropdown.addSelectedClass).not.toHaveBeenCalled();
});
});
describe('if hideOnClick is false', () => {
beforeEach(function () {
this.dropdown.hideOnClick = false;
this.dropdown.hide.calls.reset();
});
it('should not call .hide', function () {
DropDown.prototype.clickEvent.call(this.dropdown, this.event);
expect(this.dropdown.hide).not.toHaveBeenCalled();
});
});
});
@ -278,20 +316,23 @@ describe('DropDown', function () {
describe('addEvents', function () {
beforeEach(function () {
this.list = { addEventListener: () => {} };
this.list = {
addEventListener: () => {},
querySelectorAll: () => [],
};
this.dropdown = {
list: this.list,
clickEvent: () => {},
closeDropdown: () => {},
eventWrapper: {},
};
spyOn(this.list, 'addEventListener');
DropDown.prototype.addEvents.call(this.dropdown);
});
it('should call .addEventListener', function () {
spyOn(this.list, 'addEventListener');
DropDown.prototype.addEvents.call(this.dropdown);
expect(this.list.addEventListener).toHaveBeenCalledWith('click', jasmine.any(Function));
expect(this.list.addEventListener).toHaveBeenCalledWith('keyup', jasmine.any(Function));
});