Feature: enhanced chips toolbar with sorting and load select

Chips toolbar improvements:
- Filter input now uses all available space (flex: 1)
- Added sort dropdown: Order added, Name A-Z, Name Z-A
- Changed "Show X more" button to select dropdown pattern
  - Load 20, 50, 100, or All
  - Shows remaining count
- Collapse button to return to default view

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 19:44:56 +00:00
parent b7054f11f9
commit acbcf55b1c
7 changed files with 230 additions and 58 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3923,20 +3923,27 @@
$toolbar.addClass('has-chips'); $toolbar.addClass('has-chips');
this.updateChipsToolbar($toolbar, totalCount, filteredCount, searchTerm); this.updateChipsToolbar($toolbar, totalCount, filteredCount, searchTerm);
// Update load more button // Update load more select dropdown
var hiddenByPagination = filteredCount - visibleCount; var hiddenByPagination = filteredCount - visibleCount;
if (hiddenByPagination > 0 && !isExpanded) { if (hiddenByPagination > 0 && !isExpanded) {
var moreText = (trans.show_more || 'Show {count} more').replace('{count}', hiddenByPagination); var loadText = trans.load || 'Load';
$loadMore.html( var remainingText = (trans.remaining || '{count} remaining').replace('{count}', hiddenByPagination);
'<button type="button" class="btn-load-more">' + var loadMoreHtml = '<span class="load-more-label">' + loadText + '</span>' +
'<i class="icon-chevron-down"></i> ' + moreText + '<select class="load-more-select">' +
'</button>' '<option value="20">20</option>' +
).show(); '<option value="50">50</option>' +
'<option value="100">100</option>' +
'<option value="all">' + (trans.all || 'All') + ' (' + hiddenByPagination + ')</option>' +
'</select>' +
'<span class="load-more-remaining">' + remainingText + '</span>';
$loadMore.html(loadMoreHtml).show();
} else if (isExpanded && filteredCount > (this.maxVisibleChips || 12)) { } else if (isExpanded && filteredCount > (this.maxVisibleChips || 12)) {
var lessText = trans.show_less || 'Show less'; var collapseText = trans.collapse || 'Collapse';
$loadMore.html( $loadMore.html(
'<button type="button" class="btn-load-more">' + '<button type="button" class="btn-collapse-chips" style="' +
'<i class="icon-chevron-up"></i> ' + lessText + 'background:transparent;border:1px solid #dee2e6;border-radius:4px;' +
'padding:0.25rem 0.75rem;font-size:12px;color:#6c757d;cursor:pointer;">' +
'<i class="icon-chevron-up"></i> ' + collapseText +
'</button>' '</button>'
).show(); ).show();
} else { } else {
@@ -3953,13 +3960,18 @@
var trans = this.config.trans || {}; var trans = this.config.trans || {};
var $picker = $chips.closest('.value-picker'); var $picker = $chips.closest('.value-picker');
// Create wrapper structure - integrated filter toolbar // Create wrapper structure - integrated filter toolbar with sort
var wrapperHtml = '<div class="chips-wrapper">' + var wrapperHtml = '<div class="chips-wrapper">' +
'<div class="chips-toolbar">' + '<div class="chips-toolbar">' +
'<span class="chips-filter-group">' + '<span class="chips-filter-group">' +
'<i class="icon-search"></i>' + '<i class="icon-search"></i>' +
'<input type="text" class="chips-search-input" placeholder="' + (trans.filter || 'Filter') + '...">' + '<input type="text" class="chips-search-input" placeholder="' + (trans.filter_selected || 'Filter selected') + '...">' +
'</span>' + '</span>' +
'<select class="chips-sort-select" title="' + (trans.sort || 'Sort') + '">' +
'<option value="added">' + (trans.sort_added || 'Order added') + '</option>' +
'<option value="name_asc">' + (trans.sort_name_asc || 'Name A-Z') + '</option>' +
'<option value="name_desc">' + (trans.sort_name_desc || 'Name Z-A') + '</option>' +
'</select>' +
'<span class="chips-count"></span>' + '<span class="chips-count"></span>' +
'<button type="button" class="btn-chips-clear" title="' + (trans.clear_all || 'Clear all') + '">' + '<button type="button" class="btn-chips-clear" title="' + (trans.clear_all || 'Clear all') + '">' +
'<i class="icon-trash"></i> <span class="clear-text">' + (trans.clear || 'Clear') + '</span>' + '<i class="icon-trash"></i> <span class="clear-text">' + (trans.clear || 'Clear') + '</span>' +
@@ -3994,6 +4006,12 @@
}, 150); }, 150);
}); });
// Sort select
$wrapper.on('change', '.chips-sort-select', function() {
var sortBy = $(this).val();
self.sortChips($chips, sortBy);
});
// Clear all button // Clear all button
$wrapper.on('click', '.btn-chips-clear', function() { $wrapper.on('click', '.btn-chips-clear', function() {
var searchTerm = $wrapper.find('.chips-search-input').val() || ''; var searchTerm = $wrapper.find('.chips-search-input').val() || '';
@@ -4016,15 +4034,59 @@
self.updateChipsVisibility($chips); self.updateChipsVisibility($chips);
}); });
// Load more / show less // Load more select dropdown
$wrapper.on('click', '.btn-load-more', function() { $wrapper.on('change', '.load-more-select', function() {
if ($chips.hasClass('chips-expanded')) { var loadCount = $(this).val();
$chips.removeClass('chips-expanded'); if (loadCount === 'all') {
} else {
$chips.addClass('chips-expanded'); $chips.addClass('chips-expanded');
self.maxVisibleChips = 999999;
} else {
self.maxVisibleChips = (self.maxVisibleChips || 12) + parseInt(loadCount, 10);
} }
self.updateChipsVisibility($chips); self.updateChipsVisibility($chips);
}); });
// Collapse button
$wrapper.on('click', '.btn-collapse-chips', function() {
$chips.removeClass('chips-expanded');
self.maxVisibleChips = 12;
self.updateChipsVisibility($chips);
});
},
/**
* Sort chips by specified criteria
*/
sortChips: function($chips, sortBy) {
var $allChips = $chips.find('.entity-chip');
if ($allChips.length < 2) return;
var sorted = $allChips.toArray().sort(function(a, b) {
var $a = $(a);
var $b = $(b);
switch (sortBy) {
case 'name_asc':
var nameA = ($a.find('.chip-name').text() || '').toLowerCase();
var nameB = ($b.find('.chip-name').text() || '').toLowerCase();
return nameA.localeCompare(nameB);
case 'name_desc':
var nameA2 = ($a.find('.chip-name').text() || '').toLowerCase();
var nameB2 = ($b.find('.chip-name').text() || '').toLowerCase();
return nameB2.localeCompare(nameA2);
case 'added':
default:
// Keep original DOM order (order added)
return 0;
}
});
// Re-append in sorted order
$.each(sorted, function(i, chip) {
$chips.append(chip);
});
this.updateChipsVisibility($chips);
}, },
updateChipsToolbar: function($toolbar, totalCount, filteredCount, searchTerm) { updateChipsToolbar: function($toolbar, totalCount, filteredCount, searchTerm) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -145,20 +145,27 @@
$toolbar.addClass('has-chips'); $toolbar.addClass('has-chips');
this.updateChipsToolbar($toolbar, totalCount, filteredCount, searchTerm); this.updateChipsToolbar($toolbar, totalCount, filteredCount, searchTerm);
// Update load more button // Update load more select dropdown
var hiddenByPagination = filteredCount - visibleCount; var hiddenByPagination = filteredCount - visibleCount;
if (hiddenByPagination > 0 && !isExpanded) { if (hiddenByPagination > 0 && !isExpanded) {
var moreText = (trans.show_more || 'Show {count} more').replace('{count}', hiddenByPagination); var loadText = trans.load || 'Load';
$loadMore.html( var remainingText = (trans.remaining || '{count} remaining').replace('{count}', hiddenByPagination);
'<button type="button" class="btn-load-more">' + var loadMoreHtml = '<span class="load-more-label">' + loadText + '</span>' +
'<i class="icon-chevron-down"></i> ' + moreText + '<select class="load-more-select">' +
'</button>' '<option value="20">20</option>' +
).show(); '<option value="50">50</option>' +
'<option value="100">100</option>' +
'<option value="all">' + (trans.all || 'All') + ' (' + hiddenByPagination + ')</option>' +
'</select>' +
'<span class="load-more-remaining">' + remainingText + '</span>';
$loadMore.html(loadMoreHtml).show();
} else if (isExpanded && filteredCount > (this.maxVisibleChips || 12)) { } else if (isExpanded && filteredCount > (this.maxVisibleChips || 12)) {
var lessText = trans.show_less || 'Show less'; var collapseText = trans.collapse || 'Collapse';
$loadMore.html( $loadMore.html(
'<button type="button" class="btn-load-more">' + '<button type="button" class="btn-collapse-chips" style="' +
'<i class="icon-chevron-up"></i> ' + lessText + 'background:transparent;border:1px solid #dee2e6;border-radius:4px;' +
'padding:0.25rem 0.75rem;font-size:12px;color:#6c757d;cursor:pointer;">' +
'<i class="icon-chevron-up"></i> ' + collapseText +
'</button>' '</button>'
).show(); ).show();
} else { } else {
@@ -175,13 +182,18 @@
var trans = this.config.trans || {}; var trans = this.config.trans || {};
var $picker = $chips.closest('.value-picker'); var $picker = $chips.closest('.value-picker');
// Create wrapper structure - integrated filter toolbar // Create wrapper structure - integrated filter toolbar with sort
var wrapperHtml = '<div class="chips-wrapper">' + var wrapperHtml = '<div class="chips-wrapper">' +
'<div class="chips-toolbar">' + '<div class="chips-toolbar">' +
'<span class="chips-filter-group">' + '<span class="chips-filter-group">' +
'<i class="icon-search"></i>' + '<i class="icon-search"></i>' +
'<input type="text" class="chips-search-input" placeholder="' + (trans.filter || 'Filter') + '...">' + '<input type="text" class="chips-search-input" placeholder="' + (trans.filter_selected || 'Filter selected') + '...">' +
'</span>' + '</span>' +
'<select class="chips-sort-select" title="' + (trans.sort || 'Sort') + '">' +
'<option value="added">' + (trans.sort_added || 'Order added') + '</option>' +
'<option value="name_asc">' + (trans.sort_name_asc || 'Name A-Z') + '</option>' +
'<option value="name_desc">' + (trans.sort_name_desc || 'Name Z-A') + '</option>' +
'</select>' +
'<span class="chips-count"></span>' + '<span class="chips-count"></span>' +
'<button type="button" class="btn-chips-clear" title="' + (trans.clear_all || 'Clear all') + '">' + '<button type="button" class="btn-chips-clear" title="' + (trans.clear_all || 'Clear all') + '">' +
'<i class="icon-trash"></i> <span class="clear-text">' + (trans.clear || 'Clear') + '</span>' + '<i class="icon-trash"></i> <span class="clear-text">' + (trans.clear || 'Clear') + '</span>' +
@@ -216,6 +228,12 @@
}, 150); }, 150);
}); });
// Sort select
$wrapper.on('change', '.chips-sort-select', function() {
var sortBy = $(this).val();
self.sortChips($chips, sortBy);
});
// Clear all button // Clear all button
$wrapper.on('click', '.btn-chips-clear', function() { $wrapper.on('click', '.btn-chips-clear', function() {
var searchTerm = $wrapper.find('.chips-search-input').val() || ''; var searchTerm = $wrapper.find('.chips-search-input').val() || '';
@@ -238,15 +256,59 @@
self.updateChipsVisibility($chips); self.updateChipsVisibility($chips);
}); });
// Load more / show less // Load more select dropdown
$wrapper.on('click', '.btn-load-more', function() { $wrapper.on('change', '.load-more-select', function() {
if ($chips.hasClass('chips-expanded')) { var loadCount = $(this).val();
$chips.removeClass('chips-expanded'); if (loadCount === 'all') {
} else {
$chips.addClass('chips-expanded'); $chips.addClass('chips-expanded');
self.maxVisibleChips = 999999;
} else {
self.maxVisibleChips = (self.maxVisibleChips || 12) + parseInt(loadCount, 10);
} }
self.updateChipsVisibility($chips); self.updateChipsVisibility($chips);
}); });
// Collapse button
$wrapper.on('click', '.btn-collapse-chips', function() {
$chips.removeClass('chips-expanded');
self.maxVisibleChips = 12;
self.updateChipsVisibility($chips);
});
},
/**
* Sort chips by specified criteria
*/
sortChips: function($chips, sortBy) {
var $allChips = $chips.find('.entity-chip');
if ($allChips.length < 2) return;
var sorted = $allChips.toArray().sort(function(a, b) {
var $a = $(a);
var $b = $(b);
switch (sortBy) {
case 'name_asc':
var nameA = ($a.find('.chip-name').text() || '').toLowerCase();
var nameB = ($b.find('.chip-name').text() || '').toLowerCase();
return nameA.localeCompare(nameB);
case 'name_desc':
var nameA2 = ($a.find('.chip-name').text() || '').toLowerCase();
var nameB2 = ($b.find('.chip-name').text() || '').toLowerCase();
return nameB2.localeCompare(nameA2);
case 'added':
default:
// Keep original DOM order (order added)
return 0;
}
});
// Re-append in sorted order
$.each(sorted, function(i, chip) {
$chips.append(chip);
});
this.updateChipsVisibility($chips);
}, },
updateChipsToolbar: function($toolbar, totalCount, filteredCount, searchTerm) { updateChipsToolbar: function($toolbar, totalCount, filteredCount, searchTerm) {

View File

@@ -34,11 +34,13 @@
} }
} }
// Filter input styled as a search chip // Filter input styled as a search chip - takes available space
.chips-filter-group { .chips-filter-group {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.25rem; gap: 0.25rem;
flex: 1; // Take available space
min-width: 0; // Allow shrinking
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
background: $es-white; background: $es-white;
border: 1px solid $es-border-color; border: 1px solid $es-border-color;
@@ -63,8 +65,8 @@
input.chips-search-input[type="text"] { input.chips-search-input[type="text"] {
flex: 1 !important; flex: 1 !important;
min-width: 60px !important; min-width: 60px !important;
max-width: 120px !important; max-width: none !important; // No max - use available space
width: auto !important; width: 100% !important;
height: auto !important; height: auto !important;
padding: 0.125rem 0 !important; padding: 0.125rem 0 !important;
border: none !important; border: none !important;
@@ -86,6 +88,34 @@
} }
} }
// Sort dropdown for chips
.chips-sort-select,
select.chips-sort-select {
appearance: none;
padding: 0.25rem 1.5rem 0.25rem 0.5rem !important;
border: 1px solid $es-border-color !important;
border-radius: $es-radius-full !important;
background: $es-white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cpath fill='%23666' d='M0 2l4 4 4-4z'/%3E%3C/svg%3E") no-repeat right 0.5rem center !important;
background-size: 8px !important;
font-size: 11px !important;
color: $es-text-secondary;
cursor: pointer;
transition: all $es-transition-fast;
height: auto !important;
min-height: 0 !important;
line-height: 1.2 !important;
&:hover {
border-color: $es-primary !important;
}
&:focus {
outline: none !important;
border-color: $es-primary !important;
box-shadow: 0 0 0 2px rgba($es-primary, 0.1) !important;
}
}
// Count badge - smaller, pill style // Count badge - smaller, pill style
.chips-count { .chips-count {
display: inline-flex; display: inline-flex;
@@ -167,36 +197,54 @@
} }
} }
// Load more button // Load more section with select dropdown
.chips-load-more { .chips-load-more {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: $es-spacing-sm;
padding: $es-spacing-sm $es-spacing-md; padding: $es-spacing-sm $es-spacing-md;
background: $es-white; background: transparent;
border-top: 1px solid $es-border-color; border-top: 1px dashed $es-border-color;
.btn-load-more { .load-more-label {
@include button-reset; font-size: $es-font-size-xs;
display: inline-flex; color: $es-text-muted;
align-items: center; }
gap: 0.375rem;
padding: 0.5rem 1rem; .load-more-select,
color: $es-white; select.load-more-select {
font-size: $es-font-size-sm; appearance: none;
font-weight: $es-font-weight-semibold; padding: 0.25rem 1.75rem 0.25rem 0.5rem !important;
background: $es-primary; border: 1px solid $es-border-color !important;
border-radius: $es-radius-sm; border-radius: $es-radius-sm !important;
background: $es-white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cpath fill='%23666' d='M0 2l4 4 4-4z'/%3E%3C/svg%3E") no-repeat right 0.5rem center !important;
background-size: 8px !important;
font-size: $es-font-size-xs !important;
font-weight: $es-font-weight-medium;
color: $es-primary;
cursor: pointer;
transition: all $es-transition-fast; transition: all $es-transition-fast;
height: auto !important;
min-height: 0 !important;
line-height: 1.3 !important;
&:hover { &:hover {
background: $es-primary-hover; border-color: $es-primary !important;
background-color: $es-primary-light !important;
} }
i { &:focus {
font-size: 12px; outline: none !important;
border-color: $es-primary !important;
box-shadow: 0 0 0 2px rgba($es-primary, 0.1) !important;
} }
} }
.load-more-remaining {
font-size: $es-font-size-xs;
color: $es-text-muted;
}
} }
// Individual chip // Individual chip