diff --git a/src/bundle/Resources/public/js/CKEditor/common/chips-button-view/chips-button-view.js b/src/bundle/Resources/public/js/CKEditor/common/chips-button-view/chips-button-view.js new file mode 100644 index 00000000..c3916a2e --- /dev/null +++ b/src/bundle/Resources/public/js/CKEditor/common/chips-button-view/chips-button-view.js @@ -0,0 +1,80 @@ +import View from '@ckeditor/ckeditor5-ui/src/view'; + +export default class IbexaChipsButtonView extends View { + constructor() { + super(); + this.set({ + style: undefined, + text: undefined, + id: undefined, + }); + const bind = this.bindTemplate; + this.children = this.createCollection(); + this.setTemplate({ + tag: 'ul', + attributes: { + class: ['ibexa-ckeditor-dropdown-selected-items'], + style: bind.to('style'), + id: bind.to('id'), + }, + children: this.children, + }); + + this.listenTo(this, 'change:text', (event, item, value) => { + const container = event.source.element; + const selectedItems = value.split(' ').filter((selectedItem) => selectedItem !== ''); + let showOverflow = false; + let currentIndex = 0; + + this.children.clear(); + + while (!showOverflow && currentIndex < selectedItems.length) { + const selectedItem = selectedItems[currentIndex]; + const chip = this.createChip(selectedItem); + + this.children.add(chip); + + showOverflow = container.scrollWidth > container.offsetWidth; + currentIndex++; + + if (showOverflow) { + this.children.remove(this.children.last); + } + } + + if (showOverflow) { + const overflownItems = selectedItems.length - currentIndex + 1; + let overflowChip = this.createChip(`+${overflownItems}`); + + this.children.add(overflowChip); + + if (container.scrollWidth > container.offsetWidth) { + this.children.remove(this.children.last); + this.children.remove(this.children.last); + + overflowChip = this.createChip(`+${overflownItems + 1}`); + + this.children.add(overflowChip); + } + } + }); + } + + createChip(text) { + const chip = new View(); + + chip.setTemplate({ + tag: 'li', + attributes: { + class: ['ibexa-ckeditor-dropdown-selected-items__item'], + }, + children: [ + { + text, + }, + ], + }); + + return chip; + } +} diff --git a/src/bundle/Resources/public/js/CKEditor/common/multivalue-dropdown/utils.js b/src/bundle/Resources/public/js/CKEditor/common/multivalue-dropdown/utils.js new file mode 100644 index 00000000..dc31ea18 --- /dev/null +++ b/src/bundle/Resources/public/js/CKEditor/common/multivalue-dropdown/utils.js @@ -0,0 +1,38 @@ +import View from '@ckeditor/ckeditor5-ui/src/view'; +import ListView from '@ckeditor/ckeditor5-ui/src/list/listview'; + +import ChipsButtonView from '../../common/chips-button-view/chips-button-view'; + +export const addMultivalueSupport = (labeledDropdown, config) => { + labeledDropdown.fieldView.panelView.extendTemplate({ + attributes: { + class: 'ck-dropdown__panel--multiple', + }, + }); + + labeledDropdown.fieldView.buttonView.labelView = labeledDropdown.fieldView.buttonView._setupLabelView(new ChipsButtonView()); + + labeledDropdown.fieldView.panelView.children.on('add', (event, panelViewItem) => { + if (!(panelViewItem instanceof ListView)) { + return; + } + + const selectedItems = new Set(labeledDropdown.fieldView.element.value?.split(' ')); + + panelViewItem.items.forEach((item, key) => { + const itemValue = config.choices[key]; + const isSelected = selectedItems.has(itemValue); + const inputView = new View(); + + inputView.setTemplate({ + tag: 'input', + attributes: { + class: 'ibexa-ckeditor-input--checkbox', + type: 'checkbox', + checked: isSelected, + }, + }); + item.children.get(0).children.add(inputView, 0); + }); + }); +}; diff --git a/src/bundle/Resources/public/js/CKEditor/custom-attributes/ui/custom-attributes-form-view.js b/src/bundle/Resources/public/js/CKEditor/custom-attributes/ui/custom-attributes-form-view.js index a37f2d23..9e4abc65 100644 --- a/src/bundle/Resources/public/js/CKEditor/custom-attributes/ui/custom-attributes-form-view.js +++ b/src/bundle/Resources/public/js/CKEditor/custom-attributes/ui/custom-attributes-form-view.js @@ -9,6 +9,7 @@ import { addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; import { createLabeledInputNumber } from '../../common/input-number/utils'; import { createLabeledSwitchButton } from '../../common/switch-button/utils'; +import { addMultivalueSupport } from '../../common/multivalue-dropdown/utils'; class IbexaCustomAttributesFormView extends View { constructor(props) { @@ -198,7 +199,7 @@ class IbexaCustomAttributesFormView extends View { config.choices.forEach((choice) => { itemsList.add({ - type: 'button', + type: config.multiple ? 'switchbutton' : 'button', model: new Model({ withText: true, label: choice, @@ -209,8 +210,19 @@ class IbexaCustomAttributesFormView extends View { addListToDropdown(labeledDropdown.fieldView, itemsList); + if (config.multiple) { + addMultivalueSupport(labeledDropdown, config); + } + this.listenTo(labeledDropdown.fieldView, 'execute', (event) => { - const value = this.getNewValue(event.source.value, config.multiple, labeledDropdown.fieldView.element.value); + const dropdownValue = labeledDropdown.fieldView.element.value; + const value = this.getNewValue(event.source.value, config.multiple, dropdownValue); + + if (config.multiple) { + const isSelected = value.length > dropdownValue.length; + + event.source.children.get(0).element.checked = isSelected; + } labeledDropdown.fieldView.buttonView.set({ label: value, diff --git a/src/bundle/Resources/public/js/CKEditor/link/ui/link-form-view.js b/src/bundle/Resources/public/js/CKEditor/link/ui/link-form-view.js index 4c342bcd..90a0f5c7 100644 --- a/src/bundle/Resources/public/js/CKEditor/link/ui/link-form-view.js +++ b/src/bundle/Resources/public/js/CKEditor/link/ui/link-form-view.js @@ -8,6 +8,7 @@ import { addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; import { createLabeledSwitchButton } from '../../common/switch-button/utils'; import { createLabeledInputNumber } from '../../common/input-number/utils'; +import { addMultivalueSupport } from '../../common/multivalue-dropdown/utils'; import { getCustomAttributesConfig, getCustomClassesConfig } from '../../custom-attributes/helpers/config-helper'; class IbexaLinkFormView extends View { @@ -271,7 +272,7 @@ class IbexaLinkFormView extends View { config.choices.forEach((choice) => { itemsList.add({ - type: 'button', + type: config.multiple ? 'switchbutton' : 'button', model: new Model({ withText: true, label: choice, @@ -282,8 +283,19 @@ class IbexaLinkFormView extends View { addListToDropdown(labeledDropdown.fieldView, itemsList); + if (config.multiple) { + addMultivalueSupport(labeledDropdown, config); + } + this.listenTo(labeledDropdown.fieldView, 'execute', (event) => { - const value = this.getNewValue(event.source.value, config.multiple, labeledDropdown.fieldView.element.value); + const dropdownValue = labeledDropdown.fieldView.element.value; + const value = this.getNewValue(event.source.value, config.multiple, dropdownValue); + + if (config.multiple) { + const isSelected = value.length > dropdownValue.length; + + event.source.children.get(0).element.checked = isSelected; + } labeledDropdown.fieldView.buttonView.set({ label: value, diff --git a/src/bundle/Resources/public/scss/_balloon-form.scss b/src/bundle/Resources/public/scss/_balloon-form.scss index 8c330784..7faddcbc 100644 --- a/src/bundle/Resources/public/scss/_balloon-form.scss +++ b/src/bundle/Resources/public/scss/_balloon-form.scss @@ -26,11 +26,104 @@ .ck.ck-dropdown { border: calculateRem(1px) solid $ibexa-color-dark-200; border-radius: calculateRem(5px); + width: 100%; - .ck-button.ck-dropdown__button { - .ck-button__label { - width: calculateRem(198px); + .ibexa-ckeditor-dropdown-selected-items { + display: flex; + align-items: center; + flex-wrap: nowrap; + width: calculateRem(198px); + overflow: hidden; + position: relative; + + &__item { display: inline-block; + white-space: nowrap; + background: $ibexa-color-light-300; + border: calculateRem(1px) solid $ibexa-color-light-300; + border-radius: $ibexa-border-radius; + margin-right: calculateRem(4px); + font-size: calculateRem(12px); + line-height: calculateRem(16px); + padding: 0 calculateRem(8px); + } + } + + .ck-list { + padding: calculateRem(4px); + + &__item { + border-radius: $ibexa-border-radius; + + &:hover { + color: $ibexa-color-dark; + background-color: $ibexa-color-light-300; + + .ck-button { + background-color: transparent; + } + } + } + } + + .ck-button.ck-dropdown__button { + .ck-button { + &__label { + width: calculateRem(198px); + display: inline-block; + } + } + } + + .ck-dropdown__panel--multiple { + .ck.ck-button { + &__toggle { + display: none; + } + + .ibexa-ckeditor-input--checkbox { + appearance: none; + position: relative; + display: inline-block; + cursor: pointer; + outline: none; + border: calculateRem(1px) solid $ibexa-color-dark-300; + width: calculateRem(16px); + height: calculateRem(16px); + border-radius: calculateRem(2px); + margin-right: calculateRem(16px); + + &::after { + content: ' '; + position: absolute; + top: calculateRem(3px); + left: calculateRem(3px); + display: block; + width: calculateRem(8px); + height: calculateRem(5px); + border-left: calculateRem(2px) solid transparent; + border-bottom: calculateRem(2px) solid transparent; + transform: rotate(-45deg); + } + + &:hover { + border-color: $ibexa-color-primary; + } + + &:focus { + border-color: $ibexa-color-primary; + box-shadow: 0 0 0 calculateRem(4px) rgba($ibexa-color-primary, 0.25); + } + + &:checked { + border-color: $ibexa-color-primary; + background-color: $ibexa-color-primary; + + &::after { + border-color: $ibexa-color-white; + } + } + } } } } diff --git a/src/bundle/Resources/public/scss/_custom-panel.scss b/src/bundle/Resources/public/scss/_custom-panel.scss index 2ad32032..e6fd51dd 100644 --- a/src/bundle/Resources/public/scss/_custom-panel.scss +++ b/src/bundle/Resources/public/scss/_custom-panel.scss @@ -15,9 +15,10 @@ width: 100%; .ck.ck-button { - display: block; + display: flex; - .ck.ck-button__label { + .ck.ck-button__label, + .ibexa-ckeditor-dropdown-selected-items { width: calc(100% - calculateRem(20px)); height: fit-content; white-space: normal;