', {
+ class: 'trait-validation-error',
+ html: this.esIcon('warning') + ' ' + message
+ });
+ this.$wrapper.find('.condition-trait-header').after($error);
+ $('html, body').animate({ scrollTop: this.$wrapper.offset().top - 100 }, 300);
+ if (!this.$wrapper.find('.condition-trait-body').is(':visible')) {
+ this.$wrapper.find('.condition-trait-body').slideDown(200);
+ this.$wrapper.removeClass('collapsed');
+ }
+ },
+
+ clearValidationError: function() {
+ this.$wrapper.removeClass('has-validation-error');
+ this.$wrapper.find('.trait-validation-error').remove();
+ },
+
+ getBlockMode: function(blockType) {
+ var blockDef = this.config.blocks[blockType];
+ return (blockDef && blockDef.mode) ? blockDef.mode : 'multi';
+ },
+
+ isBlockSingleMode: function(blockType) {
+ return this.getBlockMode(blockType) === 'single';
+ },
+
+ getCurrentSingleSelection: function() {
+ if ((this.config.mode || 'multi') !== 'single') return null;
+ var $chip = this.$wrapper.find('.entity-chips .entity-chip').first();
+ if ($chip.length) {
+ var $block = $chip.closest('.target-block');
+ return {
+ name: $chip.find('.chip-name').text() || $chip.data('id'),
+ entityType: $block.data('block-type') || 'item'
+ };
+ }
+ return null;
+ },
+
+ /**
+ * Check if entity type supports tree browsing
+ */
+ supportsTreeBrowsing: function(entityType) {
+ return entityType === 'categories' || entityType === 'cms_categories';
+ }
+ };
+
+})(jQuery);
diff --git a/sources/sources/js/admin/entity-selector/_validation.js b/sources/sources/js/admin/entity-selector/_validation.js
new file mode 100644
index 0000000..089a57b
--- /dev/null
+++ b/sources/sources/js/admin/entity-selector/_validation.js
@@ -0,0 +1,365 @@
+/**
+ * Entity Selector - Validation Module
+ * Conflict detection and prevention for entity selections
+ * @partial _validation.js
+ *
+ * Features:
+ * - Same entity in include & exclude detection
+ * - Parent-child conflict detection for tree entities
+ * - Redundant selection detection
+ * - Error message display
+ */
+
+(function($) {
+ 'use strict';
+
+ window._EntitySelectorMixins = window._EntitySelectorMixins || {};
+
+ window._EntitySelectorMixins.validation = {
+
+ /**
+ * Validate a selection before adding it
+ * Returns { valid: true } or { valid: false, error: 'message', type: 'conflict_type' }
+ *
+ * @param {number|string} id - Entity ID to validate
+ * @param {string} name - Entity name (for error messages)
+ * @param {string} section - 'include' or 'exclude'
+ * @param {Object} data - Additional data (parent_id, etc.)
+ * @returns {Object} Validation result
+ */
+ validateSelection: function(id, name, section, data) {
+ if (!this.activeGroup) {
+ return { valid: true };
+ }
+
+ var trans = this.config.trans || {};
+ id = parseInt(id, 10);
+
+ var $block = this.$wrapper.find('.target-block[data-block-type="' + this.activeGroup.blockType + '"]');
+ var $group = $block.find('.selection-group[data-group-index="' + this.activeGroup.groupIndex + '"]');
+
+ // Get include chips
+ var includeIds = this.getChipIds($group.find('.include-picker'));
+
+ // Get all exclude chips (from all exclude rows)
+ var excludeIds = [];
+ $group.find('.exclude-row').each(function() {
+ var $excludePicker = $(this).find('.exclude-picker');
+ var ids = [];
+ $excludePicker.find('.entity-chip').each(function() {
+ ids.push(parseInt($(this).data('id'), 10));
+ });
+ excludeIds = excludeIds.concat(ids);
+ });
+
+ // 1. Check for same entity in include & exclude
+ var conflictResult = this.checkIncludeExcludeConflict(id, name, section, includeIds, excludeIds, trans);
+ if (!conflictResult.valid) {
+ return conflictResult;
+ }
+
+ // 2. Check for redundant selection (already selected in same section)
+ var redundantResult = this.checkRedundantSelection(id, name, section, includeIds, excludeIds, trans);
+ if (!redundantResult.valid) {
+ return redundantResult;
+ }
+
+ // 3. Check for parent-child conflicts (only for tree entities)
+ var searchEntity = this.activeGroup.searchEntity;
+ if (searchEntity === 'categories' || searchEntity === 'cms_categories') {
+ var treeResult = this.checkTreeConflicts(id, name, section, data, includeIds, excludeIds, trans);
+ if (!treeResult.valid) {
+ return treeResult;
+ }
+ }
+
+ return { valid: true };
+ },
+
+ /**
+ * Check if entity is in both include and exclude
+ */
+ checkIncludeExcludeConflict: function(id, name, section, includeIds, excludeIds, trans) {
+ if (section === 'include' && excludeIds.indexOf(id) !== -1) {
+ return {
+ valid: false,
+ type: 'include_exclude_conflict',
+ error: (trans.error_in_exclude || '"{name}" is already in the exclude list. Remove it from exclude first.').replace('{name}', name)
+ };
+ }
+
+ if (section === 'exclude' && includeIds.indexOf(id) !== -1) {
+ return {
+ valid: false,
+ type: 'include_exclude_conflict',
+ error: (trans.error_in_include || '"{name}" is already in the include list. Remove it from include first.').replace('{name}', name)
+ };
+ }
+
+ return { valid: true };
+ },
+
+ /**
+ * Check for redundant selection (already selected)
+ */
+ checkRedundantSelection: function(id, name, section, includeIds, excludeIds, trans) {
+ if (section === 'include' && includeIds.indexOf(id) !== -1) {
+ return {
+ valid: false,
+ type: 'redundant',
+ error: (trans.error_already_selected || '"{name}" is already selected.').replace('{name}', name)
+ };
+ }
+
+ if (section === 'exclude' && excludeIds.indexOf(id) !== -1) {
+ return {
+ valid: false,
+ type: 'redundant',
+ error: (trans.error_already_excluded || '"{name}" is already in an exclude list.').replace('{name}', name)
+ };
+ }
+
+ return { valid: true };
+ },
+
+ /**
+ * Check for parent-child conflicts in tree entities
+ */
+ checkTreeConflicts: function(id, name, section, data, includeIds, excludeIds, trans) {
+ // Need tree data for parent-child lookups
+ if (!this.treeFlatData) {
+ return { valid: true };
+ }
+
+ var parentId = data && data.parentId ? parseInt(data.parentId, 10) : null;
+
+ // Build lookup for quick access
+ var lookup = {};
+ this.treeFlatData.forEach(function(item) {
+ lookup[parseInt(item.id, 10)] = item;
+ });
+
+ // Get all ancestor IDs
+ var ancestorIds = this.getAncestorIds(id, lookup);
+
+ // Get all descendant IDs
+ var descendantIds = this.getDescendantIds(id, lookup);
+
+ if (section === 'include') {
+ // Check if any ancestor is excluded
+ for (var i = 0; i < ancestorIds.length; i++) {
+ if (excludeIds.indexOf(ancestorIds[i]) !== -1) {
+ var ancestorName = lookup[ancestorIds[i]] ? lookup[ancestorIds[i]].name : 'Parent';
+ return {
+ valid: false,
+ type: 'parent_excluded',
+ error: (trans.error_parent_excluded || 'Cannot include "{name}" because its parent "{parent}" is excluded.').replace('{name}', name).replace('{parent}', ancestorName)
+ };
+ }
+ }
+
+ // Check if any descendant is excluded
+ for (var j = 0; j < descendantIds.length; j++) {
+ if (excludeIds.indexOf(descendantIds[j]) !== -1) {
+ var descendantName = lookup[descendantIds[j]] ? lookup[descendantIds[j]].name : 'Child';
+ return {
+ valid: false,
+ type: 'child_excluded',
+ error: (trans.error_child_excluded || 'Cannot include "{name}" because its child "{child}" is excluded. Remove the child from exclude first.').replace('{name}', name).replace('{child}', descendantName)
+ };
+ }
+ }
+ }
+
+ if (section === 'exclude') {
+ // Check if any ancestor is included
+ for (var k = 0; k < ancestorIds.length; k++) {
+ if (includeIds.indexOf(ancestorIds[k]) !== -1) {
+ var parentName = lookup[ancestorIds[k]] ? lookup[ancestorIds[k]].name : 'Parent';
+ return {
+ valid: false,
+ type: 'parent_included',
+ error: (trans.error_parent_included || 'Cannot exclude "{name}" because its parent "{parent}" is included. This would create a contradiction.').replace('{name}', name).replace('{parent}', parentName)
+ };
+ }
+ }
+
+ // Check if any descendant is included (warning about implicit exclusion)
+ var includedDescendants = [];
+ for (var m = 0; m < descendantIds.length; m++) {
+ if (includeIds.indexOf(descendantIds[m]) !== -1) {
+ var childName = lookup[descendantIds[m]] ? lookup[descendantIds[m]].name : 'Child';
+ includedDescendants.push(childName);
+ }
+ }
+
+ if (includedDescendants.length > 0) {
+ return {
+ valid: false,
+ type: 'children_included',
+ error: (trans.error_children_included || 'Cannot exclude "{name}" because its children ({children}) are included. Remove them from include first.').replace('{name}', name).replace('{children}', includedDescendants.slice(0, 3).join(', ') + (includedDescendants.length > 3 ? '...' : ''))
+ };
+ }
+ }
+
+ return { valid: true };
+ },
+
+ /**
+ * Get all ancestor IDs for a given item
+ */
+ getAncestorIds: function(id, lookup) {
+ var ancestors = [];
+ var current = lookup[id];
+
+ while (current && current.parent_id) {
+ var parentId = parseInt(current.parent_id, 10);
+ if (parentId && lookup[parentId]) {
+ ancestors.push(parentId);
+ current = lookup[parentId];
+ } else {
+ break;
+ }
+ }
+
+ return ancestors;
+ },
+
+ /**
+ * Get all descendant IDs for a given item
+ */
+ getDescendantIds: function(id, lookup) {
+ var descendants = [];
+ var self = this;
+
+ // Find direct children
+ Object.keys(lookup).forEach(function(key) {
+ var item = lookup[key];
+ if (parseInt(item.parent_id, 10) === id) {
+ var childId = parseInt(item.id, 10);
+ descendants.push(childId);
+ // Recursively get children's descendants
+ var childDescendants = self.getDescendantIds(childId, lookup);
+ descendants = descendants.concat(childDescendants);
+ }
+ });
+
+ return descendants;
+ },
+
+ /**
+ * Get chip IDs from a picker
+ */
+ getChipIds: function($picker) {
+ var ids = [];
+ $picker.find('.entity-chip').each(function() {
+ ids.push(parseInt($(this).data('id'), 10));
+ });
+ return ids;
+ },
+
+ /**
+ * Validate pending selections (for tree view bulk operations)
+ * Returns array of invalid items
+ */
+ validatePendingSelections: function(pendingSelections, section) {
+ var self = this;
+ var errors = [];
+
+ if (!pendingSelections || !pendingSelections.length) {
+ return errors;
+ }
+
+ pendingSelections.forEach(function(sel) {
+ var result = self.validateSelection(sel.id, sel.name, section, sel.data || {});
+ if (!result.valid) {
+ errors.push({
+ id: sel.id,
+ name: sel.name,
+ error: result.error,
+ type: result.type
+ });
+ }
+ });
+
+ return errors;
+ },
+
+ /**
+ * Show validation error toast
+ */
+ showValidationError: function(message) {
+ var trans = this.config.trans || {};
+ var title = trans.validation_error || 'Selection Conflict';
+
+ // Remove existing toast
+ $('.es-validation-toast').remove();
+
+ // Create toast HTML
+ var html = '
';
+ html += '
' + this.esIcon('warning') + '
';
+ html += '
';
+ html += '
' + this.escapeHtml(title) + '
';
+ html += '
' + this.escapeHtml(message) + '
';
+ html += '
';
+ html += '
';
+ html += '
';
+
+ var $toast = $(html);
+ $('body').append($toast);
+
+ // Position near dropdown if visible
+ if (this.$dropdown && this.$dropdown.hasClass('show')) {
+ var dropdownOffset = this.$dropdown.offset();
+ $toast.css({
+ position: 'fixed',
+ top: dropdownOffset.top - $toast.outerHeight() - 10,
+ left: dropdownOffset.left,
+ zIndex: 10001
+ });
+ } else {
+ $toast.css({
+ position: 'fixed',
+ top: 20,
+ right: 20,
+ zIndex: 10001
+ });
+ }
+
+ // Animate in
+ $toast.hide().fadeIn(200);
+
+ // Auto-dismiss after 5 seconds
+ setTimeout(function() {
+ $toast.fadeOut(200, function() {
+ $(this).remove();
+ });
+ }, 5000);
+
+ // Close button
+ $toast.on('click', '.es-toast-close', function() {
+ $toast.fadeOut(200, function() {
+ $(this).remove();
+ });
+ });
+ },
+
+ /**
+ * Validate and add selection (wrapper that validates before adding)
+ * Returns true if added successfully, false if validation failed
+ */
+ validateAndAddSelection: function($picker, id, name, data, section) {
+ var result = this.validateSelection(id, name, section, data || {});
+
+ if (!result.valid) {
+ this.showValidationError(result.error);
+ return false;
+ }
+
+ // Validation passed, add the selection
+ this.addSelection($picker, id, name, data);
+ return true;
+ }
+ };
+
+})(jQuery);
diff --git a/sources/sources/js/admin/entity-selector/index.php b/sources/sources/js/admin/entity-selector/index.php
new file mode 100644
index 0000000..c4f371f
--- /dev/null
+++ b/sources/sources/js/admin/entity-selector/index.php
@@ -0,0 +1 @@
+visibility
+@mixin count-badge($bg: $es-primary) {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.25rem;
+ min-width: 20px;
+ height: 20px;
+ padding: 0 0.5rem;
+ background: $bg;
+ color: $es-white;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ border-radius: $es-radius-full;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+ flex-shrink: 0;
+
+ &:hover {
+ transform: scale(1.05);
+ box-shadow: 0 2px 8px rgba($bg, 0.4);
+ }
+
+ // Focus state - maintain styled appearance
+ &:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px rgba($bg, 0.3), 0 2px 8px rgba($bg, 0.4);
+ }
+
+ // Loading state - spinner icon replaces eye
+ &.loading {
+ cursor: wait;
+
+ i {
+ font-size: 10px;
+ animation: spin 0.6s linear infinite;
+ }
+
+ &:hover {
+ transform: none;
+ box-shadow: none;
+ }
+ }
+
+ // Inactive/empty state
+ &.inactive,
+ &.no-matches {
+ background: $es-slate-400;
+ cursor: default;
+
+ &:hover {
+ transform: none;
+ box-shadow: none;
+ }
+ }
+
+ // Popover open state
+ &.popover-open {
+ background: color.adjust($bg, $lightness: -10%);
+ box-shadow: 0 2px 8px rgba($bg, 0.4);
+ }
+
+ // Icon inside badge (eye, spinner, etc.)
+ i {
+ font-size: 10px;
+ line-height: 1;
+ opacity: 0.8;
+ }
+
+ &:hover i {
+ opacity: 1;
+ }
+
+ .preview-count {
+ font-weight: $es-font-weight-bold;
+ }
+}
+
+@keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+@keyframes spin-pulse {
+ 0% { transform: rotate(0deg); opacity: 1; }
+ 50% { opacity: 0.4; }
+ 100% { transform: rotate(360deg); opacity: 1; }
+}
+
+// Global spin utility classes (Material Icons replacement for icon-spin / icon-spin-pulse)
+.es-spin {
+ animation: spin 1s linear infinite;
+}
+
+.es-spin-pulse {
+ animation: spin-pulse 1s ease-in-out infinite;
+}
+
+@mixin chip {
+ display: inline-flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-xs $es-spacing-sm;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ background: $es-gray-200;
+ color: $es-gray-700;
+ border-radius: $es-radius-full;
+
+ .chip-remove {
+ @include button-reset;
+ @include flex-center;
+ width: 14px;
+ height: 14px;
+ font-size: 10px;
+ color: $es-text-muted;
+ border-radius: 50%;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.1);
+ color: $es-danger;
+ }
+ }
+}
+
+// =============================================================================
+// Toggle Switch
+// =============================================================================
+
+@mixin toggle-switch($width: 36px, $height: 20px) {
+ position: relative;
+ width: $width;
+ height: $height;
+ border-radius: $height;
+ background: $es-gray-400;
+ transition: background-color $es-transition-normal;
+ cursor: pointer;
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: $height - 4px;
+ height: $height - 4px;
+ background: $es-white;
+ border-radius: 50%;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+ transition: transform $es-transition-normal;
+ }
+
+ &.active {
+ background: $es-success;
+
+ &::after {
+ transform: translateX($width - $height);
+ }
+ }
+}
+
+// =============================================================================
+// Screen Reader Only
+// =============================================================================
+
+@mixin sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
diff --git a/sources/sources/scss/_variables.scss b/sources/sources/scss/_variables.scss
new file mode 100644
index 0000000..26b9598
--- /dev/null
+++ b/sources/sources/scss/_variables.scss
@@ -0,0 +1,154 @@
+/**
+ * Entity Selector Variables
+ * Bootstrap 4 compatible values for PrestaShop admin theme
+ *
+ * Imports shared variables from prestashop-admin package
+ * and maps them to $es-* prefixed aliases for this package
+ */
+
+// Import shared variables from prestashop-admin
+@use '../../../prestashop-admin/assets/scss/variables' as admin;
+
+// =============================================================================
+// Base Colors
+// =============================================================================
+
+$es-white: #ffffff !default;
+$es-black: #000000 !default;
+
+// Primary (from prestashop-admin)
+$es-primary: admin.$primary !default;
+$es-primary-hover: #1a9ab7 !default;
+$es-primary-light: rgba(37, 185, 215, 0.1) !default;
+
+// Semantic colors (from prestashop-admin)
+$es-success: admin.$success !default;
+$es-success-light: #d4edda !default;
+$es-success-dark: #1e7e34 !default;
+
+$es-danger: admin.$danger !default;
+$es-danger-light: #f8d7da !default;
+$es-danger-dark: #bd2130 !default;
+
+$es-warning: admin.$warning !default;
+$es-warning-light: #fff3cd !default;
+
+$es-info: admin.$info !default;
+$es-info-light: #d1ecf1 !default;
+
+// =============================================================================
+// Gray Scale (Bootstrap 4)
+// =============================================================================
+
+$es-gray-100: admin.$light !default;
+$es-gray-200: #e9ecef !default;
+$es-gray-300: admin.$border-color !default;
+$es-gray-400: #ced4da !default;
+$es-gray-500: #adb5bd !default;
+$es-gray-600: admin.$secondary !default;
+$es-gray-700: #495057 !default;
+$es-gray-800: admin.$dark !default;
+$es-gray-900: #212529 !default;
+
+// Slate (subtle variations)
+$es-slate-50: #f8fafc !default;
+$es-slate-100: #f1f5f9 !default;
+$es-slate-200: #e2e8f0 !default;
+$es-slate-300: #cbd5e1 !default;
+$es-slate-400: #94a3b8 !default;
+$es-slate-500: #64748b !default;
+$es-slate-600: #475569 !default;
+$es-slate-700: #334155 !default;
+$es-slate-800: #1e293b !default;
+$es-slate-900: #0f172a !default;
+
+// Cyan
+$es-cyan-50: #ecfeff !default;
+$es-cyan-100: #cffafe !default;
+$es-cyan-200: #a5f3fc !default;
+$es-cyan-500: #06b6d4 !default;
+$es-cyan-600: #0891b2 !default;
+$es-cyan-700: #0e7490 !default;
+
+// =============================================================================
+// Semantic Aliases
+// =============================================================================
+
+$es-bg-header: $es-gray-100 !default;
+$es-bg-hover: $es-gray-200 !default;
+$es-bg-active: $es-gray-200 !default;
+$es-bg-body: $es-white !default;
+
+$es-border-color: admin.$border-color !default;
+$es-border-light: $es-gray-200 !default;
+$es-border-dark: $es-gray-400 !default;
+
+$es-text-primary: $es-gray-900 !default;
+$es-text-secondary: $es-gray-700 !default;
+$es-text-muted: $es-gray-600 !default;
+$es-text-light: $es-gray-500 !default;
+
+// =============================================================================
+// Spacing (Bootstrap 4 compatible, derived from admin.$spacer)
+// =============================================================================
+
+$es-spacing-xs: admin.$spacer * 0.25 !default; // 4px
+$es-spacing-sm: admin.$spacer * 0.5 !default; // 8px
+$es-spacing-md: admin.$spacer !default; // 16px
+$es-spacing-lg: admin.$spacer * 1.5 !default; // 24px
+$es-spacing-xl: admin.$spacer * 2 !default; // 32px
+
+// =============================================================================
+// Border Radius (from prestashop-admin)
+// =============================================================================
+
+$es-radius-sm: admin.$border-radius-sm !default;
+$es-radius-md: admin.$border-radius !default;
+$es-radius-lg: admin.$border-radius-lg !default;
+$es-radius-xl: 0.5rem !default;
+$es-radius-full: 50rem !default;
+
+// =============================================================================
+// Box Shadows (Bootstrap 4 compatible)
+// =============================================================================
+
+$es-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !default;
+$es-shadow-md: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !default;
+$es-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175) !default;
+$es-shadow-xl: 0 1.5rem 4rem rgba(0, 0, 0, 0.2) !default;
+
+// =============================================================================
+// Transitions
+// =============================================================================
+
+$es-transition-fast: 0.15s ease-in-out !default;
+$es-transition-normal: 0.2s ease-in-out !default;
+$es-transition-slow: 0.3s ease-in-out !default;
+
+// =============================================================================
+// Z-Index (below Bootstrap modal)
+// =============================================================================
+
+$es-z-dropdown: 1000 !default;
+$es-z-modal: 1050 !default;
+$es-z-popover: 1060 !default;
+$es-z-tooltip: 1070 !default;
+
+// =============================================================================
+// Typography
+// =============================================================================
+
+$es-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default;
+
+$es-font-size-xs: 0.75rem !default; // 12px
+$es-font-size-sm: 0.875rem !default; // 14px
+$es-font-size-base: 1rem !default; // 16px
+$es-font-size-lg: 1.125rem !default; // 18px
+
+$es-font-weight-normal: 400 !default;
+$es-font-weight-medium: 500 !default;
+$es-font-weight-semibold: 600 !default;
+$es-font-weight-bold: 700 !default;
+
+$es-line-height-tight: 1.25 !default;
+$es-line-height-normal: 1.5 !default;
diff --git a/sources/sources/scss/components/_chips.scss b/sources/sources/scss/components/_chips.scss
new file mode 100644
index 0000000..835bec6
--- /dev/null
+++ b/sources/sources/scss/components/_chips.scss
@@ -0,0 +1,1071 @@
+/**
+ * Chips Component
+ * Entity chips, selection pills, tags
+ */
+
+@use "sass:color";
+@use '../variables' as *;
+@use '../mixins' as *;
+
+.target-conditions-trait,
+.entity-selector-trait {
+
+ // Chips container wrapper with toolbar
+ .chips-wrapper {
+ display: flex;
+ flex-direction: column;
+ margin-top: $es-spacing-sm;
+ background: $es-slate-50;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-md;
+ overflow: hidden;
+ }
+
+ // Chips toolbar - integrated filter bar inside chips area
+ .chips-toolbar {
+ display: none;
+ align-items: center;
+ flex-wrap: nowrap;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ padding-bottom: 0;
+ background: transparent;
+
+ &.has-chips {
+ display: flex;
+ }
+
+ // Filter input - takes available space, icon embedded as background
+ // Using [type="text"] for specificity over .bootstrap input[type="text"]
+ input[type="text"].chips-search-input {
+ all: unset;
+ display: block;
+ flex: 1 1 auto;
+ min-width: 80px;
+ width: auto;
+ height: auto;
+ padding: 0.2rem 0.5rem 0.2rem 1.5rem;
+ background: $es-white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") no-repeat 0.375rem center;
+ background-size: 10px;
+ border: 1px solid $es-slate-300;
+ border-radius: $es-radius-sm;
+ font-size: 11px;
+ line-height: 1.4;
+ color: $es-text-primary;
+ box-sizing: border-box;
+ transition: all $es-transition-fast;
+
+ &::placeholder {
+ color: $es-text-muted;
+ font-size: 11px;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $es-primary;
+ box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
+ }
+ }
+
+ // Sort dropdown - compact, auto width
+ select.chips-sort-select {
+ all: unset;
+ flex: 0 0 auto;
+ padding: 0.2rem 1.25rem 0.2rem 0.5rem;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ 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.375rem center;
+ background-size: 8px;
+ font-size: 10px;
+ line-height: 1.4;
+ color: $es-text-secondary;
+ cursor: pointer;
+ box-sizing: border-box;
+ white-space: nowrap;
+
+ &:hover {
+ border-color: $es-primary;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $es-primary;
+ box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
+ }
+ }
+ }
+
+ // Count badge - smaller, pill style
+ .chips-count {
+ display: inline-flex;
+ align-items: center;
+ flex-shrink: 0; // Don't shrink
+ gap: 0.125rem;
+ padding: 0.2rem 0.5rem;
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ font-size: 10px;
+ font-weight: $es-font-weight-semibold;
+ border-radius: $es-radius-sm;
+ white-space: nowrap;
+ line-height: 1.4;
+
+ &.has-filter {
+ background: $es-cyan-100;
+ color: $es-cyan-700;
+ }
+
+ .count-filtered {
+ font-weight: $es-font-weight-bold;
+ }
+
+ .count-separator {
+ opacity: 0.6;
+ margin: 0 0.125rem;
+ }
+ }
+
+ .chips-actions {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ margin-left: auto;
+ }
+
+ // Clear button - subtle, chip-like
+ .btn-chips-clear {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ flex-shrink: 0; // Don't shrink
+ gap: 0.25rem;
+ padding: 0.2rem 0.5rem;
+ color: $es-danger;
+ font-size: 10px;
+ font-weight: $es-font-weight-medium;
+ background: rgba($es-danger, 0.1);
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+ white-space: nowrap; // Prevent text wrapping
+ line-height: 1.4;
+
+ &:hover {
+ background: $es-danger;
+ color: $es-white;
+ }
+
+ i {
+ font-size: 9px;
+ flex-shrink: 0;
+ }
+
+ .clear-text {
+ // Hide text on small screens, keep icon
+ @media (max-width: 480px) {
+ display: none;
+ }
+ }
+ }
+
+ // Chips container - flows naturally from toolbar
+ .entity-chips {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-sm $es-spacing-md $es-spacing-md;
+ min-height: 40px;
+ max-height: 300px;
+ overflow-y: auto;
+
+ &:empty {
+ display: none;
+ }
+ }
+
+ // Load more section with select dropdown
+ .chips-load-more {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: transparent;
+ border-top: 1px dashed $es-border-color;
+
+ .load-more-label {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+
+ .load-more-select,
+ select.load-more-select {
+ appearance: none;
+ padding: 0.25rem 1.75rem 0.25rem 0.5rem !important;
+ border: 1px solid $es-border-color !important;
+ 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;
+ height: auto !important;
+ min-height: 0 !important;
+ line-height: 1.3 !important;
+
+ &:hover {
+ border-color: $es-primary !important;
+ background-color: $es-primary-light !important;
+ }
+
+ &:focus {
+ 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
+ .entity-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ background: $es-slate-100;
+ color: $es-text-secondary;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border-radius: $es-radius-full;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ }
+
+ // Chip with image
+ &.has-image {
+ padding-left: 0.25rem;
+ }
+
+ // Hidden by search filter or pagination
+ &.chip-filtered-out,
+ &.chip-paginated-out {
+ display: none;
+ }
+ }
+
+ .chip-image {
+ width: 20px;
+ height: 20px;
+ object-fit: cover;
+ border-radius: 50%;
+ flex-shrink: 0;
+ }
+
+ .chip-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ color: $es-text-muted;
+ flex-shrink: 0;
+
+ // Product/entity images inside chip
+ img {
+ width: 20px;
+ height: 20px;
+ object-fit: cover;
+ border-radius: $es-radius-sm;
+ }
+ }
+
+ // Country flag in chip
+ .chip-flag {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+
+ img {
+ width: 18px;
+ height: 12px;
+ object-fit: cover;
+ border-radius: 2px;
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
+ }
+
+ .flag-fallback {
+ width: 18px;
+ height: 12px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, #e8eaed 0%, #dadce0 100%);
+ border-radius: 2px;
+ font-size: 10px;
+ color: #5f6368;
+ }
+ }
+
+ // Holiday preview button in country chip
+ .chip-preview-holidays {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ color: $es-primary;
+ border-radius: 50%;
+ flex-shrink: 0;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: rgba($es-primary, 0.15);
+ color: darken($es-primary, 10%);
+ }
+
+ i.material-icons {
+ font-size: 14px !important;
+ }
+ }
+
+ .chip-text,
+ .chip-name {
+ // Show full name, no truncation
+ word-break: break-word;
+ }
+
+ .chip-remove {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 16px;
+ height: 16px;
+ margin-left: 0.125rem;
+ color: $es-text-muted;
+ border-radius: 50%;
+ flex-shrink: 0;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.1);
+ color: $es-danger;
+ }
+
+ i {
+ font-size: 10px;
+ }
+ }
+
+ // Chip variants
+ .entity-chip.chip-primary {
+ background: $es-primary-light;
+ color: $es-primary;
+
+ &:hover {
+ background: rgba($es-primary, 0.2);
+ }
+ }
+
+ .entity-chip.chip-success {
+ background: $es-success-light;
+ color: $es-success-dark;
+
+ &:hover {
+ background: rgba($es-success, 0.2);
+ }
+ }
+
+ .entity-chip.chip-danger {
+ background: $es-danger-light;
+ color: $es-danger;
+
+ &:hover {
+ background: rgba($es-danger, 0.2);
+ }
+ }
+
+ .entity-chip.chip-warning {
+ background: $es-warning-light;
+ color: color.adjust($es-warning, $lightness: -20%);
+
+ &:hover {
+ background: rgba($es-warning, 0.3);
+ }
+ }
+
+ // Chip loading state
+ .entity-chip.loading,
+ .entity-chip-loading {
+ opacity: 0.7;
+
+ .chip-remove {
+ display: none;
+ }
+
+ .chip-icon i {
+ animation: spin 0.6s linear infinite;
+ }
+ }
+
+ // Hidden chip (collapsed view)
+ .entity-chip.chip-hidden {
+ display: none;
+ }
+
+ // Chips expanded/collapsed states
+ .entity-chips.chips-collapsed,
+ .entity-chips.chips-expanded {
+ position: relative;
+ }
+
+ // Show more/less toggle
+ .chips-show-more-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ color: $es-primary;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ cursor: pointer;
+ transition: color $es-transition-fast;
+
+ &:hover {
+ color: $es-primary-hover;
+ }
+
+ i {
+ font-size: 10px;
+ }
+ }
+
+ .chips-collapse-toggle,
+ .chips-expand-toggle {
+ // Specific variants inherit from .chips-show-more-toggle
+ }
+
+ // More chips indicator
+ .chips-more {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.25rem 0.5rem;
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ border-radius: $es-radius-full;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-300;
+ }
+ }
+
+ // Add chip button
+ .chip-add-btn {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ background: transparent;
+ color: $es-primary;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border: 1px dashed $es-primary;
+ border-radius: $es-radius-full;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary-light;
+ }
+
+ i {
+ font-size: 10px;
+ }
+ }
+
+ // Inline chips (compact mode)
+ .entity-chips.inline {
+ display: inline-flex;
+ padding: 0;
+ min-height: auto;
+
+ .entity-chip {
+ padding: 0.125rem 0.375rem;
+ font-size: 11px;
+ }
+ }
+
+ // Selected chips section in include/exclude
+ .selected-chips-container {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-xs;
+ }
+
+ .selected-chips-label {
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-muted;
+ }
+
+ // Pattern chips (for name/reference patterns)
+ .entity-chip.chip-pattern {
+ background: #fef3c7;
+ color: #92400e;
+ font-family: monospace;
+
+ &:hover {
+ background: #fde68a;
+ }
+
+ .chip-icon {
+ color: #d97706;
+ }
+ }
+
+ // Range chips (price, quantity, etc.)
+ .entity-chip.chip-range,
+ .range-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ background: $es-cyan-50;
+ color: $es-cyan-600;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border-radius: $es-radius-full;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-cyan-100;
+ }
+ }
+
+ .range-chip-text {
+ font-family: monospace;
+ }
+
+ .btn-remove-range {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 16px;
+ height: 16px;
+ color: $es-cyan-600;
+ border-radius: 50%;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.1);
+ color: $es-danger;
+ }
+
+ i {
+ font-size: 10px;
+ }
+ }
+
+ // Multi-range chips container
+ .multi-range-chips {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-xs;
+ margin-bottom: $es-spacing-xs;
+
+ &:empty {
+ display: none;
+ }
+ }
+
+ // Pattern chips container
+ .pattern-chips {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-sm 0;
+ min-height: 32px;
+
+ &:empty::before {
+ content: attr(data-placeholder);
+ color: $es-text-muted;
+ font-size: $es-font-size-xs;
+ font-style: italic;
+ }
+ }
+
+ // Pattern tag
+ .pattern-tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ background: #fef3c7;
+ color: #92400e;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: #fde68a;
+ }
+
+ &.case-sensitive {
+ .case-icon {
+ color: $es-success;
+ font-weight: $es-font-weight-bold;
+ }
+ }
+
+ &.draft-tag {
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ padding: 0;
+ flex: 1;
+ min-width: 150px;
+
+ &:hover {
+ background: $es-white;
+ }
+
+ .pattern-input {
+ flex: 1;
+ min-width: 100px;
+ padding: 0.375rem;
+ border: 0;
+ background: transparent;
+ font-size: $es-font-size-sm;
+ font-family: inherit;
+
+ &:focus {
+ outline: none;
+ }
+
+ &::placeholder {
+ color: $es-text-muted;
+ font-style: italic;
+ }
+ }
+ }
+ }
+
+ .pattern-tag-text {
+ font-family: monospace;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .btn-toggle-case {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.1);
+ }
+ }
+
+ .case-icon {
+ font-size: 11px;
+ font-weight: $es-font-weight-semibold;
+ font-family: monospace;
+ }
+
+ .btn-remove-pattern {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ color: #d97706;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.1);
+ color: $es-danger;
+ }
+
+ i {
+ font-size: 10px;
+ }
+ }
+
+ .btn-add-pattern {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ color: $es-primary;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary-light;
+ }
+
+ i {
+ font-size: 12px;
+ }
+ }
+
+ // Pattern match count (in draft tag)
+ .pattern-match-count {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0 0.375rem;
+ color: $es-text-muted;
+ font-size: $es-font-size-xs;
+ cursor: pointer;
+
+ &.count-zero {
+ color: $es-warning;
+ }
+
+ &.count-found {
+ color: $es-success;
+ }
+
+ .count-value {
+ font-weight: $es-font-weight-semibold;
+ }
+ }
+
+ // Pattern input row
+ .pattern-input-row {
+ display: flex;
+ align-items: stretch;
+ gap: $es-spacing-xs;
+ }
+}
+
+// ==========================================================================
+// Holiday Preview Popover (Country chip eye button)
+// ==========================================================================
+
+.holiday-preview-popover {
+ position: absolute;
+ z-index: 10001;
+ width: 320px;
+ max-width: 90vw;
+ background: $es-white;
+ border-radius: $es-radius-lg;
+ box-shadow: $es-shadow-xl;
+ overflow: hidden;
+
+ .popover-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ }
+
+ .popover-title {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ }
+
+ .popover-flag {
+ border-radius: 2px;
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
+ }
+
+ .popover-close {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ color: $es-text-muted;
+ border-radius: $es-radius-md;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ }
+
+ i.material-icons {
+ font-size: 18px !important;
+ }
+ }
+
+ .popover-body {
+ max-height: 350px;
+ overflow-y: auto;
+ padding: $es-spacing-sm;
+ @include custom-scrollbar;
+ }
+
+ // Loading state
+ .holiday-preview-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xl 0;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+
+ i.material-icons {
+ font-size: 20px !important;
+ }
+
+ .es-spin {
+ animation: spin 1s linear infinite;
+ }
+ }
+
+ // Empty state
+ .holiday-preview-empty {
+ text-align: center;
+ padding: $es-spacing-xl 0;
+ color: $es-text-muted;
+
+ i.material-icons {
+ font-size: 48px !important;
+ opacity: 0.4;
+ margin-bottom: $es-spacing-sm;
+ display: block;
+ }
+
+ p {
+ margin: 0;
+ font-size: $es-font-size-sm;
+ }
+ }
+
+ // Holiday list
+ .holiday-list {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-xs;
+ }
+
+ .holiday-item {
+ display: flex;
+ align-items: flex-start;
+ gap: $es-spacing-md;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-slate-50;
+ border-radius: $es-radius-md;
+ border-left: 3px solid $es-success;
+
+ &.holiday-type-bank,
+ &.holiday-type-bank-holiday {
+ border-left-color: $es-info;
+ }
+
+ &.holiday-type-observance {
+ border-left-color: $es-warning;
+ }
+
+ &.holiday-type-regional,
+ &.holiday-type-local-holiday {
+ border-left-color: #8b5cf6;
+ }
+ }
+
+ .holiday-date {
+ flex-shrink: 0;
+ min-width: 80px;
+
+ .holiday-day {
+ display: block;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ }
+
+ .holiday-weekday {
+ display: block;
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+ }
+
+ .holiday-info {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .holiday-country-flag {
+ vertical-align: middle;
+ margin-right: 0.25rem;
+ border-radius: 2px;
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
+ }
+
+ .holiday-name {
+ display: inline;
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ word-wrap: break-word;
+ }
+
+ .holiday-type-badge {
+ display: inline-block;
+ margin-left: $es-spacing-sm;
+ padding: 0.125rem 0.375rem;
+ font-size: 10px;
+ font-weight: $es-font-weight-medium;
+ text-transform: capitalize;
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ border-radius: $es-radius-sm;
+ vertical-align: middle;
+ }
+
+ .holiday-preview-note {
+ margin-top: $es-spacing-md;
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ text-align: center;
+ }
+
+ // Filter input
+ .popover-filter {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-xs $es-spacing-md;
+ border-bottom: 1px solid $es-border-color;
+ background: $es-slate-50;
+
+ i.material-icons {
+ font-size: 18px !important;
+ color: $es-text-muted;
+ }
+
+ .holiday-filter-input {
+ flex: 1;
+ border: none;
+ background: transparent;
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ outline: none;
+ padding: $es-spacing-xs 0;
+
+ &::placeholder {
+ color: $es-text-muted;
+ }
+ }
+ }
+}
+
+// Spin animation for loading icons
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+// Bootstrap specificity overrides for chips toolbar form elements
+// PrestaShop admin uses #content .mpr-config-form... with high specificity
+// We need to match or exceed that specificity
+#content.bootstrap,
+#content .bootstrap,
+.bootstrap #content {
+ .target-conditions-trait,
+ .entity-selector-trait {
+ .chips-wrapper .chips-toolbar {
+ // Double class for extra specificity
+ input[type="text"].chips-search-input.chips-search-input {
+ all: unset;
+ display: block;
+ flex: 1 1 auto;
+ min-width: 80px;
+ width: auto;
+ height: auto;
+ padding: 0.2rem 0.5rem 0.2rem 1.5rem;
+ background: $es-white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") no-repeat 0.375rem center;
+ background-size: 10px;
+ border: 1px solid $es-slate-300;
+ border-radius: $es-radius-sm;
+ font-size: 11px;
+ line-height: 1.4;
+ color: $es-text-primary;
+ box-sizing: border-box;
+ transition: all $es-transition-fast;
+
+ &::placeholder {
+ color: $es-text-muted;
+ font-size: 11px;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $es-primary;
+ box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
+ }
+ }
+
+ // Double class for extra specificity
+ select.chips-sort-select.chips-sort-select {
+ all: unset;
+ flex: 0 0 auto;
+ padding: 0.2rem 1.25rem 0.2rem 0.5rem;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ 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.375rem center;
+ background-size: 8px;
+ font-size: 10px;
+ line-height: 1.4;
+ color: $es-text-secondary;
+ cursor: pointer;
+ box-sizing: border-box;
+ white-space: nowrap;
+ height: auto;
+
+ &:hover {
+ border-color: $es-primary;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $es-primary;
+ box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
+ }
+ }
+ }
+ }
+}
diff --git a/sources/sources/scss/components/_combinations.scss b/sources/sources/scss/components/_combinations.scss
new file mode 100644
index 0000000..f130032
--- /dev/null
+++ b/sources/sources/scss/components/_combinations.scss
@@ -0,0 +1,396 @@
+/**
+ * Combination Attributes Picker Component
+ * Product attribute combination selection styles
+ */
+
+@use "sass:color";
+@use '../variables' as *;
+@use '../mixins' as *;
+
+.target-conditions-trait,
+.entity-selector-trait {
+
+ // Main container
+ .combination-attributes-picker {
+ display: flex;
+ flex-direction: column;
+ gap: 0.625rem;
+ }
+
+ // Mode toggle (Any/All)
+ .combination-mode-toggle {
+ display: inline-flex;
+ gap: 0.25rem;
+ padding: 0.125rem;
+ background: $es-slate-100;
+ border-radius: $es-radius-md;
+ margin-bottom: 0.5rem;
+ }
+
+ .combination-mode-option {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+ cursor: pointer;
+ font-size: 11px;
+ color: $es-text-muted;
+ padding: 0.25rem 0.625rem;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ input[type="radio"] {
+ display: none;
+ }
+
+ .mode-label {
+ user-select: none;
+ }
+
+ &:hover {
+ color: $es-primary;
+ background: rgba($es-primary, 0.1);
+ }
+
+ &:has(input[type="radio"]:checked) {
+ background: $es-primary;
+ color: $es-white;
+ font-weight: $es-font-weight-medium;
+ }
+ }
+
+ // Groups container
+ .combination-groups-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-md;
+ }
+
+ // Loading/Empty/Error states
+ .combination-loading,
+ .combination-empty,
+ .combination-error {
+ color: $es-text-muted;
+ font-style: italic;
+ padding: 0.5rem;
+ }
+
+ .combination-error {
+ color: $es-danger;
+ }
+
+ // Section header
+ .combinations-section {
+ margin-bottom: $es-spacing-md;
+ }
+
+ .combinations-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: $es-spacing-sm;
+ }
+
+ .combinations-label {
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-muted;
+ }
+
+ .combinations-help {
+ font-size: 11px;
+ color: $es-slate-400;
+ }
+
+ // Toggle combinations button
+ .btn-toggle-combinations {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ color: $es-primary;
+ background: transparent;
+ border: 1px solid $es-primary;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary-light;
+ }
+ }
+
+ .btn-remove-combinations {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ color: $es-danger;
+ background: transparent;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ // =============================================================================
+ // Attribute Group
+ // =============================================================================
+
+ .comb-attr-group {
+ flex: none;
+ min-width: 120px;
+ max-width: 200px;
+ background: $es-white;
+ border: 1px solid $es-gray-300;
+ border-radius: $es-radius-sm;
+ overflow: hidden;
+
+ &.has-selections {
+ border-color: $es-primary;
+ }
+ }
+
+ .comb-attr-group-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.375rem 0.625rem;
+ background: $es-slate-100;
+ border-bottom: 1px solid $es-gray-300;
+ font-weight: $es-font-weight-semibold;
+ font-size: $es-font-size-xs;
+ color: $es-slate-800;
+
+ .comb-attr-group.has-selections & {
+ background: $es-cyan-50;
+ border-bottom-color: $es-cyan-200;
+ }
+ }
+
+ .comb-attr-group-name {
+ flex: 1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .comb-attr-group-count {
+ flex-shrink: 0;
+ min-width: 18px;
+ height: 18px;
+ padding: 0 0.25rem;
+ background: $es-gray-300;
+ border-radius: $es-radius-full;
+ font-size: 11px;
+ font-weight: $es-font-weight-semibold;
+ line-height: 18px;
+ text-align: center;
+ color: $es-text-muted;
+
+ .comb-attr-group.has-selections & {
+ background: $es-primary;
+ color: $es-white;
+ }
+ }
+
+ // Toolbar
+ .comb-attr-toolbar {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.375rem;
+ background: $es-slate-50;
+ border-bottom: 1px solid $es-slate-100;
+ }
+
+ .comb-toolbar-btn {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ padding: 0;
+ background: $es-white;
+ border: 1px solid $es-gray-300;
+ border-radius: $es-radius-sm;
+ color: $es-text-muted;
+ cursor: pointer;
+ font-size: $es-font-size-xs;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-100;
+ border-color: $es-slate-400;
+ color: $es-slate-800;
+ }
+ }
+
+ .comb-attr-search {
+ flex: 1;
+ min-width: 60px;
+ padding: 0.125rem 0.375rem;
+ border: 1px solid $es-gray-300;
+ border-radius: $es-radius-sm;
+ font-size: 11px;
+ outline: none;
+
+ &:focus {
+ border-color: $es-primary;
+ }
+
+ &::placeholder {
+ color: $es-slate-400;
+ }
+ }
+
+ // Values container
+ .comb-attr-values {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+ padding: 0.375rem;
+ max-height: 150px;
+ overflow-y: auto;
+ @include custom-scrollbar;
+ }
+
+ .comb-attr-loading,
+ .comb-attr-empty,
+ .comb-attr-error {
+ width: 100%;
+ text-align: center;
+ color: $es-slate-400;
+ font-size: 11px;
+ padding: 0.25rem;
+ }
+
+ .comb-attr-error {
+ color: $es-danger;
+ }
+
+ // Individual value
+ .comb-attr-value {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.125rem 0.5rem;
+ background: $es-white;
+ border: 1px solid $es-slate-400;
+ border-radius: 0.75rem;
+ font-size: 11px;
+ color: $es-slate-600;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+ white-space: nowrap;
+
+ &:hover {
+ background: $es-slate-100;
+ border-color: $es-text-muted;
+ }
+
+ &.selected {
+ background: $es-primary;
+ border-color: $es-primary-hover;
+ color: $es-white;
+
+ &:hover {
+ background: $es-primary-hover;
+ border-color: color.adjust($es-primary-hover, $lightness: -5%);
+ }
+ }
+ }
+
+ .comb-attr-value-count {
+ font-size: 9px;
+ color: $es-slate-400;
+ background: $es-slate-100;
+ padding: 1px 0.25rem;
+ border-radius: 0.5rem;
+ min-width: 14px;
+ text-align: center;
+
+ .comb-attr-value.selected & {
+ color: $es-white;
+ background: rgba(255, 255, 255, 0.3);
+ }
+ }
+
+ // =============================================================================
+ // Combination Conditions (Row-based)
+ // =============================================================================
+
+ .combination-conditions-container {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-sm;
+ }
+
+ .combination-condition-row {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm;
+ background: $es-slate-50;
+ border-radius: $es-radius-sm;
+ }
+
+ .combination-group-select,
+ .combination-values-select {
+ flex: 1;
+ min-width: 120px;
+ }
+
+ .combination-equals {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ padding: 0 0.25rem;
+ }
+
+ .btn-add-combination-condition {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.375rem 0.75rem;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-primary;
+ background: transparent;
+ border: 1px dashed $es-primary;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary-light;
+ }
+
+ i {
+ font-size: 10px;
+ }
+ }
+
+ .btn-remove-combination-row {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: rgba($es-danger, 0.1);
+ color: $es-danger;
+ }
+
+ i {
+ font-size: 12px;
+ }
+ }
+}
diff --git a/sources/sources/scss/components/_condition-trait.scss b/sources/sources/scss/components/_condition-trait.scss
new file mode 100644
index 0000000..a87e124
--- /dev/null
+++ b/sources/sources/scss/components/_condition-trait.scss
@@ -0,0 +1,358 @@
+/**
+ * Condition Trait Base Styles
+ * Shared styling for all condition trait components
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+// Base condition trait container
+.condition-trait {
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-lg;
+ margin-bottom: $es-spacing-lg;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+}
+
+// Collapsed state
+.condition-trait.collapsed {
+ .condition-trait-header {
+ border-bottom-color: transparent;
+ border-radius: $es-radius-lg;
+ }
+
+ .collapse-icon {
+ transform: rotate(180deg);
+ }
+}
+
+// =============================================================================
+// Trait Header
+// =============================================================================
+
+.condition-trait-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-lg;
+ flex-wrap: wrap;
+ padding: 0.875rem $es-spacing-lg;
+ background: $es-slate-50;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: $es-radius-lg $es-radius-lg 0 0;
+ cursor: pointer;
+ transition: background-color $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-100;
+ }
+}
+
+.trait-header-left {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-md;
+ min-width: 0;
+ flex: 1;
+}
+
+.trait-icon {
+ font-size: 1.125rem;
+ color: $es-text-muted;
+ flex-shrink: 0;
+}
+
+.trait-title-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.125rem;
+ min-width: 0;
+}
+
+.trait-title {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-slate-800;
+ white-space: nowrap;
+}
+
+.trait-subtitle {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+// Schedule summary (shows current config at a glance)
+.trait-summary {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-primary;
+ background: rgba($es-primary, 0.08);
+ border-radius: $es-radius-full;
+ white-space: nowrap;
+ margin-left: $es-spacing-md;
+ flex-shrink: 0;
+ max-width: 320px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ &:empty {
+ display: none;
+ }
+}
+
+.trait-header-right {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-md;
+ flex-shrink: 0;
+ margin-left: auto;
+}
+
+.trait-header-actions {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+}
+
+// Collapse icon
+.collapse-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.5rem;
+ height: 1.5rem;
+ font-size: $es-font-size-sm;
+ color: $es-text-muted;
+ cursor: pointer;
+ transition: all 0.2s;
+ border-radius: $es-radius-sm;
+ background: transparent;
+
+ &:hover {
+ color: $es-primary;
+ background: rgba($es-primary, 0.08);
+ }
+}
+
+// Show all toggle
+.trait-show-all-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ font-size: $es-font-size-xs;
+ color: $es-primary;
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+// Trait total count badge (global fallback)
+.trait-total-count {
+ @include count-badge($es-primary);
+}
+
+// Required indicator
+.trait-required {
+ color: $es-danger;
+ font-size: $es-font-size-xs;
+}
+
+// Validation error
+.trait-validation-error {
+ color: $es-danger;
+ font-size: $es-font-size-xs;
+ margin-top: 0.25rem;
+}
+
+// Trait toggle button
+.trait-toggle {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.375rem 0.75rem;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-secondary;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-md;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-50;
+ border-color: $es-gray-300;
+ }
+
+ &.active {
+ color: $es-primary;
+ border-color: $es-primary;
+ background: $es-primary-light;
+ }
+}
+
+// =============================================================================
+// Trait Body
+// =============================================================================
+
+.condition-trait-body {
+ padding: $es-spacing-lg;
+ border-radius: 0 0 $es-radius-lg $es-radius-lg;
+ background: $es-white;
+ animation: slideDown 0.2s ease-out;
+}
+
+// Condition trait collapsed - hide body
+.condition-trait.collapsed .condition-trait-body {
+ display: none;
+}
+
+// =============================================================================
+// Section Styles
+// =============================================================================
+
+.schedule-section,
+.context-section {
+ margin-bottom: 1.25rem;
+ padding-bottom: 1.25rem;
+ border-bottom: 1px solid $es-slate-100;
+
+ &:last-child {
+ margin-bottom: 0;
+ padding-bottom: 0;
+ border-bottom: 0;
+ }
+}
+
+.section-label {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 13px;
+ font-weight: $es-font-weight-semibold;
+ color: $es-slate-600;
+ margin-bottom: $es-spacing-md;
+
+ i {
+ font-size: $es-font-size-sm;
+ color: $es-slate-400;
+ margin-right: 0.25rem;
+ }
+}
+
+.section-content {
+ // Container for section content
+}
+
+.section-hint {
+ margin-top: 0.5rem;
+ font-size: 11px;
+ color: $es-slate-400;
+}
+
+// =============================================================================
+// Full-width Form Group Override
+// =============================================================================
+
+.form-group.condition-trait-fullwidth {
+ display: block !important;
+
+ > .control-label {
+ display: none !important;
+ }
+
+ > .col-lg-8,
+ > .col-lg-8.col-lg-offset-3 {
+ width: 100% !important;
+ max-width: 100% !important;
+ flex: 0 0 100% !important;
+ padding-left: $es-spacing-lg !important;
+ padding-right: $es-spacing-lg !important;
+ margin: 0 !important;
+ margin-left: 0 !important;
+ }
+}
+
+// Condition traits group label
+.condition-traits-group-label {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-slate-700;
+ margin-bottom: $es-spacing-md;
+}
+
+.condition-traits-wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-md;
+}
+
+// =============================================================================
+// Collapse Header (form-content layout)
+// =============================================================================
+
+.entity-selector-collapse-header {
+ padding: 0;
+ margin-bottom: $es-spacing-sm;
+
+ .btn-collapse-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0;
+ background: none;
+ border: none;
+ color: $es-primary;
+ font-size: $es-font-size-sm;
+ cursor: pointer;
+ transition: color $es-transition-fast;
+
+ &:hover {
+ color: $es-primary-hover;
+ }
+
+ .collapse-icon {
+ font-size: 1.25rem;
+ transition: transform 0.2s;
+ }
+
+ .collapse-label {
+ font-weight: $es-font-weight-medium;
+ }
+ }
+}
+
+// When collapsed, rotate icon
+.condition-trait.collapsed .entity-selector-collapse-header {
+ .collapse-icon {
+ // Icon already shows expand_more when collapsed
+ }
+}
+
+// =============================================================================
+// Animations
+// =============================================================================
+
+@keyframes slideDown {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
diff --git a/sources/sources/scss/components/_dropdown.scss b/sources/sources/scss/components/_dropdown.scss
new file mode 100644
index 0000000..17a6fc1
--- /dev/null
+++ b/sources/sources/scss/components/_dropdown.scss
@@ -0,0 +1,2547 @@
+/**
+ * Search Dropdown Component
+ * Includes search input, filter panel, results grid
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+.target-conditions-trait,
+.entity-selector-trait {
+
+ // Search wrapper
+ .target-search-wrapper {
+ position: relative;
+ }
+
+ // Search dropdown
+ .target-search-dropdown {
+ @include dropdown-container;
+ display: none;
+ width: 600px;
+ max-width: calc(100vw - 40px);
+
+ &.show {
+ display: block;
+ }
+ }
+
+ // Dropdown header
+ .dropdown-header {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: $es-radius-lg $es-radius-lg 0 0;
+ }
+
+ .dropdown-search-input {
+ @include input-base;
+ flex: 1;
+ padding: $es-spacing-sm $es-spacing-md;
+ }
+
+ .dropdown-close-btn {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ color: $es-text-muted;
+ border-radius: $es-radius-md;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ }
+ }
+
+ // Dropdown controls bar
+ .dropdown-controls {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-white;
+ border-bottom: 1px solid $es-border-color;
+ }
+
+ .dropdown-controls-left,
+ .dropdown-controls-right {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ }
+
+ // View mode buttons
+ .view-mode-btn {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ color: $es-text-muted;
+ background: transparent;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-100;
+ color: $es-text-secondary;
+ }
+
+ &.active {
+ background: $es-primary;
+ color: $es-white;
+ }
+ }
+
+ // Results count
+ .dropdown-results-count {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+
+ // Dropdown body
+ .dropdown-body {
+ max-height: 400px;
+ overflow-y: auto;
+ @include custom-scrollbar;
+ }
+
+ // Results container
+ .dropdown-results {
+ padding: 0 $es-spacing-sm;
+ }
+
+ // Results count text
+ .results-count {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ padding: $es-spacing-xs $es-spacing-sm;
+ }
+
+ // Results header (for list view columns)
+ .results-header {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xs $es-spacing-md;
+ background: $es-slate-100;
+ border-bottom: 1px solid $es-border-color;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-secondary;
+ }
+
+ // Grid view
+ .dropdown-results-grid {
+ display: grid;
+ gap: $es-spacing-sm;
+
+ &.view-list {
+ grid-template-columns: 1fr;
+ }
+
+ &.view-grid-2 {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ &.view-grid-3 {
+ grid-template-columns: repeat(3, 1fr);
+ }
+ }
+
+ // Result item (both class names for compatibility)
+ // Note: Main dropdown-item styling is in the global .target-search-dropdown section below
+ .dropdown-result-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm 0;
+ background: $es-white;
+ border: none;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: 0;
+ cursor: pointer;
+ transition: background $es-transition-fast;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+
+ &.selected {
+ background: $es-primary-light;
+ }
+
+ &.disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+
+ &:hover {
+ background: $es-white;
+ }
+ }
+ }
+
+ .result-item-image,
+ .result-image {
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ overflow: hidden;
+ border-radius: $es-radius-sm;
+ background: $es-slate-100;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ &.result-flag {
+ width: 32px;
+ height: 24px;
+ border-radius: 2px;
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
+ background: transparent;
+
+ img {
+ object-fit: contain;
+ }
+
+ .flag-fallback {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(135deg, #e8eaed 0%, #dadce0 100%);
+ font-size: 14px;
+ color: #5f6368;
+ }
+ }
+ }
+
+ .result-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ background: $es-slate-100;
+ border-radius: $es-radius-sm;
+ color: $es-text-muted;
+
+ i {
+ font-size: 16px;
+ }
+ }
+
+ .result-item-info,
+ .result-info {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .result-item-name,
+ .result-name {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-primary;
+ @include text-truncate;
+ }
+
+ .result-item-meta,
+ .result-subtitle {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+
+ .subtitle-line {
+ @include text-truncate;
+ }
+
+ .subtitle-line-primary {
+ color: $es-text-secondary;
+ }
+
+ .subtitle-line-secondary {
+ color: $es-text-muted;
+ font-size: 11px;
+ }
+
+ // Result columns (for product list view)
+ .result-col {
+ flex-shrink: 0;
+ width: 70px;
+ text-align: right;
+ font-size: $es-font-size-xs;
+ }
+
+ .result-col-price {
+ color: $es-text-secondary;
+ }
+
+ .result-col-sale {
+ color: $es-danger;
+ font-weight: $es-font-weight-semibold;
+ }
+
+ .result-col-stock {
+ .col-value {
+ &.stock-ok {
+ color: $es-success;
+ }
+
+ &.stock-low {
+ color: $es-warning;
+ }
+
+ &.stock-out {
+ color: $es-danger;
+ }
+ }
+ }
+
+ .result-col-sales {
+ color: $es-text-muted;
+ }
+
+ .col-value {
+ display: block;
+ }
+
+ .result-item-checkbox,
+ .result-checkbox {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 18px;
+ height: 18px;
+ border: 2px solid $es-border-dark;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ i {
+ display: none;
+ font-size: 10px;
+ color: $es-white;
+ }
+
+ .dropdown-result-item.selected &,
+ .dropdown-item.selected & {
+ background: $es-primary;
+ border-color: $es-primary;
+
+ i {
+ display: block;
+ }
+ }
+ }
+
+ // Product-specific result item
+ .result-item-product {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ }
+
+ // No results state
+ .no-results {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xl;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+
+ i {
+ font-size: 1.25rem;
+ opacity: 0.5;
+ }
+ }
+
+ // Empty state
+ .dropdown-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xl;
+ text-align: center;
+ color: $es-text-muted;
+
+ i {
+ font-size: 2rem;
+ opacity: 0.5;
+ }
+
+ p {
+ margin: 0;
+ font-size: $es-font-size-sm;
+ }
+ }
+
+ // Loading state
+ .dropdown-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: $es-spacing-xl;
+ color: $es-text-muted;
+
+ i {
+ font-size: 1.5rem;
+ animation: spin 1s linear infinite;
+ }
+ }
+
+ // Unified dropdown footer - combines load more and actions
+ .dropdown-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-md;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-slate-50;
+ border-top: 1px solid $es-border-color;
+ border-radius: 0 0 $es-radius-lg $es-radius-lg;
+ }
+
+ // Left side: load more controls
+ .dropdown-footer-left {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+
+ .load-label {
+ color: $es-text-muted;
+ }
+
+ .load-more-select,
+ select.load-more-select {
+ appearance: none;
+ padding: 0.25rem 1.5rem 0.25rem 0.5rem;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ 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.4rem center;
+ background-size: 8px;
+ font-size: $es-font-size-xs;
+ color: $es-text-secondary;
+ cursor: pointer;
+ min-width: 55px;
+
+ &:hover {
+ border-color: $es-primary;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $es-primary;
+ }
+ }
+
+ .remaining-text {
+ color: $es-text-muted;
+
+ strong {
+ color: $es-text-secondary;
+ font-weight: $es-font-weight-semibold;
+ }
+ }
+
+ .btn-load-all {
+ @include button-reset;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ color: $es-primary;
+ background: transparent;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary-light;
+ }
+ }
+ }
+
+ // Right side: action buttons
+ .dropdown-footer-right {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ }
+
+ .dropdown-action-btn {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.375rem 0.75rem;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ .btn-shortcut {
+ font-size: 10px;
+ padding: 0.125rem 0.25rem;
+ background: rgba(0, 0, 0, 0.08);
+ border-radius: 3px;
+ font-weight: $es-font-weight-normal;
+ }
+
+ &.btn-cancel {
+ color: $es-text-secondary;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+
+ &:hover {
+ background: $es-slate-100;
+ border-color: $es-border-dark;
+ }
+ }
+
+ &.btn-apply,
+ &.btn-save {
+ color: $es-white;
+ background: $es-primary;
+ border: 1px solid $es-primary;
+
+ &:hover {
+ background: $es-primary-hover;
+ border-color: $es-primary-hover;
+ }
+
+ .btn-shortcut {
+ background: rgba(255, 255, 255, 0.2);
+ }
+ }
+ }
+
+ // Legacy support - hide old load more section when new footer exists
+ .dropdown-load-more {
+ display: none;
+ }
+
+ .load-more-controls {
+ display: none;
+ }
+
+ // Filter panel
+ .dropdown-filter-panel {
+ padding: $es-spacing-md;
+ background: $es-slate-50;
+ border-bottom: 1px solid $es-border-color;
+ }
+
+ .filter-panel-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: $es-spacing-sm;
+ }
+
+ .filter-panel-title {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ }
+
+ .filter-panel-toggle {
+ @include button-reset;
+ font-size: $es-font-size-xs;
+ color: $es-primary;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ .filter-panel-content {
+ display: grid;
+ gap: $es-spacing-sm;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ }
+
+ .filter-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ }
+
+ .filter-label {
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-secondary;
+ }
+
+ .filter-select,
+ .filter-input {
+ @include input-base;
+ padding: 0.375rem $es-spacing-sm;
+ font-size: $es-font-size-xs;
+ }
+}
+
+// Category tree view
+.target-conditions-trait,
+.entity-selector-trait {
+ .category-tree {
+ padding: $es-spacing-sm;
+ }
+
+ .tree-container {
+ // Contains tree items
+ }
+
+ .tree-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xl;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+
+ i {
+ animation: spin 0.6s linear infinite;
+ }
+ }
+
+ // Tree item (used by JavaScript)
+ .tree-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ padding: 0.375rem $es-spacing-sm;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: background-color $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+
+ &.selected {
+ background: $es-primary-light;
+
+ .tree-checkbox {
+ background: $es-primary;
+ border-color: $es-primary;
+
+ i {
+ display: block;
+ }
+ }
+ }
+
+ &.has-children {
+ // Parent node styling
+ }
+ }
+
+ // tree-toggle, btn-select-children, tree-checkbox, tree-icon styles in _tree.scss
+
+ .tree-info {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ flex: 1;
+ min-width: 0;
+ }
+
+ .tree-name {
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ @include text-truncate;
+ }
+
+ .tree-subtitle {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+
+ // Legacy category tree classes (for compatibility)
+ .category-tree-item {
+ padding: 0.25rem 0;
+ }
+
+ .category-tree-node {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xs $es-spacing-sm;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: background-color $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+
+ &.selected {
+ background: $es-primary-light;
+ }
+ }
+
+ .category-tree-toggle {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 16px;
+ height: 16px;
+ color: $es-text-muted;
+ transition: transform $es-transition-fast;
+
+ &.expanded {
+ transform: rotate(90deg);
+ }
+
+ &.empty {
+ visibility: hidden;
+ }
+ }
+
+ .category-tree-checkbox {
+ flex-shrink: 0;
+ width: 16px;
+ height: 16px;
+ border: 2px solid $es-border-dark;
+ border-radius: 3px;
+ transition: all $es-transition-fast;
+
+ &.checked {
+ background: $es-primary;
+ border-color: $es-primary;
+
+ &::after {
+ content: '\2713';
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ color: $es-white;
+ font-size: 10px;
+ }
+ }
+
+ &.indeterminate {
+ background: $es-primary;
+ border-color: $es-primary;
+
+ &::after {
+ content: '\2212';
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ color: $es-white;
+ font-size: 10px;
+ }
+ }
+ }
+
+ .category-tree-name {
+ flex: 1;
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ @include text-truncate;
+ }
+
+ .category-tree-count {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+
+ .category-tree-children {
+ margin-left: 24px;
+ }
+
+ // Search history
+ .search-history-list {
+ padding: $es-spacing-sm;
+ }
+
+ .history-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: background-color $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+
+ > i {
+ color: $es-text-muted;
+ font-size: 14px;
+ }
+ }
+
+ .history-query {
+ flex: 1;
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ }
+
+ .btn-delete-history {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ opacity: 0;
+ transition: all $es-transition-fast;
+
+ .history-item:hover & {
+ opacity: 1;
+ }
+
+ &:hover {
+ background: $es-danger-light;
+ color: $es-danger;
+ }
+
+ i {
+ font-size: 12px;
+ }
+ }
+
+ // Filter panel
+ .filter-panel {
+ display: none;
+ padding: $es-spacing-md;
+ background: $es-slate-50;
+ border-bottom: 1px solid $es-border-color;
+
+ &.show {
+ display: block;
+ }
+ }
+
+ .filter-row {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: $es-spacing-sm;
+ margin-bottom: $es-spacing-sm;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ // View mode select
+ .view-mode-select {
+ @include input-base;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ min-width: 80px;
+ }
+}
+
+// =============================================================================
+// Global dropdown styles (when appended to body instead of inside wrapper)
+// Duplicates key styles for when dropdown is outside .entity-selector-trait
+// =============================================================================
+body > .target-search-dropdown,
+.target-search-dropdown {
+ @include dropdown-container;
+ display: none;
+ width: 600px;
+ max-width: calc(100vw - 40px);
+
+ &.show {
+ display: block;
+ }
+
+ // Dropdown header
+ .dropdown-header {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: $es-radius-lg $es-radius-lg 0 0;
+ flex-wrap: wrap;
+ }
+
+ .results-count {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ white-space: nowrap;
+ }
+
+ .dropdown-actions {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ flex-wrap: wrap;
+ flex: 1;
+ justify-content: flex-end;
+ }
+
+ .btn-select-all,
+ .btn-clear-selection {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ color: $es-text-secondary;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ border-color: $es-slate-300;
+ }
+
+ kbd {
+ font-size: 0.65rem;
+ padding: 0.125rem 0.25rem;
+ background: $es-slate-100;
+ border-radius: 2px;
+ color: $es-text-muted;
+ }
+ }
+
+ .sort-controls {
+ display: flex;
+ align-items: center;
+ // No gap - elements are connected
+ }
+
+ .sort-field-select {
+ @include input-base;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ min-width: 80px;
+ height: 28px; // Match btn-sort-dir height
+ border-radius: $es-radius-sm 0 0 $es-radius-sm;
+ border-right: none;
+ }
+
+ .btn-sort-dir {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 38px;
+ min-width: 38px;
+ flex-shrink: 0;
+ height: 28px;
+ padding: 0;
+ margin: 0;
+ color: $es-text-muted;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: 0 $es-radius-sm $es-radius-sm 0;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ i {
+ font-size: 14px;
+ }
+
+ &:hover {
+ background: $es-bg-hover;
+ color: $es-text-primary;
+ }
+
+ &.active {
+ background: $es-primary-light;
+ border-color: $es-primary;
+ color: $es-primary;
+ }
+ }
+
+ .view-mode-select {
+ @include input-base;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ min-width: 80px;
+ margin-left: 0.25rem;
+ }
+
+ .btn-toggle-filters,
+ .btn-show-history {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ min-width: 32px;
+ flex-shrink: 0;
+ height: 28px;
+ padding: 0;
+ margin: 0;
+ color: $es-text-muted;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ i {
+ font-size: 14px;
+ }
+
+ &:hover {
+ background: $es-bg-hover;
+ color: $es-text-primary;
+ }
+
+ &.active {
+ background: $es-primary-light;
+ border-color: $es-primary;
+ color: $es-primary;
+ }
+ }
+
+ .refine-compact {
+ display: flex;
+ align-items: center;
+ // No gap - elements are connected
+
+ // Connected to refine-input
+ .btn-refine-negate {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ min-width: 32px;
+ flex-shrink: 0;
+ height: 28px;
+ padding: 0;
+ margin: 0;
+ color: $es-text-muted;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-right: none;
+ border-radius: $es-radius-sm 0 0 $es-radius-sm;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ color: $es-text-primary;
+ }
+
+ &.active {
+ background: $es-danger-light;
+ color: $es-danger;
+ border-color: $es-danger;
+ }
+ }
+
+ .refine-input {
+ @include input-base;
+ width: 100px;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ border-radius: 0 $es-radius-sm $es-radius-sm 0;
+ }
+
+ .btn-clear-refine {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ padding: 0;
+ margin: 0;
+ margin-left: -1px; // Overlap input border when visible
+ color: $es-text-muted;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: 0 $es-radius-sm $es-radius-sm 0;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ color: $es-text-primary;
+ }
+ }
+ }
+
+ // Filter panel
+ .filter-panel {
+ display: none;
+ padding: $es-spacing-md;
+ background: $es-slate-50;
+ border-bottom: 1px solid $es-border-color;
+
+ &.show {
+ display: block;
+ }
+ }
+
+ .filter-row {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ flex-wrap: wrap;
+ margin-bottom: $es-spacing-sm;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .filter-label {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ font-size: $es-font-size-xs;
+ color: $es-text-secondary;
+ cursor: pointer;
+
+ input[type="checkbox"] {
+ margin: 0;
+ }
+ }
+
+ .filter-price-range {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+
+ .filter-price-label {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+
+ .filter-price-min,
+ .filter-price-max {
+ @include input-base;
+ width: 70px;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ }
+
+ .filter-price-sep {
+ color: $es-text-muted;
+ }
+ }
+
+ .btn-clear-filters {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ margin-left: auto;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-primary;
+ }
+ }
+
+ // Multi-row filters
+ .filter-row-multi {
+ flex-direction: column;
+ align-items: stretch;
+ gap: $es-spacing-sm;
+ }
+
+ .filter-subrow {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ flex-wrap: wrap;
+ }
+
+ .filter-range-group,
+ .filter-date-group,
+ .filter-select-group {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+ }
+
+ .filter-range-label,
+ .filter-date-label,
+ .filter-select-label,
+ .filter-row-label {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ white-space: nowrap;
+
+ i {
+ margin-right: 0.25rem;
+ }
+ }
+
+ .filter-range-sep {
+ color: $es-text-muted;
+ }
+
+ .filter-product-count-min,
+ .filter-product-count-max,
+ .filter-sales-min,
+ .filter-sales-max,
+ .filter-turnover-min,
+ .filter-turnover-max,
+ .filter-date-add-from,
+ .filter-date-add-to,
+ .filter-last-product-from,
+ .filter-last-product-to {
+ @include input-base;
+ width: 70px;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ }
+
+ .filter-depth-select,
+ .filter-attribute-group-select,
+ .filter-feature-group-select {
+ @include input-base;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ min-width: 100px;
+ }
+
+ .filter-attributes-container,
+ .filter-features-container,
+ .filter-values-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+ }
+
+ // Filter group toggle buttons (attribute/feature groups)
+ .filter-group-toggle {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ color: $es-text-secondary;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ border-color: $es-slate-300;
+ }
+
+ &.active {
+ background: $es-primary-light;
+ border-color: $es-primary;
+ color: $es-primary;
+
+ .toggle-count {
+ color: $es-primary;
+ }
+ }
+
+ &.has-selection {
+ border-color: $es-success;
+ background: rgba($es-success, 0.05);
+
+ .toggle-count {
+ color: $es-success;
+ font-weight: $es-font-weight-semibold;
+ }
+ }
+
+ .toggle-name {
+ font-weight: $es-font-weight-medium;
+ }
+
+ // Count with eye icon (like group-count-badge)
+ .toggle-count {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.125rem;
+ color: $es-text-muted;
+ font-size: 0.65rem;
+
+ i {
+ font-size: 10px;
+ color: $es-primary;
+ }
+
+ // Clickable preview badge
+ &.clickable {
+ cursor: pointer;
+ padding: 0.125rem 0.25rem;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: rgba($es-primary, 0.1);
+ color: $es-primary;
+
+ i {
+ color: $es-primary;
+ }
+ }
+
+ &.popover-open {
+ background: $es-primary;
+ color: $es-white;
+
+ i {
+ color: $es-white;
+ }
+ }
+
+ &.loading {
+ i {
+ animation: spin 0.6s linear infinite;
+ }
+ }
+ }
+ }
+ }
+
+ // Filter value chips
+ .filter-chip {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ color: $es-text-secondary;
+ background: $es-slate-100;
+ border: 1px solid transparent;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ }
+
+ &.active {
+ background: $es-primary;
+ color: $es-white;
+ }
+ }
+
+ // Filter attribute chip (specific)
+ .filter-attr-chip,
+ .filter-feat-chip {
+ @extend .filter-chip;
+ }
+
+ // Filter row for values (expandable)
+ .filter-row-values {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+
+ .filter-values-container {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.25rem;
+ flex: 1;
+ }
+
+ .filter-values-label {
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-secondary;
+ white-space: nowrap;
+ }
+
+ .btn-close-values {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 20px;
+ height: 20px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-primary;
+ }
+ }
+ }
+
+ // Filter chip name and count
+ .chip-name {
+ font-weight: $es-font-weight-medium;
+ }
+
+ .chip-count {
+ font-size: 0.6rem;
+ color: $es-text-muted;
+ margin-left: 0.125rem;
+ }
+
+ .filter-chip.active .chip-count {
+ color: rgba(255, 255, 255, 0.8);
+ }
+
+ // Dropdown content
+ .dropdown-content {
+ max-height: 400px;
+ overflow-y: auto;
+ @include custom-scrollbar;
+ }
+
+ .dropdown-items {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-sm;
+ }
+
+ .item-checkbox {
+ flex-shrink: 0;
+ width: 16px;
+ height: 16px;
+ border: 2px solid $es-border-dark;
+ border-radius: 3px;
+ transition: all $es-transition-fast;
+
+ .dropdown-item.selected & {
+ background: $es-primary;
+ border-color: $es-primary;
+ }
+ }
+
+ .item-image {
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ object-fit: cover;
+ border-radius: $es-radius-sm;
+ background: $es-slate-100;
+ }
+
+ .item-info {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .item-name {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-primary;
+ @include text-truncate;
+ }
+
+ .item-meta {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ @include text-truncate;
+ }
+
+ // Dropdown footer - unified structure
+ .dropdown-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-md;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-slate-50;
+ border-top: 1px solid $es-border-color;
+ border-radius: 0 0 $es-radius-lg $es-radius-lg;
+ }
+
+ .dropdown-footer-left {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+
+ .load-label {
+ color: $es-text-muted;
+ }
+
+ .load-more-select,
+ select.load-more-select {
+ appearance: none;
+ padding: 0.25rem 1.5rem 0.25rem 0.5rem;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ 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.4rem center;
+ background-size: 8px;
+ font-size: $es-font-size-xs;
+ color: $es-text-secondary;
+ cursor: pointer;
+ min-width: 55px;
+
+ &:hover {
+ border-color: $es-primary;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $es-primary;
+ }
+ }
+
+ .remaining-text {
+ color: $es-text-muted;
+
+ strong {
+ color: $es-text-secondary;
+ font-weight: $es-font-weight-semibold;
+ }
+ }
+ }
+
+ .dropdown-footer-right {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ }
+
+ .dropdown-action-btn {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.375rem 0.75rem;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+ white-space: nowrap;
+
+ .btn-shortcut {
+ font-size: 10px;
+ padding: 0.125rem 0.25rem;
+ background: rgba(0, 0, 0, 0.08);
+ border-radius: 3px;
+ font-weight: $es-font-weight-normal;
+ }
+
+ &.btn-cancel {
+ color: $es-text-secondary;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+
+ &:hover {
+ background: $es-slate-100;
+ border-color: $es-border-dark;
+ }
+ }
+
+ &.btn-apply,
+ &.btn-save {
+ color: $es-white;
+ background: $es-primary;
+ border: 1px solid $es-primary;
+
+ &:hover {
+ background: $es-primary-hover;
+ border-color: $es-primary-hover;
+ }
+
+ .btn-shortcut {
+ background: rgba(255, 255, 255, 0.2);
+ }
+ }
+ }
+
+ .dropdown-footer-info {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+
+ // Legacy button support
+ .btn-cancel-dropdown {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.375rem 0.75rem;
+ font-size: $es-font-size-sm;
+ color: $es-white;
+ background: $es-primary;
+ border: 1px solid $es-primary;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+
+ &:hover {
+ background: $es-primary-hover;
+ border-color: $es-primary-hover;
+ }
+
+ i {
+ font-size: 10px;
+ }
+
+ kbd {
+ font-size: 0.65rem;
+ padding: 0.125rem 0.25rem;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 2px;
+ color: rgba(255, 255, 255, 0.8);
+ }
+ }
+
+ // Filter chips in dropdown
+ .filter-chips-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-xs $es-spacing-md;
+ border-bottom: 1px solid $es-border-color;
+ }
+
+ .filter-chip {
+ @include chip;
+ }
+
+ // Empty and loading states
+ .dropdown-empty,
+ .dropdown-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: $es-spacing-xl;
+ text-align: center;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+
+ i {
+ font-size: 2rem;
+ opacity: 0.5;
+ margin-bottom: $es-spacing-sm;
+ }
+ }
+
+ // Search history panel
+ .search-history-panel {
+ display: none;
+ padding: $es-spacing-sm;
+ background: $es-white;
+ border-bottom: 1px solid $es-border-color;
+
+ &.show {
+ display: block;
+ }
+ }
+
+ .history-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xs $es-spacing-sm;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: background $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+
+ i {
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+ }
+
+ span {
+ flex: 1;
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ }
+
+ .btn-remove-history,
+ .btn-delete-history {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-danger;
+ }
+ }
+ }
+
+ // Search history list container
+ .search-history-list {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-xs;
+ }
+
+ // Results header (for list view columns)
+ .results-header {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xs $es-spacing-md;
+ background: $es-slate-100;
+ border-bottom: 1px solid $es-border-color;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-secondary;
+
+ .header-spacer {
+ width: 58px; // checkbox + image width
+ flex-shrink: 0;
+ }
+
+ .header-col {
+ flex-shrink: 0;
+ width: 70px;
+ text-align: right;
+ }
+
+ .header-col-name {
+ flex: 1;
+ text-align: left;
+ }
+ }
+
+ // Results container
+ .dropdown-results {
+ padding: 0 $es-spacing-sm;
+ background: $es-white;
+ min-height: 200px;
+ }
+
+ // Dropdown item (search result)
+ .dropdown-item {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm 0;
+ background: $es-white;
+ border: none;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: 0;
+ cursor: pointer;
+ transition: background $es-transition-fast;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+
+ &.selected {
+ background: $es-primary-light;
+
+ .result-checkbox {
+ background: $es-primary;
+ border-color: $es-primary;
+ color: $es-white;
+
+ i {
+ display: block;
+ }
+ }
+ }
+
+ &.disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+ }
+
+ // Checkbox indicator
+ .result-checkbox {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 18px;
+ height: 18px;
+ background: $es-white;
+ border: 2px solid $es-border-dark;
+ border-radius: 3px;
+ transition: all $es-transition-fast;
+
+ i {
+ display: none;
+ font-size: 10px;
+ }
+ }
+
+ // View mode classes (applied to dropdown container) - no gap/padding for shared borders
+ &.view-cols-2 .dropdown-results { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
+ &.view-cols-3 .dropdown-results { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
+ &.view-cols-4 .dropdown-results { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
+ &.view-cols-5 .dropdown-results { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
+ &.view-cols-6 .dropdown-results { display: grid; grid-template-columns: repeat(6, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
+ &.view-cols-7 .dropdown-results { display: grid; grid-template-columns: repeat(7, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
+ &.view-cols-8 .dropdown-results { display: grid; grid-template-columns: repeat(8, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
+
+ // Grid view item styling (compact cards with shared borders)
+ &.view-cols-2,
+ &.view-cols-3,
+ &.view-cols-4,
+ &.view-cols-5,
+ &.view-cols-6,
+ &.view-cols-7,
+ &.view-cols-8 {
+ .dropdown-item {
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ padding: $es-spacing-sm;
+ border: none;
+ border-right: 1px solid $es-border-color;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: 0;
+
+ .result-checkbox {
+ position: absolute;
+ top: $es-spacing-xs;
+ left: $es-spacing-xs;
+ }
+
+ .result-image,
+ .result-icon {
+ width: 48px;
+ height: 48px;
+ margin-bottom: $es-spacing-xs;
+ }
+
+ .result-info {
+ width: 100%;
+ }
+
+ .result-name {
+ font-size: $es-font-size-xs;
+ line-height: 1.3;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+
+ .result-subtitle {
+ display: none;
+ }
+
+ // Show compact product info in grid
+ .result-col {
+ display: none;
+ }
+
+ .result-grid-info {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 0.25rem;
+ margin-top: $es-spacing-xs;
+ font-size: 0.65rem;
+
+ .grid-price {
+ color: $es-text-primary;
+ font-weight: $es-font-weight-semibold;
+ }
+
+ .grid-stock {
+ color: $es-text-muted;
+
+ &.stock-out { color: $es-danger; }
+ &.stock-low { color: $es-warning; }
+ }
+
+ .grid-discount {
+ color: $es-success;
+ font-weight: $es-font-weight-medium;
+ }
+ }
+ }
+
+ // Hide results header in grid views
+ .results-header {
+ display: none;
+ }
+ }
+
+ // Remove right border from last item in each row (per column count)
+ &.view-cols-2 .dropdown-results .dropdown-item:nth-child(2n) { border-right: none; }
+ &.view-cols-3 .dropdown-results .dropdown-item:nth-child(3n) { border-right: none; }
+ &.view-cols-4 .dropdown-results .dropdown-item:nth-child(4n) { border-right: none; }
+ &.view-cols-5 .dropdown-results .dropdown-item:nth-child(5n) { border-right: none; }
+ &.view-cols-6 .dropdown-results .dropdown-item:nth-child(6n) { border-right: none; }
+ &.view-cols-7 .dropdown-results .dropdown-item:nth-child(7n) { border-right: none; }
+ &.view-cols-8 .dropdown-results .dropdown-item:nth-child(8n) { border-right: none; }
+
+ // Smaller items for higher column counts
+ &.view-cols-5,
+ &.view-cols-6,
+ &.view-cols-7,
+ &.view-cols-8 {
+ .dropdown-item {
+ .result-image,
+ .result-icon {
+ width: 40px;
+ height: 40px;
+ }
+
+ .result-name {
+ font-size: 0.65rem;
+ }
+ }
+ }
+
+ // Product-specific result item
+ .result-item-product {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ flex: 1;
+ min-width: 0;
+ }
+
+ .result-item-image,
+ .result-image {
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ overflow: hidden;
+ border-radius: $es-radius-sm;
+ background: $es-slate-100;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+
+ .result-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ background: $es-slate-100;
+ border-radius: $es-radius-sm;
+ color: $es-text-muted;
+
+ i {
+ font-size: 16px;
+ }
+ }
+
+ .result-item-info,
+ .result-info {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .result-item-name,
+ .result-name {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-primary;
+ @include text-truncate;
+ }
+
+ .result-item-meta,
+ .result-subtitle {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+
+ .subtitle-line {
+ @include text-truncate;
+ }
+
+ .subtitle-line-primary {
+ color: $es-text-secondary;
+ }
+
+ .subtitle-line-secondary {
+ color: $es-text-muted;
+ font-size: 11px;
+ }
+
+ // Result columns (for product list view)
+ .result-col {
+ flex-shrink: 0;
+ width: 70px;
+ text-align: right;
+ font-size: $es-font-size-xs;
+ }
+
+ .result-col-price {
+ color: $es-text-secondary;
+ }
+
+ .result-col-sale {
+ color: $es-danger;
+ font-weight: $es-font-weight-semibold;
+ }
+
+ .result-col-stock {
+ .col-value {
+ &.stock-ok {
+ color: $es-success;
+ }
+
+ &.stock-low {
+ color: $es-warning;
+ }
+
+ &.stock-out {
+ color: $es-danger;
+ }
+ }
+ }
+
+ .result-col-sales {
+ color: $es-text-muted;
+ }
+
+ .col-value {
+ display: block;
+ }
+
+ // Result checkbox
+ .result-item-checkbox,
+ .result-checkbox {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 18px;
+ height: 18px;
+ border: 2px solid $es-border-dark;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ i {
+ display: none;
+ font-size: 10px;
+ color: $es-white;
+ }
+
+ .dropdown-result-item.selected &,
+ .dropdown-item.selected & {
+ background: $es-primary;
+ border-color: $es-primary;
+
+ i {
+ display: block;
+ }
+ }
+ }
+
+ // No results state
+ .no-results {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xl;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+
+ i {
+ font-size: 1.25rem;
+ opacity: 0.5;
+ }
+ }
+
+ // Load more controls
+ .load-more-controls {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+
+ .load-more-label,
+ .load-more-of {
+ white-space: nowrap;
+ }
+
+ .remaining-count {
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-secondary;
+ }
+
+ .load-more-select {
+ @include input-base;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ min-width: 60px;
+ }
+
+ .btn-load-more {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: $es-spacing-xs;
+ margin: 0;
+ border: none;
+ color: $es-primary;
+ background: $es-primary-light !important;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+ font: inherit;
+
+ i {
+ font-size: 14px;
+ }
+
+ &:hover {
+ background: rgba($es-primary, 0.2) !important;
+ }
+ }
+ }
+
+ // Load more button
+ .dropdown-load-more {
+ display: flex;
+ justify-content: center;
+ padding: $es-spacing-md;
+ border-top: 1px solid $es-border-color;
+
+ .load-more-btn {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-sm $es-spacing-md;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ color: $es-primary;
+ background: $es-primary-light;
+ border-radius: $es-radius-md;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: rgba($es-primary, 0.2);
+ }
+
+ &.loading {
+ opacity: 0.7;
+ cursor: wait;
+ }
+ }
+ }
+
+ // Dropdown body
+ .dropdown-body {
+ max-height: 400px;
+ overflow-y: auto;
+ @include custom-scrollbar;
+ }
+
+ // Tree view styles (for categories)
+ .tree-container {
+ padding: $es-spacing-sm;
+ }
+
+ .tree-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xl;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+
+ i {
+ animation: spin 0.6s linear infinite;
+ }
+ }
+
+ .tree-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ padding: 0.375rem $es-spacing-sm;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: background-color $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+
+ &.selected {
+ background: $es-primary-light;
+
+ .tree-checkbox {
+ background: $es-primary;
+ border-color: $es-primary;
+
+ i {
+ display: block;
+ }
+ }
+ }
+ }
+
+ // tree-toggle, btn-select-children, tree-checkbox, tree-icon styles in _tree.scss
+
+ .tree-info {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ flex: 1;
+ min-width: 0;
+ }
+
+ .tree-name {
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ @include text-truncate;
+ }
+
+ .tree-subtitle {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+}
+
+// ============================================================================
+// Standalone dropdown styles (for when dropdown is appended to body)
+// These selectors work because .target-search-dropdown is on the dropdown itself
+// ============================================================================
+
+.target-search-dropdown {
+ // Results container - scrollable
+ .dropdown-results {
+ max-height: 400px;
+ overflow-y: auto;
+ padding: 0 $es-spacing-sm;
+ @include custom-scrollbar;
+ }
+
+ // Results header (for list view columns)
+ .results-header {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xs $es-spacing-md;
+ background: $es-slate-100;
+ border-bottom: 1px solid $es-border-color;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-secondary;
+
+ .header-spacer {
+ width: 58px; // checkbox + image width
+ flex-shrink: 0;
+ }
+
+ .header-col {
+ flex-shrink: 0;
+ width: 70px;
+ text-align: right;
+ }
+
+ .header-col-name {
+ flex: 1;
+ text-align: left;
+ }
+ }
+
+ // Hide results-header by default, show only for products in list view
+ &:not(.view-list) .results-header,
+ &.view-tree .results-header {
+ display: none;
+ }
+
+ // Result item for products
+ .result-item-product {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ flex: 1;
+ min-width: 0;
+ }
+
+ // Result columns for product data
+ .result-col {
+ flex-shrink: 0;
+ width: 70px;
+ text-align: right;
+ font-size: $es-font-size-xs;
+ }
+
+ .result-col-price {
+ color: $es-text-secondary;
+ }
+
+ .result-col-sale {
+ color: $es-danger;
+ font-weight: $es-font-weight-semibold;
+ }
+
+ .result-col-stock {
+ .col-value {
+ &.stock-ok { color: $es-success; }
+ &.stock-low { color: $es-warning; }
+ &.stock-out { color: $es-danger; }
+ }
+ }
+
+ .result-col-sales {
+ color: $es-text-muted;
+ }
+
+ // Dropdown item styling
+ .dropdown-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: 0;
+ border: none;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: 0;
+ cursor: pointer;
+ transition: background-color $es-transition-fast;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+
+ &.selected {
+ background: $es-primary-light;
+ }
+ }
+
+ // Result checkbox styling
+ .result-checkbox {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ border: 2px solid $es-border-dark;
+ border-radius: 3px;
+ transition: all $es-transition-fast;
+
+ i {
+ font-size: 10px;
+ color: transparent;
+ }
+
+ .dropdown-item.selected & {
+ background: $es-primary;
+ border-color: $es-primary;
+
+ i {
+ color: $es-white;
+ }
+ }
+ }
+
+ // Result image
+ .result-image {
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ overflow: hidden;
+ border-radius: $es-radius-sm;
+ background: $es-slate-100;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+
+ // Result icon (for non-image entities)
+ .result-icon {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ background: $es-slate-100;
+ border-radius: $es-radius-sm;
+
+ i {
+ font-size: 16px;
+ color: $es-text-muted;
+ }
+ }
+
+ // Result info container
+ .result-info {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .result-name {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-primary;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .result-subtitle {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+
+ .subtitle-line {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .subtitle-line-primary {
+ color: $es-text-secondary;
+ }
+
+ .subtitle-line-secondary {
+ color: $es-text-muted;
+ font-size: 11px;
+ }
+
+ // Hide columns in grid/tree views
+ &[class*="view-cols-"] .result-col,
+ &.view-tree .result-col {
+ display: none;
+ }
+
+ // Entity search box - full width
+ .entity-search-box {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ width: 100%;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-white;
+ border: none;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: 0;
+
+ .entity-search-icon {
+ color: $es-text-muted;
+ flex-shrink: 0;
+ margin-left: $es-spacing-xs;
+ }
+
+ // Override Bootstrap/parent form input styles
+ input.entity-search-input,
+ input.entity-search-input[type="text"] {
+ flex: 1;
+ min-width: 0;
+ width: auto !important;
+ max-width: none !important;
+ height: auto;
+ padding: 0;
+ margin: 0;
+ border: none !important;
+ outline: none;
+ background: transparent !important;
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ box-shadow: none !important;
+
+ &::placeholder {
+ color: $es-text-muted;
+ }
+
+ &:focus {
+ border: none !important;
+ box-shadow: none !important;
+ outline: none;
+ }
+ }
+
+ .search-loading {
+ color: $es-text-muted;
+ }
+ }
+}
+
+// Body-level dropdown (when appended to body for z-index)
+body > .target-search-dropdown {
+ // Override dropdown-item border when inside body-appended dropdown
+ .dropdown-item {
+ border: none;
+ border-radius: 0;
+
+ &:not(:last-child) {
+ border-bottom: 1px solid $es-border-color;
+ }
+ }
+}
+
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/sources/sources/scss/components/_entity-item.scss b/sources/sources/scss/components/_entity-item.scss
new file mode 100644
index 0000000..dc82f9d
--- /dev/null
+++ b/sources/sources/scss/components/_entity-item.scss
@@ -0,0 +1,516 @@
+/**
+ * Entity Item - Shared Base Component
+ * Unified styling for entity items in chips, lists, and previews
+ *
+ * Variants:
+ * - .entity-item (base) - default list-row style
+ * - .entity-item.chip-style - pill/chip style (compact)
+ * - .entity-item.card-style - card/grid style
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+// =============================================================================
+// Entity Item Sizing
+// =============================================================================
+
+$entity-item-image-sm: 20px;
+$entity-item-image-md: 32px;
+$entity-item-image-lg: 48px;
+
+// =============================================================================
+// Base Entity Item (list-row layout)
+// =============================================================================
+
+.entity-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm;
+ background: $es-white;
+ border-radius: $es-radius-sm;
+ transition: background $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+
+ // Clickable variant
+ &.clickable {
+ cursor: pointer;
+ }
+
+ // Selected state
+ &.selected {
+ background: $es-primary-light;
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Entity Item Image
+// -----------------------------------------------------------------------------
+
+.entity-item-image {
+ flex-shrink: 0;
+ width: $entity-item-image-md;
+ height: $entity-item-image-md;
+ object-fit: cover;
+ border-radius: $es-radius-sm;
+ background: $es-slate-100;
+}
+
+// Size variants
+.entity-item-image--sm {
+ width: $entity-item-image-sm;
+ height: $entity-item-image-sm;
+ border-radius: 50%;
+}
+
+.entity-item-image--lg {
+ width: $entity-item-image-lg;
+ height: $entity-item-image-lg;
+}
+
+// No-image placeholder
+.entity-item-no-image {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: $entity-item-image-md;
+ height: $entity-item-image-md;
+ background: $es-slate-100;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ font-size: $es-font-size-sm;
+
+ &--sm {
+ width: $entity-item-image-sm;
+ height: $entity-item-image-sm;
+ font-size: 10px;
+ border-radius: 50%;
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Entity Item Info (name + meta)
+// -----------------------------------------------------------------------------
+
+.entity-item-info {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.125rem;
+}
+
+.entity-item-name {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-primary;
+ @include text-truncate;
+}
+
+.entity-item-meta {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ @include text-truncate;
+}
+
+// -----------------------------------------------------------------------------
+// Entity Item Badge/Price (right side)
+// -----------------------------------------------------------------------------
+
+.entity-item-badge {
+ flex-shrink: 0;
+ padding: 0.125rem 0.5rem;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ background: $es-slate-100;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+}
+
+.entity-item-price {
+ flex-shrink: 0;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-primary;
+}
+
+// -----------------------------------------------------------------------------
+// Entity Item Actions (remove button, etc.)
+// -----------------------------------------------------------------------------
+
+.entity-item-action {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 20px;
+ height: 20px;
+ color: $es-text-muted;
+ border-radius: 50%;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.1);
+ color: $es-danger;
+ }
+
+ i {
+ font-size: 10px;
+ }
+}
+
+// =============================================================================
+// Chip Style Variant (compact pill)
+// =============================================================================
+
+.entity-item.chip-style {
+ display: inline-flex;
+ gap: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ background: $es-slate-100;
+ border-radius: $es-radius-full;
+
+ &:hover {
+ background: $es-slate-200;
+ }
+
+ .entity-item-image {
+ width: $entity-item-image-sm;
+ height: $entity-item-image-sm;
+ border-radius: 50%;
+ }
+
+ .entity-item-no-image {
+ width: $entity-item-image-sm;
+ height: $entity-item-image-sm;
+ font-size: 10px;
+ border-radius: 50%;
+ }
+
+ .entity-item-info {
+ flex-direction: row;
+ align-items: center;
+ gap: 0.25rem;
+ }
+
+ .entity-item-name {
+ font-size: $es-font-size-xs;
+ }
+
+ .entity-item-meta {
+ display: none;
+ }
+
+ .entity-item-action {
+ width: 16px;
+ height: 16px;
+ margin-left: 0.125rem;
+ }
+}
+
+// =============================================================================
+// List Style Variant (bordered rows)
+// =============================================================================
+
+.entity-item.list-style {
+ padding: $es-spacing-sm 0;
+ background: transparent;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: 0;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+}
+
+// =============================================================================
+// Entity Item Container (wrapper for multiple items)
+// =============================================================================
+
+.entity-items-container {
+ display: flex;
+ flex-direction: column;
+ background: $es-slate-50;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-md;
+ overflow: hidden;
+}
+
+// Toolbar (filter, sort, count, clear)
+.entity-items-toolbar {
+ display: none;
+ align-items: center;
+ flex-wrap: nowrap;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ padding-bottom: 0;
+ background: transparent;
+
+ &.has-items {
+ display: flex;
+ }
+}
+
+// Filter input
+.entity-items-filter {
+ all: unset;
+ display: block;
+ flex: 1 1 auto;
+ min-width: 80px;
+ width: auto;
+ height: auto;
+ padding: 0.2rem 0.5rem 0.2rem 1.5rem;
+ background: $es-white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") no-repeat 0.375rem center;
+ background-size: 10px;
+ border: 1px solid $es-slate-300;
+ border-radius: $es-radius-sm;
+ font-size: 11px;
+ line-height: 1.4;
+ color: $es-text-primary;
+ box-sizing: border-box;
+ transition: all $es-transition-fast;
+
+ &::placeholder {
+ color: $es-text-muted;
+ font-size: 11px;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $es-primary;
+ box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
+ }
+}
+
+// Sort dropdown
+.entity-items-sort {
+ all: unset;
+ flex: 0 0 auto;
+ padding: 0.2rem 1.25rem 0.2rem 0.5rem;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ 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.375rem center;
+ background-size: 8px;
+ font-size: 10px;
+ line-height: 1.4;
+ color: $es-text-secondary;
+ cursor: pointer;
+ box-sizing: border-box;
+ white-space: nowrap;
+
+ &:hover {
+ border-color: $es-primary;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $es-primary;
+ box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
+ }
+}
+
+// Count badge
+.entity-items-count {
+ display: inline-flex;
+ align-items: center;
+ flex-shrink: 0;
+ gap: 0.125rem;
+ padding: 0.2rem 0.5rem;
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ font-size: 10px;
+ font-weight: $es-font-weight-semibold;
+ border-radius: $es-radius-sm;
+ white-space: nowrap;
+ line-height: 1.4;
+
+ &.has-filter {
+ background: $es-cyan-100;
+ color: $es-cyan-700;
+ }
+}
+
+// Clear button
+.entity-items-clear {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ flex-shrink: 0;
+ gap: 0.25rem;
+ padding: 0.2rem 0.5rem;
+ color: $es-danger;
+ font-size: 10px;
+ font-weight: $es-font-weight-medium;
+ background: rgba($es-danger, 0.1);
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+ white-space: nowrap;
+ line-height: 1.4;
+
+ &:hover {
+ background: $es-danger;
+ color: $es-white;
+ }
+
+ i {
+ font-size: 9px;
+ flex-shrink: 0;
+ }
+}
+
+// Items list area
+.entity-items-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-sm $es-spacing-md $es-spacing-md;
+ min-height: 40px;
+ max-height: 300px;
+ overflow-y: auto;
+ @include custom-scrollbar;
+
+ &:empty {
+ display: none;
+ }
+
+ // List layout (vertical)
+ &.list-layout {
+ flex-direction: column;
+ flex-wrap: nowrap;
+ gap: 0;
+ padding: $es-spacing-sm;
+ }
+}
+
+// Load more section
+.entity-items-load-more {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: transparent;
+ border-top: 1px dashed $es-border-color;
+
+ .load-more-label {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+
+ .load-more-select {
+ appearance: none;
+ padding: 0.25rem 1.75rem 0.25rem 0.5rem;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ 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;
+ background-size: 8px;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-primary;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+ height: auto;
+ min-height: 0;
+ line-height: 1.3;
+
+ &:hover {
+ border-color: $es-primary;
+ background-color: $es-primary-light;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $es-primary;
+ box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
+ }
+ }
+
+ .load-more-remaining {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+
+ .remaining-count {
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-secondary;
+ }
+ }
+
+ .btn-load-more {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: $es-spacing-xs;
+ margin: 0;
+ border: none;
+ color: $es-primary;
+ background: $es-primary-light;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+ font: inherit;
+
+ i {
+ font-size: 14px;
+ }
+
+ &:hover {
+ background: rgba($es-primary, 0.2);
+ }
+
+ &.loading {
+ cursor: wait;
+
+ i {
+ animation: spin 0.6s linear infinite;
+ }
+ }
+ }
+}
+
+// =============================================================================
+// Empty & Loading States
+// =============================================================================
+
+.entity-items-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xl;
+ text-align: center;
+ color: $es-text-muted;
+
+ i {
+ font-size: 2rem;
+ opacity: 0.5;
+ }
+
+ p {
+ margin: 0;
+ font-size: $es-font-size-sm;
+ }
+}
+
+.entity-items-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: $es-spacing-xl;
+ color: $es-text-muted;
+
+ i {
+ font-size: 20px;
+ animation: spin 0.6s linear infinite;
+ }
+}
diff --git a/sources/sources/scss/components/_entity-selector.scss b/sources/sources/scss/components/_entity-selector.scss
new file mode 100644
index 0000000..2b69857
--- /dev/null
+++ b/sources/sources/scss/components/_entity-selector.scss
@@ -0,0 +1,450 @@
+/**
+ * Entity Selector - Main Component Styles
+ * Wrapper, header, body, tabs, blocks
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+// Main wrapper (supports both .target-conditions-trait and .entity-selector-trait)
+.target-conditions-trait,
+.entity-selector-trait {
+ position: relative;
+ overflow: visible;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-lg;
+
+ // Base Material Icons sizing — 18px throughout entity-selector
+ // !important needed to override .bootstrap .material-icons:not(.js-mobile-menu) { font-size: inherit }
+ .material-icons {
+ font-size: 18px !important;
+ }
+
+ // Trait Header (collapsible)
+ .condition-trait-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: $es-spacing-md;
+ padding: 0.875rem $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: $es-radius-lg $es-radius-lg 0 0;
+ cursor: pointer;
+ user-select: none;
+ transition: background-color $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+ }
+
+ .trait-header-left {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ min-width: 0;
+ flex: 1;
+ }
+
+ .trait-icon {
+ font-size: $es-font-size-lg;
+ color: $es-text-muted;
+ flex-shrink: 0;
+ }
+
+ .trait-title-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.125rem;
+ min-width: 0;
+ }
+
+ .trait-title {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ white-space: nowrap;
+ }
+
+ .trait-subtitle {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ // Total count badge in header
+ .trait-total-count {
+ @include count-badge($es-primary);
+ margin-left: $es-spacing-sm;
+ }
+
+ // Show all toggle switch
+ .trait-show-all-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ margin-right: 0.75rem;
+ padding: 0.25rem $es-spacing-sm;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ user-select: none;
+ transition: background-color $es-transition-fast;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.05);
+ }
+
+ .toggle-label {
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-muted;
+ }
+
+ .show-all-checkbox {
+ display: none;
+ }
+
+ .toggle-slider {
+ position: relative;
+ width: 36px;
+ height: 20px;
+ background: $es-slate-300;
+ border-radius: $es-radius-full;
+ transition: background-color $es-transition-normal;
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 16px;
+ height: 16px;
+ background: $es-white;
+ border-radius: 50%;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+ transition: transform $es-transition-normal;
+ }
+ }
+
+ .show-all-checkbox:checked + .toggle-slider {
+ background: $es-success;
+
+ &::after {
+ transform: translateX(16px);
+ }
+ }
+ }
+
+ // Validation error states
+ &.has-validation-error {
+ border-color: $es-danger;
+ box-shadow: 0 0 0 3px rgba($es-danger, 0.1);
+
+ .condition-trait-header {
+ border-bottom-color: $es-danger;
+ }
+ }
+
+ .trait-validation-error {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: 0.625rem $es-spacing-md;
+ background: $es-danger-light;
+ color: #b91c1c;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ border-bottom: 1px solid #fecaca;
+
+ i {
+ color: $es-danger;
+ }
+ }
+
+ // Required indicator
+ &.trait-required .trait-title::after {
+ content: ' *';
+ color: $es-danger;
+ }
+
+ // Body
+ .condition-trait-body {
+ padding: 0;
+ background: $es-white;
+ border-radius: 0 0 $es-radius-lg $es-radius-lg;
+ }
+
+ // Block type tabs
+ .target-block-tabs {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0;
+ padding: 0;
+ background: $es-slate-100;
+ border-bottom: 1px solid $es-border-color;
+ }
+
+ .target-block-tab {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ flex: none;
+ min-width: 0;
+ padding: 0.625rem $es-spacing-md;
+ margin-bottom: -1px;
+ background: transparent;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-slate-700;
+ }
+
+ &.active {
+ background: $es-white;
+ border-bottom-color: $es-cyan-500;
+ color: $es-primary;
+ }
+
+ i.material-icons {
+ font-size: 18px !important;
+ }
+
+ .tab-label {
+ white-space: nowrap;
+ }
+
+ .tab-badge {
+ @include count-badge($es-primary);
+ }
+
+ &.has-data:not(.active) .tab-badge {
+ @include count-badge($es-slate-400);
+ }
+ }
+
+ // Tabs row with actions (form-content layout)
+ .entity-selector-tabs-row {
+ display: flex;
+ align-items: stretch;
+ background: $es-slate-100;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: $es-radius-lg $es-radius-lg 0 0;
+
+ .target-block-tabs {
+ flex: 1;
+ border-bottom: 0;
+ border-radius: $es-radius-lg 0 0 0;
+ }
+ }
+
+ // Expand/collapse groups button (standalone, in tabs row)
+ .entity-selector-actions:not(.btn-toggle-blocks) {
+ display: flex;
+ align-items: center;
+ padding: $es-spacing-xs $es-spacing-md;
+ border-left: 1px solid $es-border-color;
+
+ .btn-toggle-groups {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 34px;
+ height: 34px;
+ padding: 0;
+ background: $es-white;
+ border: 1px solid $es-slate-400;
+ border-radius: $es-radius-md;
+ color: $es-slate-700;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary-light;
+ color: $es-primary;
+ border-color: $es-primary;
+ }
+
+ .material-icons {
+ font-size: 18px !important;
+ }
+ }
+ }
+
+ // Expand/collapse toggle area (entire section is clickable)
+ .entity-selector-actions.btn-toggle-blocks {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 $es-spacing-md;
+ background: $es-slate-100;
+ border-left: 1px solid $es-border-color;
+ color: $es-slate-400;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-primary;
+ }
+
+ .material-icons {
+ font-size: 20px !important;
+ }
+ }
+
+ // When expanded - highlight the toggle area
+ .entity-selector-trait:not(.blocks-collapsed) .entity-selector-actions.btn-toggle-blocks {
+ background: $es-primary-light;
+ border-left-color: $es-primary;
+ color: $es-primary;
+ }
+
+ // Blocks content wrapper (for form-content layout collapse)
+ .entity-selector-blocks-content {
+ // Inherits styles from condition-trait-body context
+ }
+
+ // Block container
+ .target-block-container {
+ display: none;
+
+ &.active {
+ display: block;
+ }
+ }
+
+ .target-block-content {
+ padding: $es-spacing-md;
+ }
+
+ .target-block-groups {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-md;
+ }
+
+ // Block header (for standalone blocks)
+ .target-block-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ }
+
+ // Empty state
+ .target-block-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xl;
+ text-align: center;
+ color: $es-text-muted;
+
+ i {
+ font-size: 2rem !important;
+ opacity: 0.5;
+ }
+
+ p {
+ margin: 0;
+ font-size: $es-font-size-sm;
+ }
+ }
+
+ // Collapse toggle
+ .trait-collapse-toggle,
+ .collapse-toggle {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-text-muted;
+ cursor: pointer;
+ transition: transform $es-transition-normal;
+
+ &.collapsed {
+ transform: rotate(-90deg);
+ }
+ }
+
+ // Header actions
+ .trait-header-right {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ }
+
+ // Collapsed state
+ &.collapsed {
+ .condition-trait-body {
+ display: none;
+ }
+
+ .condition-trait-header {
+ border-radius: $es-radius-lg;
+ }
+ }
+}
+
+// Single mode specific styles
+.target-conditions-trait.single-mode,
+.entity-selector-trait.single-mode {
+ .target-block-tabs {
+ display: none;
+ }
+
+ .target-block-container {
+ display: block;
+ }
+}
+
+// Header action buttons
+.target-conditions-trait,
+.entity-selector-trait {
+ .header-actions {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ }
+
+ .header-action-btn {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem $es-spacing-sm;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-muted;
+ background: transparent;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ }
+
+ i {
+ font-size: 14px !important;
+ }
+ }
+}
diff --git a/sources/sources/scss/components/_groups.scss b/sources/sources/scss/components/_groups.scss
new file mode 100644
index 0000000..4c17f30
--- /dev/null
+++ b/sources/sources/scss/components/_groups.scss
@@ -0,0 +1,982 @@
+/**
+ * Groups Component
+ * Selection groups, include/exclude sections, method selectors
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+.target-conditions-trait,
+.entity-selector-trait {
+
+ // Group container
+ .target-group {
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-lg;
+ overflow: hidden;
+ }
+
+ // Group header
+ .target-group-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-md;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ }
+
+ .target-group-title {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+
+ .group-number {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 20px;
+ height: 20px;
+ padding: 0 0.25rem;
+ background: $es-primary;
+ color: $es-white;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-bold;
+ border-radius: $es-radius-full;
+ }
+ }
+
+ .target-group-actions {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ }
+
+ .group-action-btn {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ }
+
+ &.danger:hover {
+ background: $es-danger-light;
+ color: $es-danger;
+ }
+ }
+
+ // Group body
+ .target-group-body,
+ .group-body {
+ padding: $es-spacing-md;
+ }
+
+ // Include section
+ .include-section {
+ margin-bottom: $es-spacing-md;
+ }
+
+ .section-label {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ margin-bottom: $es-spacing-sm;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+
+ &.label-include {
+ color: $es-success-dark;
+
+ i {
+ color: $es-success;
+ }
+ }
+
+ &.label-exclude {
+ color: $es-danger;
+
+ i {
+ color: $es-danger;
+ }
+ }
+ }
+
+ // Method selector
+ .method-selector {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ margin-bottom: $es-spacing-sm;
+ }
+
+ .method-selector-wrapper {
+ flex: 1;
+ position: relative;
+ }
+
+ .method-select {
+ @include input-base;
+ padding-right: 2rem;
+ cursor: pointer;
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
+ background-position: right 0.5rem center;
+ background-repeat: no-repeat;
+ background-size: 1.5em 1.5em;
+ }
+
+ .method-help-btn {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ color: $es-text-muted;
+ border-radius: $es-radius-full;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-100;
+ color: $es-primary;
+ }
+ }
+
+ // Value picker (search trigger)
+ .value-picker {
+ position: relative;
+ }
+
+ .value-picker-trigger {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ width: 100%;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-md;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+ text-align: left;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ border-color: $es-slate-300;
+ }
+
+ &:focus {
+ border-color: $es-primary;
+ @include focus-ring($es-primary);
+ }
+
+ i {
+ color: $es-text-light;
+ }
+ }
+
+ // Pattern input (text input for patterns)
+ .pattern-input-wrapper {
+ position: relative;
+ }
+
+ .pattern-input {
+ @include input-base;
+ font-family: monospace;
+ }
+
+ .pattern-add-btn {
+ @include button-reset;
+ position: absolute;
+ right: 0.25rem;
+ top: 50%;
+ transform: translateY(-50%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ color: $es-primary;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary-light;
+ }
+ }
+
+ // Multi-range input (price ranges)
+ .multi-range-container {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-sm;
+ }
+
+ .range-row {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ }
+
+ .range-input {
+ @include input-base;
+ width: 100px;
+ text-align: center;
+ }
+
+ .range-separator {
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+ }
+
+ .range-remove-btn {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-danger-light;
+ color: $es-danger;
+ }
+ }
+
+ .range-add-btn {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ color: $es-primary;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary-light;
+ }
+ }
+
+ // Multi-select tiles (stock status, etc.)
+ .multi-select-tiles {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-xs;
+ }
+
+ .multi-select-tile {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.375rem 0.75rem;
+ background: $es-slate-100;
+ color: $es-text-secondary;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border: 1px solid transparent;
+ border-radius: $es-radius-full;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ }
+
+ &.selected {
+ background: $es-primary-light;
+ color: $es-primary;
+ border-color: $es-primary;
+ }
+ }
+
+ // Exclude section
+ .exclude-section {
+ margin-top: $es-spacing-md;
+ padding-top: $es-spacing-md;
+ border-top: 1px dashed $es-border-color;
+ }
+
+ // Legacy exclude-rows (if used elsewhere)
+ .exclude-rows {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-sm;
+ }
+
+ .exclude-row-content {
+ flex: 1;
+ }
+
+ .exclude-remove-btn {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ flex-shrink: 0;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-danger-light;
+ color: $es-danger;
+ }
+ }
+
+ .add-exclude-btn {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ margin-top: $es-spacing-sm;
+ padding: 0.25rem 0.5rem;
+ color: $es-danger;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border: 1px dashed $es-danger;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-danger-light;
+ }
+ }
+
+ // Add group button (used in block-footer)
+ .btn-add-group {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.5rem 0.875rem;
+ color: $es-primary;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ background: rgba($es-primary, 0.05);
+ border: 1px dashed $es-primary;
+ border-radius: 0.375rem;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: rgba($es-primary, 0.1);
+ }
+
+ i {
+ font-size: 12px;
+ }
+ }
+
+ // Block footer
+ .block-footer {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-md;
+ border-top: 1px solid $es-border-color;
+ }
+
+ // Block body
+ .block-body {
+ padding: 0;
+ }
+
+ // Groups container
+ .groups-container {
+ padding: $es-spacing-md;
+ }
+
+ // Groups empty state
+ .groups-empty-state {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: $es-spacing-xl;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+ }
+
+ // Selection group
+ .selection-group {
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-lg;
+ margin-bottom: $es-spacing-md;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ &.collapsed {
+ .group-body {
+ display: none;
+ }
+
+ .group-collapse-toggle i {
+ transform: rotate(-90deg);
+ }
+ }
+ }
+
+ // Group header
+ .group-header {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: $es-radius-lg $es-radius-lg 0 0;
+ cursor: pointer;
+
+ &.group-header-single {
+ padding: $es-spacing-xs $es-spacing-md;
+ background: transparent;
+ border-bottom: none;
+ }
+ }
+
+ .group-collapse-toggle {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ color: $es-text-muted;
+
+ i {
+ font-size: 20px !important;
+ transition: transform $es-transition-fast;
+ }
+ }
+
+ .group-name-wrapper {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ }
+
+ .group-name-input {
+ flex: 1;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ background: transparent;
+ border: 1px solid transparent;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover,
+ &:focus {
+ background: $es-white;
+ border-color: $es-border-color;
+ outline: none;
+ }
+
+ &::placeholder {
+ color: $es-text-muted;
+ font-weight: $es-font-weight-medium;
+ }
+ }
+
+ .group-count-badge {
+ @include count-badge($es-primary);
+ }
+
+ .btn-remove-group {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-danger-light;
+ color: $es-danger;
+ }
+ }
+
+ // Group include section - green accent to distinguish from exclude
+ .group-include {
+ margin-bottom: $es-spacing-md;
+ padding: $es-spacing-sm;
+ background: rgba($es-success, 0.03);
+ border: 1px solid rgba($es-success, 0.2);
+ border-radius: $es-radius-md;
+ }
+
+ .section-row {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-sm;
+ }
+
+ // Method selector wrapper (from PHP)
+ .method-selector-wrapper {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ }
+
+ .method-info-placeholder {
+ display: flex;
+ align-items: center;
+ min-width: 20px;
+ }
+
+ .include-method-select,
+ .exclude-method-select {
+ flex: 1;
+ @include input-base;
+ cursor: pointer;
+ }
+
+ // Lock indicator for method selector (when excludes are present)
+ .selector-locked {
+ .include-method-select {
+ opacity: 0.7;
+ cursor: not-allowed;
+ }
+ }
+
+ .lock-indicator {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-warning;
+ cursor: help;
+
+ i {
+ font-size: 16px !important;
+ }
+
+ .mpr-tooltip {
+ display: none;
+ position: absolute;
+ bottom: calc(100% + 8px);
+ left: 50%;
+ transform: translateX(-50%);
+ padding: $es-spacing-xs $es-spacing-sm;
+ background: $es-slate-800;
+ color: $es-white;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-normal;
+ white-space: nowrap;
+ border-radius: $es-radius-sm;
+ z-index: 100;
+ }
+
+ &:hover .mpr-tooltip {
+ display: block;
+ }
+ }
+
+ // Group excludes section
+ .group-excludes {
+ margin-top: $es-spacing-md;
+ }
+
+ .except-separator {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ margin: 0 0 $es-spacing-sm 0;
+
+ // Lines on both sides
+ &::before,
+ &::after {
+ content: '';
+ flex: 1;
+ height: 1px;
+ background: rgba($es-danger, 0.3);
+ }
+ }
+
+ .except-label {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.75rem;
+ background: $es-danger-light;
+ color: $es-danger;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ border-radius: $es-radius-full;
+ white-space: nowrap;
+ flex-shrink: 0;
+
+ i {
+ font-size: 12px !important;
+ }
+ }
+
+ .exclude-rows-container {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-sm;
+ }
+
+ .exclude-row {
+ display: flex;
+ flex-direction: column;
+ padding: $es-spacing-sm;
+ background: rgba($es-danger, 0.03);
+ border: 1px solid rgba($es-danger, 0.15);
+ border-radius: $es-radius-md;
+
+ // Value picker inside exclude row - full width
+ .value-picker {
+ width: 100%;
+ margin-top: $es-spacing-sm;
+ }
+ }
+
+ .exclude-header-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-sm;
+ width: 100%;
+
+ .method-selector-wrapper {
+ flex: 1;
+ }
+
+ // Delete button at the far right
+ .btn-remove-exclude-row {
+ flex-shrink: 0;
+ margin-left: auto;
+ }
+ }
+
+ .btn-remove-exclude-row {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-danger-light;
+ color: $es-danger;
+ }
+ }
+
+ .btn-add-exclude,
+ .btn-add-another-exclude {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ margin-top: $es-spacing-sm;
+ padding: 0.375rem 0.625rem;
+ color: $es-danger;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ background: transparent;
+ border: 1px dashed rgba($es-danger, 0.5);
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-danger-light;
+ border-color: $es-danger;
+ }
+
+ i {
+ font-size: 12px !important;
+ }
+ }
+
+ // Group modifiers (inline version from PHP)
+ // Uses negative margins to break out of .group-body padding
+ // Elements use .mpr-input-compact class to opt out of admin.css global sizing
+ .group-modifiers {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: $es-spacing-md;
+ padding: $es-spacing-sm $es-spacing-md;
+ margin: $es-spacing-md (-$es-spacing-md) (-$es-spacing-md);
+ background: $es-slate-50;
+ border-top: 1px solid $es-border-color;
+ border-radius: 0 0 $es-radius-lg $es-radius-lg;
+ }
+
+ .modifier-inline {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ flex-shrink: 0;
+ }
+
+ // Common height for all modifier controls
+ $modifier-height: 26px;
+
+ .group-modifier-limit {
+ width: 50px;
+ max-width: 50px;
+ min-width: 50px;
+ height: $modifier-height;
+ padding: 0 0.375rem;
+ font-size: $es-font-size-xs;
+ text-align: center;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ box-sizing: border-box;
+
+ &:focus {
+ border-color: $es-primary;
+ outline: none;
+ }
+ }
+
+ // Sort modifier - input group style (select + button glued together)
+ .modifier-sort {
+ gap: 0; // Remove gap to glue select + button together
+
+ .modifier-label {
+ margin-right: 0.375rem; // Keep space between label and input group
+ }
+
+ .group-modifier-sort {
+ width: auto;
+ height: $modifier-height;
+ padding: 0 1.25rem 0 0.5rem;
+ font-size: $es-font-size-xs;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm 0 0 $es-radius-sm;
+ border-right: none;
+ cursor: pointer;
+ box-sizing: border-box;
+
+ &:focus {
+ border-color: $es-primary;
+ outline: none;
+ position: relative;
+ z-index: 1;
+ }
+ }
+
+ .btn-sort-dir {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: $modifier-height;
+ height: $modifier-height;
+ color: $es-text-muted;
+ background: $es-slate-100;
+ border: 1px solid $es-border-color;
+ border-radius: 0 $es-radius-sm $es-radius-sm 0;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ }
+
+ i {
+ font-size: 14px !important;
+ }
+ }
+ }
+
+ // Fallback for elements outside .modifier-sort context
+ .group-modifier-sort {
+ height: $modifier-height;
+ padding: 0 0.5rem;
+ font-size: $es-font-size-xs;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+
+ &:focus {
+ border-color: $es-primary;
+ outline: none;
+ }
+ }
+
+ .btn-sort-dir {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: $modifier-height;
+ height: $modifier-height;
+ color: $es-text-muted;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-100;
+ color: $es-text-secondary;
+ }
+ }
+
+ .group-preview-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ background: $es-slate-100;
+ color: $es-text-muted;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border-radius: $es-radius-full;
+ transition: all $es-transition-fast;
+
+ &.clickable {
+ cursor: pointer;
+
+ &:hover {
+ background: $es-primary-light;
+ color: $es-primary;
+ }
+ }
+ }
+
+ // OR separator between groups
+ .group-separator {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: $es-spacing-sm 0;
+ color: $es-text-muted;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+
+ &::before,
+ &::after {
+ content: '';
+ flex: 1;
+ height: 1px;
+ background: $es-border-color;
+ margin: 0 $es-spacing-md;
+ }
+ }
+
+ // Group modifiers (limit, sort)
+ .group-modifiers {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-md;
+ padding-top: $es-spacing-md;
+ margin-top: $es-spacing-md;
+ border-top: 1px solid $es-border-color;
+ }
+
+ .modifier-group {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ }
+
+ .modifier-label {
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-muted;
+ white-space: nowrap;
+ }
+
+ .modifier-input {
+ @include input-base;
+ width: 80px;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ }
+
+ .modifier-select {
+ @include input-base;
+ width: auto;
+ padding: 0.25rem 1.5rem 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ cursor: pointer;
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
+ background-position: right 0.25rem center;
+ background-repeat: no-repeat;
+ background-size: 1.25em 1.25em;
+ }
+
+ // Condition match count badge
+ .condition-match-count {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.125rem 0.375rem;
+ background: $es-slate-100;
+ color: $es-text-muted;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border-radius: $es-radius-full;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ }
+
+ &.has-results {
+ background: $es-primary-light;
+ color: $es-primary;
+ }
+
+ // Country holidays variant - use calendar icon style
+ &.country-holidays {
+ background: rgba(139, 92, 246, 0.1);
+ color: #8b5cf6;
+
+ &:hover {
+ background: rgba(139, 92, 246, 0.2);
+ }
+
+ &.clickable {
+ background: rgba(139, 92, 246, 0.15);
+ }
+ }
+
+ i {
+ font-size: 12px !important;
+ }
+ }
+}
diff --git a/sources/sources/scss/components/_list-preview.scss b/sources/sources/scss/components/_list-preview.scss
new file mode 100644
index 0000000..fd1e064
--- /dev/null
+++ b/sources/sources/scss/components/_list-preview.scss
@@ -0,0 +1,644 @@
+/**
+ * List Preview Component
+ * Popover and modal views for entity preview
+ *
+ * Uses shared entity-item base for item styling.
+ * This file only contains popover/modal container styles.
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+// =============================================================================
+// Preview Popover Container
+// =============================================================================
+
+.target-preview-popover,
+.target-list-preview-popover {
+ position: absolute;
+ z-index: 10000;
+ min-width: 320px;
+ max-width: 480px;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-lg;
+ box-shadow: $es-shadow-lg;
+ overflow: hidden;
+
+ // Arrow pointing to badge
+ &::before {
+ content: '';
+ position: absolute;
+ top: -8px;
+ left: 50%;
+ transform: translateX(-50%);
+ border-left: 8px solid transparent;
+ border-right: 8px solid transparent;
+ border-bottom: 8px solid $es-border-color;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: -6px;
+ left: 50%;
+ transform: translateX(-50%);
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid $es-white;
+ }
+
+ // Positioned to the right - arrow on left
+ &.position-right {
+ &::before,
+ &::after {
+ left: 20px;
+ transform: none;
+ }
+ }
+
+ // Positioned to the left - arrow on right
+ &.position-left {
+ &::before,
+ &::after {
+ left: auto;
+ right: 20px;
+ transform: none;
+ }
+ }
+}
+
+// =============================================================================
+// Preview Header
+// =============================================================================
+
+.preview-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+
+ .preview-title {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ }
+
+ .preview-close {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-primary;
+ }
+ }
+}
+
+// =============================================================================
+// Preview Tabs (entity type switcher)
+// =============================================================================
+
+.preview-tabs {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0;
+ padding: 0;
+ background: $es-slate-50;
+ border-bottom: 1px solid $es-border-color;
+}
+
+.preview-tab {
+ display: flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.5rem 0.75rem;
+ background: transparent;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ margin-bottom: -1px;
+ color: $es-text-muted;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+ white-space: nowrap;
+
+ &:hover {
+ background: $es-slate-100;
+ color: $es-text-secondary;
+ }
+
+ &.active {
+ background: $es-white;
+ border-bottom-color: $es-primary;
+ color: $es-primary;
+ }
+
+ i {
+ font-size: 12px;
+ }
+}
+
+// =============================================================================
+// Preview Filter
+// =============================================================================
+
+.preview-filter {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-white;
+ border-bottom: 1px solid $es-border-color;
+
+ i {
+ color: $es-text-muted;
+ font-size: 12px;
+ }
+
+ .preview-filter-input {
+ all: unset;
+ flex: 1;
+ padding: 0.25rem 0;
+ font-size: $es-font-size-xs;
+ color: $es-text-primary;
+ box-sizing: border-box;
+
+ &::placeholder {
+ color: $es-text-muted;
+ }
+ }
+}
+
+// =============================================================================
+// Preview Contents (tabbed content areas)
+// =============================================================================
+
+.preview-contents {
+ max-height: 350px;
+ overflow: hidden;
+}
+
+.preview-content {
+ display: none;
+ max-height: 350px;
+ overflow-y: auto;
+ @include custom-scrollbar;
+
+ &.active {
+ display: block;
+ }
+}
+
+// =============================================================================
+// Preview Items Container
+// =============================================================================
+
+.preview-items {
+ display: flex;
+ flex-direction: column;
+ padding: $es-spacing-xs $es-spacing-sm;
+}
+
+// =============================================================================
+// Preview Item - Uses entity-item patterns
+// Maps legacy classes to shared styling
+// =============================================================================
+
+.preview-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm;
+ background: $es-white;
+ border-radius: $es-radius-sm;
+ transition: background $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+
+ // Clickable items
+ &[data-id] {
+ cursor: pointer;
+ }
+}
+
+// Image - matches chip image sizing for consistency
+.preview-item-image {
+ flex-shrink: 0;
+ width: 32px;
+ height: 32px;
+ object-fit: cover;
+ border-radius: $es-radius-sm;
+ background: $es-slate-100;
+}
+
+// No-image placeholder
+.preview-item-no-image {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 32px;
+ height: 32px;
+ background: $es-slate-100;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ font-size: $es-font-size-sm;
+}
+
+// Info container
+.preview-item-info {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.125rem;
+}
+
+// Name
+.preview-item-name {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-primary;
+ @include text-truncate;
+}
+
+// Meta/ref (category, email, etc.)
+.preview-item-ref,
+.preview-item-meta {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ @include text-truncate;
+}
+
+// Price badge
+.preview-item-price {
+ flex-shrink: 0;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ color: $es-primary;
+ background: $es-primary-light;
+ border-radius: $es-radius-sm;
+}
+
+// =============================================================================
+// Preview Footer (load more)
+// =============================================================================
+
+.preview-footer {
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-slate-50;
+ border-top: 1px solid $es-border-color;
+}
+
+.load-more-controls {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+
+ .load-more-label {
+ white-space: nowrap;
+ }
+
+ .load-more-select {
+ appearance: none;
+ padding: 0.25rem 1.75rem 0.25rem 0.5rem;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ 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;
+ background-size: 8px;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-primary;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+ height: auto;
+ min-height: 0;
+ line-height: 1.3;
+
+ &:hover {
+ border-color: $es-primary;
+ background-color: $es-primary-light;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: $es-primary;
+ box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
+ }
+ }
+
+ .load-more-of {
+ white-space: nowrap;
+ }
+
+ .remaining-count {
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-secondary;
+ }
+
+ .btn-load-more {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: $es-spacing-xs;
+ margin: 0;
+ border: none;
+ color: $es-primary;
+ background: $es-primary-light;
+ border-radius: $es-radius-sm;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+ font: inherit;
+
+ i {
+ font-size: 14px;
+ }
+
+ &:hover {
+ background: rgba($es-primary, 0.2);
+ }
+
+ &.loading {
+ cursor: wait;
+
+ i {
+ animation: spin 0.6s linear infinite;
+ }
+ }
+ }
+}
+
+// =============================================================================
+// Preview States
+// =============================================================================
+
+.preview-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xl;
+ text-align: center;
+ color: $es-text-muted;
+
+ i {
+ font-size: 2rem;
+ opacity: 0.5;
+ }
+
+ p {
+ margin: 0;
+ font-size: $es-font-size-sm;
+ }
+}
+
+.preview-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: $es-spacing-xl;
+ color: $es-text-muted;
+
+ i {
+ font-size: 20px;
+ color: $es-primary;
+ animation: spin 0.6s linear infinite;
+ }
+}
+
+// =============================================================================
+// Total Summary Popover (header total badge click)
+// =============================================================================
+
+.total-preview-popover {
+ min-width: 240px;
+ max-width: 320px;
+
+ .preview-popover-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-md;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+
+ .preview-popover-title {
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ font-size: $es-font-size-sm;
+ }
+
+ .preview-popover-count {
+ flex-shrink: 0;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-muted;
+ background: $es-slate-200;
+ padding: 0.125rem 0.5rem;
+ border-radius: $es-radius-sm;
+ }
+ }
+
+ .preview-popover-body {
+ padding: $es-spacing-xs 0;
+ }
+
+ .total-summary-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ }
+
+ .total-summary-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ cursor: pointer;
+ transition: background-color 0.15s ease;
+
+ &:hover {
+ background: $es-slate-50;
+ }
+
+ i {
+ width: 18px;
+ text-align: center;
+ color: $es-text-muted;
+ font-size: 14px;
+ }
+
+ .summary-item-label {
+ flex: 1;
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ }
+
+ .summary-item-count {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-primary;
+ background: rgba($es-primary, 0.1);
+ padding: 2px 8px;
+ border-radius: $es-radius-sm;
+ }
+ }
+}
+
+// Make trait-total-count clickable
+.trait-total-count {
+ cursor: pointer;
+ transition: all 0.15s ease;
+
+ &:hover {
+ opacity: 0.8;
+ }
+
+ &.popover-open {
+ opacity: 0.9;
+ }
+}
+
+// =============================================================================
+// Schedule Dropdown Preview
+// Inline dropdown for schedule details in admin list
+// =============================================================================
+
+.mpr-dropdown-preview {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: 6px;
+ z-index: 1000;
+ padding: 0.625rem 0.75rem;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-lg;
+ box-shadow: $es-shadow-lg;
+ font-size: 12px;
+ text-align: left;
+ text-transform: none;
+ font-weight: normal;
+ white-space: nowrap; // Allow dropdown to grow as needed
+
+ &.is-open {
+ display: block;
+ }
+}
+
+.mpr-dropdown-preview__item {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.375rem 0;
+ color: #666;
+ line-height: 1.4;
+
+ &:not(:last-child) {
+ margin-bottom: 0.25rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid rgba($es-border-color, 0.5);
+ }
+
+ // Icon styles
+ > .material-icons,
+ > i:first-child {
+ flex-shrink: 0;
+ width: 16px;
+ font-size: 14px !important;
+ color: #999;
+ text-align: center;
+ }
+}
+
+.mpr-dropdown-preview__muted {
+ color: #999;
+}
+
+// Schedule inline layout
+.mpr-dropdown-preview__schedule {
+ display: inline;
+}
+
+.mpr-dropdown-preview__schedule-row {
+ display: inline;
+
+ &:not(:last-child)::after {
+ content: ',';
+ margin-right: 0.5rem;
+ }
+}
+
+.mpr-dropdown-preview__day {
+ color: #666;
+}
+
+.mpr-dropdown-preview__hours {
+ color: $es-primary;
+ font-weight: 500;
+ margin-left: 0.25rem;
+}
+
+// Badge trigger for preview dropdown/popover
+.mpr-badge--preview {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.25rem;
+ position: relative;
+ min-width: 20px;
+ height: 20px;
+ padding: 0 0.5rem;
+ font-size: 0.75rem;
+ font-weight: 600;
+ border-radius: 50rem;
+ cursor: pointer;
+ text-transform: none;
+
+ .material-icons {
+ font-size: 12px !important;
+ line-height: 1;
+ opacity: 0.8;
+ }
+}
+
+.mpr-badge--preview-primary {
+ background: $es-primary;
+ color: $es-white;
+}
+
+.mpr-badge--preview-success {
+ background: #d4edda;
+ color: #155724;
+}
+
+.mpr-badge--preview-warning {
+ background: #fff3cd;
+ color: #856404;
+}
+
+.mpr-badge--preview-muted {
+ background: $es-slate-200;
+ color: $es-text-muted;
+}
diff --git a/sources/sources/scss/components/_method-dropdown.scss b/sources/sources/scss/components/_method-dropdown.scss
new file mode 100644
index 0000000..9d31d11
--- /dev/null
+++ b/sources/sources/scss/components/_method-dropdown.scss
@@ -0,0 +1,225 @@
+/**
+ * Method Dropdown Component
+ * Custom select dropdown with icons for method selection
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+.target-conditions-trait,
+.entity-selector-trait {
+
+ // Method dropdown trigger button
+ .method-dropdown-trigger {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ height: 36px;
+ padding: 0 $es-spacing-md;
+ border-radius: $es-radius-md;
+ background: $es-white;
+ color: $es-slate-800;
+ font-size: $es-font-size-sm;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+ min-width: 180px;
+ max-width: 320px;
+ border: 1px solid $es-border-color;
+
+ &:hover {
+ background: $es-slate-50;
+ border-color: $es-gray-300;
+ }
+
+ &:focus,
+ &:active {
+ outline: none;
+ border-color: $es-primary;
+ box-shadow: 0 0 0 3px rgba($es-primary, 0.1);
+ }
+ }
+
+ .method-trigger-icon {
+ font-size: $es-font-size-sm;
+ color: $es-text-muted;
+ flex-shrink: 0;
+ width: 18px;
+ text-align: center;
+ }
+
+ .method-trigger-label {
+ flex: 1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-weight: $es-font-weight-medium;
+ }
+
+ .method-trigger-caret {
+ font-size: $es-font-size-xs;
+ color: $es-slate-400;
+ flex-shrink: 0;
+ margin-left: auto;
+ }
+
+ // Locked state
+ .selector-locked .method-dropdown-trigger {
+ background: $es-slate-100;
+ color: $es-slate-400;
+ cursor: not-allowed;
+ border-color: $es-border-color;
+
+ &:hover {
+ background: $es-slate-100;
+ border-color: $es-border-color;
+ }
+ }
+
+ // Method selector wrapper
+ .method-selector-wrapper {
+ position: relative;
+ }
+
+ // Hidden select (for form submission)
+ .method-select-hidden {
+ position: absolute !important;
+ opacity: 0 !important;
+ pointer-events: none !important;
+ width: 0 !important;
+ height: 0 !important;
+ overflow: hidden !important;
+ }
+}
+
+// Global fallback for hidden method selects
+.method-select-hidden {
+ position: absolute !important;
+ opacity: 0 !important;
+ pointer-events: none !important;
+ width: 0 !important;
+ height: 0 !important;
+ overflow: hidden !important;
+}
+
+// =============================================================================
+// Method Dropdown Menu (appended to body, outside trait wrappers)
+// =============================================================================
+
+.method-dropdown-menu {
+ position: absolute;
+ z-index: $es-z-dropdown + 1;
+ min-width: 200px;
+ max-width: 360px;
+ max-height: 400px;
+ overflow-y: auto;
+ background: $es-white;
+ border-radius: $es-radius-lg;
+ padding: 0.375rem 0;
+ border: 1px solid $es-border-color;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
+ animation: methodDropdownFadeIn 0.15s ease;
+ @include custom-scrollbar;
+}
+
+@keyframes methodDropdownFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-4px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+// Method dropdown item
+.method-dropdown-item {
+ display: flex;
+ align-items: center;
+ gap: 0.625rem;
+ padding: 0.5rem $es-spacing-md;
+ cursor: pointer;
+ transition: background-color 0.1s;
+ position: relative;
+
+ &:hover {
+ background: $es-slate-100;
+ }
+
+ &.selected {
+ background: rgba($es-primary, 0.08);
+ }
+
+ .method-item-icon {
+ font-size: $es-font-size-sm;
+ color: $es-text-muted;
+ width: 18px;
+ text-align: center;
+ flex-shrink: 0;
+ }
+
+ &.selected .method-item-icon {
+ color: $es-primary;
+ }
+
+ .method-item-label {
+ flex: 1;
+ font-size: $es-font-size-sm;
+ color: $es-slate-700;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &.selected .method-item-label {
+ color: $es-cyan-700;
+ font-weight: $es-font-weight-medium;
+ }
+
+ .method-item-check {
+ font-size: $es-font-size-xs;
+ flex-shrink: 0;
+ margin-left: auto;
+ color: $es-primary;
+ }
+}
+
+// Method dropdown optgroup
+.method-dropdown-optgroup {
+ margin-top: 0.25rem;
+
+ &:first-child {
+ margin-top: 0;
+ }
+}
+
+.method-optgroup-label {
+ padding: 0.5rem $es-spacing-md;
+ font-size: 11px;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-muted;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ background: $es-slate-50;
+ border-top: 1px solid $es-slate-100;
+ border-bottom: 1px solid $es-slate-100;
+
+ .method-dropdown-optgroup:first-child & {
+ border-top: 0;
+ }
+}
+
+.method-optgroup-items {
+ padding: 0.25rem 0;
+
+ .method-dropdown-item {
+ padding-left: $es-spacing-lg;
+ }
+}
+
+// Method info placeholder
+.method-info-placeholder {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ font-style: italic;
+}
diff --git a/sources/sources/scss/components/_modal.scss b/sources/sources/scss/components/_modal.scss
new file mode 100644
index 0000000..9d699e0
--- /dev/null
+++ b/sources/sources/scss/components/_modal.scss
@@ -0,0 +1,488 @@
+/**
+ * Modal Component
+ * Preview modals, confirmation dialogs
+ */
+
+@use "sass:color";
+@use '../variables' as *;
+@use '../mixins' as *;
+
+// Modal backdrop
+.mpr-modal-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: $es-z-modal;
+ opacity: 0;
+ transition: opacity $es-transition-normal;
+
+ &.show {
+ opacity: 1;
+ }
+}
+
+// Modal container
+.mpr-modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) scale(0.95);
+ z-index: $es-z-modal + 1;
+ width: 90%;
+ max-width: 600px;
+ max-height: 90vh;
+ background: $es-white;
+ border-radius: $es-radius-xl;
+ box-shadow: $es-shadow-xl;
+ opacity: 0;
+ transition: all $es-transition-normal;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+
+ &.show {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+ }
+
+ &.modal-sm {
+ max-width: 400px;
+ }
+
+ &.modal-lg {
+ max-width: 800px;
+ }
+
+ &.modal-xl {
+ max-width: 1000px;
+ }
+
+ &.modal-fullscreen {
+ width: 95%;
+ max-width: none;
+ height: 90vh;
+ max-height: none;
+ }
+}
+
+// Modal header
+.mpr-modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-md;
+ padding: $es-spacing-md $es-spacing-lg;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ flex-shrink: 0;
+}
+
+.mpr-modal-title {
+ font-size: $es-font-size-base;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ margin: 0;
+}
+
+.mpr-modal-close {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ color: $es-text-muted;
+ border-radius: $es-radius-md;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ }
+
+ i {
+ font-size: $es-font-size-lg;
+ }
+}
+
+// Modal body
+.mpr-modal-body {
+ flex: 1;
+ overflow-y: auto;
+ padding: $es-spacing-lg;
+ @include custom-scrollbar;
+}
+
+// Modal footer
+.mpr-modal-footer {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-md $es-spacing-lg;
+ background: $es-bg-header;
+ border-top: 1px solid $es-border-color;
+ flex-shrink: 0;
+}
+
+.mpr-modal-btn {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-sm $es-spacing-md;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ border-radius: $es-radius-md;
+ transition: all $es-transition-fast;
+
+ &.btn-secondary {
+ color: $es-text-secondary;
+ background: $es-slate-100;
+
+ &:hover {
+ background: $es-slate-200;
+ }
+ }
+
+ &.btn-primary {
+ color: $es-white;
+ background: $es-primary;
+
+ &:hover {
+ background: $es-primary-hover;
+ }
+ }
+
+ &.btn-danger {
+ color: $es-white;
+ background: $es-danger;
+
+ &:hover {
+ background: color.adjust($es-danger, $lightness: -10%);
+ }
+ }
+
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+}
+
+// Preview popover styles moved to _list-preview.scss
+
+.popover-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: $es-radius-lg $es-radius-lg 0 0;
+}
+
+.popover-title {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+}
+
+.popover-close {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ }
+}
+
+.popover-body {
+ max-height: 300px;
+ overflow-y: auto;
+ padding: $es-spacing-sm;
+ @include custom-scrollbar;
+}
+
+.popover-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-bg-header;
+ border-top: 1px solid $es-border-color;
+ border-radius: 0 0 $es-radius-lg $es-radius-lg;
+}
+
+.popover-info {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+}
+
+.popover-load-more {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-primary;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary-light;
+ }
+}
+
+// Popover arrow
+.popover-arrow {
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ transform: rotate(45deg);
+
+ &.arrow-top {
+ top: -7px;
+ left: 50%;
+ margin-left: -6px;
+ border-right: none;
+ border-bottom: none;
+ }
+
+ &.arrow-bottom {
+ bottom: -7px;
+ left: 50%;
+ margin-left: -6px;
+ border-left: none;
+ border-top: none;
+ }
+}
+
+// ==========================================================================
+// Holiday Preview Modal
+// ==========================================================================
+
+#mpr-holiday-preview-modal {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: $es-z-modal;
+
+ &.show {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .mpr-modal-backdrop {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ cursor: pointer;
+ }
+
+ .mpr-modal-dialog {
+ position: relative;
+ width: 90%;
+ max-width: 480px;
+ max-height: 80vh;
+ background: $es-white;
+ border-radius: $es-radius-lg;
+ box-shadow: $es-shadow-xl;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
+
+ .mpr-modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-md;
+ padding: $es-spacing-md $es-spacing-lg;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ flex-shrink: 0;
+ }
+
+ .mpr-modal-title {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ font-size: $es-font-size-base;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ margin: 0;
+
+ i.material-icons {
+ font-size: 20px !important;
+ color: $es-primary;
+ }
+ }
+
+ .mpr-modal-close {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ color: $es-text-muted;
+ border-radius: $es-radius-md;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ }
+
+ i {
+ font-size: 18px;
+ }
+ }
+
+ .mpr-modal-body {
+ flex: 1;
+ overflow-y: auto;
+ padding: $es-spacing-lg;
+ @include custom-scrollbar;
+ }
+
+ // Loading state
+ .holiday-preview-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xl 0;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+
+ i {
+ font-size: $es-font-size-lg;
+ }
+ }
+
+ // Empty state
+ .holiday-preview-empty {
+ text-align: center;
+ padding: $es-spacing-xl 0;
+ color: $es-text-muted;
+
+ i.material-icons {
+ font-size: 48px !important;
+ opacity: 0.5;
+ margin-bottom: $es-spacing-md;
+ }
+
+ p {
+ margin: 0 0 $es-spacing-xs;
+ }
+
+ .hint {
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+ }
+
+ // Holiday list
+ .holiday-list {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-sm;
+ }
+
+ .holiday-item {
+ display: flex;
+ align-items: flex-start;
+ gap: $es-spacing-md;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-slate-50;
+ border-radius: $es-radius-md;
+ border-left: 3px solid $es-success;
+
+ &.holiday-type-bank {
+ border-left-color: $es-info;
+ }
+
+ &.holiday-type-observance {
+ border-left-color: $es-warning;
+ }
+
+ &.holiday-type-regional {
+ border-left-color: #8b5cf6;
+ }
+ }
+
+ .holiday-date {
+ flex-shrink: 0;
+ min-width: 100px;
+
+ .holiday-day {
+ display: block;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+ }
+
+ .holiday-weekday {
+ display: block;
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ }
+ }
+
+ .holiday-info {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .holiday-name {
+ display: block;
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ word-wrap: break-word;
+ }
+
+ .holiday-type-badge {
+ display: inline-block;
+ margin-top: $es-spacing-xs;
+ padding: 0.125rem 0.375rem;
+ font-size: 10px;
+ font-weight: $es-font-weight-medium;
+ text-transform: capitalize;
+ background: $es-slate-200;
+ color: $es-text-secondary;
+ border-radius: $es-radius-sm;
+ }
+
+ .holiday-preview-note {
+ margin-top: $es-spacing-md;
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+ text-align: center;
+ }
+}
diff --git a/sources/sources/scss/components/_schedule.scss b/sources/sources/scss/components/_schedule.scss
new file mode 100644
index 0000000..e6d1c7f
--- /dev/null
+++ b/sources/sources/scss/components/_schedule.scss
@@ -0,0 +1,369 @@
+/**
+ * Schedule Conditions Component
+ * DateTime picker, weekly timeline, holidays
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+// Schedule conditions wrapper
+.schedule-conditions-trait {
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-lg;
+}
+
+// Schedule header
+.schedule-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: $es-spacing-md;
+ padding: 0.875rem $es-spacing-md;
+ background: $es-bg-header;
+ border-bottom: 1px solid $es-border-color;
+ border-radius: $es-radius-lg $es-radius-lg 0 0;
+ cursor: pointer;
+ user-select: none;
+ transition: background-color $es-transition-fast;
+
+ &:hover {
+ background: $es-bg-hover;
+ }
+}
+
+.schedule-title {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+
+ i {
+ color: $es-text-muted;
+ }
+}
+
+// Schedule body
+.schedule-body {
+ padding: $es-spacing-md;
+}
+
+// Schedule section
+.schedule-section {
+ margin-bottom: $es-spacing-lg;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.schedule-section-title {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ margin-bottom: $es-spacing-sm;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-text-primary;
+
+ i {
+ color: $es-text-muted;
+ }
+}
+
+.schedule-section-description {
+ margin-bottom: $es-spacing-md;
+ font-size: $es-font-size-xs;
+ color: $es-text-muted;
+}
+
+// DateTime range picker
+.datetime-range {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-md;
+}
+
+.datetime-field {
+ flex: 1;
+ min-width: 200px;
+}
+
+.datetime-label {
+ display: block;
+ margin-bottom: 0.25rem;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-secondary;
+}
+
+.datetime-input {
+ @include input-base;
+}
+
+// Weekly schedule
+.weekly-schedule {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-sm;
+}
+
+.day-row {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-md;
+ padding: $es-spacing-sm;
+ background: $es-slate-50;
+ border-radius: $es-radius-md;
+
+ &.disabled {
+ opacity: 0.5;
+ }
+}
+
+.day-toggle {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ min-width: 100px;
+}
+
+.day-checkbox {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+}
+
+.day-name {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-primary;
+}
+
+// Timeline slider
+.timeline-slider {
+ flex: 1;
+ position: relative;
+ height: 24px;
+ background: $es-slate-200;
+ border-radius: $es-radius-full;
+ cursor: pointer;
+}
+
+.timeline-fill {
+ position: absolute;
+ top: 0;
+ height: 100%;
+ background: $es-primary;
+ border-radius: $es-radius-full;
+ transition: all $es-transition-fast;
+}
+
+.timeline-handle {
+ position: absolute;
+ top: 50%;
+ width: 16px;
+ height: 16px;
+ background: $es-white;
+ border: 2px solid $es-primary;
+ border-radius: 50%;
+ transform: translate(-50%, -50%);
+ cursor: grab;
+ box-shadow: $es-shadow-sm;
+ transition: box-shadow $es-transition-fast;
+
+ &:hover {
+ box-shadow: $es-shadow-md;
+ }
+
+ &:active {
+ cursor: grabbing;
+ }
+
+ &.handle-start {
+ z-index: 2;
+ }
+
+ &.handle-end {
+ z-index: 1;
+ }
+}
+
+// Time display
+.day-times {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ min-width: 120px;
+ font-size: $es-font-size-xs;
+ font-family: monospace;
+ color: $es-text-secondary;
+}
+
+.time-separator {
+ color: $es-text-muted;
+}
+
+// Holiday exclusions
+.holiday-section {
+ padding: $es-spacing-md;
+ background: $es-slate-50;
+ border-radius: $es-radius-md;
+}
+
+.holiday-toggle {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ margin-bottom: $es-spacing-md;
+}
+
+.holiday-checkbox {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+}
+
+.holiday-label {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-primary;
+}
+
+.holiday-countries {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-xs;
+}
+
+.holiday-country-chip {
+ @include chip;
+ cursor: pointer;
+
+ &.selected {
+ background: $es-primary-light;
+ color: $es-primary;
+ }
+}
+
+// Server time display
+.server-time {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-info-light;
+ border-radius: $es-radius-md;
+ font-size: $es-font-size-xs;
+ color: $es-info;
+
+ i {
+ font-size: $es-font-size-sm;
+ }
+
+ .time-value {
+ font-family: monospace;
+ font-weight: $es-font-weight-semibold;
+ }
+}
+
+// Schedule summary
+.schedule-summary {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-md;
+ background: $es-slate-50;
+ border-radius: $es-radius-md;
+ font-size: $es-font-size-sm;
+ color: $es-text-secondary;
+
+ .summary-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+
+ i {
+ color: $es-success;
+ font-size: $es-font-size-sm;
+ }
+
+ &.inactive i {
+ color: $es-text-muted;
+ }
+ }
+}
+
+// Collapsed state
+.schedule-conditions-trait.collapsed {
+ .schedule-body {
+ display: none;
+ }
+
+ .schedule-header {
+ border-radius: $es-radius-lg;
+ }
+}
+
+// Schedule toggle row (form-content layout)
+.schedule-toggle-row {
+ display: flex;
+ align-items: center;
+ background: $es-slate-100;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-lg;
+
+ .schedule-toggle-switch {
+ padding: $es-spacing-sm $es-spacing-md;
+ }
+
+ .schedule-toggle-actions {
+ padding: $es-spacing-sm $es-spacing-md;
+ border-left: 1px solid $es-border-color;
+ cursor: pointer;
+ transition: background-color $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-200;
+ }
+
+ .material-icons {
+ color: $es-slate-400;
+ font-size: 20px !important;
+ }
+ }
+}
+
+// Schedule summary badges (read-only indicators in header)
+.schedule-summary-badges {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-left: auto;
+ padding: 0 $es-spacing-sm;
+}
+
+.schedule-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.5rem;
+ background: $es-slate-200;
+ color: $es-slate-600;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ border-radius: $es-radius-full;
+ white-space: nowrap;
+
+ .material-icons {
+ font-size: 14px !important;
+ opacity: 0.7;
+ }
+}
+
+// Section hint after embedded entity selector - add margin
+.schedule-holidays .section-hint {
+ margin-top: $es-spacing-md;
+}
diff --git a/sources/sources/scss/components/_tips.scss b/sources/sources/scss/components/_tips.scss
new file mode 100644
index 0000000..8762bd5
--- /dev/null
+++ b/sources/sources/scss/components/_tips.scss
@@ -0,0 +1,142 @@
+/**
+ * Tips Box Component
+ * Pro tips and help information display
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+.target-conditions-trait,
+.entity-selector-trait {
+
+ // Tips box container
+ .target-tips-box {
+ margin: $es-spacing-lg $es-spacing-md $es-spacing-md;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-lg;
+ overflow: hidden;
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+ }
+
+ // Tips header (clickable to expand/collapse)
+ .tips-header {
+ display: flex;
+ align-items: center;
+ gap: 0.625rem;
+ padding: $es-spacing-md $es-spacing-lg;
+ cursor: pointer;
+ user-select: none;
+ transition: background-color $es-transition-fast;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.02);
+ }
+
+ // Lightbulb icon
+ > i:first-child {
+ font-size: 1rem;
+ color: $es-warning;
+ }
+
+ // Title text
+ > span {
+ flex: 1;
+ font-size: 13px;
+ font-weight: $es-font-weight-semibold;
+ color: $es-slate-600;
+ }
+ }
+
+ // Toggle chevron icon
+ .tips-toggle {
+ font-size: $es-font-size-xs;
+ color: $es-slate-400;
+ transition: transform 0.2s;
+ }
+
+ // Expanded state
+ .target-tips-box.expanded {
+ .tips-toggle {
+ transform: rotate(180deg);
+ }
+
+ .tips-content {
+ display: block;
+ }
+ }
+
+ // Tips content (hidden by default)
+ .tips-content {
+ display: none;
+ padding: 0 $es-spacing-lg $es-spacing-lg;
+ }
+
+ // Tips grid layout
+ .tips-grid {
+ display: grid;
+ gap: $es-spacing-md;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ }
+
+ // Individual tip item
+ .tip-item {
+ display: flex;
+ gap: $es-spacing-md;
+ padding: $es-spacing-md;
+ background: $es-white;
+ border-radius: $es-radius-md;
+ border: 1px solid $es-border-color;
+ }
+
+ // Tip icon
+ .tip-icon {
+ flex-shrink: 0;
+ width: 2rem;
+ height: 2rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: $es-primary-light;
+ border-radius: $es-radius-md;
+ color: $es-primary;
+ font-size: $es-font-size-sm;
+ }
+
+ // Tip text content
+ .tip-text {
+ flex: 1;
+ min-width: 0;
+
+ strong {
+ display: block;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-semibold;
+ color: $es-slate-700;
+ margin-bottom: 0.25rem;
+ }
+
+ p {
+ font-size: 11px;
+ color: $es-text-muted;
+ line-height: 1.625;
+ margin: 0;
+ }
+ }
+
+ // Tips footer
+ .tips-footer {
+ margin-top: $es-spacing-md;
+ padding: 0.625rem $es-spacing-md;
+ background: $es-white;
+ border-radius: $es-radius-md;
+ border: 1px dashed $es-gray-300;
+ font-size: 11px;
+ color: $es-text-muted;
+ line-height: 1.625;
+
+ i {
+ color: $es-primary;
+ margin-right: 0.25rem;
+ }
+ }
+}
diff --git a/sources/sources/scss/components/_tooltip.scss b/sources/sources/scss/components/_tooltip.scss
new file mode 100644
index 0000000..750d8b9
--- /dev/null
+++ b/sources/sources/scss/components/_tooltip.scss
@@ -0,0 +1,107 @@
+/**
+ * Tooltip Component
+ * Info tooltips for method help
+ */
+
+@use '../variables' as *;
+
+// =============================================================================
+// Info Wrapper (tooltip trigger)
+// =============================================================================
+
+.mpr-info-wrapper {
+ display: inline-flex;
+ align-items: center;
+ position: relative;
+ cursor: help;
+ vertical-align: middle;
+ margin-left: 0.25rem;
+
+ .material-icons {
+ font-size: 16px !important;
+ color: $es-text-muted;
+ transition: color 0.15s ease;
+ }
+
+ &:hover .material-icons {
+ color: $es-primary;
+ }
+}
+
+// =============================================================================
+// Fixed Tooltip (appended to body on hover)
+// =============================================================================
+
+.mpr-tooltip-fixed {
+ position: fixed;
+ background: $es-white;
+ color: $es-slate-800;
+ padding: $es-spacing-md $es-spacing-lg;
+ border-radius: $es-radius-md;
+ font-size: 13px;
+ line-height: 1.5;
+ white-space: normal;
+ z-index: 10500;
+ max-width: 320px;
+ min-width: 180px;
+ text-align: left;
+ box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 1px 0px,
+ rgba(64, 68, 82, 0.16) 0px 0px 0px 1px,
+ rgba(64, 68, 82, 0.08) 0px 2px 5px 0px;
+ pointer-events: none;
+
+ // Pinned tooltip allows interaction
+ &.pinned {
+ pointer-events: auto;
+ padding-right: $es-spacing-xl + 1rem;
+ }
+
+ strong {
+ display: block;
+ margin-bottom: 0.375rem;
+ font-weight: $es-font-weight-semibold;
+ color: $es-primary;
+ }
+
+ p {
+ margin: 0;
+ color: $es-text-secondary;
+ }
+
+ ul {
+ margin: 0.5rem 0 0;
+ padding-left: 1.25rem;
+
+ li {
+ margin: 0.25rem 0;
+ color: $es-text-secondary;
+ }
+ }
+}
+
+// Close button for pinned tooltips
+.mpr-tooltip-close {
+ position: absolute;
+ top: 0.375rem;
+ right: 0.375rem;
+ padding: 0.125rem;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ border-radius: $es-radius-sm;
+ line-height: 1;
+ transition: background-color 0.15s ease;
+
+ .material-icons {
+ font-size: 16px !important;
+ color: $es-text-muted;
+ }
+
+ &:hover {
+ background: $es-slate-100;
+
+ .material-icons {
+ color: $es-slate-700;
+ }
+ }
+}
diff --git a/sources/sources/scss/components/_tree.scss b/sources/sources/scss/components/_tree.scss
new file mode 100644
index 0000000..d6e9214
--- /dev/null
+++ b/sources/sources/scss/components/_tree.scss
@@ -0,0 +1,343 @@
+/**
+ * Category Tree Component
+ * Hierarchical tree view for category selection inside dropdown
+ */
+
+@use "sass:color";
+@use '../variables' as *;
+@use '../mixins' as *;
+
+// Category tree container (inside dropdown)
+.category-tree {
+ display: flex;
+ flex-direction: column;
+}
+
+// Tree toolbar inside dropdown
+.category-tree .tree-toolbar {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xs $es-spacing-sm;
+ background: $es-slate-50;
+ border-bottom: 1px solid $es-border-light;
+ flex-shrink: 0;
+
+ .btn-expand-all,
+ .btn-collapse-all {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: $es-spacing-xs $es-spacing-sm;
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-medium;
+ color: $es-text-secondary;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-100;
+ border-color: $es-slate-300;
+ }
+
+ i {
+ font-size: 12px;
+ }
+ }
+}
+
+// Tree items container
+.category-tree .tree-items {
+ padding: 0;
+}
+
+// Tree item
+.tree-item {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ padding: $es-spacing-xs $es-spacing-sm;
+ cursor: pointer;
+ transition: background $es-transition-fast;
+ border-radius: 0;
+
+ &:hover {
+ background: $es-slate-100;
+ }
+
+ &.selected {
+ background: $es-primary-light;
+
+ .tree-name {
+ font-weight: $es-font-weight-semibold;
+ color: $es-primary;
+ }
+
+ .tree-checkbox {
+ color: $es-primary;
+
+ i {
+ opacity: 1;
+ }
+ }
+ }
+
+ &.inactive {
+ opacity: 0.6;
+
+ .tree-name {
+ font-style: italic;
+ }
+ }
+
+ &.filtered-out {
+ display: none;
+ }
+
+ &.filter-match {
+ background: $es-warning-light;
+
+ &.selected {
+ background: $es-primary-light;
+ }
+ }
+}
+
+// All tree element styles nested under .category-tree for specificity
+.category-tree {
+ // Tree indentation
+ .tree-indent {
+ flex-shrink: 0;
+ }
+
+ // Tree toggle (expand/collapse)
+ .tree-toggle {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 12px;
+ height: 12px;
+ box-sizing: border-box;
+ color: $es-text-secondary;
+ flex-shrink: 0;
+ border-radius: $es-radius-sm;
+ transition: all $es-transition-fast;
+ cursor: pointer;
+
+ &:hover {
+ background: $es-slate-200;
+ color: $es-text-primary;
+ }
+
+ &.tree-leaf {
+ cursor: default;
+ visibility: hidden;
+
+ &:hover {
+ background: transparent;
+ }
+ }
+
+ i {
+ font-size: 10px;
+ transition: transform $es-transition-fast;
+ }
+ }
+
+ .tree-item.collapsed > .tree-toggle i {
+ transform: rotate(-90deg);
+ }
+
+ // Tree checkbox indicator - 12x12 to match PrestaShop admin standards
+ .tree-checkbox {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 12px;
+ height: 12px;
+ box-sizing: border-box;
+ flex-shrink: 0;
+ border: 1px solid $es-border-color;
+ border-radius: 2px;
+ background: $es-white;
+
+ i {
+ font-size: 8px;
+ opacity: 0;
+ color: $es-white;
+ transition: opacity $es-transition-fast;
+ }
+ }
+
+ .tree-item.selected .tree-checkbox {
+ background: $es-primary;
+ border-color: $es-primary;
+
+ i {
+ opacity: 1;
+ }
+ }
+
+ // Tree icon
+ .tree-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 12px;
+ height: 12px;
+ box-sizing: border-box;
+ color: $es-text-muted;
+ flex-shrink: 0;
+
+ i {
+ font-size: 12px; // match visual weight of other icons
+ }
+ }
+
+ .tree-item.selected .tree-icon {
+ color: $es-primary;
+ }
+
+ // Tree name
+ .tree-name {
+ flex: 1;
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ // Tree product/page count with preview
+ .tree-count {
+ @include count-badge($es-primary);
+ height: 18px;
+ min-width: 18px;
+ padding: 0 $es-spacing-sm;
+
+ i {
+ font-size: 10px;
+ }
+
+ &.clickable {
+ &.loading {
+ pointer-events: none;
+
+ i {
+ animation: spin 1s linear infinite;
+ }
+ }
+
+ &.popover-open {
+ background: color.adjust($es-primary, $lightness: -10%);
+ }
+ }
+ }
+
+ // Select children button - positioned on the left next to toggle
+ .btn-select-children {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 12px;
+ height: 12px;
+ box-sizing: border-box;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ opacity: 0.3;
+ transition: all $es-transition-fast;
+ flex-shrink: 0;
+
+ i {
+ font-size: 14px; // larger to visually match other icons
+ }
+
+ &:hover {
+ color: $es-primary;
+ opacity: 1;
+ }
+ }
+
+ .tree-item:hover .btn-select-children {
+ opacity: 0.6;
+ }
+
+ // Tree badge (inactive, etc.)
+ .tree-badge {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.125rem $es-spacing-xs;
+ font-size: 9px;
+ font-weight: $es-font-weight-semibold;
+ text-transform: uppercase;
+ letter-spacing: 0.025em;
+ border-radius: $es-radius-sm;
+ flex-shrink: 0;
+
+ &.inactive {
+ color: $es-warning;
+ background: $es-warning-light;
+ }
+ }
+
+ // Tree children container
+ .tree-children {
+ display: block;
+
+ &.filter-expanded {
+ display: block !important;
+ }
+ }
+
+ .tree-item.collapsed + .tree-children {
+ display: none;
+ }
+
+ // Filtering - must be inside .category-tree for specificity
+ .tree-item.filtered-out {
+ display: none !important;
+ }
+} // end .category-tree
+
+// Loading/empty/error states
+.category-tree .tree-loading,
+.category-tree .dropdown-empty,
+.category-tree .dropdown-error {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: $es-spacing-xl;
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+
+ i {
+ margin-right: $es-spacing-sm;
+ }
+}
+
+.category-tree .dropdown-error {
+ color: $es-danger;
+}
+
+// Tree view mode in dropdown
+.target-search-dropdown.view-tree {
+ .dropdown-results {
+ padding: 0;
+ }
+
+ .category-tree {
+ max-height: 100%;
+ overflow-y: auto;
+ @include custom-scrollbar;
+ }
+
+ .tree-items {
+ max-height: calc(100% - 40px);
+ overflow-y: auto;
+ @include custom-scrollbar;
+ }
+}
diff --git a/sources/sources/scss/components/_validation.scss b/sources/sources/scss/components/_validation.scss
new file mode 100644
index 0000000..a076fb2
--- /dev/null
+++ b/sources/sources/scss/components/_validation.scss
@@ -0,0 +1,87 @@
+/**
+ * Validation Toast Component
+ * Error notifications for selection conflicts
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+// Validation error toast
+.es-validation-toast {
+ display: flex;
+ align-items: flex-start;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-md;
+ background: $es-white;
+ border: 1px solid $es-danger;
+ border-left: 4px solid $es-danger;
+ border-radius: $es-radius-md;
+ box-shadow: $es-shadow-lg;
+ max-width: 400px;
+ animation: es-toast-slide-in 0.2s ease-out;
+
+ .es-toast-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ color: $es-danger;
+ flex-shrink: 0;
+
+ i {
+ font-size: 18px;
+ }
+ }
+
+ .es-toast-content {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .es-toast-title {
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-semibold;
+ color: $es-danger;
+ margin-bottom: 2px;
+ }
+
+ .es-toast-message {
+ font-size: $es-font-size-xs;
+ color: $es-text-secondary;
+ line-height: 1.4;
+ }
+
+ .es-toast-close {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ color: $es-text-muted;
+ border-radius: $es-radius-sm;
+ flex-shrink: 0;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-slate-100;
+ color: $es-text-primary;
+ }
+
+ i {
+ font-size: 12px;
+ }
+ }
+}
+
+@keyframes es-toast-slide-in {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
diff --git a/sources/sources/scss/components/_value-picker.scss b/sources/sources/scss/components/_value-picker.scss
new file mode 100644
index 0000000..fe94aff
--- /dev/null
+++ b/sources/sources/scss/components/_value-picker.scss
@@ -0,0 +1,281 @@
+/**
+ * Value Picker Component
+ * Search boxes, input types, range inputs
+ */
+
+@use '../variables' as *;
+@use '../mixins' as *;
+
+.target-conditions-trait,
+.entity-selector-trait {
+
+ // Value picker container
+ .value-picker {
+ padding: $es-spacing-sm 0;
+
+ &[style*="display: none"],
+ &[style*="display:none"] {
+ padding: 0;
+ }
+ }
+
+ .include-picker,
+ .exclude-picker {
+ // Section-specific picker styles
+ }
+
+ // Entity search box
+ .entity-search-box {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-sm;
+ padding: $es-spacing-xs;
+ background: $es-white;
+ border: 1px solid $es-border-color;
+ border-radius: $es-radius-md;
+ transition: all $es-transition-fast;
+
+ &:focus-within {
+ border-color: $es-primary;
+ box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
+ }
+ }
+
+ // Separation between chips and search box
+ .chips-wrapper + .entity-search-box {
+ margin-top: $es-spacing-md;
+ }
+
+ .entity-search-icon {
+ color: $es-text-muted;
+ font-size: 14px;
+ flex-shrink: 0;
+ margin-left: $es-spacing-xs;
+ }
+
+ // Override parent form's max-width on search input
+ input.entity-search-input,
+ input.entity-search-input[type="text"] {
+ @include input-reset;
+ flex: 1;
+ min-width: 0;
+ width: auto !important;
+ max-width: none !important;
+ padding: 0.375rem;
+ font-size: $es-font-size-sm;
+ color: $es-text-primary;
+ border: none !important;
+ background: transparent !important;
+ box-shadow: none !important;
+
+ &::placeholder {
+ color: $es-text-muted;
+ }
+
+ &:focus {
+ border: none !important;
+ box-shadow: none !important;
+ outline: none;
+ }
+ }
+
+ .search-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: $es-primary;
+
+ i {
+ animation: spin 0.6s linear infinite;
+ }
+ }
+
+ // Browse tree button (for categories)
+ .btn-browse-tree {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ margin-left: auto;
+ color: $es-primary;
+ background: $es-primary-light;
+ border-radius: $es-radius-sm;
+ flex-shrink: 0;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary;
+ color: $es-white;
+ }
+
+ i {
+ font-size: 14px;
+ }
+ }
+
+ // Numeric range box
+ .numeric-range-box,
+ .multi-range-input-row {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ }
+
+ .range-min-input,
+ .range-max-input {
+ @include input-base;
+ width: 100px;
+ padding: $es-spacing-sm;
+ text-align: center;
+ font-size: $es-font-size-sm;
+
+ &::-webkit-inner-spin-button,
+ &::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ }
+ -moz-appearance: textfield;
+ }
+
+ .range-separator {
+ color: $es-text-muted;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ }
+
+ .btn-add-range {
+ @include button-reset;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ color: $es-white;
+ background: $es-primary;
+ border-radius: $es-radius-md;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ background: $es-primary-hover;
+ }
+
+ i {
+ font-size: 12px;
+ }
+ }
+
+ // Multi-range container
+ .multi-range-container {
+ display: flex;
+ flex-direction: column;
+ gap: $es-spacing-sm;
+ }
+
+ // Date range box
+ .date-range-box {
+ display: flex;
+ align-items: center;
+ gap: $es-spacing-xs;
+ }
+
+ .date-from-input,
+ .date-to-input {
+ @include input-base;
+ width: 140px;
+ padding: $es-spacing-sm;
+ font-size: $es-font-size-sm;
+ }
+
+ // Multi-select tiles
+ .multi-select-tiles {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $es-spacing-xs;
+ }
+
+ .tile-option {
+ @include button-reset;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.375rem 0.75rem;
+ color: $es-text-muted;
+ background: transparent;
+ border: 1px dashed $es-border-color;
+ border-radius: 100px; // Pill shape
+ font-size: $es-font-size-xs;
+ font-weight: $es-font-weight-normal;
+ cursor: pointer;
+ transition: all $es-transition-fast;
+
+ &:hover {
+ color: $es-text-secondary;
+ border-color: $es-slate-400;
+ border-style: solid;
+ }
+
+ &.selected {
+ color: $es-primary;
+ background: $es-primary-light;
+ border: 1px solid $es-primary;
+ font-weight: $es-font-weight-medium;
+ }
+
+ i {
+ font-size: 11px;
+ opacity: 0.6;
+ }
+
+ &.selected i {
+ opacity: 1;
+ }
+ }
+
+ .tile-label {
+ white-space: nowrap;
+ }
+
+ // Select input box
+ .select-input-box {
+ display: inline-block;
+ }
+
+ .select-value-input {
+ @include input-base;
+ padding: $es-spacing-sm $es-spacing-md;
+ font-size: $es-font-size-sm;
+ min-width: 150px;
+ }
+
+ // Boolean input box
+ .boolean-input-box {
+ display: inline-flex;
+ align-items: center;
+ padding: $es-spacing-sm $es-spacing-md;
+ background: $es-success-light;
+ color: $es-success-dark;
+ border-radius: $es-radius-md;
+ font-size: $es-font-size-sm;
+ font-weight: $es-font-weight-medium;
+ }
+
+ .boolean-label {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+
+ &::before {
+ content: '\2713';
+ font-weight: bold;
+ }
+ }
+
+ // Condition match count badge
+ .condition-match-count {
+ @include count-badge($es-primary);
+ margin-left: $es-spacing-sm;
+ }
+}
diff --git a/sources/sources/scss/components/index.php b/sources/sources/scss/components/index.php
new file mode 100644
index 0000000..c4f371f
--- /dev/null
+++ b/sources/sources/scss/components/index.php
@@ -0,0 +1 @@
+ .control-label {
+ display: none;
+ }
+
+ > .col-lg-8 {
+ width: 100%;
+ max-width: 100%;
+ padding-left: $es-spacing-md;
+ padding-right: $es-spacing-md;
+ flex: 0 0 100% !important;
+ }
+}
+
+// Fallback class for browsers without :has() support
+.form-group.condition-trait-fullwidth {
+ display: block;
+
+ > .control-label {
+ display: none;
+ }
+
+ > .col-lg-8 {
+ width: 100%;
+ max-width: 100%;
+ padding-left: $es-spacing-md;
+ padding-right: $es-spacing-md;
+ flex: 0 0 100% !important;
+ }
+}
+
+// SAFEGUARD: Force label visibility for form-group layout widgets
+// This overrides any conflicting rules (including fallback class rules)
+// when the widget has layout-form-group class indicating standard form integration
+.form-group:has(.layout-form-group) > .control-label {
+ display: flex !important;
+}
+
+// Dropdown overflow fix
+// When dropdown is open, parent containers must allow overflow
+.panel:has(.target-search-dropdown.show),
+.card:has(.target-search-dropdown.show),
+.form-wrapper:has(.target-search-dropdown.show),
+.panel-body:has(.target-search-dropdown.show),
+.card-body:has(.target-search-dropdown.show),
+.form-group:has(.target-search-dropdown.show),
+.col-lg-8:has(.target-search-dropdown.show),
+.col-lg-12:has(.target-search-dropdown.show) {
+ overflow: visible !important;
+}
+
+// Target conditions wrapper hierarchy overflow fix
+.target-conditions-trait:has(.target-search-dropdown.show),
+.entity-selector-trait:has(.target-search-dropdown.show),
+.condition-trait-body:has(.target-search-dropdown.show),
+.target-block-content:has(.target-search-dropdown.show),
+.target-block-groups:has(.target-search-dropdown.show),
+.target-group:has(.target-search-dropdown.show),
+.target-group-body:has(.target-search-dropdown.show),
+.target-search-wrapper:has(.target-search-dropdown.show) {
+ overflow: visible !important;
+}
+
+// =============================================================================
+// Embedded Layout
+// =============================================================================
+// Use .layout-embedded for entity selectors nested inside other components
+// Removes outer wrapper styling to avoid redundant borders/backgrounds
+
+.target-conditions-trait.layout-embedded,
+.entity-selector-trait.layout-embedded {
+ background: transparent;
+ border: none;
+ border-radius: 0;
+
+ // Remove padding from groups container when embedded
+ .groups-container {
+ padding: 0;
+ }
+
+ // Remove block body padding
+ .block-body {
+ padding: 0;
+ }
+
+ // Remove block footer border when embedded
+ .block-footer {
+ border-top: none;
+ padding: $es-spacing-sm 0 0;
+ }
+
+ // Simplify selection group when embedded - single thin border only
+ .selection-group {
+ background: $es-white;
+ border: 1px solid $es-slate-200;
+ border-radius: $es-radius-md;
+
+ // Lighter group header in embedded mode
+ .group-header {
+ background: $es-slate-50;
+ border-bottom-color: $es-slate-200;
+ padding: $es-spacing-xs $es-spacing-sm;
+ border-radius: $es-radius-md $es-radius-md 0 0;
+ }
+
+ // Reduce group body padding (slightly more than $es-spacing-sm for readability)
+ .group-body {
+ padding: 0.75rem;
+ }
+
+ // Reduce group-include section padding
+ .group-include {
+ padding: $es-spacing-xs;
+ margin-bottom: $es-spacing-sm;
+ }
+
+ // Smaller modifiers section
+ .group-modifiers {
+ padding: $es-spacing-xs $es-spacing-sm;
+ margin: $es-spacing-sm (-$es-spacing-sm) (-$es-spacing-sm);
+ }
+ }
+
+ // Empty state - smaller padding
+ .groups-empty-state {
+ padding: $es-spacing-md;
+ }
+
+ // Smaller add group button
+ .btn-add-group {
+ padding: 0.375rem 0.625rem;
+ font-size: $es-font-size-xs;
+ }
+}
diff --git a/sources/sources/scss/layouts/_responsive.scss b/sources/sources/scss/layouts/_responsive.scss
new file mode 100644
index 0000000..358933d
--- /dev/null
+++ b/sources/sources/scss/layouts/_responsive.scss
@@ -0,0 +1,63 @@
+/**
+ * Responsive Styles
+ * Media query adjustments for different screen sizes
+ */
+
+@use '../variables' as *;
+
+// Tablet and below
+@media (max-width: 991px) {
+ .target-conditions-trait,
+ .entity-selector-trait {
+ .condition-trait-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: $es-spacing-sm;
+ }
+
+ .trait-header-right {
+ width: 100%;
+ justify-content: flex-end;
+ }
+
+ .target-block-tabs {
+ flex-wrap: wrap;
+ }
+ }
+}
+
+// Mobile
+@media (max-width: 767px) {
+ .target-conditions-trait,
+ .entity-selector-trait {
+ .target-block-tab {
+ padding: $es-spacing-sm;
+ font-size: $es-font-size-xs;
+ }
+
+ .target-group-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .target-search-dropdown {
+ width: 100% !important;
+ left: 0 !important;
+ right: 0 !important;
+ }
+
+ .dropdown-results-grid {
+ grid-template-columns: 1fr !important;
+ }
+ }
+}
+
+// High-resolution displays
+@media (min-width: 1600px) {
+ .target-conditions-trait,
+ .entity-selector-trait {
+ .dropdown-results-grid.view-grid-3 {
+ grid-template-columns: repeat(4, 1fr);
+ }
+ }
+}
diff --git a/sources/sources/scss/layouts/index.php b/sources/sources/scss/layouts/index.php
new file mode 100644
index 0000000..c4f371f
--- /dev/null
+++ b/sources/sources/scss/layouts/index.php
@@ -0,0 +1 @@
+getFileName();
-
- // Go from src/EntitySelector.php up to package root, then to assets/
- $packageDir = dirname(dirname($traitFile));
- $assetsDir = $packageDir . '/assets/';
+ // Derive path from the calling module, not the trait file location.
+ // __TRAIT__ reflection returns wherever PHP's autoloader first loaded the trait from,
+ // which may be a completely different module. $this->module is always correct.
+ $modulePath = $this->module->getLocalPath();
+ $assetsDir = $modulePath . 'vendor/myprestarocks/prestashop-entity-selector/assets/';
// Convert filesystem path to URL path relative to PS root
- // Use realpath() on PS root to handle symlinks consistently
- $psRoot = realpath(_PS_ROOT_DIR_) . '/';
- $assetsReal = realpath($packageDir . '/assets');
- $relativePath = $assetsReal ? str_replace($psRoot, '', $assetsReal . '/') : null;
-
- // If symlink resolution caused the path to escape PS root, fall back to module URI
- if (!$relativePath || strpos($relativePath, '/') === 0 || strpos($relativePath, '..') !== false) {
- // Use module's web-accessible path as base
- $modulePath = $this->module->getPathUri();
- return $modulePath . 'vendor/myprestarocks/prestashop-entity-selector/assets/';
- }
+ $psRoot = _PS_ROOT_DIR_ . '/';
+ $relativePath = str_replace($psRoot, '', $assetsDir);
return __PS_BASE_URI__ . $relativePath;
}
@@ -213,13 +199,16 @@ trait EntitySelector
$assetPath = $this->getEntitySelectorAssetPath();
+ // Cache-busting version — bump when assets change
+ $v = '?v=' . filemtime(dirname(__DIR__) . '/assets/js/admin/entity-selector.js');
+
// Load compiled CSS (SCSS-based, Bootstrap 4 compatible)
- $this->addCSS($assetPath . 'css/admin/entity-selector.css');
+ $this->addCSS($assetPath . 'css/admin/entity-selector.css' . $v);
// Note: modal.js is NOT loaded here - MPRAdminController already provides MPRModal
// If using EntitySelector outside of MPRAdminController context, you may need to load modal separately
- $this->addJS($assetPath . 'js/admin/entity-selector.js');
+ $this->addJS($assetPath . 'js/admin/entity-selector.js' . $v);
$this->entitySelectorAssetsLoaded = true;
}
@@ -287,9 +276,6 @@ trait EntitySelector
case 'previewFilterGroupProducts':
$this->ajaxPreviewFilterGroupProducts();
return true;
- case 'previewFilterValueProducts':
- $this->ajaxPreviewFilterValueProducts();
- return true;
case 'previewCategoryProducts':
$this->ajaxPreviewCategoryProducts();
return true;
@@ -852,114 +838,6 @@ trait EntitySelector
}
}
- /**
- * AJAX: Preview products for a specific attribute/feature value
- * Used by filter chip eye button
- */
- protected function ajaxPreviewFilterValueProducts()
- {
- $valueId = (int) Tools::getValue('value_id');
- $valueType = Tools::getValue('value_type'); // 'attribute' or 'feature'
- $groupId = (int) Tools::getValue('group_id');
- $limit = (int) Tools::getValue('limit', 10);
- $filter = Tools::getValue('filter', '');
-
- if (!$valueId || !in_array($valueType, ['attribute', 'feature'])) {
- die(json_encode([
- 'success' => false,
- 'error' => 'Invalid parameters'
- ]));
- }
-
- $idLang = (int) Context::getContext()->language->id;
- $idShop = (int) Context::getContext()->shop->id;
-
- try {
- $db = Db::getInstance();
-
- if ($valueType === 'attribute') {
- $sql = new DbQuery();
- $sql->select('DISTINCT p.id_product');
- $sql->from('product', 'p');
- $sql->innerJoin('product_shop', 'ps', 'ps.id_product = p.id_product AND ps.id_shop = ' . $idShop);
- $sql->innerJoin('product_attribute', 'pa', 'pa.id_product = p.id_product');
- $sql->innerJoin('product_attribute_combination', 'pac', 'pac.id_product_attribute = pa.id_product_attribute');
- $sql->where('pac.id_attribute = ' . $valueId);
- $sql->where('ps.active = 1');
- } else {
- $sql = new DbQuery();
- $sql->select('DISTINCT p.id_product');
- $sql->from('product', 'p');
- $sql->innerJoin('product_shop', 'ps', 'ps.id_product = p.id_product AND ps.id_shop = ' . $idShop);
- $sql->innerJoin('feature_product', 'fp', 'fp.id_product = p.id_product');
- $sql->where('fp.id_feature_value = ' . $valueId);
- $sql->where('ps.active = 1');
- }
-
- $results = $db->executeS($sql);
- $productIds = array_column($results, 'id_product');
-
- if (!empty($filter) && !empty($productIds)) {
- $productIds = $this->getEntityPreviewHandler()->filterProductIdsByQuery($productIds, $filter, $idLang, $idShop);
- }
-
- $totalCount = count($productIds);
- $previewIds = array_slice($productIds, 0, $limit);
-
- $items = [];
- if (!empty($previewIds)) {
- $productSql = new DbQuery();
- $productSql->select('p.id_product, pl.name, p.reference, i.id_image, m.name as manufacturer');
- $productSql->from('product', 'p');
- $productSql->innerJoin('product_lang', 'pl', 'pl.id_product = p.id_product AND pl.id_lang = ' . $idLang . ' AND pl.id_shop = ' . $idShop);
- $productSql->leftJoin('image', 'i', 'i.id_product = p.id_product AND i.cover = 1');
- $productSql->leftJoin('manufacturer', 'm', 'm.id_manufacturer = p.id_manufacturer');
- $productSql->where('p.id_product IN (' . implode(',', array_map('intval', $previewIds)) . ')');
-
- $products = $db->executeS($productSql);
-
- $productsById = [];
- foreach ($products as $product) {
- $imageUrl = null;
- if ($product['id_image']) {
- $imageUrl = Context::getContext()->link->getImageLink(
- Tools::link_rewrite($product['name']),
- $product['id_product'] . '-' . $product['id_image'],
- 'small_default'
- );
- }
-
- $productsById[(int) $product['id_product']] = [
- 'id' => (int) $product['id_product'],
- 'name' => $product['name'],
- 'reference' => $product['reference'],
- 'manufacturer' => $product['manufacturer'],
- 'image' => $imageUrl
- ];
- }
-
- foreach ($previewIds as $id) {
- if (isset($productsById[(int) $id])) {
- $items[] = $productsById[(int) $id];
- }
- }
- }
-
- die(json_encode([
- 'success' => true,
- 'items' => $items,
- 'count' => $totalCount,
- 'hasMore' => $totalCount > count($items)
- ]));
-
- } catch (\Exception $e) {
- die(json_encode([
- 'success' => false,
- 'error' => $e->getMessage()
- ]));
- }
- }
-
/**
* AJAX: Preview products in a category
* Used by tree view product count click
@@ -1402,8 +1280,6 @@ trait EntitySelector
// Sorting
'sort_by' => $sortBy,
'sort_dir' => $sortDir,
- // Product selection level
- 'product_selection_level' => Tools::getValue('product_selection_level', 'product'),
];
// Delegate to EntitySearchEngine
@@ -1427,7 +1303,6 @@ trait EntitySelector
protected function ajaxGetTargetEntitiesByIds()
{
$entityType = Tools::getValue('entity_type', '');
- $productSelectionLevel = Tools::getValue('product_selection_level', 'product');
$ids = Tools::getValue('ids', '');
if (is_string($ids)) {
$ids = json_decode($ids, true);
@@ -1437,7 +1312,7 @@ trait EntitySelector
}
// Delegate to EntitySearchEngine
- $entities = $this->getEntitySearchEngine()->getByIds($entityType, $ids, $productSelectionLevel);
+ $entities = $this->getEntitySearchEngine()->getByIds($entityType, $ids);
$this->ajaxDie(json_encode([
'success' => true,
@@ -1453,7 +1328,7 @@ trait EntitySelector
protected function ajaxGetTargetEntitiesByIdsBulk()
{
$entitiesParam = Tools::getValue('entities', '');
- $productSelectionLevel = Tools::getValue('product_selection_level', 'product');
+ \PrestaShopLogger::addLog('[EntitySelector] ajaxGetTargetEntitiesByIdsBulk called, raw param: ' . substr($entitiesParam, 0, 500), 1);
if (is_string($entitiesParam)) {
$entitiesParam = json_decode($entitiesParam, true);
@@ -1462,20 +1337,21 @@ trait EntitySelector
$entitiesParam = [];
}
+ \PrestaShopLogger::addLog('[EntitySelector] Parsed entities param: ' . json_encode($entitiesParam), 1);
+
$result = [];
$searchEngine = $this->getEntitySearchEngine();
foreach ($entitiesParam as $entityType => $ids) {
if (!is_array($ids) || empty($ids)) {
+ \PrestaShopLogger::addLog('[EntitySelector] Skipping entityType=' . $entityType . ' - empty or not array', 1);
continue;
}
- // For combination-level products, keep string IDs (c:32, p:17); otherwise intval
- if ($entityType === 'products' && ($productSelectionLevel === 'combination' || $productSelectionLevel === 'both')) {
- $ids = array_unique(array_map('strval', $ids));
- } else {
- $ids = array_unique(array_map('intval', $ids));
- }
- $result[$entityType] = $searchEngine->getByIds($entityType, $ids, $productSelectionLevel);
+ // Deduplicate and sanitize IDs
+ $ids = array_unique(array_map('intval', $ids));
+ \PrestaShopLogger::addLog('[EntitySelector] Fetching entityType=' . $entityType . ' ids=' . implode(',', $ids), 1);
+ $result[$entityType] = $searchEngine->getByIds($entityType, $ids);
+ \PrestaShopLogger::addLog('[EntitySelector] Got ' . count($result[$entityType]) . ' results for ' . $entityType, 1);
}
$this->ajaxDie(json_encode([
@@ -1492,42 +1368,42 @@ trait EntitySelector
return [
'products' => [
'label' => $this->transEntitySelector('Products'),
- 'icon' => 'icon-cube',
+ 'icon' => 'inventory',
'entity_label' => $this->transEntitySelector('product'),
'entity_label_plural' => $this->transEntitySelector('products'),
'selection_methods' => $this->getProductSelectionMethods(),
],
'categories' => [
'label' => $this->transEntitySelector('Categories'),
- 'icon' => 'icon-folder-open',
+ 'icon' => 'folder_open',
'entity_label' => $this->transEntitySelector('category'),
'entity_label_plural' => $this->transEntitySelector('categories'),
'selection_methods' => $this->getCategorySelectionMethods(),
],
'manufacturers' => [
'label' => $this->transEntitySelector('Manufacturers'),
- 'icon' => 'icon-building',
+ 'icon' => 'business',
'entity_label' => $this->transEntitySelector('manufacturer'),
'entity_label_plural' => $this->transEntitySelector('manufacturers'),
'selection_methods' => $this->getManufacturerSelectionMethods(),
],
'suppliers' => [
'label' => $this->transEntitySelector('Suppliers'),
- 'icon' => 'icon-archive',
+ 'icon' => 'inventory_2',
'entity_label' => $this->transEntitySelector('supplier'),
'entity_label_plural' => $this->transEntitySelector('suppliers'),
'selection_methods' => $this->getSupplierSelectionMethods(),
],
'cms' => [
'label' => $this->transEntitySelector('CMS Pages'),
- 'icon' => 'icon-file-text-o',
+ 'icon' => 'description',
'entity_label' => $this->transEntitySelector('CMS page'),
'entity_label_plural' => $this->transEntitySelector('CMS pages'),
'selection_methods' => $this->getCmsSelectionMethods(),
],
'cms_categories' => [
'label' => $this->transEntitySelector('CMS Categories'),
- 'icon' => 'icon-folder-o',
+ 'icon' => 'folder',
'entity_label' => $this->transEntitySelector('CMS category'),
'entity_label_plural' => $this->transEntitySelector('CMS categories'),
'selection_methods' => $this->getCmsCategorySelectionMethods(),
@@ -1535,84 +1411,84 @@ trait EntitySelector
// Transactional / System entities
'employees' => [
'label' => $this->transEntitySelector('Employees'),
- 'icon' => 'icon-briefcase',
+ 'icon' => 'work',
'entity_label' => $this->transEntitySelector('employee'),
'entity_label_plural' => $this->transEntitySelector('employees'),
'selection_methods' => $this->getEmployeeSelectionMethods(),
],
'customers' => [
'label' => $this->transEntitySelector('Customers'),
- 'icon' => 'icon-user',
+ 'icon' => 'person',
'entity_label' => $this->transEntitySelector('customer'),
'entity_label_plural' => $this->transEntitySelector('customers'),
'selection_methods' => $this->getCustomerSelectionMethods(),
],
'customer_groups' => [
'label' => $this->transEntitySelector('Customer Groups'),
- 'icon' => 'icon-users',
+ 'icon' => 'group',
'entity_label' => $this->transEntitySelector('customer group'),
'entity_label_plural' => $this->transEntitySelector('customer groups'),
'selection_methods' => $this->getCustomerGroupSelectionMethods(),
],
'carriers' => [
'label' => $this->transEntitySelector('Carriers'),
- 'icon' => 'icon-truck',
+ 'icon' => 'local_shipping',
'entity_label' => $this->transEntitySelector('carrier'),
'entity_label_plural' => $this->transEntitySelector('carriers'),
'selection_methods' => $this->getCarrierSelectionMethods(),
],
'zones' => [
'label' => $this->transEntitySelector('Zones'),
- 'icon' => 'icon-globe',
+ 'icon' => 'public',
'entity_label' => $this->transEntitySelector('zone'),
'entity_label_plural' => $this->transEntitySelector('zones'),
'selection_methods' => $this->getZoneSelectionMethods(),
],
'countries' => [
'label' => $this->transEntitySelector('Countries'),
- 'icon' => 'icon-flag',
+ 'icon' => 'flag',
'entity_label' => $this->transEntitySelector('country'),
'entity_label_plural' => $this->transEntitySelector('countries'),
'selection_methods' => $this->getCountrySelectionMethods(),
],
'currencies' => [
'label' => $this->transEntitySelector('Currencies'),
- 'icon' => 'icon-money',
+ 'icon' => 'payments',
'entity_label' => $this->transEntitySelector('currency'),
'entity_label_plural' => $this->transEntitySelector('currencies'),
'selection_methods' => $this->getCurrencySelectionMethods(),
],
'languages' => [
'label' => $this->transEntitySelector('Languages'),
- 'icon' => 'icon-language',
+ 'icon' => 'language',
'entity_label' => $this->transEntitySelector('language'),
'entity_label_plural' => $this->transEntitySelector('languages'),
'selection_methods' => $this->getLanguageSelectionMethods(),
],
'shops' => [
'label' => $this->transEntitySelector('Shops'),
- 'icon' => 'icon-shopping-cart',
+ 'icon' => 'shopping_cart',
'entity_label' => $this->transEntitySelector('shop'),
'entity_label_plural' => $this->transEntitySelector('shops'),
'selection_methods' => $this->getShopSelectionMethods(),
],
'profiles' => [
'label' => $this->transEntitySelector('Employee Profiles'),
- 'icon' => 'icon-key',
+ 'icon' => 'key',
'entity_label' => $this->transEntitySelector('profile'),
'entity_label_plural' => $this->transEntitySelector('profiles'),
'selection_methods' => $this->getProfileSelectionMethods(),
],
'order_states' => [
'label' => $this->transEntitySelector('Order States'),
- 'icon' => 'icon-tasks',
+ 'icon' => 'task_alt',
'entity_label' => $this->transEntitySelector('order state'),
'entity_label_plural' => $this->transEntitySelector('order states'),
'selection_methods' => $this->getOrderStateSelectionMethods(),
],
'taxes' => [
'label' => $this->transEntitySelector('Taxes'),
- 'icon' => 'icon-calculator',
+ 'icon' => 'calculate',
'entity_label' => $this->transEntitySelector('tax'),
'entity_label_plural' => $this->transEntitySelector('taxes'),
'selection_methods' => $this->getTaxSelectionMethods(),
@@ -1629,7 +1505,7 @@ trait EntitySelector
// No group - always first
'all' => [
'label' => $this->transEntitySelector('All products'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
@@ -1637,56 +1513,56 @@ trait EntitySelector
// ===== BY ENTITY =====
'specific' => [
'label' => $this->transEntitySelector('Specific products'),
- 'icon' => 'icon-cube',
+ 'icon' => 'inventory',
'value_type' => 'entity_search',
'search_entity' => 'products',
'group' => 'by_entity',
],
'by_category' => [
'label' => $this->transEntitySelector('Category'),
- 'icon' => 'icon-folder-open',
+ 'icon' => 'folder_open',
'value_type' => 'entity_search',
'search_entity' => 'categories',
'group' => 'by_entity',
],
'by_manufacturer' => [
'label' => $this->transEntitySelector('Manufacturer'),
- 'icon' => 'icon-building',
+ 'icon' => 'business',
'value_type' => 'entity_search',
'search_entity' => 'manufacturers',
'group' => 'by_entity',
],
'by_supplier' => [
'label' => $this->transEntitySelector('Supplier'),
- 'icon' => 'icon-truck',
+ 'icon' => 'local_shipping',
'value_type' => 'entity_search',
'search_entity' => 'suppliers',
'group' => 'by_entity',
],
'by_tag' => [
'label' => $this->transEntitySelector('Tag'),
- 'icon' => 'icon-tags',
+ 'icon' => 'sell',
'value_type' => 'entity_search',
'search_entity' => 'tags',
'group' => 'by_entity',
],
'by_attribute' => [
'label' => $this->transEntitySelector('Attribute'),
- 'icon' => 'icon-paint-brush',
+ 'icon' => 'brush',
'value_type' => 'entity_search',
'search_entity' => 'attributes',
'group' => 'by_entity',
],
'by_feature' => [
'label' => $this->transEntitySelector('Feature'),
- 'icon' => 'icon-list-ul',
+ 'icon' => 'format_list_bulleted',
'value_type' => 'entity_search',
'search_entity' => 'features',
'group' => 'by_entity',
],
'by_combination' => [
'label' => $this->transEntitySelector('Combination'),
- 'icon' => 'icon-th',
+ 'icon' => 'grid_view',
'value_type' => 'combination_attributes',
'group' => 'by_entity',
],
@@ -1694,205 +1570,205 @@ trait EntitySelector
// ===== BY PROPERTY =====
'by_condition' => [
'label' => $this->transEntitySelector('Condition'),
- 'icon' => 'icon-certificate',
+ 'icon' => 'verified',
'value_type' => 'multi_select_tiles',
'options' => [
- 'new' => ['label' => $this->transEntitySelector('New'), 'icon' => 'icon-star'],
- 'used' => ['label' => $this->transEntitySelector('Used'), 'icon' => 'icon-recycle'],
- 'refurbished' => ['label' => $this->transEntitySelector('Refurbished'), 'icon' => 'icon-refresh'],
+ 'new' => ['label' => $this->transEntitySelector('New'), 'icon' => 'star'],
+ 'used' => ['label' => $this->transEntitySelector('Used'), 'icon' => 'recycling'],
+ 'refurbished' => ['label' => $this->transEntitySelector('Refurbished'), 'icon' => 'refresh'],
],
'group' => 'by_property',
],
'by_visibility' => [
'label' => $this->transEntitySelector('Visibility'),
- 'icon' => 'icon-eye',
+ 'icon' => 'visibility',
'value_type' => 'multi_select_tiles',
'options' => [
- 'both' => ['label' => $this->transEntitySelector('Everywhere'), 'icon' => 'icon-globe'],
- 'catalog' => ['label' => $this->transEntitySelector('Catalog only'), 'icon' => 'icon-book'],
- 'search' => ['label' => $this->transEntitySelector('Search only'), 'icon' => 'icon-search'],
- 'none' => ['label' => $this->transEntitySelector('Nowhere'), 'icon' => 'icon-eye-slash'],
+ 'both' => ['label' => $this->transEntitySelector('Everywhere'), 'icon' => 'public'],
+ 'catalog' => ['label' => $this->transEntitySelector('Catalog only'), 'icon' => 'menu_book'],
+ 'search' => ['label' => $this->transEntitySelector('Search only'), 'icon' => 'search'],
+ 'none' => ['label' => $this->transEntitySelector('Nowhere'), 'icon' => 'visibility_off'],
],
'group' => 'by_property',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Active status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'multi_select_tiles',
'options' => [
- 'active' => ['label' => $this->transEntitySelector('Active'), 'icon' => 'icon-check'],
- 'inactive' => ['label' => $this->transEntitySelector('Inactive'), 'icon' => 'icon-times'],
+ 'active' => ['label' => $this->transEntitySelector('Active'), 'icon' => 'check'],
+ 'inactive' => ['label' => $this->transEntitySelector('Inactive'), 'icon' => 'close'],
],
'group' => 'by_property',
],
'by_stock_status' => [
'label' => $this->transEntitySelector('Stock status'),
- 'icon' => 'icon-archive',
+ 'icon' => 'inventory_2',
'value_type' => 'multi_select_tiles',
'options' => [
- 'in_stock' => ['label' => $this->transEntitySelector('In stock'), 'icon' => 'icon-check-circle'],
- 'out_of_stock' => ['label' => $this->transEntitySelector('Out of stock'), 'icon' => 'icon-times-circle'],
- 'low_stock' => ['label' => $this->transEntitySelector('Low stock'), 'icon' => 'icon-exclamation-triangle'],
+ 'in_stock' => ['label' => $this->transEntitySelector('In stock'), 'icon' => 'check_circle'],
+ 'out_of_stock' => ['label' => $this->transEntitySelector('Out of stock'), 'icon' => 'cancel'],
+ 'low_stock' => ['label' => $this->transEntitySelector('Low stock'), 'icon' => 'warning'],
],
'group' => 'by_property',
],
'by_on_sale' => [
'label' => $this->transEntitySelector('On sale'),
- 'icon' => 'icon-tag',
+ 'icon' => 'label',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_has_specific_price' => [
'label' => $this->transEntitySelector('Has discount'),
- 'icon' => 'icon-ticket',
+ 'icon' => 'confirmation_number',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_is_virtual' => [
'label' => $this->transEntitySelector('Virtual product'),
- 'icon' => 'icon-cloud-download',
+ 'icon' => 'cloud_download',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_is_pack' => [
'label' => $this->transEntitySelector('Pack product'),
- 'icon' => 'icon-gift',
+ 'icon' => 'redeem',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_has_combinations' => [
'label' => $this->transEntitySelector('Has combinations'),
- 'icon' => 'icon-sitemap',
+ 'icon' => 'account_tree',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_available_for_order' => [
'label' => $this->transEntitySelector('Available for order'),
- 'icon' => 'icon-shopping-cart',
+ 'icon' => 'shopping_cart',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_online_only' => [
'label' => $this->transEntitySelector('Online only'),
- 'icon' => 'icon-laptop',
+ 'icon' => 'laptop',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_has_related' => [
'label' => $this->transEntitySelector('Has related products'),
- 'icon' => 'icon-link',
+ 'icon' => 'link',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_has_customization' => [
'label' => $this->transEntitySelector('Has customization'),
- 'icon' => 'icon-pencil-square-o',
+ 'icon' => 'edit_note',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_has_attachments' => [
'label' => $this->transEntitySelector('Has attachments'),
- 'icon' => 'icon-paperclip',
+ 'icon' => 'attach_file',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_out_of_stock_behavior' => [
'label' => $this->transEntitySelector('Out of stock behavior'),
- 'icon' => 'icon-ban',
+ 'icon' => 'block',
'value_type' => 'multi_select_tiles',
'options' => [
- 'deny' => ['label' => $this->transEntitySelector('Deny orders'), 'icon' => 'icon-times-circle'],
- 'allow' => ['label' => $this->transEntitySelector('Allow orders'), 'icon' => 'icon-check-circle'],
- 'default' => ['label' => $this->transEntitySelector('Use default'), 'icon' => 'icon-cog'],
+ 'deny' => ['label' => $this->transEntitySelector('Deny orders'), 'icon' => 'cancel'],
+ 'allow' => ['label' => $this->transEntitySelector('Allow orders'), 'icon' => 'check_circle'],
+ 'default' => ['label' => $this->transEntitySelector('Use default'), 'icon' => 'settings'],
],
'group' => 'by_property',
],
'by_delivery_time' => [
'label' => $this->transEntitySelector('Delivery time setting'),
- 'icon' => 'icon-clock-o',
+ 'icon' => 'schedule',
'value_type' => 'multi_select_tiles',
'options' => [
- 'none' => ['label' => $this->transEntitySelector('None'), 'icon' => 'icon-times'],
- 'default' => ['label' => $this->transEntitySelector('Default'), 'icon' => 'icon-cog'],
- 'specific' => ['label' => $this->transEntitySelector('Specific'), 'icon' => 'icon-pencil'],
+ 'none' => ['label' => $this->transEntitySelector('None'), 'icon' => 'close'],
+ 'default' => ['label' => $this->transEntitySelector('Default'), 'icon' => 'settings'],
+ 'specific' => ['label' => $this->transEntitySelector('Specific'), 'icon' => 'edit'],
],
'group' => 'by_property',
],
'by_has_additional_shipping' => [
'label' => $this->transEntitySelector('Has additional shipping cost'),
- 'icon' => 'icon-truck',
+ 'icon' => 'local_shipping',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'icon-check', 'color' => 'green'],
- 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'icon-times', 'color' => 'red'],
+ 'yes' => ['label' => $this->transEntitySelector('Yes'), 'icon' => 'check', 'color' => 'green'],
+ 'no' => ['label' => $this->transEntitySelector('No'), 'icon' => 'close', 'color' => 'red'],
],
'group' => 'by_property',
],
'by_carrier_restriction' => [
'label' => $this->transEntitySelector('Carrier restriction'),
- 'icon' => 'icon-truck',
+ 'icon' => 'local_shipping',
'value_type' => 'multi_select_tiles',
'exclusive' => true,
'options' => [
- 'restricted' => ['label' => $this->transEntitySelector('Has restriction'), 'icon' => 'icon-lock', 'color' => 'red'],
- 'all' => ['label' => $this->transEntitySelector('All carriers'), 'icon' => 'icon-unlock', 'color' => 'green'],
+ 'restricted' => ['label' => $this->transEntitySelector('Has restriction'), 'icon' => 'lock', 'color' => 'red'],
+ 'all' => ['label' => $this->transEntitySelector('All carriers'), 'icon' => 'lock_open', 'color' => 'green'],
],
'group' => 'by_property',
],
'by_carrier' => [
'label' => $this->transEntitySelector('Specific carrier'),
- 'icon' => 'icon-truck',
+ 'icon' => 'local_shipping',
'value_type' => 'entity_search',
'search_entity' => 'carriers',
'group' => 'by_entity',
@@ -1901,61 +1777,61 @@ trait EntitySelector
// ===== BY TEXT =====
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'by_text',
],
'by_reference_pattern' => [
'label' => $this->transEntitySelector('Reference contains'),
- 'icon' => 'icon-barcode',
+ 'icon' => 'qr_code',
'value_type' => 'pattern',
'group' => 'by_text',
],
'by_description_pattern' => [
'label' => $this->transEntitySelector('Description contains'),
- 'icon' => 'icon-align-left',
+ 'icon' => 'format_align_left',
'value_type' => 'pattern',
'group' => 'by_text',
],
'by_long_description_pattern' => [
'label' => $this->transEntitySelector('Long description contains'),
- 'icon' => 'icon-file-text-o',
+ 'icon' => 'description',
'value_type' => 'pattern',
'group' => 'by_text',
],
'by_ean13_pattern' => [
'label' => $this->transEntitySelector('EAN-13 contains'),
- 'icon' => 'icon-barcode',
+ 'icon' => 'qr_code',
'value_type' => 'pattern',
'group' => 'by_text',
],
'by_upc_pattern' => [
'label' => $this->transEntitySelector('UPC contains'),
- 'icon' => 'icon-barcode',
+ 'icon' => 'qr_code',
'value_type' => 'pattern',
'group' => 'by_text',
],
'by_isbn_pattern' => [
'label' => $this->transEntitySelector('ISBN contains'),
- 'icon' => 'icon-book',
+ 'icon' => 'menu_book',
'value_type' => 'pattern',
'group' => 'by_text',
],
'by_mpn_pattern' => [
'label' => $this->transEntitySelector('MPN contains'),
- 'icon' => 'icon-cog',
+ 'icon' => 'settings',
'value_type' => 'pattern',
'group' => 'by_text',
],
'by_meta_title_pattern' => [
'label' => $this->transEntitySelector('Meta title contains'),
- 'icon' => 'icon-header',
+ 'icon' => 'title',
'value_type' => 'pattern',
'group' => 'by_text',
],
'by_meta_description_pattern' => [
'label' => $this->transEntitySelector('Meta description contains'),
- 'icon' => 'icon-file-text-o',
+ 'icon' => 'description',
'value_type' => 'pattern',
'group' => 'by_text',
],
@@ -1963,7 +1839,7 @@ trait EntitySelector
// ===== BY RANGE =====
'by_id_range' => [
'label' => $this->transEntitySelector('ID range'),
- 'icon' => 'icon-list-ol',
+ 'icon' => 'format_list_numbered',
'value_type' => 'multi_numeric_range',
'step' => 1,
'min' => 1,
@@ -1971,7 +1847,7 @@ trait EntitySelector
],
'by_price_range' => [
'label' => $this->transEntitySelector('Price range'),
- 'icon' => 'icon-money',
+ 'icon' => 'payments',
'value_type' => 'multi_numeric_range',
'step' => 0.01,
'min' => 0,
@@ -1979,7 +1855,7 @@ trait EntitySelector
],
'by_weight_range' => [
'label' => $this->transEntitySelector('Weight range'),
- 'icon' => 'icon-dashboard',
+ 'icon' => 'dashboard',
'value_type' => 'multi_numeric_range',
'step' => 0.001,
'min' => 0,
@@ -1987,14 +1863,14 @@ trait EntitySelector
],
'by_quantity_range' => [
'label' => $this->transEntitySelector('Quantity range'),
- 'icon' => 'icon-cubes',
+ 'icon' => 'view_in_ar',
'value_type' => 'multi_numeric_range',
'step' => 1,
'group' => 'by_range',
],
'by_position_range' => [
'label' => $this->transEntitySelector('Position range'),
- 'icon' => 'icon-sort-numeric-asc',
+ 'icon' => 'sort',
'value_type' => 'multi_numeric_range',
'step' => 1,
'min' => 0,
@@ -2002,13 +1878,13 @@ trait EntitySelector
],
'by_date_added' => [
'label' => $this->transEntitySelector('Date added'),
- 'icon' => 'icon-calendar',
+ 'icon' => 'calendar_today',
'value_type' => 'date_range',
'group' => 'by_range',
],
'by_date_updated' => [
'label' => $this->transEntitySelector('Date modified'),
- 'icon' => 'icon-calendar-o',
+ 'icon' => 'event',
'value_type' => 'date_range',
'group' => 'by_range',
],
@@ -2036,38 +1912,38 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All categories'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific categories'),
- 'icon' => 'icon-folder-open',
+ 'icon' => 'folder_open',
'value_type' => 'entity_search',
'search_entity' => 'categories',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_product_count' => [
'label' => $this->transEntitySelector('Product count'),
- 'icon' => 'icon-cubes',
+ 'icon' => 'view_in_ar',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
'by_depth_level' => [
'label' => $this->transEntitySelector('Depth level'),
- 'icon' => 'icon-sitemap',
+ 'icon' => 'account_tree',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2086,32 +1962,32 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All manufacturers'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific manufacturers'),
- 'icon' => 'icon-building',
+ 'icon' => 'business',
'value_type' => 'entity_search',
'search_entity' => 'manufacturers',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_product_count' => [
'label' => $this->transEntitySelector('Product count'),
- 'icon' => 'icon-cubes',
+ 'icon' => 'view_in_ar',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2130,32 +2006,32 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All suppliers'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific suppliers'),
- 'icon' => 'icon-truck',
+ 'icon' => 'local_shipping',
'value_type' => 'entity_search',
'search_entity' => 'suppliers',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_product_count' => [
'label' => $this->transEntitySelector('Product count'),
- 'icon' => 'icon-cubes',
+ 'icon' => 'view_in_ar',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2174,33 +2050,33 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All CMS pages'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific CMS pages'),
- 'icon' => 'icon-file-text-o',
+ 'icon' => 'description',
'value_type' => 'entity_search',
'search_entity' => 'cms',
'group' => 'select_by',
],
'by_cms_category' => [
'label' => $this->transEntitySelector('CMS pages in category'),
- 'icon' => 'icon-folder-o',
+ 'icon' => 'folder',
'value_type' => 'entity_search',
'search_entity' => 'cms_categories',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Title contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2210,7 +2086,7 @@ trait EntitySelector
],
'by_indexable' => [
'label' => $this->transEntitySelector('Indexable'),
- 'icon' => 'icon-search',
+ 'icon' => 'search',
'value_type' => 'select',
'options' => [
'yes' => $this->transEntitySelector('Yes'),
@@ -2229,26 +2105,26 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All CMS categories'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific CMS categories'),
- 'icon' => 'icon-folder-o',
+ 'icon' => 'folder',
'value_type' => 'entity_search',
'search_entity' => 'cms_categories',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2258,7 +2134,7 @@ trait EntitySelector
],
'by_page_count' => [
'label' => $this->transEntitySelector('Page count'),
- 'icon' => 'icon-file-text-o',
+ 'icon' => 'description',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
@@ -2273,33 +2149,33 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All employees'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific employees'),
- 'icon' => 'icon-user-secret',
+ 'icon' => 'admin_panel_settings',
'value_type' => 'entity_search',
'search_entity' => 'employees',
'group' => 'select_by',
],
'by_profile' => [
'label' => $this->transEntitySelector('By profile'),
- 'icon' => 'icon-key',
+ 'icon' => 'key',
'value_type' => 'entity_search',
'search_entity' => 'profiles',
'group' => 'filter_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2318,39 +2194,39 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All customers'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific customers'),
- 'icon' => 'icon-user',
+ 'icon' => 'person',
'value_type' => 'entity_search',
'search_entity' => 'customers',
'group' => 'select_by',
],
'by_group' => [
'label' => $this->transEntitySelector('By customer group'),
- 'icon' => 'icon-group',
+ 'icon' => 'group',
'value_type' => 'entity_search',
'search_entity' => 'customer_groups',
'group' => 'filter_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-user',
+ 'icon' => 'person',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_email_pattern' => [
'label' => $this->transEntitySelector('Email contains'),
- 'icon' => 'icon-at',
+ 'icon' => 'alternate_email',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_company' => [
'label' => $this->transEntitySelector('Has company'),
- 'icon' => 'icon-building',
+ 'icon' => 'business',
'value_type' => 'select',
'options' => [
'yes' => $this->transEntitySelector('Has company'),
@@ -2360,31 +2236,31 @@ trait EntitySelector
],
'by_company_pattern' => [
'label' => $this->transEntitySelector('Company name contains'),
- 'icon' => 'icon-building',
+ 'icon' => 'business',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_address_count' => [
'label' => $this->transEntitySelector('Number of addresses'),
- 'icon' => 'icon-map-marker',
+ 'icon' => 'location_on',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
'by_order_count' => [
'label' => $this->transEntitySelector('Number of orders'),
- 'icon' => 'icon-shopping-cart',
+ 'icon' => 'shopping_cart',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
'by_turnover' => [
'label' => $this->transEntitySelector('Total spent'),
- 'icon' => 'icon-money',
+ 'icon' => 'payments',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2394,7 +2270,7 @@ trait EntitySelector
],
'by_newsletter' => [
'label' => $this->transEntitySelector('Newsletter'),
- 'icon' => 'icon-envelope',
+ 'icon' => 'email',
'value_type' => 'select',
'options' => [
'subscribed' => $this->transEntitySelector('Subscribed'),
@@ -2404,7 +2280,7 @@ trait EntitySelector
],
'by_guest' => [
'label' => $this->transEntitySelector('Account type'),
- 'icon' => 'icon-user-times',
+ 'icon' => 'person_off',
'value_type' => 'select',
'options' => [
'guest' => $this->transEntitySelector('Guest'),
@@ -2423,26 +2299,26 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All customer groups'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific groups'),
- 'icon' => 'icon-group',
+ 'icon' => 'group',
'value_type' => 'entity_search',
'search_entity' => 'customer_groups',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_price_display' => [
'label' => $this->transEntitySelector('Price display'),
- 'icon' => 'icon-money',
+ 'icon' => 'payments',
'value_type' => 'select',
'options' => [
'tax_excl' => $this->transEntitySelector('Tax excluded'),
@@ -2461,26 +2337,26 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All carriers'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific carriers'),
- 'icon' => 'icon-truck',
+ 'icon' => 'local_shipping',
'value_type' => 'entity_search',
'search_entity' => 'carriers',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2490,7 +2366,7 @@ trait EntitySelector
],
'by_shipping_handling' => [
'label' => $this->transEntitySelector('Shipping handling'),
- 'icon' => 'icon-cog',
+ 'icon' => 'settings',
'value_type' => 'select',
'options' => [
'with_handling' => $this->transEntitySelector('With handling'),
@@ -2500,7 +2376,7 @@ trait EntitySelector
],
'by_free_shipping' => [
'label' => $this->transEntitySelector('Free shipping'),
- 'icon' => 'icon-gift',
+ 'icon' => 'redeem',
'value_type' => 'select',
'options' => [
'free' => $this->transEntitySelector('Free shipping'),
@@ -2510,27 +2386,27 @@ trait EntitySelector
],
'by_zone' => [
'label' => $this->transEntitySelector('By zone'),
- 'icon' => 'icon-globe',
+ 'icon' => 'public',
'value_type' => 'entity_search',
'search_entity' => 'zones',
'group' => 'filter_by',
],
'by_customer_group' => [
'label' => $this->transEntitySelector('By customer group'),
- 'icon' => 'icon-group',
+ 'icon' => 'group',
'value_type' => 'entity_search',
'search_entity' => 'customer_groups',
'group' => 'filter_by',
],
'by_price_range' => [
'label' => $this->transEntitySelector('Shipping price range'),
- 'icon' => 'icon-money',
+ 'icon' => 'payments',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
'by_weight_range' => [
'label' => $this->transEntitySelector('Max weight'),
- 'icon' => 'icon-dashboard',
+ 'icon' => 'dashboard',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
@@ -2545,26 +2421,26 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All zones'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific zones'),
- 'icon' => 'icon-globe',
+ 'icon' => 'public',
'value_type' => 'entity_search',
'search_entity' => 'zones',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2583,33 +2459,33 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All countries'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific countries'),
- 'icon' => 'icon-flag',
+ 'icon' => 'flag',
'value_type' => 'entity_search',
'search_entity' => 'countries',
'group' => 'select_by',
],
'by_zone' => [
'label' => $this->transEntitySelector('By zone'),
- 'icon' => 'icon-globe',
+ 'icon' => 'public',
'value_type' => 'entity_search',
'search_entity' => 'zones',
'group' => 'filter_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2619,7 +2495,7 @@ trait EntitySelector
],
'by_contains_states' => [
'label' => $this->transEntitySelector('Has states'),
- 'icon' => 'icon-map-marker',
+ 'icon' => 'location_on',
'value_type' => 'select',
'options' => [
'yes' => $this->transEntitySelector('Yes'),
@@ -2629,7 +2505,7 @@ trait EntitySelector
],
'by_need_zip_code' => [
'label' => $this->transEntitySelector('Requires ZIP code'),
- 'icon' => 'icon-envelope',
+ 'icon' => 'email',
'value_type' => 'select',
'options' => [
'yes' => $this->transEntitySelector('Yes'),
@@ -2639,13 +2515,13 @@ trait EntitySelector
],
'by_zip_format' => [
'label' => $this->transEntitySelector('ZIP format contains'),
- 'icon' => 'icon-barcode',
+ 'icon' => 'qr_code',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_need_identification' => [
'label' => $this->transEntitySelector('Requires ID number'),
- 'icon' => 'icon-credit-card',
+ 'icon' => 'credit_card',
'value_type' => 'select',
'options' => [
'yes' => $this->transEntitySelector('Yes'),
@@ -2664,26 +2540,26 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All currencies'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific currencies'),
- 'icon' => 'icon-money',
+ 'icon' => 'payments',
'value_type' => 'entity_search',
'search_entity' => 'currencies',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name/code contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2702,26 +2578,26 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All languages'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific languages'),
- 'icon' => 'icon-language',
+ 'icon' => 'language',
'value_type' => 'entity_search',
'search_entity' => 'languages',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name/code contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2731,7 +2607,7 @@ trait EntitySelector
],
'by_rtl' => [
'label' => $this->transEntitySelector('Text direction'),
- 'icon' => 'icon-align-left',
+ 'icon' => 'format_align_left',
'value_type' => 'select',
'options' => [
'ltr' => $this->transEntitySelector('LTR (Left-to-right)'),
@@ -2750,26 +2626,26 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All shops'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific shops'),
- 'icon' => 'icon-shopping-cart',
+ 'icon' => 'shopping_cart',
'value_type' => 'entity_search',
'search_entity' => 'shops',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
@@ -2788,20 +2664,20 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All profiles'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific profiles'),
- 'icon' => 'icon-key',
+ 'icon' => 'key',
'value_type' => 'entity_search',
'search_entity' => 'profiles',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
@@ -2816,26 +2692,26 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All order states'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific states'),
- 'icon' => 'icon-tasks',
+ 'icon' => 'task_alt',
'value_type' => 'entity_search',
'search_entity' => 'order_states',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_paid' => [
'label' => $this->transEntitySelector('Payment status'),
- 'icon' => 'icon-money',
+ 'icon' => 'payments',
'value_type' => 'select',
'options' => [
'paid' => $this->transEntitySelector('Paid'),
@@ -2845,7 +2721,7 @@ trait EntitySelector
],
'by_shipped' => [
'label' => $this->transEntitySelector('Shipping status'),
- 'icon' => 'icon-truck',
+ 'icon' => 'local_shipping',
'value_type' => 'select',
'options' => [
'shipped' => $this->transEntitySelector('Shipped'),
@@ -2855,7 +2731,7 @@ trait EntitySelector
],
'by_delivery' => [
'label' => $this->transEntitySelector('Delivery status'),
- 'icon' => 'icon-check-circle',
+ 'icon' => 'check_circle',
'value_type' => 'select',
'options' => [
'delivered' => $this->transEntitySelector('Delivered'),
@@ -2874,32 +2750,32 @@ trait EntitySelector
return [
'all' => [
'label' => $this->transEntitySelector('All taxes'),
- 'icon' => 'icon-asterisk',
+ 'icon' => 'star',
'value_type' => 'none',
'group' => '',
],
'specific' => [
'label' => $this->transEntitySelector('Specific taxes'),
- 'icon' => 'icon-money',
+ 'icon' => 'payments',
'value_type' => 'entity_search',
'search_entity' => 'taxes',
'group' => 'select_by',
],
'by_name_pattern' => [
'label' => $this->transEntitySelector('Name contains'),
- 'icon' => 'icon-font',
+ 'icon' => 'text_fields',
'value_type' => 'pattern',
'group' => 'filter_by',
],
'by_rate_range' => [
'label' => $this->transEntitySelector('Rate range'),
- 'icon' => 'icon-calculator',
+ 'icon' => 'calculate',
'value_type' => 'numeric_range',
'group' => 'filter_by',
],
'by_active_status' => [
'label' => $this->transEntitySelector('Status'),
- 'icon' => 'icon-toggle-on',
+ 'icon' => 'toggle_on',
'value_type' => 'select',
'options' => [
'active' => $this->transEntitySelector('Active'),
diff --git a/src/EntitySelector/EntitySelectorRenderer.php b/src/EntitySelector/EntitySelectorRenderer.php
index 1408f32..c4a2b95 100644
--- a/src/EntitySelector/EntitySelectorRenderer.php
+++ b/src/EntitySelector/EntitySelectorRenderer.php
@@ -53,6 +53,210 @@ class EntitySelectorRenderer
$this->translator = $translator;
}
+ // ---------------------------------------------------------------
+ // Icon framework abstraction (Material Icons vs FontAwesome 4)
+ // ---------------------------------------------------------------
+
+ /**
+ * Material Icons → FontAwesome 4 class mapping
+ */
+ private static $fa4Map = [
+ 'account_tree' => 'icon-sitemap',
+ 'add' => 'icon-plus',
+ 'add_box' => 'icon-plus-square',
+ 'arrow_downward' => 'icon-sort-desc',
+ 'arrow_drop_down' => 'icon-caret-down',
+ 'arrow_right' => 'icon-chevron-right',
+ 'arrow_upward' => 'icon-sort-asc',
+ 'block' => 'icon-ban',
+ 'brush' => 'icon-paint-brush',
+ 'business' => 'icon-building',
+ 'check' => 'icon-check',
+ 'check_box' => 'icon-check-square',
+ 'check_box_outline_blank' => 'icon-square-o',
+ 'check_circle' => 'icon-check-circle',
+ 'close' => 'icon-times',
+ 'delete' => 'icon-trash',
+ 'description' => 'icon-file-text',
+ 'error' => 'icon-exclamation-circle',
+ 'event' => 'icon-calendar',
+ 'event_busy' => 'icon-calendar-times-o',
+ 'expand_less' => 'icon-chevron-up',
+ 'expand_more' => 'icon-chevron-down',
+ 'filter_list' => 'icon-filter',
+ 'flag' => 'icon-flag',
+ 'folder' => 'icon-folder',
+ 'folder_open' => 'icon-folder-open',
+ 'indeterminate_check_box' => 'icon-minus-square',
+ 'info' => 'icon-info-circle',
+ 'inventory_2' => 'icon-archive',
+ 'label' => 'icon-tag',
+ 'language' => 'icon-globe',
+ 'lightbulb' => 'icon-lightbulb-o',
+ 'list' => 'icon-list',
+ 'list_alt' => 'icon-list-alt',
+ 'local_shipping' => 'icon-truck',
+ 'lock' => 'icon-lock',
+ 'my_location' => 'icon-crosshairs',
+ 'open_in_full' => 'icon-expand',
+ 'payments' => 'icon-credit-card',
+ 'progress_activity' => 'icon-circle-o-notch',
+ 'schedule' => 'icon-clock-o',
+ 'search' => 'icon-search',
+ 'shopping_cart' => 'icon-shopping-cart',
+ 'sort' => 'icon-sort',
+ 'sort_by_alpha' => 'icon-sort-alpha-asc',
+ 'star' => 'icon-star',
+ 'sync' => 'icon-refresh',
+ 'tune' => 'icon-sliders',
+ 'visibility' => 'icon-eye',
+ 'warning' => 'icon-warning',
+ 'widgets' => 'icon-th-large',
+ ];
+
+ /**
+ * FontAwesome 4 class → Material Icons reverse mapping.
+ * Built once from $fa4Map on first use.
+ * @var array|null
+ */
+ private static $reverseFa4Map = null;
+
+ /**
+ * Extra FA4→Material mappings for icon names used in block configs
+ * that don't appear in the standard fa4Map (e.g. icon-cube, icon-folder-o).
+ */
+ private static $extraReverseMappings = [
+ 'icon-cube' => 'inventory',
+ 'icon-folder-o' => 'folder',
+ 'icon-file-text-o' => 'description',
+ 'icon-briefcase' => 'work',
+ 'icon-user' => 'person',
+ 'icon-users' => 'group',
+ 'icon-money' => 'payments',
+ 'icon-tasks' => 'checklist',
+ 'icon-calculator' => 'calculate',
+ 'icon-asterisk' => 'star',
+ 'icon-bar-chart' => 'bar_chart',
+ 'icon-cogs' => 'settings',
+ 'icon-cog' => 'settings',
+ 'icon-tags' => 'label',
+ 'icon-list-ul' => 'list',
+ 'icon-th' => 'grid_view',
+ 'icon-certificate' => 'verified',
+ 'icon-power-off' => 'power_settings_new',
+ 'icon-circle-o' => 'radio_button_unchecked',
+ ];
+
+ /**
+ * Get the reverse FA4→Material mapping (built lazily from $fa4Map + extras).
+ *
+ * @return array
+ */
+ private static function getReverseFa4Map()
+ {
+ if (self::$reverseFa4Map === null) {
+ self::$reverseFa4Map = array_flip(self::$fa4Map);
+ // Merge extras (extras take priority for icons not in the flipped map)
+ foreach (self::$extraReverseMappings as $fa4Class => $materialName) {
+ if (!isset(self::$reverseFa4Map[$fa4Class])) {
+ self::$reverseFa4Map[$fa4Class] = $materialName;
+ }
+ }
+ }
+ return self::$reverseFa4Map;
+ }
+
+ /**
+ * Normalize an icon name to the canonical format for the current mode.
+ * Handles both Material Icons names and FA4 class names as input.
+ *
+ * @param string $name Icon name (Material or FA4 format)
+ * @return array ['name' => string, 'extra' => string] normalized name + any extra classes
+ */
+ protected function normalizeIconName($name)
+ {
+ $extra = '';
+
+ // If name starts with 'icon-', it's an FA4 class name
+ if (strpos($name, 'icon-') === 0) {
+ // Extract extra CSS classes (e.g. "icon-power-off text-success" → "icon-power-off" + "text-success")
+ $parts = explode(' ', $name, 2);
+ $fa4Class = $parts[0];
+ if (isset($parts[1])) {
+ $extra = $parts[1];
+ }
+
+ if ($this->getIconMode() === 'material') {
+ // Reverse map FA4→Material
+ $reverseMap = self::getReverseFa4Map();
+ $materialName = $reverseMap[$fa4Class] ?? null;
+ if ($materialName) {
+ return ['name' => $materialName, 'extra' => $extra];
+ }
+ // Last resort: strip 'icon-' prefix and convert hyphens to underscores
+ $fallback = str_replace('-', '_', substr($fa4Class, 5));
+ return ['name' => $fallback, 'extra' => $extra];
+ }
+
+ // Already FA4 and mode is FA4 — use as-is
+ return ['name' => $fa4Class, 'extra' => $extra, 'raw_fa4' => true];
+ }
+
+ // Material Icons name — use as-is for material mode, map for FA4 mode
+ return ['name' => $name, 'extra' => $extra];
+ }
+
+ /**
+ * Detect icon mode based on PrestaShop version.
+ * PS 8+ / 9+ → material, PS 1.6 / 1.7 → fa4.
+ *
+ * @return string 'material' or 'fa4'
+ */
+ protected function getIconMode()
+ {
+ return version_compare(_PS_VERSION_, '8.0.0', '>=') ? 'material' : 'fa4';
+ }
+
+ /**
+ * Render an icon element that works on both legacy and modern PS.
+ * Accepts both Material Icons names and FA4 class names as input.
+ *
+ * @param string $name Icon name (Material or FA4 format, e.g. 'shopping_cart' or 'icon-cube')
+ * @param string $extraClass Additional CSS class(es)
+ * @return string HTML
+ */
+ protected function renderIcon($name, $extraClass = '')
+ {
+ $normalized = $this->normalizeIconName($name);
+ $iconName = $normalized['name'];
+ // Merge extra classes from normalization (e.g. "text-success" from "icon-power-off text-success")
+ if (!empty($normalized['extra'])) {
+ $extraClass = $extraClass ? $extraClass . ' ' . $normalized['extra'] : $normalized['extra'];
+ }
+
+ if ($this->getIconMode() === 'material') {
+ $cls = 'material-icons es-icon';
+ if ($extraClass) {
+ $cls .= ' ' . $extraClass;
+ }
+ return '
' . htmlspecialchars($iconName, ENT_QUOTES, 'UTF-8') . '';
+ }
+
+ // FA4 mode
+ if (!empty($normalized['raw_fa4'])) {
+ // Input was already an FA4 class name — use directly
+ $cls = $iconName . ' es-icon';
+ } else {
+ // Input was a Material name — map to FA4
+ $mapped = self::$fa4Map[$iconName] ?? 'icon-circle';
+ $cls = $mapped . ' es-icon';
+ }
+ if ($extraClass) {
+ $cls .= ' ' . $extraClass;
+ }
+ return '
';
+ }
+
/**
* Set block definitions
*
@@ -121,7 +325,6 @@ class EntitySelectorRenderer
'show_cms' => true,
'show_cms_categories' => true,
'combination_mode' => 'products',
- 'product_selection_level' => 'product',
'mode' => 'multi',
'blocks' => [],
'customBlocks' => [],
@@ -140,9 +343,6 @@ class EntitySelectorRenderer
if (is_string($savedData)) {
$savedData = json_decode($savedData, true) ?: [];
}
- if (!is_array($savedData)) {
- $savedData = [];
- }
// Determine which block is active
$enabledBlocks = [];
@@ -166,7 +366,7 @@ class EntitySelectorRenderer
'label' => $blockDef['label'] ?? $blockType,
'entity_label' => $blockDef['label'] ?? $blockType,
'entity_label_plural' => $blockDef['label'] ?? $blockType,
- 'icon' => $blockDef['icon'] ?? 'icon-cog',
+ 'icon' => $blockDef['icon'] ?? 'settings',
'search_entity' => $blockType,
'selection_methods' => [],
], $blockDef);
@@ -217,6 +417,7 @@ class EntitySelectorRenderer
$html = '
escapeAttr($config['id']) . '"';
$html .= ' data-mode="' . $this->escapeAttr($globalMode) . '"';
+ $html .= ' data-icon-mode="' . $this->getIconMode() . '"';
if (!empty($config['required'])) {
$html .= ' data-required="1"';
$requiredMsg = !empty($config['required_message'])
@@ -246,6 +447,7 @@ class EntitySelectorRenderer
$html .= '';
$html .= '
'; // End condition-trait-body
+
$html .= '
'; // End target-conditions-trait
return $html;
@@ -284,6 +486,7 @@ class EntitySelectorRenderer
$html .= '