Apply styling and tweaks to autocomplete dropdown

This commit is contained in:
Alfredo Sumaran 2016-03-08 19:39:14 -05:00 committed by Jacob Schatz
parent c9e202c1d7
commit 6a7f4a0767
9 changed files with 186 additions and 86 deletions

View file

@ -14,4 +14,36 @@ $.widget( "custom.catcomplete", $.ui.autocomplete,
if item.category?
li.attr('aria-label', item.category + " : " + item.label)
_renderItem: (ul, item) ->
# Highlight occurrences
item.label = item.label.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + $.ui.autocomplete.escapeRegex(this.term) + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
return $( "<li></li>" )
.data( "item.autocomplete", item )
.append( "<a>#{item.label}</a>" )
.appendTo( ul );
_resizeMenu: ->
if (isNaN(this.options.maxShowItems))
return
ul = this.menu.element.css(overflowX: '', overflowY: '', width: '', maxHeight: '')
lis = ul.children('li').css('whiteSpace', 'nowrap');
if (lis.length > this.options.maxShowItems)
ulW = ul.prop('clientWidth')
ul.css(
overflowX: 'hidden'
overflowY: 'auto'
maxHeight: lis.eq(0).outerHeight() * this.options.maxShowItems + 1
)
barW = ulW - ul.prop('clientWidth');
ul.width('+=' + barW);
# Original code from jquery.ui.autocomplete.js _resizeMenu()
ul.outerWidth(Math.max(ul.outerWidth() + 1, this.element.outerWidth()));
)

View file

@ -24,7 +24,10 @@ class @SearchAutocomplete
@scopeInputEl = @$('#scope')
@saveOriginalState()
@createAutocomplete()
if @locationBadgeEl.is(':empty')
@createAutocomplete()
@bindEvents()
$: (selector) ->
@ -66,6 +69,12 @@ class @SearchAutocomplete
appendTo: 'form.navbar-form'
source: @autocompletePath + @query
minLength: 1
maxShowItems: 15
position:
# { my: "left top", at: "left bottom", collision: "none" }
my: "left-10 top+9"
at: "left bottom"
collision: "none"
close: (e) ->
e.preventDefault()
@ -89,7 +98,9 @@ class @SearchAutocomplete
bindEvents: ->
@searchInput.on 'keydown', @onSearchKeyDown
@searchInput.on 'keydown', @onSearchInputKeyDown
@searchInput.on 'focus', @onSearchInputFocus
@searchInput.on 'blur', @onSearchInputBlur
@wrap.on 'click', '.remove-badge', @onRemoveLocationBadgeClick
onRemoveLocationBadgeClick: (e) =>
@ -97,7 +108,7 @@ class @SearchAutocomplete
@removeLocationBadge()
@searchInput.focus()
onSearchKeyDown: (e) =>
onSearchInputKeyDown: (e) =>
# Remove tag when pressing backspace and input search is empty
if e.keyCode is @keyCode.BACKSPACE and e.currentTarget.value is ''
@removeLocationBadge()
@ -106,14 +117,24 @@ class @SearchAutocomplete
else if e.keyCode is @keyCode.ESCAPE
@restoreOriginalState()
else
# Create new autocomplete instance if it's not created
@createAutocomplete() unless @catcomplete?
# Create new autocomplete if hasn't been created yet and there's no badge
if !@catComplete? and @locationBadgeEl.is(':empty')
@createAutocomplete()
onSearchInputFocus: =>
@wrap.addClass('search-active')
onSearchInputBlur: =>
@wrap.removeClass('search-active')
# If input is blank then restore state
@restoreOriginalState() if @searchInput.val() is ''
addLocationBadge: (item) ->
category = if item.category? then "#{item.category}: " else ''
value = if item.value? then item.value else ''
html = "<span class='label label-primary'>
html = "<span class='location-badge'>
<i class='location-text'>#{category}#{value}</i>
<a class='remove-badge' href='#'>x</a>
</span>"
@ -160,5 +181,5 @@ class @SearchAutocomplete
location.href = result.url
destroyAutocomplete: ->
@catComplete.destroy() if @catcomplete?
@catComplete.destroy() if @catComplete?
@catComplete = null

View file

