diff --git a/composer.json b/composer.json
index 97beea181e..e34fc9058c 100644
--- a/composer.json
+++ b/composer.json
@@ -27,6 +27,7 @@
"ibexa/content-forms": "~6.0.x-dev",
"ibexa/core": "~6.0.x-dev",
"ibexa/design-engine": "~6.0.x-dev",
+ "ibexa/design-system-twig": "~6.0.x-dev",
"ibexa/rest": "~6.0.x-dev",
"ibexa/search": "~6.0.x-dev",
"ibexa/twig-components": "~6.0.x-dev",
@@ -97,5 +98,11 @@
"branch-alias": {
"dev-main": "6.0.x-dev"
}
- }
+ },
+ "repositories": [
+ {
+ "type": "vcs",
+ "url": "https://github.com/ibexa/design-system-twig"
+ }
+ ]
}
diff --git a/src/bundle/Resources/encore/ibexa.css.config.js b/src/bundle/Resources/encore/ibexa.css.config.js
index 62dfba962e..283f45eb1f 100644
--- a/src/bundle/Resources/encore/ibexa.css.config.js
+++ b/src/bundle/Resources/encore/ibexa.css.config.js
@@ -5,6 +5,7 @@ module.exports = (Encore) => {
path.resolve(__dirname, '../public/scss/ibexa-bootstrap.scss'),
path.resolve(__dirname, '../public/scss/ibexa.scss'),
path.resolve(__dirname, '../public/scss/ui/ibexa-modules.scss'),
+ path.resolve('./vendor/ibexa/admin-ui-assets/src/bundle/Resources/public/vendors/ids-assets/dist/css/styles.css'),
path.resolve('./vendor/ibexa/admin-ui-assets/src/bundle/Resources/public/vendors/flatpickr/dist/flatpickr.min.css'),
path.resolve(
'./vendor/ibexa/admin-ui-assets/src/bundle/Resources/public/vendors/flatpickr/dist/plugins/confirmDate/confirmDate.css',
diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js
index 30b8296fd9..b12c548364 100644
--- a/src/bundle/Resources/encore/ibexa.js.config.js
+++ b/src/bundle/Resources/encore/ibexa.js.config.js
@@ -7,6 +7,10 @@ const layout = [
path.resolve(__dirname, '../public/js/scripts/admin.format.date.js'),
path.resolve(__dirname, '../public/js/scripts/core/draggable.js'),
path.resolve(__dirname, '../public/js/scripts/core/dropdown.js'),
+ path.resolve(
+ __dirname,
+ '../../../../../design-system-twig/src/bundle/Resources/public/ts/init_components.ts',
+ ),
path.resolve(__dirname, '../public/js/scripts/core/backdrop.js'),
path.resolve(__dirname, '../public/js/scripts/core/custom.tooltip.js'),
path.resolve(__dirname, '../public/js/scripts/core/base.chart.js'),
diff --git a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js
index eb8935244d..4c9c04a2ef 100644
--- a/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js
+++ b/src/bundle/Resources/public/js/scripts/admin.contenttype.edit.js
@@ -1,3 +1,5 @@
+import { DropdownMultiInput, DropdownSingleInput } from '../../../../../../../design-system-twig/src/bundle/Resources/public/ts/components/dropdown';
+
(function (global, doc, ibexa, Routing, Translator, bootstrap) {
const SELECTOR_INPUTS_TO_VALIDATE = '.ibexa-input[required]:not([disabled]):not([hidden])';
const SELETOR_FIELD_INPUTS =
@@ -102,6 +104,35 @@
return fieldNode;
};
const attachFieldDefinitionNodeEvents = (fieldNode, { targetContainer }) => {
+ const initDropdown = (dropdownContainer) => {
+ const dropdownAlreadyInitialized = !!ibexa.helpers.objectInstances.getInstance(dropdownContainer);
+
+ if (dropdownAlreadyInitialized) {
+ return;
+ }
+
+ if (dropdownContainer.classList.contains('ids-dropdown--multi')) {
+ const dropdown = new DropdownMultiInput(dropdownContainer);
+
+ dropdown.init();
+
+ return;
+ }
+
+ if (dropdownContainer.classList.contains('ids-dropdown--single')) {
+ const dropdown = new DropdownSingleInput(dropdownContainer);
+
+ dropdown.init();
+
+ return;
+ }
+
+ const dropdown = new ibexa.core.Dropdown({
+ container: dropdownContainer,
+ });
+
+ dropdown.init();
+ };
const fieldGroupInput = fieldNode.querySelector('.ibexa-input--field-group');
const fieldDefinitionsInputs = fieldNode.querySelectorAll(SELETOR_FIELD_INPUTS);
const removeFieldBtns = fieldNode.querySelectorAll('.ibexa-collapse__extra-action-button--remove-field-definitions');
@@ -122,20 +153,10 @@
});
}
- const dropdowns = fieldNode.querySelectorAll('.ibexa-dropdown');
+ const dropdowns = fieldNode.querySelectorAll('.ibexa-dropdown, .ids-dropdown');
dropdowns.forEach((dropdownContainer) => {
- const dropdownAlreadyInitialized = !!ibexa.helpers.objectInstances.getInstance(dropdownContainer);
-
- if (dropdownAlreadyInitialized) {
- return;
- }
-
- const dropdown = new ibexa.core.Dropdown({
- container: dropdownContainer,
- });
-
- dropdown.init();
+ initDropdown(dropdownContainer);
});
draggableGroups.forEach((group) => {
diff --git a/src/bundle/Resources/public/js/scripts/admin.location.add.custom_url.js b/src/bundle/Resources/public/js/scripts/admin.location.add.custom_url.js
index 8668cff04f..b271b85ea8 100644
--- a/src/bundle/Resources/public/js/scripts/admin.location.add.custom_url.js
+++ b/src/bundle/Resources/public/js/scripts/admin.location.add.custom_url.js
@@ -18,11 +18,21 @@
};
const toggleSiteAccessSelect = (event) => {
const isChecked = event.target.checked;
- const siteAccessSelect = modal.querySelector('.ibexa-custom-url-from__item--siteacces .ibexa-dropdown');
+ const siteAccessSelect = modal.querySelector('.ibexa-custom-url-from__item--siteacces .ibexa-dropdown, .ibexa-custom-url-from__item--siteacces .ids-dropdown');
+ const sourceSelect = siteAccessSelect?.querySelector('.ibexa-input--select, .ids-dropdown__source select');
- siteAccessSelect.classList.toggle('ibexa-dropdown--is-disabled', isChecked);
+ if (siteAccessSelect?.classList.contains('ibexa-dropdown')) {
+ siteAccessSelect.classList.toggle('ibexa-dropdown--disabled', isChecked);
+ } else if (siteAccessSelect?.classList.contains('ids-dropdown')) {
+ siteAccessSelect.classList.toggle('ids-dropdown--disabled', isChecked);
+ }
+
+ if (sourceSelect) {
+ sourceSelect.disabled = isChecked;
+ }
};
+ toggleSiteAccessSelect({ target: siteRootCheckbox });
input.addEventListener('input', toggleButtonState, false);
siteRootCheckbox.addEventListener('change', toggleSiteAccessSelect, false);
discardBtns.forEach((btn) => btn.addEventListener('click', clearValues, false));
diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js
index 33fb21d9d9..70b8320398 100644
--- a/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js
+++ b/src/bundle/Resources/public/js/scripts/admin.notifications.filters.js
@@ -12,7 +12,9 @@
return;
}
- const sourceSelect = filterNode.querySelector('.ibexa-list-filters__item-content .ibexa-dropdown__source .ibexa-input--select');
+ const sourceSelect = filterNode.querySelector(
+ '.ibexa-list-filters__item-content .ibexa-dropdown__source .ibexa-input--select, .ibexa-list-filters__item-content .ids-dropdown__source select',
+ );
const checkboxes = filterNode.querySelectorAll(
'.ibexa-list-filters__item-content .ibexa-input--checkbox:not([name="dropdown-checkbox"])',
);
@@ -57,7 +59,9 @@
return;
}
- const sourceSelect = filterNode.querySelector('.ibexa-list-filters__item-content .ibexa-dropdown__source .ibexa-input--select');
+ const sourceSelect = filterNode.querySelector(
+ '.ibexa-list-filters__item-content .ibexa-dropdown__source .ibexa-input--select, .ibexa-list-filters__item-content .ids-dropdown__source select',
+ );
sourceSelect?.addEventListener('change', filterChange, false);
};
@@ -79,7 +83,7 @@
return;
}
- const select = filterNode.querySelector('.ibexa-dropdown__source .ibexa-input--select');
+ const select = filterNode.querySelector('.ibexa-dropdown__source .ibexa-input--select, .ids-dropdown__source select');
const checkedCheckboxes = filterNode.querySelectorAll('.ibexa-input--checkbox:checked');
if (isNodeDatePicker(filterNode)) {
diff --git a/src/bundle/Resources/public/js/scripts/admin.search.filters.js b/src/bundle/Resources/public/js/scripts/admin.search.filters.js
index 7f6b1dcc1c..6959eda4af 100644
--- a/src/bundle/Resources/public/js/scripts/admin.search.filters.js
+++ b/src/bundle/Resources/public/js/scripts/admin.search.filters.js
@@ -10,9 +10,16 @@
const clearBtn = filters.querySelector('.ibexa-btn--clear');
const applyBtn = filters.querySelector('.ibexa-btn--apply');
const contentTypeSelect = doc.querySelector('.ibexa-filters__item--content-type .ibexa-filters__select');
- const sectionSelect = doc.querySelector('.ibexa-filters__item--section .ibexa-filters__select');
- const lastModifiedSelectNode = doc.querySelector('.ibexa-filters__item--modified .ibexa-filters__select');
- const lastModifiedSelect = getInstance(lastModifiedSelectNode);
+ const sectionSelect = filters.querySelector(
+ '.ibexa-filters__item--section .ibexa-input--select, .ibexa-filters__item--section .ids-dropdown__source select',
+ );
+ const lastModifiedDropdownNode = filters.querySelector(
+ '.ibexa-filters__item--modified .ibexa-dropdown, .ibexa-filters__item--modified .ids-dropdown',
+ );
+ const lastModifiedSelectNode = filters.querySelector(
+ '.ibexa-filters__item--modified .ibexa-input--select, .ibexa-filters__item--modified .ids-dropdown__source select',
+ );
+ const lastModifiedSelect = getInstance(lastModifiedDropdownNode);
const lastModifiedDateRangeNode = doc.querySelector('.ibexa-filters__item--modified .ibexa-date-time-range-single');
const lastModifiedDateRange = getInstance(lastModifiedDateRangeNode);
const {
@@ -23,8 +30,13 @@
const lastModifiedPeriod = doc.querySelector(lastModifiedPeriodSelector);
const lastModifiedStartDate = doc.querySelector(lastModifiedStartSelector);
const lastModifiedEndDate = doc.querySelector(lastModifiedEndSelector);
- const lastCreatedSelectNode = doc.querySelector('.ibexa-filters__item--created .ibexa-filters__select');
- const lastCreatedSelect = getInstance(lastCreatedSelectNode);
+ const lastCreatedDropdownNode = filters.querySelector(
+ '.ibexa-filters__item--created .ibexa-dropdown, .ibexa-filters__item--created .ids-dropdown',
+ );
+ const lastCreatedSelectNode = filters.querySelector(
+ '.ibexa-filters__item--created .ibexa-input--select, .ibexa-filters__item--created .ids-dropdown__source select',
+ );
+ const lastCreatedSelect = getInstance(lastCreatedDropdownNode);
const lastCreatedDateRangeNode = doc.querySelector('.ibexa-filters__item--created .ibexa-date-time-range-single');
const lastCreatedDateRange = getInstance(lastCreatedDateRangeNode);
const {
@@ -63,9 +75,8 @@
sectionSelect[0].selected = true;
}
- lastModifiedSelectNode[0].selected = true;
- lastCreatedSelectNode[0].selected = true;
- lastModifiedSelectNode.querySelector('option').selected = true;
+ lastModifiedSelect.selectFirstOption();
+ lastCreatedSelect.selectFirstOption();
lastModifiedPeriod.value = '';
lastModifiedEnd.value = '';
lastCreatedPeriod.value = '';
@@ -240,7 +251,10 @@
removeSearchTag(event);
};
const clearSection = (event) => {
- sectionSelect[0].selected = true;
+ if (sectionSelect) {
+ sectionSelect[0].selected = true;
+ }
+
removeSearchTag(event);
};
const clearSubtree = (event) => {
diff --git a/src/bundle/Resources/public/js/scripts/filters.action.btns.js b/src/bundle/Resources/public/js/scripts/filters.action.btns.js
index ea1569d8f3..c7f2566bc5 100644
--- a/src/bundle/Resources/public/js/scripts/filters.action.btns.js
+++ b/src/bundle/Resources/public/js/scripts/filters.action.btns.js
@@ -4,7 +4,7 @@
containers.forEach((container) => {
const clearBtn = container.querySelector('.ibexa-adaptive-filters__clear-btn');
const applyBtn = container.querySelector('.ibexa-adaptive-filters__submit-btn');
- const dropdownNodes = [...container.querySelectorAll('.ibexa-dropdown')];
+ const dropdownNodes = [...container.querySelectorAll('.ibexa-dropdown, .ids-dropdown')];
const textInputNodes = [...container.querySelectorAll('.ibexa-input--text')];
const dateInputNodes = [...container.querySelectorAll('.ibexa-input--date')];
const originalValuesMap = new Map();
@@ -40,8 +40,8 @@
return (
textInputNodes.every((textInputNode) => textInputNode.disabled || textInputNode.value === '') &&
dropdownNodes.every((dropdownNode) => {
- const isDisabled = dropdownNode.classList.contains('ibexa-dropdown--disabled');
- const selectNode = dropdownNode.querySelector('.ibexa-input--select');
+ const isDisabled = dropdownNode.classList.contains('ibexa-dropdown--disabled') || dropdownNode.classList.contains('ids-dropdown--disabled');
+ const selectNode = dropdownNode.querySelector('.ibexa-input--select, .ids-dropdown__source select');
const dropdown = dropdownNode.ibexaInstance;
return isDisabled || (dropdown.canSelectOnlyOne ? selectNode.selectedIndex === 0 : selectNode.selectedIndex === -1);
@@ -78,7 +78,7 @@
}
});
dropdownNodes.forEach((dropdownNode) => {
- const isDisabled = dropdownNode.classList.contains('ibexa-dropdown--disabled');
+ const isDisabled = dropdownNode.classList.contains('ibexa-dropdown--disabled') || dropdownNode.classList.contains('ids-dropdown--disabled');
if (!isDisabled) {
const dropdown = dropdownNode.ibexaInstance;
@@ -135,7 +135,7 @@
}
dropdownNodes.forEach((dropdownNode) => {
- const select = dropdownNode.querySelector('.ibexa-input--select');
+ const select = dropdownNode.querySelector('.ibexa-input--select, .ids-dropdown__source select');
select.addEventListener('change', handleInputChange, false);
});
diff --git a/src/bundle/Resources/public/js/scripts/helpers/icon.helper.js b/src/bundle/Resources/public/js/scripts/helpers/icon.helper.js
index f9d9193c28..a8b1d27fec 100644
--- a/src/bundle/Resources/public/js/scripts/helpers/icon.helper.js
+++ b/src/bundle/Resources/public/js/scripts/helpers/icon.helper.js
@@ -1,9 +1,9 @@
import { getAdminUiConfig } from './context.helper';
-const getIconPath = (aliasPath, iconSet) => {
+const getIconPath = (iconName, iconSet, useLegacyNames = true) => {
const adminUiConfig = getAdminUiConfig();
const { defaultIconSet, iconSets, iconAliases } = adminUiConfig.iconPaths;
- const path = iconAliases[aliasPath] || aliasPath;
+ const path = useLegacyNames ? iconAliases[iconName] || iconName : iconName;
if (!iconSet) {
iconSet = defaultIconSet;
diff --git a/src/bundle/Resources/public/js/scripts/sidebar/extra.actions.js b/src/bundle/Resources/public/js/scripts/sidebar/extra.actions.js
index e531ccf33c..ded5d4d6c3 100644
--- a/src/bundle/Resources/public/js/scripts/sidebar/extra.actions.js
+++ b/src/bundle/Resources/public/js/scripts/sidebar/extra.actions.js
@@ -36,7 +36,7 @@ import { getInstance } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scri
node.checked = value;
} else if (node.tagName === 'SELECT') {
- const dropdownContainer = node.closest('.ibexa-dropdown');
+ const dropdownContainer = node.closest('.ibexa-dropdown, .ids-dropdown');
if (dropdownContainer) {
const dropdownInstance = getInstance(dropdownContainer);
diff --git a/src/bundle/Resources/public/scss/_dropdown.scss b/src/bundle/Resources/public/scss/_dropdown.scss
index 319de4f673..e407e47d31 100644
--- a/src/bundle/Resources/public/scss/_dropdown.scss
+++ b/src/bundle/Resources/public/scss/_dropdown.scss
@@ -561,6 +561,14 @@
}
}
+.ids-dropdown {
+ &__selection-info,
+ &__selection-info-items,
+ .ids-overflow-list {
+ min-width: 0;
+ }
+}
+
.form-inline {
.ibexa-dropdown {
margin: calculateRem(5px) calculateRem(8px);
diff --git a/src/bundle/Resources/views/themes/admin/content/tab/content.html.twig b/src/bundle/Resources/views/themes/admin/content/tab/content.html.twig
index 9686cd352f..4dcbf9ff5c 100644
--- a/src/bundle/Resources/views/themes/admin/content/tab/content.html.twig
+++ b/src/bundle/Resources/views/themes/admin/content/tab/content.html.twig
@@ -36,12 +36,12 @@
{% endset %}
- {% include '@ibexadesign/ui/component/dropdown/dropdown.html.twig' with {
+ {% include '@ibexadesign/ui/component/dropdown/dropdown_ds.html.twig' with {
source: source,
choices: choices,
value: value,
is_small: true,
- } %}
+ } only %}
{% endif %}
{% endblock %}
diff --git a/src/bundle/Resources/views/themes/admin/content_type/tab/view.html.twig b/src/bundle/Resources/views/themes/admin/content_type/tab/view.html.twig
index 8e44d00a31..63d6b7e373 100644
--- a/src/bundle/Resources/views/themes/admin/content_type/tab/view.html.twig
+++ b/src/bundle/Resources/views/themes/admin/content_type/tab/view.html.twig
@@ -79,12 +79,12 @@
{% endset %}
- {% include '@ibexadesign/ui/component/dropdown/dropdown.html.twig' with {
+ {% include '@ibexadesign/ui/component/dropdown/dropdown_ds.html.twig' with {
source: source,
choices: choices,
value: value,
is_small: true,
- } %}
+ } only %}
{% endif %}
{% endset %}
diff --git a/src/bundle/Resources/views/themes/admin/ui/component/dropdown/dropdown_ds.html.twig b/src/bundle/Resources/views/themes/admin/ui/component/dropdown/dropdown_ds.html.twig
new file mode 100644
index 0000000000..82fa542963
--- /dev/null
+++ b/src/bundle/Resources/views/themes/admin/ui/component/dropdown/dropdown_ds.html.twig
@@ -0,0 +1,177 @@
+{% set preferred_choices = preferred_choices|default([]) %}
+{% set translation_domain = translation_domain|default(false) %}
+{% set value = value is defined ? value : null %}
+{% set multiple = multiple|default(false) %}
+{% set custom_init = custom_init|default(false) %}
+{% set is_disabled = is_disabled|default(false) or attr.readonly|default(false) %}
+
+{% set source = source|default(null) %}
+{% set source_attr = source_attr|default({}) %}
+{% set source_id = source_id|default(source_attr.id|default(null)) %}
+{% set source_name = source_name|default(source_attr.name|default(name|default('dropdown'))) %}
+{% set normalized_source_attr = source_attr|merge({
+ name: source_name,
+ disabled: is_disabled,
+ required: required|default(false),
+}) %}
+{% set normalized_source_attr = source_id is not null
+ ? normalized_source_attr|merge({ id: source_id })
+ : normalized_source_attr
+%}
+{% set translated_placeholder = placeholder is defined and placeholder is not none
+ ? (translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain))
+ : null
+%}
+{% set empty_placeholder_fallback = 'ibexa.dropdown.placeholder.all'|trans({}, 'ibexa_design_system_twig')|desc('All') %}
+{% set empty_placeholder_label = translated_placeholder is not same as(null)
+ ? translated_placeholder|trim|default(empty_placeholder_fallback)
+ : null
+%}
+
+{% set has_selected_item_template_path = selected_item_template_path is defined and selected_item_template_path is not null %}
+{% set has_list_item_template_path = list_item_template_path is defined and list_item_template_path is not null %}
+{% set has_selected_item_label = selected_item_label is defined and selected_item_label is not empty %}
+{% set has_items_class = items_class is defined and items_class is not empty %}
+{% set has_items_list_header = items_list_header is defined and items_list_header is not empty %}
+{% set has_min_search_items = min_search_items is defined and min_search_items is not null %}
+{% set has_min_select_all_toggler_items = min_select_all_toggler_items is defined and min_select_all_toggler_items is not null %}
+{% set has_min_item_width = min_item_width is defined and min_item_width is not null %}
+{% set has_grouped_preferred_choices = preferred_choices|filter(choice => choice.choices is defined)|length > 0 %}
+{% set has_grouped_choices = choices|filter(choice => choice.choices is defined)|length > 0 %}
+
+{% set can_use_ds = not (
+ custom_init
+ or has_selected_item_template_path
+ or has_list_item_template_path
+ or has_selected_item_label
+ or is_dynamic|default(false)
+ or is_selector|default(false)
+ or has_items_class
+ or has_items_list_header
+ or is_small|default(false)
+ or is_ghost|default(false)
+ or has_min_search_items
+ or has_min_item_width
+ or has_grouped_preferred_choices
+ or has_grouped_choices
+) %}
+
+{% if can_use_ds %}
+ {% set max_visible_items = 5 %}
+ {% set normalized_preferred_choices = preferred_choices|map(choice => {
+ value: choice.value ~ '',
+ label: translation_domain is same as(false) ? choice.label : choice.label|trans({}, translation_domain),
+ }) %}
+ {% set normalized_choices = choices|map(choice => {
+ value: choice.value ~ '',
+ label: translation_domain is same as(false) ? choice.label : choice.label|trans({}, translation_domain),
+ }) %}
+ {% set normalized_items = normalized_preferred_choices
+ | merge(normalized_choices)
+ | reduce((carry, choice) => choice.value in carry.values
+ ? carry
+ : {
+ items: carry.items|merge([choice]),
+ values: carry.values|merge([choice.value]),
+ }, {
+ items: [],
+ values: [],
+ })
+ %}
+ {% set normalized_ds_items = normalized_items.items|map(choice => {
+ id: choice.value,
+ label: choice.label,
+ }) %}
+ {% set ds_items = not multiple and translated_placeholder is not same as(null)
+ ? [{ id: '', label: empty_placeholder_label }]|merge(normalized_ds_items)
+ : normalized_ds_items
+ %}
+ {% set ds_value = multiple
+ ? (value is iterable ? value|map(item => item ~ '') : [])
+ : (value is not null ? value ~ '' : '')
+ %}
+ {% set ds_dropdown_props = {
+ name: source_name,
+ items: ds_items,
+ value: ds_value,
+ disabled: is_disabled,
+ maxVisibleItems: max_visible_items,
+ class: class|default(''),
+ source,
+ sourceAttributes: normalized_source_attr,
+ } %}
+ {% set ds_dropdown_props = translated_placeholder is not same as(null)
+ ? ds_dropdown_props|merge({ placeholder: translated_placeholder })
+ : ds_dropdown_props
+ %}
+ {% set ds_dropdown_props = is_hidden|default(false)
+ ? ds_dropdown_props|merge({ hidden: true })
+ : ds_dropdown_props
+ %}
+
+ {% if multiple %}
+