diff --git a/src/implementations/twig/components/select/__snapshots__/select.test.js.snap b/src/implementations/twig/components/select/__snapshots__/select.test.js.snap index 9db85bf8a5b..79f89ffa37a 100644 --- a/src/implementations/twig/components/select/__snapshots__/select.test.js.snap +++ b/src/implementations/twig/components/select/__snapshots__/select.test.js.snap @@ -28,57 +28,75 @@ exports[`Select Default renders correctly 1`] = ` class="ecl-select" id="select-default" > - - - - - - - - - - + + + + + + + + + + + + + + +
- - - - - - - - - - + + + + + + + + + + + + + + +
- - - - - - - - - - + + + + + + + + + + + + + + +
- - - - - - - - - - + + + + + + + + + + + + + + +
- - - - - - - - - - + + + + + + + + + + + + + + +
- - - - - - - - - - + + + + + + + + + + + + + + +
- - - - - - - - - - + + + + + + + + + + + + + + +
{% for _option in _options %} - + {% endfor %} + + {% else %} + {% endif %} - {% if _option.disabled %} - disabled - {% endif %} - > - {{- _option.label -}} - {% endfor %} diff --git a/src/implementations/vanilla/components/select/_select.scss b/src/implementations/vanilla/components/select/_select.scss index 6a0cf07db0a..946c371f907 100644 --- a/src/implementations/vanilla/components/select/_select.scss +++ b/src/implementations/vanilla/components/select/_select.scss @@ -18,6 +18,9 @@ $_border-width: 1px; $_border-width-focus: null !default; $_color-input: null !default; $_color-placeholder: null !default; +$_group-separator-color: null !default; +$_group-title-font: null !default; +$_group-title-color: null !default; $_shadow: null !default; $_shadow-hover: null !default; $_arrow-background-invalid-focus: null !default; @@ -250,6 +253,7 @@ $_multiple_counter_background: null !default; background-color: $_multiple-dropdown-background-color; border: $_multiple-dropdown-border; border-radius: $_multiple-dropdown-border-radius; + cursor: default; box-shadow: $_multiple-dropdown-shadow; box-sizing: border-box; position: absolute; @@ -266,28 +270,19 @@ $_multiple_counter_background: null !default; } .ecl-select__multiple-options { + box-sizing: border-box; max-height: $_multiple-dropdown-container-max-height; overflow-y: auto; - } - - .ecl-select__multiple-no-results { - align-items: center; - color: map.get(theme.$color, 'grey-100'); - display: flex; - font: map.get(theme.$font, 'm'); - height: 100%; - justify-content: center; - min-height: 100px; - width: 100%; + padding: map.get(theme.$spacing, 's') 0; } .ecl-checkbox__label { padding-bottom: $_multiple-checkbox-label-vertical-padding; - padding-inline-start: map.get(theme.$spacing, 'm'); - padding-inline-end: map.get(theme.$spacing, 'm'); + padding-inline-start: map.get(theme.$spacing, 's'); + padding-inline-end: map.get(theme.$spacing, 's'); padding-top: $_multiple-checkbox-label-vertical-padding; width: calc( - 100% - #{map.get(theme.$spacing, 'm')} - #{map.get(theme.$spacing, 'm')} + 100% - #{map.get(theme.$spacing, 's')} - #{map.get(theme.$spacing, 's')} ); } @@ -296,6 +291,44 @@ $_multiple_counter_background: null !default; background-color: $_multiple-checkbox-background-hover-color; } + .ecl-select__multiple-group { + border-bottom: 1px solid $_group-separator-color; + margin: 0 map.get(theme.$spacing, 's'); + padding: map.get(theme.$spacing, 's') 0; + + &:first-child { + padding-top: 0; + } + + .ecl-checkbox__label { + margin-inline-start: -#{map.get(theme.$spacing, 's')}; + margin-inline-end: -#{map.get(theme.$spacing, 's')}; + width: 100%; + } + } + + .ecl-select__multiple-group + .ecl-checkbox { + margin-top: map.get(theme.$spacing, 's'); + } + + .ecl-select__multiple-group__title { + color: $_group-title-color; + font: $_group-title-font; + font-weight: map.get(theme.$font-weight, 'bold'); + margin: map.get(theme.$spacing, 'xs') 0; + } + + .ecl-select__multiple-no-results { + align-items: center; + color: map.get(theme.$color, 'grey-100'); + display: flex; + font: map.get(theme.$font, 'm'); + height: 100%; + justify-content: center; + min-height: 100px; + width: 100%; + } + .ecl-select-multiple-toolbar { border-top: 1px solid $_multiple-dropdown-separator-color; display: flex; diff --git a/src/implementations/vanilla/components/select/select-ec.scss b/src/implementations/vanilla/components/select/select-ec.scss index ffa7f67d65e..000ff1f46c3 100644 --- a/src/implementations/vanilla/components/select/select-ec.scss +++ b/src/implementations/vanilla/components/select/select-ec.scss @@ -10,6 +10,9 @@ $_border-width-focus: 3px, $_color-input: map.get(theme.$color, 'grey-100'), $_color-placeholder: map.get(theme.$color, 'grey-50'), + $_group-separator-color: map.get(theme.$color, 'grey-25'), + $_group-title-font: map.get(theme.$font-prolonged, 'm'), + $_group-title-color: map.get(theme.$color, 'grey-75'), $_shadow: none, $_shadow-hover: none, $_arrow-background-color: map.get(theme.$color, 'grey-100'), @@ -23,8 +26,8 @@ $_multiple-dropdown-shadow: none, $_multiple-dropdown-input-margin: 0, $_multiple-dropdown-input-width: 100%, - $_multiple-dropdown-container-max-height: 280px, + $_multiple-dropdown-container-max-height: 252px, $_multiple-checkbox-background-hover-color: map.get(theme.$color, 'grey-15'), - $_multiple-checkbox-label-vertical-padding: map.get(theme.$spacing, 'm'), + $_multiple-checkbox-label-vertical-padding: map.get(theme.$spacing, 's'), $_multiple_counter_background: #707070 ); diff --git a/src/implementations/vanilla/components/select/select-eu.scss b/src/implementations/vanilla/components/select/select-eu.scss index bcc3bc66dbc..74aed63cf38 100644 --- a/src/implementations/vanilla/components/select/select-eu.scss +++ b/src/implementations/vanilla/components/select/select-eu.scss @@ -10,6 +10,9 @@ $_border-width-focus: 2px, $_color-input: map.get(theme.$color, 'grey-140'), $_color-placeholder: map.get(theme.$color, 'grey-40'), + $_group-separator-color: map.get(theme.$color, 'blue-20'), + $_group-title-font: map.get(theme.$font, 'm'), + $_group-title-color: map.get(theme.$color, 'grey-80'), $_shadow: map.get(theme.$shadow-inner, '1'), $_shadow-hover: map.get(theme.$shadow-inner, '2'), $_arrow-background-invalid-focus: map.get(theme.$color, 'red-100'), @@ -27,7 +30,7 @@ calc( 100% - #{map.get(theme.$spacing, 'xs')} - #{map.get(theme.$spacing, 'xs')} ), - $_multiple-dropdown-container-max-height: 240px, + $_multiple-dropdown-container-max-height: 252px, $_multiple-checkbox-background-hover-color: map.get(theme.$color, 'blue-5'), $_multiple-checkbox-label-vertical-padding: map.get(theme.$spacing, 's'), $_multiple_counter_background: map.get(theme.$color, 'blue-100') diff --git a/src/implementations/vanilla/components/select/select.js b/src/implementations/vanilla/components/select/select.js index 2824f85ebf5..380cb662128 100644 --- a/src/implementations/vanilla/components/select/select.js +++ b/src/implementations/vanilla/components/select/select.js @@ -370,13 +370,43 @@ export class Select { if (this.select.options && this.select.options.length > 0) { this.checkboxes = Array.from(this.select.options).map((option) => { + let optgroup = ''; + let checkbox = ''; + if (option.parentNode.tagName === 'OPTGROUP') { + if ( + !this.optionsContainer.querySelector( + `div[data-ecl-multiple-group="${option.parentNode.getAttribute( + 'label' + )}"]` + ) + ) { + optgroup = document.createElement('div'); + const title = document.createElement('h5'); + title.classList.add('ecl-select__multiple-group__title'); + title.innerHTML = option.parentNode.getAttribute('label'); + optgroup.appendChild(title); + optgroup.setAttribute( + 'data-ecl-multiple-group', + option.parentNode.getAttribute('label') + ); + optgroup.classList.add('ecl-select__multiple-group'); + this.optionsContainer.appendChild(optgroup); + } else { + optgroup = this.optionsContainer.querySelector( + `div[data-ecl-multiple-group="${option.parentNode.getAttribute( + 'label' + )}"]` + ); + } + } + if (option.selected) { this.updateSelectionsCount(1); if (this.dropDownToolbar) { this.dropDownToolbar.style.display = 'flex'; } } - const checkbox = Select.createCheckbox( + checkbox = Select.createCheckbox( { // spread operator does not work in storybook context so we map 1:1 id: option.value, @@ -386,12 +416,18 @@ export class Select { }, this.selectMultipleId ); + checkbox.setAttribute('data-visible', true); if (!checkbox.classList.contains('ecl-checkbox--disabled')) { checkbox.addEventListener('click', this.handleClickOption); checkbox.addEventListener('keydown', this.handleKeyboardOnOption); } - this.optionsContainer.appendChild(checkbox); + if (optgroup) { + optgroup.appendChild(checkbox); + } else { + this.optionsContainer.appendChild(checkbox); + } + return checkbox; }); } @@ -623,7 +659,7 @@ export class Select { .toLocaleLowerCase() .includes(keyword) ) { - checkbox.setAttribute('data-visible', false); + checkbox.removeAttribute('data-visible'); checkbox.style.display = 'none'; } else { checkbox.setAttribute('data-visible', true); @@ -656,6 +692,23 @@ export class Select { const noResultsElement = this.searchContainer.querySelector( '.ecl-select__multiple-no-results' ); + const groups = this.optionsContainer.getElementsByClassName( + 'ecl-select__multiple-group' + ); + // eslint-disable-next-line no-restricted-syntax + for (const group of groups) { + group.style.display = 'none'; + // eslint-disable-next-line no-restricted-syntax + const groupedCheckboxes = [...group.children].filter((node) => + node.classList.contains('ecl-checkbox') + ); + groupedCheckboxes.forEach((single) => { + if (single.hasAttribute('data-visible')) { + single.closest('.ecl-select__multiple-group').style.display = 'block'; + } + }); + } + if (visible.length === 0 && !noResultsElement) { // Create no-results element. const noResultsContainer = document.createElement('div'); @@ -860,11 +913,20 @@ export class Select { */ moveFocus(upOrDown) { const activeEl = document.activeElement; - const options = Array.from( - activeEl.parentElement.parentElement.querySelectorAll( - '.ecl-checkbox__input' - ) + const hasGroups = activeEl.parentElement.parentElement.classList.contains( + 'ecl-select__multiple-group' ); + const options = !hasGroups + ? Array.from( + activeEl.parentElement.parentElement.querySelectorAll( + '.ecl-checkbox__input' + ) + ) + : Array.from( + activeEl.parentElement.parentElement.parentElement.querySelectorAll( + '.ecl-checkbox__input' + ) + ); const activeIndex = options.indexOf(activeEl); if (upOrDown === 'down') { const nextSiblings = options diff --git a/src/specs/components/select/demo/data-multiple.js b/src/specs/components/select/demo/data-multiple.js index eecea4f7f55..1651f53da01 100644 --- a/src/specs/components/select/demo/data-multiple.js +++ b/src/specs/components/select/demo/data-multiple.js @@ -15,45 +15,67 @@ module.exports = { width: 'm', options: [ { - value: '1', - label: 'Belgium', + optgroup: { + label: 'European countries', + options: [ + { + value: '1', + label: 'Belgium', + }, + { + value: '2', + label: 'France', + }, + { + value: '3', + label: 'Luxembourg', + disabled: true, + }, + { + value: '4', + label: 'Germany', + }, + { + value: '5', + label: 'Bulgaria', + }, + { + value: '6', + label: 'Italy', + }, + { + value: '7', + label: 'Romania', + }, + { + value: '8', + label: 'Greece', + }, + { + value: '9', + label: 'Hungary', + }, + { + value: '10', + label: 'Portugal', + }, + ], + }, }, { - value: '2', - label: 'France', + optgroup: { + label: 'Non European countries', + options: [ + { + value: '11', + label: 'China', + }, + ], + }, }, { - value: '3', - label: 'Luxembourg', - disabled: true, - }, - { - value: '4', - label: 'Germany', - }, - { - value: '5', - label: 'Bulgaria', - }, - { - value: '6', - label: 'Italy', - }, - { - value: '7', - label: 'Romania', - }, - { - value: '8', - label: 'Greece', - }, - { - value: '9', - label: 'Democratic Republic of the Congo', - }, - { - value: '10', - label: 'Portugal', + value: '12', + label: 'standalone option', }, ], multiple: true, diff --git a/src/specs/components/select/demo/data-single.js b/src/specs/components/select/demo/data-single.js index d42d675e9de..aa858258832 100644 --- a/src/specs/components/select/demo/data-single.js +++ b/src/specs/components/select/demo/data-single.js @@ -15,46 +15,68 @@ module.exports = { width: 'm', options: [ { - value: '1', - label: 'Belgium', + optgroup: { + label: 'European countries', + options: [ + { + value: '1', + label: 'Belgium', + }, + { + value: '2', + label: 'France', + }, + { + value: '3', + label: 'Luxembourg', + disabled: true, + }, + { + value: '4', + label: 'Germany', + }, + { + value: '5', + label: 'Bulgaria', + selected: true, + }, + { + value: '6', + label: 'Italy', + }, + { + value: '7', + label: 'Romania', + }, + { + value: '8', + label: 'Greece', + }, + { + value: '9', + label: 'Hungary', + }, + { + value: '10', + label: 'Portugal', + }, + ], + }, }, { - value: '2', - label: 'France', + optgroup: { + label: 'Non European countries', + options: [ + { + value: '11', + label: 'China', + }, + ], + }, }, { - value: '3', - label: 'Luxembourg', - disabled: true, - }, - { - value: '4', - label: 'Germany', - }, - { - value: '5', - label: 'Bulgaria', - selected: true, - }, - { - value: '6', - label: 'Italy', - }, - { - value: '7', - label: 'Romania', - }, - { - value: '8', - label: 'Greece', - }, - { - value: '9', - label: 'Hungary', - }, - { - value: '10', - label: 'Portugal', + value: '12', + label: 'standalone option', }, ], };