@ -6,40 +6,6 @@ input {
border-radius: $border-radius-base;
}
input[type='search'] {
background-color: white;
padding-left: 10px;
}
input[type='search'].search-input {
background-repeat: no-repeat;
background-position: 10px;
background-size: 16px;
background-position-x: 30%;
padding-left: 10px;
background-color: $gray-light;
&.search-input[value=""] {
background-image: url('data:image/png;base64,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');
}
&.search-input::-webkit-input-placeholder {
text-align: center;
}
&.search-input:-moz-placeholder { /* Firefox 18- */
text-align: center;
}
&.search-input::-moz-placeholder { /* Firefox 19+ */
text-align: center;
}
&.search-input:-ms-input-placeholder {
text-align: center;
}
}
input[type='text'].danger {
background: #f2dede!important;
border-color: #d66;

View file

@ -112,26 +112,6 @@ header {
}
}
.search {
margin-right: 10px;
margin-left: 10px;
margin-top: ($header-height - 36) / 2;
form {
margin: 0;
padding: 0;
}
.search-input {
width: 220px;
&:focus {
@include box-shadow(none);
outline: none;
}
}
}
.impersonation i {
color: $red-normal;
}

View file

@ -23,9 +23,39 @@
padding: 0;
margin-top: 2px;
z-index: 1001;
width: 240px;
margin-bottom: 0;
padding: 10px 10px;
font-size: 14px;
font-weight: normal;
background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
.ui-menu-item a {
padding: 4px 10px;
.ui-menu-item {
display: block;
position: relative;
padding: 0 10px;
color: $dropdown-link-color;
line-height: 34px;
text-overflow: ellipsis;
border-radius: 2px;
white-space: nowrap;
overflow: hidden;
border: none;
&.ui-state-focus {
background-color: $dropdown-link-hover-bg;
text-decoration: none;
margin: 0;
}
}
.ui-autocomplete-category {
text-transform: uppercase;
font-size: 11px;
color: #7f8fa4;
}
}

View file

@ -21,3 +21,73 @@
}
}
.search {
margin-right: 10px;
margin-left: 10px;
margin-top: ($header-height - 35) / 2;
&.search-active {
form {
@extend .form-control:focus;
}
.location-badge {
@include transition(all .15s);
background-color: $input-border-focus;
color: $white-light;
}
}
form {
@extend .form-control;
margin: 0;
padding: 4px;
width: 350px;
line-height: 24px;
overflow: hidden;
}
.location-text {
font-style: normal;
}
.remove-badge {
display: none;
}
.search-input {
border: none;
font-size: 14px;
outline: none;
padding: 0;
margin-left: 2px;
line-height: 25px;
width: 100%;
}
.location-badge {
line-height: 25px;
padding: 0 5px;
border-radius: 2px;
font-size: 14px;
font-style: normal;
color: #AAAAAA;
display: inline-block;
background-color: #F5F5F5;
vertical-align: top;
}
.search-input-container {
display: flex;
}
.search-location-badge, .search-input-wrap {
// Fallback if flex is not supported
display: inline-block;
}
.search-input-wrap {
width: 100%;
}
}

View file

@ -48,20 +48,19 @@ module SearchHelper
# Autocomplete results for the current project, if it's defined
def project_autocomplete
if @project && @project.repository.exists? && @project.repository.root_ref
prefix = "Project - " + search_result_sanitize(@project.name_with_namespace)
ref = @ref || @project.repository.root_ref
[
{ category: prefix, label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
{ category: prefix, label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
{ category: prefix, label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
{ category: prefix, label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
{ category: prefix, label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) },
{ category: prefix, label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
{ category: prefix, label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
{ category: prefix, label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
{ category: prefix, label: "Members", url: namespace_project_project_members_path(@project.namespace, @project) },
{ category: prefix, label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
{ category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
{ category: "Current Project", label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
{ category: "Current Project", label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
{ category: "Current Project", label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
{ category: "Current Project", label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) },
{ category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
{ category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
{ category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
{ category: "Current Project", label: "Members", url: namespace_project_project_members_path(@project.namespace, @project) },
{ category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
]
else
[]

View file

@ -1,9 +1,12 @@
.search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
= render 'shared/location_badge'
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1"
= hidden_field_tag :group_id, @group.try(:id)
.search.search-form
= form_tag search_path, method: :get, class: 'navbar-form' do |f|
.search-input-container
.search-location-badge
= render 'shared/location_badge'
.search-input-wrap
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input", spellcheck: false, tabindex: "1", autocomplete: 'off'
= hidden_field_tag :group_id, @group.try(:id)
= hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : ''
- if @project && @project.persisted?

View file

@ -3,11 +3,10 @@
- if controller.controller_path =~ /^projects/
- label = 'This project'
.search-location-badge
- if label.present?
%span.label.label-primary
%i.location-text
= label
- if label.present?
%span.location-badge
%i.location-text
= label
%a.remove-badge{href: '#'}
x
%a.remove-badge{href: '#'}
x