Commit 9612e575 authored by Jacob Schatz's avatar Jacob Schatz Committed by Rémy Coutable

Merge branch 'dropdown-alignment-fixes' into 'master'

Dropdown pixel perfect 🎉

Closes #14333, #14331, #14472, #14515

See merge request !3337
parent 329fd857
...@@ -5,6 +5,7 @@ v 8.6.3 (unreleased) ...@@ -5,6 +5,7 @@ v 8.6.3 (unreleased)
v 8.6.2 v 8.6.2
- Fix dropdown alignment. !3298 - Fix dropdown alignment. !3298
- Fix issuable sidebar overlaps on tablet. !3299 - Fix issuable sidebar overlaps on tablet. !3299
- Make dropdowns pixel perfect. !3337
v 8.6.1 v 8.6.1
- Add option to reload the schema before restoring a database backup. !2807 - Add option to reload the schema before restoring a database backup. !2807
......
class GitLabDropdownFilter class GitLabDropdownFilter
BLUR_KEYCODES = [27, 40] BLUR_KEYCODES = [27, 40]
HAS_VALUE_CLASS = "has-value"
constructor: (@dropdown, @options) -> constructor: (@input, @options) ->
@input = @dropdown.find(".dropdown-input .dropdown-input-field") $inputContainer = @input.parent()
$clearButton = $inputContainer.find('.js-dropdown-input-clear')
# Clear click
$clearButton.on 'click', (e) =>
e.preventDefault()
e.stopPropagation()
@input
.val('')
.trigger('keyup')
.focus()
# Key events # Key events
timeout = "" timeout = ""
@input.on "keyup", (e) => @input.on "keyup", (e) =>
if e.keyCode is 13 && @input.val() isnt "" if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.addClass HAS_VALUE_CLASS
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.removeClass HAS_VALUE_CLASS
if e.keyCode is 13 and @input.val() isnt ""
if @options.enterCallback if @options.enterCallback
@options.enterCallback() @options.enterCallback()
return return
...@@ -95,7 +111,9 @@ class GitLabDropdown ...@@ -95,7 +111,9 @@ class GitLabDropdown
# Init filiterable # Init filiterable
if @options.filterable if @options.filterable
@filter = new GitLabDropdownFilter @dropdown, @input = @dropdown.find('.dropdown-input .dropdown-input-field')
@filter = new GitLabDropdownFilter @input,
remote: @options.filterRemote remote: @options.filterRemote
query: @options.data query: @options.data
keys: @options.search.fields keys: @options.search.fields
...@@ -103,6 +121,7 @@ class GitLabDropdown ...@@ -103,6 +121,7 @@ class GitLabDropdown
return @fullData return @fullData
callback: (data) => callback: (data) =>
@parseData data @parseData data
@highlightRow 1
enterCallback: => enterCallback: =>
@selectFirstRow() @selectFirstRow()
...@@ -224,11 +243,19 @@ class GitLabDropdown ...@@ -224,11 +243,19 @@ class GitLabDropdown
noResults: -> noResults: ->
html = "<li>" html = "<li>"
html += "<a href='#' class='is-focused'>" html += "<a href='#' class='dropdown-menu-empty-link is-focused'>"
html += "No matching results." html += "No matching results."
html += "</a>" html += "</a>"
html += "</li>" html += "</li>"
highlightRow: (index) ->
if @input.val() isnt ""
selector = '.dropdown-content li:first-child a'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a"
$(selector).addClass 'is-focused'
rowClicked: (el) -> rowClicked: (el) ->
fieldName = @options.fieldName fieldName = @options.fieldName
field = @dropdown.parent().find("input[name='#{fieldName}']") field = @dropdown.parent().find("input[name='#{fieldName}']")
...@@ -272,7 +299,7 @@ class GitLabDropdown ...@@ -272,7 +299,7 @@ class GitLabDropdown
if @dropdown.find(".dropdown-toggle-page").length if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a" selector = ".dropdown-page-one .dropdown-content li:first-child a"
# similute a click on the first link # simulate a click on the first link
$(selector).trigger "click" $(selector).trigger "click"
$.fn.glDropdown = (opts) -> $.fn.glDropdown = (opts) ->
......
...@@ -6,7 +6,7 @@ class @LabelsSelect ...@@ -6,7 +6,7 @@ class @LabelsSelect
labelUrl = $dropdown.data('labels') labelUrl = $dropdown.data('labels')
selectedLabel = $dropdown.data('selected') selectedLabel = $dropdown.data('selected')
if selectedLabel if selectedLabel
selectedLabel = selectedLabel.split(',') selectedLabel = selectedLabel.toString().split(',')
newLabelField = $('#new_label_name') newLabelField = $('#new_label_name')
newColorField = $('#new_label_color') newColorField = $('#new_label_color')
showNo = $dropdown.data('show-no') showNo = $dropdown.data('show-no')
...@@ -14,18 +14,61 @@ class @LabelsSelect ...@@ -14,18 +14,61 @@ class @LabelsSelect
defaultLabel = $dropdown.data('default-label') defaultLabel = $dropdown.data('default-label')
if newLabelField.length if newLabelField.length
$newLabelCreateButton = $('.js-new-label-btn')
$colorPreview = $('.js-dropdown-label-color-preview')
$newLabelError = $dropdown.parent().find('.js-label-error') $newLabelError = $dropdown.parent().find('.js-label-error')
$newLabelError.hide() $newLabelError.hide()
# Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on 'click', (e) -> $('.suggest-colors-dropdown a').on 'click', (e) ->
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
newColorField.val $(this).data('color') newColorField
$('.js-dropdown-label-color-preview') .val($(this).data('color'))
.trigger('change')
$colorPreview
.css 'background-color', $(this).data('color') .css 'background-color', $(this).data('color')
.parent()
.addClass 'is-active' .addClass 'is-active'
$('.js-new-label-btn').on 'click', (e) -> # Cancel button takes back to first page
resetForm = ->
newLabelField
.val ''
.trigger 'change'
newColorField
.val ''
.trigger 'change'
$colorPreview
.css 'background-color', ''
.parent()
.removeClass 'is-active'
$('.dropdown-menu-back').on 'click', ->
resetForm()
$('.js-cancel-label-btn').on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
resetForm()
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
# Listen for change and keyup events on label and color field
# This allows us to enable the button when ready
enableLabelCreateButton = ->
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
newLabelField.on 'keyup change', enableLabelCreateButton
newColorField.on 'keyup change', enableLabelCreateButton
# Send the API call to create the label
$newLabelCreateButton
.disable()
.on 'click', (e) ->
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
...@@ -78,8 +121,11 @@ class @LabelsSelect ...@@ -78,8 +121,11 @@ class @LabelsSelect
else else
selected = if label.title is selectedLabel then 'is-active' else '' selected = if label.title is selectedLabel then 'is-active' else ''
color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
"<li> "<li>
<a href='#' class='#{selected}'> <a href='#' class='#{selected}'>
#{color}
#{label.title} #{label.title}
</a> </a>
</li>" </li>"
......
...@@ -30,6 +30,7 @@ class @UsersSelect ...@@ -30,6 +30,7 @@ class @UsersSelect
if showNullUser if showNullUser
showDivider += 1 showDivider += 1
users.unshift( users.unshift(
beforeDivider: true
name: 'Unassigned', name: 'Unassigned',
id: 0 id: 0
) )
...@@ -39,6 +40,7 @@ class @UsersSelect ...@@ -39,6 +40,7 @@ class @UsersSelect
name = showAnyUser name = showAnyUser
name = 'Any User' if name == true name = 'Any User' if name == true
anyUser = { anyUser = {
beforeDivider: true
name: name, name: name,
id: null id: null
} }
...@@ -75,6 +77,13 @@ class @UsersSelect ...@@ -75,6 +77,13 @@ class @UsersSelect
selected = if user.id is selectedId then "is-active" else "" selected = if user.id is selectedId then "is-active" else ""
img = "" img = ""
if user.beforeDivider?
"<li>
<a href='#' class='#{selected}'>
#{user.name}
</a>
</li>"
else
if avatar if avatar
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />" img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
......
...@@ -130,6 +130,12 @@ ...@@ -130,6 +130,12 @@
text-decoration: none; text-decoration: none;
outline: 0; outline: 0;
} }
&.dropdown-menu-empty-link {
&.is-focused {
background-color: $dropdown-empty-row-bg;
}
}
} }
} }
...@@ -183,7 +189,7 @@ ...@@ -183,7 +189,7 @@
} }
.dropdown-select { .dropdown-select {
width: 280px; width: 300px;
} }
.dropdown-menu-align-right { .dropdown-menu-align-right {
...@@ -237,7 +243,7 @@ ...@@ -237,7 +243,7 @@
.dropdown-title-button { .dropdown-title-button {
position: absolute; position: absolute;
top: -1px; top: 0;
padding: 0; padding: 0;
color: $dropdown-title-btn-color; color: $dropdown-title-btn-color;
font-size: 14px; font-size: 14px;
...@@ -270,6 +276,22 @@ ...@@ -270,6 +276,22 @@
font-size: 12px; font-size: 12px;
pointer-events: none; pointer-events: none;
} }
.dropdown-input-clear {
display: none;
cursor: pointer;
pointer-events: all;
}
&.has-value {
.dropdown-input-clear {
display: block;
}
.dropdown-input-search {
display: none;
}
}
} }
.dropdown-input-field { .dropdown-input-field {
...@@ -286,13 +308,13 @@ ...@@ -286,13 +308,13 @@
border-color: $dropdown-input-focus-border; border-color: $dropdown-input-focus-border;
box-shadow: 0 0 4px $dropdown-input-focus-shadow; box-shadow: 0 0 4px $dropdown-input-focus-shadow;
+ .fa { ~ .fa {
color: $dropdown-link-color; color: $dropdown-link-color;
} }
} }
&:hover { &:hover {
+ .fa { ~ .fa {
color: $dropdown-link-color; color: $dropdown-link-color;
} }
} }
...@@ -338,11 +360,12 @@ ...@@ -338,11 +360,12 @@
} }
} }
.dropdown-menu-labels { .dropdown-label-box {
.label {
position: relative; position: relative;
width: 30px; top: 3px;
margin-right: 5px; margin-right: 5px;
text-indent: -99999px; display: inline-block;
} width: 15px;
height: 15px;
border-radius: $border-radius-base;
} }
...@@ -138,13 +138,14 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif ...@@ -138,13 +138,14 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif
*/ */
$dropdown-bg: #fff; $dropdown-bg: #fff;
$dropdown-link-color: #555; $dropdown-link-color: #555;
$dropdown-link-hover-bg: rgba(#000, .04); $dropdown-link-hover-bg: $row-hover;
$dropdown-empty-row-bg: rgba(#000, .04);
$dropdown-border-color: rgba(#000, .1); $dropdown-border-color: rgba(#000, .1);
$dropdown-shadow-color: rgba(#000, .1); $dropdown-shadow-color: rgba(#000, .1);
$dropdown-divider-color: rgba(#000, .1); $dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494; $dropdown-header-color: #959494;
$dropdown-title-btn-color: #bfbfbf; $dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #c7c7c7; $dropdown-input-color: #555;
$dropdown-input-focus-border: rgb(58, 171, 240); $dropdown-input-focus-border: rgb(58, 171, 240);
$dropdown-input-focus-shadow: rgba(#000, .2); $dropdown-input-focus-shadow: rgba(#000, .2);
$dropdown-loading-bg: rgba(#fff, .6); $dropdown-loading-bg: rgba(#fff, .6);
......
...@@ -9,28 +9,45 @@ ...@@ -9,28 +9,45 @@
} }
&.suggest-colors-dropdown { &.suggest-colors-dropdown {
margin-bottom: 5px; margin-top: 10px;
margin-bottom: 10px;
border-radius: $border-radius-base;
overflow: hidden;
a { a {
@include border-radius(0); @include border-radius(0);
width: 36.7px; width: (100% / 7);
margin-right: 0; margin-right: 0;
margin-bottom: -5px; margin-bottom: -5px;
} }
} }
} }
.dropdown-label-color-preview { .dropdown-new-label {
display: none; .dropdown-content {
margin-top: 5px; max-height: 260px;
width: 100%; }
height: 25px; }
.dropdown-label-color-input {
position: relative;
margin-bottom: 10px;
&.is-active { &.is-active {
display: block; padding-left: 32px;
} }
} }
.dropdown-label-color-preview {
position: absolute;
left: 0;
top: 0;
width: 32px;
height: 32px;
border-top-left-radius: $border-radius-base;
border-bottom-left-radius: $border-radius-base;
}
.label-row { .label-row {
.label { .label {
padding: 9px; padding: 9px;
......
...@@ -70,7 +70,8 @@ module DropdownsHelper ...@@ -70,7 +70,8 @@ module DropdownsHelper
def dropdown_filter(placeholder) def dropdown_filter(placeholder)
content_tag :div, class: "dropdown-input" do content_tag :div, class: "dropdown-input" do
filter_output = search_field_tag nil, nil, class: "dropdown-input-field", placeholder: placeholder filter_output = search_field_tag nil, nil, class: "dropdown-input-field", placeholder: placeholder
filter_output << icon('search') filter_output << icon('search', class: "dropdown-input-search")
filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button")
filter_output.html_safe filter_output.html_safe
end end
......
...@@ -24,17 +24,21 @@ ...@@ -24,17 +24,21 @@
- else - else
View labels View labels
- if can? current_user, :admin_label, @project and @project - if can? current_user, :admin_label, @project and @project
.dropdown-page-two .dropdown-page-two.dropdown-new-label
= dropdown_title("Create new label", back: true) = dropdown_title("Create new label", back: true)
= dropdown_content do = dropdown_content do
.dropdown-labels-error.js-label-error .dropdown-labels-error.js-label-error
%input#new_label_color{type: "hidden"}
%input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"} %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
.dropdown-label-color-preview.js-dropdown-label-color-preview
.suggest-colors.suggest-colors-dropdown .suggest-colors.suggest-colors-dropdown
- suggested_colors.each do |color| - suggested_colors.each do |color|
= link_to '#', style: "background-color: #{color}", data: { color: color } do = link_to '#', style: "background-color: #{color}", data: { color: color } do
&nbsp &nbsp
%button.btn.btn-primary.js-new-label-btn{type: "button"} .dropdown-label-color-input
.dropdown-label-color-preview.js-dropdown-label-color-preview
%input#new_label_color.dropdown-input-field{ type: "text" }
.clearfix
%button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"}
Create Create
%button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"}
Cancel
= dropdown_loading = dropdown_loading
...@@ -43,10 +43,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps ...@@ -43,10 +43,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I click "All" link' do step 'I click "All" link' do
find('.js-author-search').click find('.js-author-search').click
find('.dropdown-menu-user-full-name', match: :first).click find('.dropdown-content a', match: :first).click
find('.js-assignee-search').click find('.js-assignee-search').click
find('.dropdown-menu-user-full-name', match: :first).click find('.dropdown-content a', match: :first).click
end end
def should_see(issue) def should_see(issue)
......
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